@@ -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,129 @@ 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
+ content = trim_spaces (content);
109
+
110
+ std::string_view key;
111
+ std::string_view value;
112
+
113
+ while (!content.empty ()) {
114
+ // Skip empty lines and comments
115
+ if (content.front () == ' \n ' || content.front () == ' #' ) {
116
+ auto newline = content.find (' \n ' );
117
+ if (newline != std::string_view::npos) {
118
+ content.remove_prefix (newline + 1 );
119
+ continue ;
120
+ }
121
+ }
122
+
123
+ // If there is no equal character, then ignore everything
124
+ auto equal = content.find (' =' );
125
+ if (equal == std::string_view::npos) {
126
+ break ;
127
+ }
107
128
108
- std::smatch match ;
109
- while ( std::regex_search (lines, match, LINE)) {
110
- const std::string key = match[ 1 ]. str ( );
129
+ key = content. substr ( 0 , equal) ;
130
+ content. remove_prefix (equal + 1 );
131
+ key = trim_spaces (key );
111
132
112
- // Default undefined or null to an empty string
113
- std::string value = match[2 ].str ();
133
+ if (key.empty ()) {
134
+ break ;
135
+ }
114
136
115
- // Remove leading whitespaces
116
- value.erase (0 , value.find_first_not_of (" \t " ));
137
+ // Remove export prefix from key
138
+ auto have_export = key.compare (0 , 7 , " export " ) == 0 ;
139
+ if (have_export) {
140
+ key.remove_prefix (7 );
141
+ }
117
142
118
- // Remove trailing whitespaces
119
- if (!value .empty ()) {
120
- value. erase (value. find_last_not_of ( " \t " ) + 1 ) ;
143
+ // SAFETY: Content is guaranteed to have at least one character
144
+ if (content .empty ()) {
145
+ break ;
121
146
}
122
147
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 " );
148
+ // Expand new line if \n it's inside double quotes
149
+ // Example: EXPAND_NEWLINES = 'expand\nnew\nlines'
150
+ if (content.front () == ' "' ) {
151
+ auto closing_quote = content.find (content.front (), 1 );
152
+ if (closing_quote != std::string_view::npos) {
153
+ value = content.substr (1 , closing_quote - 1 );
154
+ std::string multi_line_value = std::string (value);
155
+
156
+ size_t pos = 0 ;
157
+ while ((pos = multi_line_value.find (" \\ n" , pos)) !=
158
+ std::string_view::npos) {
159
+ multi_line_value.replace (pos, 2 , " \n " );
160
+ pos += 1 ;
161
+ }
162
+
163
+ store_.insert_or_assign (std::string (key), multi_line_value);
164
+ content.remove_prefix (content.find (' \n ' , closing_quote + 1 ));
165
+ continue ;
166
+ }
126
167
}
127
168
128
- // Remove surrounding quotes
129
- value = trim_quotes (value);
169
+ // Check if the value is wrapped in quotes, single quotes or backticks
170
+ if ((content.front () == ' \' ' || content.front () == ' "' ||
171
+ content.front () == ' `' )) {
172
+ auto closing_quote = content.find (content.front (), 1 );
173
+
174
+ // Check if the closing quote is not found
175
+ // Example: KEY="value
176
+ if (closing_quote == std::string_view::npos) {
177
+ // Check if newline exist. If it does, take the entire line as the value
178
+ // Example: KEY="value\nKEY2=value2
179
+ // The value pair should be `"value`
180
+ auto newline = content.find (' \n ' );
181
+ if (newline != std::string_view::npos) {
182
+ value = content.substr (0 , newline);
183
+ store_.insert_or_assign (std::string (key), value);
184
+ content.remove_prefix (newline);
185
+ }
186
+ } else {
187
+ // Example: KEY="value"
188
+ value = content.substr (1 , closing_quote - 1 );
189
+ store_.insert_or_assign (std::string (key), value);
190
+ // Select the first newline after the closing quotation mark
191
+ // since there could be newline characters inside the value.
192
+ content.remove_prefix (content.find (' \n ' , closing_quote + 1 ));
193
+ }
194
+ } else {
195
+ // Regular key value pair.
196
+ // Example: `KEY=this is value`
197
+ auto newline = content.find (' \n ' );
198
+
199
+ if (newline != std::string_view::npos) {
200
+ value = content.substr (0 , newline);
201
+ auto hash_character = value.find (' #' );
202
+ // Check if there is a comment in the line
203
+ // Example: KEY=value # comment
204
+ // The value pair should be `value`
205
+ if (hash_character != std::string_view::npos) {
206
+ value = content.substr (0 , hash_character);
207
+ }
208
+ content.remove_prefix (newline);
209
+ } else {
210
+ // In case the last line is a single key/value pair
211
+ // Example: KEY=VALUE (without a newline at the EOF)
212
+ value = content.substr (0 );
213
+ }
130
214
131
- store_.insert_or_assign (std::string (key), value);
132
- lines = match.suffix ();
215
+ value = trim_spaces (value);
216
+ store_.insert_or_assign (std::string (key), value);
217
+ }
133
218
}
134
219
}
135
220
@@ -179,13 +264,4 @@ void Dotenv::AssignNodeOptionsIfAvailable(std::string* node_options) {
179
264
}
180
265
}
181
266
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
267
} // namespace node
0 commit comments