@@ -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
@@ -134,48 +137,69 @@ void Dotenv::ParseContent(const std::string_view input) {
134137
135138 while (!content.empty ()) {
136139 // Skip empty lines and comments
140+ // Example:
141+ // # This is a comment
137142 if (content.front () == ' \n ' || content.front () == ' #' ) {
138- auto newline = content.find (' \n ' );
139- if (newline != std::string_view::npos) {
140- content.remove_prefix (newline + 1 );
141- continue ;
143+ // Check if the first character of the content is a newline or a hash
144+ if (content.front () == ' \n ' ) {
145+ // If the first character is a newline, remove it
146+ content.remove_prefix (1 );
147+ } else {
148+ // If the first character is a hash, find the next newline character
149+ auto newline = content.find (' \n ' );
150+ if (newline != std::string_view::npos) {
151+ // If a newline is found, remove the comment line including the
152+ // newline character.
153+ content.remove_prefix (newline + 1 );
154+ }
142155 }
156+
157+ // Skip the remaining code in the loop and continue with the next
158+ // iteration.
159+ continue ;
143160 }
144161
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
152- content.remove_prefix (newline + 1 );
153- } else {
154- content = {};
162+ // Find the next equals sign or newline in a single pass.
163+ // This optimizes the search by avoiding multiple iterations.
164+ auto equal_or_newline = content.find_first_of (" =\n " );
165+
166+ // If we found nothing or found a newline before equals, the line is invalid
167+ if (equal_or_newline == std::string_view::npos ||
168+ content.at (equal_or_newline) == ' \n ' ) {
169+ if (equal_or_newline != std::string_view::npos) {
170+ content.remove_prefix (equal_or_newline + 1 );
171+ content = trim_spaces (content);
172+ continue ;
155173 }
156- // No valid data here, skip to next line
157- continue ;
174+ break ;
158175 }
159176
160- key = content.substr (0 , equal);
161- content.remove_prefix (equal + 1 );
177+ // We found an equals sign, extract the key
178+ key = content.substr (0 , equal_or_newline);
179+ content.remove_prefix (equal_or_newline + 1 );
162180 key = trim_spaces (key);
163181
164- // If the value is not present (e.g. KEY=) set is to an empty string
182+ // If the value is not present (e.g. KEY=) set it to an empty string
165183 if (content.empty () || content.front () == ' \n ' ) {
166184 store_.insert_or_assign (std::string (key), " " );
167185 continue ;
168186 }
169187
170188 content = trim_spaces (content);
171189
172- if (key.empty ()) {
173- break ;
174- }
190+ // Skip lines with empty keys after trimming spaces.
191+ // Examples of invalid keys that would be skipped:
192+ // =value
193+ // " "=value
194+ if (key.empty ()) continue ;
175195
176- // Remove export prefix from key
196+ // Remove export prefix from key and ensure proper spacing.
197+ // Example: export FOO=bar -> FOO=bar
177198 if (key.starts_with (" export " )) {
178199 key.remove_prefix (7 );
200+ // Trim spaces after removing export prefix to handle cases like:
201+ // export FOO=bar
202+ key = trim_spaces (key);
179203 }
180204
181205 // SAFETY: Content is guaranteed to have at least one character
@@ -194,6 +218,7 @@ void Dotenv::ParseContent(const std::string_view input) {
194218 value = content.substr (1 , closing_quote - 1 );
195219 std::string multi_line_value = std::string (value);
196220
221+ // Replace \n with actual newlines in double-quoted strings
197222 size_t pos = 0 ;
198223 while ((pos = multi_line_value.find (" \\ n" , pos)) !=
199224 std::string_view::npos) {
@@ -212,9 +237,9 @@ void Dotenv::ParseContent(const std::string_view input) {
212237 }
213238 }
214239
215- // Check if the value is wrapped in quotes, single quotes or backticks
216- if (( content.front () == ' \' ' || content.front () == ' "' ||
217- content.front () == ' `' ) ) {
240+ // Handle quoted values (single quotes, double quotes, backticks)
241+ if (content.front () == ' \' ' || content.front () == ' "' ||
242+ content.front () == ' `' ) {
218243 auto closing_quote = content.find (content.front (), 1 );
219244
220245 // Check if the closing quote is not found
@@ -228,13 +253,16 @@ void Dotenv::ParseContent(const std::string_view input) {
228253 value = content.substr (0 , newline);
229254 store_.insert_or_assign (std::string (key), value);
230255 content.remove_prefix (newline + 1 );
256+ } else {
257+ // No newline - take rest of content
258+ value = content;
259+ store_.insert_or_assign (std::string (key), value);
260+ break ;
231261 }
232262 } else {
233- // Example: KEY="value"
263+ // Found closing quote - take content between quotes
234264 value = content.substr (1 , closing_quote - 1 );
235265 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.
238266 auto newline = content.find (' \n ' , closing_quote + 1 );
239267 if (newline != std::string_view::npos) {
240268 // Use +1 to discard the '\n' itself => next line
@@ -257,13 +285,13 @@ void Dotenv::ParseContent(const std::string_view input) {
257285 // Example: KEY=value # comment
258286 // The value pair should be `value`
259287 if (hash_character != std::string_view::npos) {
260- value = content .substr (0 , hash_character);
288+ value = value .substr (0 , hash_character);
261289 }
262- store_.insert_or_assign (std::string (key), trim_spaces (value));
290+ value = trim_spaces (value);
291+ store_.insert_or_assign (std::string (key), std::string (value));
263292 content.remove_prefix (newline + 1 );
264293 } else {
265- // In case the last line is a single key/value pair
266- // Example: KEY=VALUE (without a newline at the EOF)
294+ // Last line without newline
267295 value = content;
268296 auto hash_char = value.find (' #' );
269297 if (hash_char != std::string_view::npos) {
@@ -272,9 +300,9 @@ void Dotenv::ParseContent(const std::string_view input) {
272300 store_.insert_or_assign (std::string (key), trim_spaces (value));
273301 content = {};
274302 }
275-
276- store_.insert_or_assign (std::string (key), trim_spaces (value));
277303 }
304+
305+ content = trim_spaces (content);
278306 }
279307}
280308
0 commit comments