@@ -12,15 +12,6 @@ using v8::NewStringType;
12
12
using v8::Object;
13
13
using v8::String;
14
14
15
- /* *
16
- * The inspiration for this implementation comes from the original dotenv code,
17
- * available at https://github.com/motdotla/dotenv
18
- */
19
- const std::regex LINE (
20
- " \\ s*(?:export\\ s+)?([\\ w.-]+)(?:\\ s*=\\ s*?|:\\ s+?)(\\ s*'(?:\\\\ '|[^']"
21
- " )*'|\\ s*\" (?:\\\\\" |[^\" ])*\" |\\ s*`(?:\\\\ `|[^`])*`|[^#\r\n ]+)?\\ s*(?"
22
- " :#.*)?" ); // NOLINT(whitespace/line_length)
23
-
24
15
std::vector<std::string> Dotenv::GetPathFromArgs (
25
16
const std::vector<std::string>& args) {
26
17
const auto find_match = [](const std::string& arg) {
@@ -101,35 +92,127 @@ Local<Object> Dotenv::ToObject(Environment* env) {
101
92
return result;
102
93
}
103
94
104
- void Dotenv::ParseContent (const std::string_view content) {
105
- std::string lines = std::string (content);
106
- lines = std::regex_replace (lines, std::regex (" \r\n ?" ), " \n " );
95
+ std::string_view trim_spaces (std::string_view input) {
96
+ if (input.empty ()) return " " ;
97
+ if (input.front () == ' ' ) {
98
+ input.remove_prefix (input.find_first_not_of (' ' ));
99
+ }
100
+ if (!input.empty () && input.back () == ' ' ) {
101
+ input = input.substr (0 , input.find_last_not_of (' ' ) + 1 );
102
+ }
103
+ return input;
104
+ }
105
+
106
+ void Dotenv::ParseContent (const std::string_view input) {
107
+ std::string_view content = input;
108
+
109
+ std::string_view key;
110
+ std::string_view value;
107
111
108
- std::smatch match;
109
- while (std::regex_search (lines, match, LINE)) {
110
- const std::string key = match[1 ].str ();
112
+ content = trim_spaces (content);
113
+
114
+ while (!content.empty ()) {
115
+ // Skip empty lines and comments
116
+ if (content.front () == ' \n ' || content.front () == ' #' ) {
117
+ auto newline = content.find (' \n ' );
118
+ if (newline != std::string_view::npos) {
119
+ content.remove_prefix (newline + 1 );
120
+ continue ;
121
+ }
122
+ }
123
+
124
+ // If there is no equal character, then ignore everything
125
+ auto equal = content.find (' =' );
126
+ if (equal == std::string_view::npos) {
127
+ break ;
128
+ }
111
129
112
- // Default undefined or null to an empty string
113
- std::string value = match[2 ].str ();
130
+ key = content.substr (0 , equal);
131
+ content.remove_prefix (equal + 1 );
132
+ key = trim_spaces (key);
114
133
115
- // Remove leading whitespaces
116
- value.erase (0 , value.find_first_not_of (" \t " ));
134
+ if (key.empty ()) {
135
+ break ;
136
+ }
117
137
118
- // Remove trailing whitespaces
119
- if (!value.empty ()) {
120
- value.erase (value.find_last_not_of (" \t " ) + 1 );
138
+ // Remove export prefix from key
139
+ auto have_export = key.compare (0 , 7 , " export " ) == 0 ;
140
+ if (have_export) {
141
+ key = key.substr (7 );
121
142
}
122
143
123
- if (!value. empty () && value. front () == ' " ' ) {
124
- value = std::regex_replace (value, std::regex ( " \\\\ n " ), " \n " );
125
- value = std::regex_replace (value, std::regex ( " \\\\ r " ), " \r " ) ;
144
+ // SAFETY: Content is guaranteed to have at least one character
145
+ if (content. empty ()) {
146
+ break ;
126
147
}
127
148
128
- // Remove surrounding quotes
129
- value = trim_quotes (value);
149
+ // Expand new line if \n it's inside double quotes
150
+ // Example: EXPAND_NEWLINES = 'expand\nnew\nlines'
151
+ if (content.front () == ' "' ) {
152
+ auto closing_quote = content.find (content.front (), 1 );
153
+ value = content.substr (1 , closing_quote - 1 );
154
+ if (closing_quote != std::string_view::npos) {
155
+ std::string multi_line_value = std::string (value);
156
+
157
+ size_t pos = 0 ;
158
+ while ((pos = multi_line_value.find (" \\ n" , pos)) !=
159
+ std::string_view::npos) {
160
+ multi_line_value.replace (pos, 2 , " \n " );
161
+ pos += 1 ;
162
+ }
163
+
164
+ store_.insert_or_assign (std::string (key), multi_line_value);
165
+ content.remove_prefix (content.find (' \n ' , closing_quote + 1 ));
166
+ continue ;
167
+ }
168
+ }
130
169
131
- store_.insert_or_assign (std::string (key), value);
132
- lines = match.suffix ();
170
+ // Check if the value is wrapped in quotes, single quotes or backticks
171
+ if ((content.front () == ' \' ' || content.front () == ' "' ||
172
+ content.front () == ' `' )) {
173
+ auto closing_quote = content.find (content.front (), 1 );
174
+
175
+ // Check if the closing quote is not found
176
+ // Example: KEY="value
177
+ if (closing_quote == std::string_view::npos) {
178
+ // Check if newline exist. If it does, take the entire line as the value
179
+ // Example: KEY="value\nKEY2=value2
180
+ // The value pair should be `"value`
181
+ auto newline = content.find (' \n ' );
182
+ if (newline != std::string_view::npos) {
183
+ value = content.substr (0 , newline);
184
+ store_.insert_or_assign (std::string (key), value);
185
+ content.remove_prefix (newline);
186
+ }
187
+ } else {
188
+ // Example: KEY="value"
189
+ value = content.substr (1 , closing_quote - 1 );
190
+ store_.insert_or_assign (std::string (key), value);
191
+ // Select the first newline after the closing quotation mark
192
+ // since there could be newline characters inside the value.
193
+ content.remove_prefix (content.find (' \n ' , closing_quote + 1 ));
194
+ }
195
+ } else {
196
+ // Regular key value pair.
197
+ // Example: `KEY=this is value`
198
+ auto newline = content.find (' \n ' );
199
+
200
+ if (newline != std::string_view::npos) {
201
+ value = content.substr (0 , newline);
202
+ auto hash_character = value.find (' #' );
203
+ // Check if there is a comment in the line
204
+ // Example: KEY=value # comment
205
+ // The value pair should be `value`
206
+ if (hash_character != std::string_view::npos) {
207
+ value = content.substr (0 , hash_character);
208
+ }
209
+ value = trim_spaces (value);
210
+ content.remove_prefix (newline);
211
+ store_.insert_or_assign (std::string (key), value);
212
+ } else {
213
+ break ;
214
+ }
215
+ }
133
216
}
134
217
}
135
218
@@ -179,13 +262,4 @@ void Dotenv::AssignNodeOptionsIfAvailable(std::string* node_options) {
179
262
}
180
263
}
181
264
182
- std::string_view Dotenv::trim_quotes (std::string_view str) {
183
- static const std::unordered_set<char > quotes = {' "' , ' \' ' , ' `' };
184
- if (str.size () >= 2 && quotes.count (str.front ()) &&
185
- quotes.count (str.back ())) {
186
- str = str.substr (1 , str.size () - 2 );
187
- }
188
- return str;
189
- }
190
-
191
265
} // namespace node
0 commit comments