@@ -102,8 +102,11 @@ MaybeLocal<Object> Dotenv::ToObject(Environment* env) const {
102102 return scope.Escape (result);
103103}
104104
105- // Removes space characters (spaces, tabs and newlines) from
106- // the start and end of a given input string
105+ // Removes leading and trailing spaces from a string_view.
106+ // Returns an empty string_view if the input is empty.
107+ // Example:
108+ // trim_spaces(" hello ") -> "hello"
109+ // trim_spaces("") -> ""
107110std::string_view trim_spaces (std::string_view input) {
108111 if (input.empty ()) return " " ;
109112
@@ -135,47 +138,62 @@ void Dotenv::ParseContent(const std::string_view input) {
135138 while (!content.empty ()) {
136139 // Skip empty lines and comments
137140 if (content.front () == ' \n ' || content.front () == ' #' ) {
141+ // Check if the first character of the content is a newline or a hash
138142 auto newline = content.find (' \n ' );
139143 if (newline != std::string_view::npos) {
140- content.remove_prefix (newline + 1 );
141- continue ;
142- }
143- }
144-
145- // If there is no equal character, then ignore everything
146- auto equal = content.find (' =' );
147- if (equal == std::string_view::npos) {
148- auto newline = content.find (' \n ' );
149- if (newline != std::string_view::npos) {
150- // If we used `newline` only,
151- // the '\n' might remain and cause an empty-line parse
144+ // Remove everything up to and including the newline character
152145 content.remove_prefix (newline + 1 );
153146 } else {
147+ // If no newline is found, clear the content
154148 content = {};
155149 }
156- // No valid data here, skip to next line
150+
151+ // Skip the remaining code in the loop and continue with the next
152+ // iteration.
157153 continue ;
158154 }
159155
160- key = content.substr (0 , equal);
161- content.remove_prefix (equal + 1 );
156+ // Find the next equals sign or newline in a single pass.
157+ // This optimizes the search by avoiding multiple iterations.
158+ auto equal_or_newline = content.find_first_of (" =\n " );
159+
160+ // If we found nothing or found a newline before equals, the line is invalid
161+ if (equal_or_newline == std::string_view::npos ||
162+ content.at (equal_or_newline) == ' \n ' ) {
163+ if (equal_or_newline != std::string_view::npos) {
164+ content.remove_prefix (equal_or_newline + 1 );
165+ content = trim_spaces (content);
166+ continue ;
167+ }
168+ break ;
169+ }
170+
171+ // We found an equals sign, extract the key
172+ key = content.substr (0 , equal_or_newline);
173+ content.remove_prefix (equal_or_newline + 1 );
162174 key = trim_spaces (key);
163175
164- // If the value is not present (e.g. KEY=) set is to an empty string
176+ // If the value is not present (e.g. KEY=) set it to an empty string
165177 if (content.empty () || content.front () == ' \n ' ) {
166178 store_.insert_or_assign (std::string (key), " " );
167179 continue ;
168180 }
169181
170182 content = trim_spaces (content);
171183
172- if (key.empty ()) {
173- break ;
174- }
184+ // Skip lines with empty keys after trimming spaces.
185+ // Examples of invalid keys that would be skipped:
186+ // =value
187+ // " "=value
188+ if (key.empty ()) continue ;
175189
176- // Remove export prefix from key
190+ // Remove export prefix from key and ensure proper spacing.
191+ // Example: export FOO=bar -> FOO=bar
177192 if (key.starts_with (" export " )) {
178193 key.remove_prefix (7 );
194+ // Trim spaces after removing export prefix to handle cases like:
195+ // export FOO=bar
196+ key = trim_spaces (key);
179197 }
180198
181199 // SAFETY: Content is guaranteed to have at least one character
@@ -194,6 +212,7 @@ void Dotenv::ParseContent(const std::string_view input) {
194212 value = content.substr (1 , closing_quote - 1 );
195213 std::string multi_line_value = std::string (value);
196214
215+ // Replace \n with actual newlines in double-quoted strings
197216 size_t pos = 0 ;
198217 while ((pos = multi_line_value.find (" \\ n" , pos)) !=
199218 std::string_view::npos) {
@@ -206,15 +225,17 @@ void Dotenv::ParseContent(const std::string_view input) {
206225 if (newline != std::string_view::npos) {
207226 content.remove_prefix (newline + 1 );
208227 } else {
228+ // In case the last line is a single key/value pair
229+ // Example: KEY=VALUE (without a newline at the EOF
209230 content = {};
210231 }
211232 continue ;
212233 }
213234 }
214235
215- // Check if the value is wrapped in quotes, single quotes or backticks
216- if (( content.front () == ' \' ' || content.front () == ' "' ||
217- content.front () == ' `' ) ) {
236+ // Handle quoted values (single quotes, double quotes, backticks)
237+ if (content.front () == ' \' ' || content.front () == ' "' ||
238+ content.front () == ' `' ) {
218239 auto closing_quote = content.find (content.front (), 1 );
219240
220241 // Check if the closing quote is not found
@@ -228,13 +249,16 @@ void Dotenv::ParseContent(const std::string_view input) {
228249 value = content.substr (0 , newline);
229250 store_.insert_or_assign (std::string (key), value);
230251 content.remove_prefix (newline + 1 );
252+ } else {
253+ // No newline - take rest of content
254+ value = content;
255+ store_.insert_or_assign (std::string (key), value);
256+ break ;
231257 }
232258 } else {
233- // Example: KEY="value"
259+ // Found closing quote - take content between quotes
234260 value = content.substr (1 , closing_quote - 1 );
235261 store_.insert_or_assign (std::string (key), value);
236- // Select the first newline after the closing quotation mark
237- // since there could be newline characters inside the value.
238262 auto newline = content.find (' \n ' , closing_quote + 1 );
239263 if (newline != std::string_view::npos) {
240264 // Use +1 to discard the '\n' itself => next line
@@ -257,13 +281,13 @@ void Dotenv::ParseContent(const std::string_view input) {
257281 // Example: KEY=value # comment
258282 // The value pair should be `value`
259283 if (hash_character != std::string_view::npos) {
260- value = content .substr (0 , hash_character);
284+ value = value .substr (0 , hash_character);
261285 }
262- store_.insert_or_assign (std::string (key), trim_spaces (value));
286+ value = trim_spaces (value);
287+ store_.insert_or_assign (std::string (key), std::string (value));
263288 content.remove_prefix (newline + 1 );
264289 } else {
265- // In case the last line is a single key/value pair
266- // Example: KEY=VALUE (without a newline at the EOF)
290+ // Last line without newline
267291 value = content;
268292 auto hash_char = value.find (' #' );
269293 if (hash_char != std::string_view::npos) {
@@ -272,9 +296,9 @@ void Dotenv::ParseContent(const std::string_view input) {
272296 store_.insert_or_assign (std::string (key), trim_spaces (value));
273297 content = {};
274298 }
275-
276- store_.insert_or_assign (std::string (key), trim_spaces (value));
277299 }
300+
301+ content = trim_spaces (content);
278302 }
279303}
280304
0 commit comments