@@ -87,6 +87,17 @@ Local<Object> Dotenv::ToObject(Environment* env) const {
87
87
return result;
88
88
}
89
89
90
+ std::string_view trim_quotes (std::string_view input) {
91
+ if (input.empty ()) return " " ;
92
+ auto first = input.front ();
93
+ if ((first == ' \' ' || first == ' "' || first == ' `' ) &&
94
+ input.back () == first) {
95
+ input = input.substr (1 , input.size () - 2 );
96
+ }
97
+
98
+ return input;
99
+ }
100
+
90
101
std::string_view trim_spaces (std::string_view input) {
91
102
if (input.empty ()) return " " ;
92
103
if (input.front () == ' ' ) {
@@ -98,127 +109,99 @@ std::string_view trim_spaces(std::string_view input) {
98
109
return input;
99
110
}
100
111
101
- void Dotenv::ParseContent (const std::string_view input) {
102
- std::string lines (input);
112
+ std::string_view parse_key (std::string_view key) {
113
+ key = trim_spaces (key);
114
+ if (key.empty ()) return key;
103
115
104
- // Handle windows newlines "\r\n": remove "\r" and keep only "\n"
105
- lines.erase (std::remove (lines.begin (), lines.end (), ' \r ' ), lines.end ());
116
+ if (key.starts_with (" export " )) {
117
+ key.remove_prefix (7 );
118
+ }
119
+ return key;
120
+ }
106
121
107
- std::string_view content = lines;
108
- content = trim_spaces (content);
122
+ std::string parse_value (std::string_view value) {
123
+ value = trim_spaces (value);
124
+ if (value.empty ()) return " " ;
125
+
126
+ auto trimmed = trim_quotes (value);
127
+ if (value.front () == ' \" ' && value.back () == ' \" ' ) {
128
+ // Expand \n to newline in double-quote strings
129
+ size_t pos = 0 ;
130
+ auto expanded = std::string (trimmed);
131
+ while ((pos = expanded.find (" \\ n" , pos)) != std::string_view::npos) {
132
+ expanded.replace (pos, 2 , " \n " );
133
+ pos += 1 ;
134
+ }
135
+ return expanded;
136
+ } else {
137
+ return std::string (trimmed);
138
+ }
139
+ }
109
140
141
+ /* *
142
+ * Parse the content of a .env file.
143
+ * We want to be compatible with motdotla/dotenv js package,
144
+ * so some edge-cases might be handled differently than you expect.
145
+ *
146
+ * Check the test cases in test/cctest/test_dotenv.cc for more details.
147
+ */
148
+ void Dotenv::ParseContent (const std::string_view input) {
110
149
std::string_view key;
111
150
std::string_view value;
112
151
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 ;
152
+ char quote = 0 ;
153
+ bool inComment = false ;
154
+ std::string::size_type start = 0 ;
155
+ std::string::size_type end = 0 ;
156
+
157
+ for (std::string::size_type i = 0 ; i < input.size (); i++) {
158
+ char c = input[i];
159
+ // Finished parsing a new key
160
+ if (!inComment && c == ' =' && key.empty ()) {
161
+ key = parse_key (input.substr (start, i - start));
162
+ while (i + 1 < input.size () && input[i + 1 ] == ' ' ) {
163
+ // Skip whitespace after key
164
+ i++;
120
165
}
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
- }
128
-
129
- key = content.substr (0 , equal);
130
- content.remove_prefix (equal + 1 );
131
- key = trim_spaces (key);
132
- content = trim_spaces (content);
133
-
134
- if (key.empty ()) {
135
- break ;
136
- }
137
-
138
- // Remove export prefix from key
139
- if (key.starts_with (" export " )) {
140
- key.remove_prefix (7 );
141
- }
142
-
143
- // SAFETY: Content is guaranteed to have at least one character
144
- if (content.empty ()) {
145
- // In case the last line is a single key without value
146
- // Example: KEY= (without a newline at the EOF)
147
- store_.insert_or_assign (std::string (key), " " );
148
- break ;
149
- }
150
-
151
- // Expand new line if \n it's inside double quotes
152
- // Example: EXPAND_NEWLINES = 'expand\nnew\nlines'
153
- if (content.front () == ' "' ) {
154
- auto closing_quote = content.find (content.front (), 1 );
155
- if (closing_quote != std::string_view::npos) {
156
- value = content.substr (1 , closing_quote - 1 );
157
- std::string multi_line_value = std::string (value);
158
-
159
- size_t pos = 0 ;
160
- while ((pos = multi_line_value.find (" \\ n" , pos)) !=
161
- std::string_view::npos) {
162
- multi_line_value.replace (pos, 2 , " \n " );
163
- pos += 1 ;
164
- }
165
-
166
- store_.insert_or_assign (std::string (key), multi_line_value);
167
- content.remove_prefix (content.find (' \n ' , closing_quote + 1 ));
168
- continue ;
166
+ start = i + 1 ;
167
+ end = i + 1 ;
168
+ continue ;
169
+ } else if (!inComment && (c == ' "' || c == ' \' ' || c == ' `' )) {
170
+ if (start == i) {
171
+ quote = c;
172
+ } else if (quote == c) {
173
+ quote = 0 ;
169
174
}
170
- }
171
175
172
- // Check if the value is wrapped in quotes, single quotes or backticks
173
- if ((content.front () == ' \' ' || content.front () == ' "' ||
174
- content.front () == ' `' )) {
175
- auto closing_quote = content.find (content.front (), 1 );
176
-
177
- // Check if the closing quote is not found
178
- // Example: KEY="value
179
- if (closing_quote == std::string_view::npos) {
180
- // Check if newline exist. If it does, take the entire line as the value
181
- // Example: KEY="value\nKEY2=value2
182
- // The value pair should be `"value`
183
- auto newline = content.find (' \n ' );
184
- if (newline != std::string_view::npos) {
185
- value = content.substr (0 , newline);
186
- store_.insert_or_assign (std::string (key), value);
187
- content.remove_prefix (newline);
188
- }
189
- } else {
190
- // Example: KEY="value"
191
- value = content.substr (1 , closing_quote - 1 );
192
- store_.insert_or_assign (std::string (key), value);
193
- // Select the first newline after the closing quotation mark
194
- // since there could be newline characters inside the value.
195
- content.remove_prefix (content.find (' \n ' , closing_quote + 1 ));
196
- }
197
- } else {
198
- // Regular key value pair.
199
- // Example: `KEY=this is value`
200
- auto newline = content.find (' \n ' );
201
-
202
- if (newline != std::string_view::npos) {
203
- value = content.substr (0 , newline);
204
- auto hash_character = value.find (' #' );
205
- // Check if there is a comment in the line
206
- // Example: KEY=value # comment
207
- // The value pair should be `value`
208
- if (hash_character != std::string_view::npos) {
209
- value = content.substr (0 , hash_character);
210
- }
211
- content.remove_prefix (newline);
212
- } else {
213
- // In case the last line is a single key/value pair
214
- // Example: KEY=VALUE (without a newline at the EOF)
215
- value = content.substr (0 );
176
+ end++;
177
+ } else if (!inComment && c == ' #' && quote == 0 ) {
178
+ end = i;
179
+ inComment = true ;
180
+ } else if ((c == ' \n ' || c == ' \r ' ) && quote == 0 ) {
181
+ if (!key.empty ()) {
182
+ auto value_str = parse_value (input.substr (start, end - start));
183
+ store_.insert_or_assign (std::string (key), value_str);
216
184
}
217
185
218
- value = trim_spaces (value);
219
- store_.insert_or_assign (std::string (key), value);
186
+ // Skip \n if it is a part of a \r
187
+ if (i + 1 < input.size () && input[i + 1 ] == ' \n ' ) {
188
+ i++;
189
+ }
190
+ start = i + 1 ;
191
+ end = start;
192
+ value = " " ;
193
+ key = " " ;
194
+ quote = 0 ;
195
+ inComment = false ;
196
+ } else if (!inComment) {
197
+ end++;
220
198
}
221
199
}
200
+
201
+ if (!key.empty ()) {
202
+ auto value_str = parse_value (input.substr (start, end - start));
203
+ store_.insert_or_assign (std::string (key), value_str);
204
+ }
222
205
}
223
206
224
207
Dotenv::ParseResult Dotenv::ParsePath (const std::string_view path) {
@@ -267,4 +250,13 @@ void Dotenv::AssignNodeOptionsIfAvailable(std::string* node_options) const {
267
250
}
268
251
}
269
252
253
+ std::optional<std::string> Dotenv::GetValue (const std::string_view key) const {
254
+ auto match = store_.find (key.data ());
255
+
256
+ if (match != store_.end ()) {
257
+ return std::optional<std::string>{match->second };
258
+ }
259
+ return std::nullopt;
260
+ }
261
+
270
262
} // namespace node
0 commit comments