@@ -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 ' 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" 
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 () == ' \' ' front () == ' "' 
217-           content.front () == ' `' ) ) {
236+     //  Handle quoted values (single  quotes, double  quotes,  backticks) 
237+     if  (content.front () == ' \' ' 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 ' 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