@@ -12,15 +12,6 @@ using v8::NewStringType;
1212using v8::Object;
1313using v8::String;
1414
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-
2415std::vector<std::string> Dotenv::GetPathFromArgs (
2516 const std::vector<std::string>& args) {
2617 const auto find_match = [](const std::string& arg) {
@@ -101,35 +92,127 @@ Local<Object> Dotenv::ToObject(Environment* env) {
10192 return result;
10293}
10394
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+
109+ std::string_view key;
110+ std::string_view value;
107111
108- std::smatch match;
109- while (std::regex_search (lines, match, LINE)) {
110- const std::string key = match[1 ].str ();
112+ content = trim_spaces (content);
113+
114+ while (!content.empty ()) {
115+ // Skip empty lines and comments
116+ if (content.front () == ' \n ' || content.front () == ' #' ) {
117+ auto newline = content.find (' \n ' );
118+ if (newline != std::string_view::npos) {
119+ content.remove_prefix (newline + 1 );
120+ continue ;
121+ }
122+ }
123+
124+ // If there is no equal character, then ignore everything
125+ auto equal = content.find (' =' );
126+ if (equal == std::string_view::npos) {
127+ break ;
128+ }
111129
112- // Default undefined or null to an empty string
113- std::string value = match[2 ].str ();
130+ key = content.substr (0 , equal);
131+ content.remove_prefix (equal + 1 );
132+ key = trim_spaces (key);
114133
115- // Remove leading whitespaces
116- value.erase (0 , value.find_first_not_of (" \t " ));
134+ if (key.empty ()) {
135+ break ;
136+ }
117137
118- // Remove trailing whitespaces
119- if (!value.empty ()) {
120- value.erase (value.find_last_not_of (" \t " ) + 1 );
138+ // Remove export prefix from key
139+ auto have_export = key.compare (0 , 7 , " export " ) == 0 ;
140+ if (have_export) {
141+ key = key.substr (7 );
121142 }
122143
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 " ) ;
144+ // SAFETY: Content is guaranteed to have at least one character
145+ if (content. empty ()) {
146+ break ;
126147 }
127148
128- // Remove surrounding quotes
129- value = trim_quotes (value);
149+ // Expand new line if \n it's inside double quotes
150+ // Example: EXPAND_NEWLINES = 'expand\nnew\nlines'
151+ if (content.front () == ' "' ) {
152+ auto closing_quote = content.find (content.front (), 1 );
153+ value = content.substr (1 , closing_quote - 1 );
154+ if (closing_quote != std::string_view::npos) {
155+ std::string multi_line_value = std::string (value);
156+
157+ size_t pos = 0 ;
158+ while ((pos = multi_line_value.find (" \\ n" , pos)) !=
159+ std::string_view::npos) {
160+ multi_line_value.replace (pos, 2 , " \n " );
161+ pos += 1 ;
162+ }
163+
164+ store_.insert_or_assign (std::string (key), multi_line_value);
165+ content.remove_prefix (content.find (' \n ' , closing_quote + 1 ));
166+ continue ;
167+ }
168+ }
130169
131- store_.insert_or_assign (std::string (key), value);
132- lines = match.suffix ();
170+ // Check if the value is wrapped in quotes, single quotes or backticks
171+ if ((content.front () == ' \' ' || content.front () == ' "' ||
172+ content.front () == ' `' )) {
173+ auto closing_quote = content.find (content.front (), 1 );
174+
175+ // Check if the closing quote is not found
176+ // Example: KEY="value
177+ if (closing_quote == std::string_view::npos) {
178+ // Check if newline exist. If it does, take the entire line as the value
179+ // Example: KEY="value\nKEY2=value2
180+ // The value pair should be `"value`
181+ auto newline = content.find (' \n ' );
182+ if (newline != std::string_view::npos) {
183+ value = content.substr (0 , newline);
184+ store_.insert_or_assign (std::string (key), value);
185+ content.remove_prefix (newline);
186+ }
187+ } else {
188+ // Example: KEY="value"
189+ value = content.substr (1 , closing_quote - 1 );
190+ store_.insert_or_assign (std::string (key), value);
191+ // Select the first newline after the closing quotation mark
192+ // since there could be newline characters inside the value.
193+ content.remove_prefix (content.find (' \n ' , closing_quote + 1 ));
194+ }
195+ } else {
196+ // Regular key value pair.
197+ // Example: `KEY=this is value`
198+ auto newline = content.find (' \n ' );
199+
200+ if (newline != std::string_view::npos) {
201+ value = content.substr (0 , newline);
202+ auto hash_character = value.find (' #' );
203+ // Check if there is a comment in the line
204+ // Example: KEY=value # comment
205+ // The value pair should be `value`
206+ if (hash_character != std::string_view::npos) {
207+ value = content.substr (0 , hash_character);
208+ }
209+ value = trim_spaces (value);
210+ content.remove_prefix (newline);
211+ store_.insert_or_assign (std::string (key), value);
212+ } else {
213+ break ;
214+ }
215+ }
133216 }
134217}
135218
@@ -179,13 +262,4 @@ void Dotenv::AssignNodeOptionsIfAvailable(std::string* node_options) {
179262 }
180263}
181264
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-
191265} // namespace node
0 commit comments