11#include " node_dotenv.h"
2- #include < regex> // NOLINT(build/c++11)
32#include < unordered_set>
43#include " env-inl.h"
54#include " node_file.h"
@@ -12,15 +11,6 @@ using v8::NewStringType;
1211using v8::Object;
1312using v8::String;
1413
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-
2414std::vector<std::string> Dotenv::GetPathFromArgs (
2515 const std::vector<std::string>& args) {
2616 const auto find_match = [](const std::string& arg) {
@@ -101,35 +91,137 @@ Local<Object> Dotenv::ToObject(Environment* env) {
10191 return result;
10292}
10393
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 " );
94+ std::string_view trim_spaces (std::string_view input) {
95+ if (input.empty ()) return " " ;
96+ if (input.front () == ' ' ) {
97+ input.remove_prefix (input.find_first_not_of (' ' ));
98+ }
99+ if (!input.empty () && input.back () == ' ' ) {
100+ input = input.substr (0 , input.find_last_not_of (' ' ) + 1 );
101+ }
102+ return input;
103+ }
104+
105+ void Dotenv::ParseContent (const std::string_view input) {
106+ std::string lines (input);
107+
108+ // Handle windows newlines "\r\n": remove "\r" and keep only "\n"
109+ lines.erase (std::remove (lines.begin (), lines.end (), ' \r ' ), lines.end ());
110+
111+ std::string_view content = lines;
112+ content = trim_spaces (content);
113+
114+ std::string_view key;
115+ std::string_view value;
116+
117+ while (!content.empty ()) {
118+ // Skip empty lines and comments
119+ if (content.front () == ' \n ' || content.front () == ' #' ) {
120+ auto newline = content.find (' \n ' );
121+ if (newline != std::string_view::npos) {
122+ content.remove_prefix (newline + 1 );
123+ continue ;
124+ }
125+ }
126+
127+ // If there is no equal character, then ignore everything
128+ auto equal = content.find (' =' );
129+ if (equal == std::string_view::npos) {
130+ break ;
131+ }
107132
108- std::smatch match ;
109- while ( std::regex_search (lines, match, LINE)) {
110- const std::string key = match[ 1 ]. str ( );
133+ key = content. substr ( 0 , equal) ;
134+ content. remove_prefix (equal + 1 );
135+ key = trim_spaces (key );
111136
112- // Default undefined or null to an empty string
113- std::string value = match[2 ].str ();
137+ if (key.empty ()) {
138+ break ;
139+ }
114140
115- // Remove leading whitespaces
116- value.erase (0 , value.find_first_not_of (" \t " ));
141+ // Remove export prefix from key
142+ auto have_export = key.compare (0 , 7 , " export " ) == 0 ;
143+ if (have_export) {
144+ key.remove_prefix (7 );
145+ }
117146
118- // Remove trailing whitespaces
119- if (!value.empty ()) {
120- value.erase (value.find_last_not_of (" \t " ) + 1 );
147+ // SAFETY: Content is guaranteed to have at least one character
148+ if (content.empty ()) {
149+ // In case the last line is a single key without value
150+ // Example: KEY= (without a newline at the EOF)
151+ store_.insert_or_assign (std::string (key), " " );
152+ break ;
121153 }
122154
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 " );
155+ // Expand new line if \n it's inside double quotes
156+ // Example: EXPAND_NEWLINES = 'expand\nnew\nlines'
157+ if (content.front () == ' "' ) {
158+ auto closing_quote = content.find (content.front (), 1 );
159+ if (closing_quote != std::string_view::npos) {
160+ value = content.substr (1 , closing_quote - 1 );
161+ std::string multi_line_value = std::string (value);
162+
163+ size_t pos = 0 ;
164+ while ((pos = multi_line_value.find (" \\ n" , pos)) !=
165+ std::string_view::npos) {
166+ multi_line_value.replace (pos, 2 , " \n " );
167+ pos += 1 ;
168+ }
169+
170+ store_.insert_or_assign (std::string (key), multi_line_value);
171+ content.remove_prefix (content.find (' \n ' , closing_quote + 1 ));
172+ continue ;
173+ }
126174 }
127175
128- // Remove surrounding quotes
129- value = trim_quotes (value);
176+ // Check if the value is wrapped in quotes, single quotes or backticks
177+ if ((content.front () == ' \' ' || content.front () == ' "' ||
178+ content.front () == ' `' )) {
179+ auto closing_quote = content.find (content.front (), 1 );
180+
181+ // Check if the closing quote is not found
182+ // Example: KEY="value
183+ if (closing_quote == std::string_view::npos) {
184+ // Check if newline exist. If it does, take the entire line as the value
185+ // Example: KEY="value\nKEY2=value2
186+ // The value pair should be `"value`
187+ auto newline = content.find (' \n ' );
188+ if (newline != std::string_view::npos) {
189+ value = content.substr (0 , newline);
190+ store_.insert_or_assign (std::string (key), value);
191+ content.remove_prefix (newline);
192+ }
193+ } else {
194+ // Example: KEY="value"
195+ value = content.substr (1 , closing_quote - 1 );
196+ store_.insert_or_assign (std::string (key), value);
197+ // Select the first newline after the closing quotation mark
198+ // since there could be newline characters inside the value.
199+ content.remove_prefix (content.find (' \n ' , closing_quote + 1 ));
200+ }
201+ } else {
202+ // Regular key value pair.
203+ // Example: `KEY=this is value`
204+ auto newline = content.find (' \n ' );
205+
206+ if (newline != std::string_view::npos) {
207+ value = content.substr (0 , newline);
208+ auto hash_character = value.find (' #' );
209+ // Check if there is a comment in the line
210+ // Example: KEY=value # comment
211+ // The value pair should be `value`
212+ if (hash_character != std::string_view::npos) {
213+ value = content.substr (0 , hash_character);
214+ }
215+ content.remove_prefix (newline);
216+ } else {
217+ // In case the last line is a single key/value pair
218+ // Example: KEY=VALUE (without a newline at the EOF)
219+ value = content.substr (0 );
220+ }
130221
131- store_.insert_or_assign (std::string (key), value);
132- lines = match.suffix ();
222+ value = trim_spaces (value);
223+ store_.insert_or_assign (std::string (key), value);
224+ }
133225 }
134226}
135227
@@ -179,13 +271,4 @@ void Dotenv::AssignNodeOptionsIfAvailable(std::string* node_options) {
179271 }
180272}
181273
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-
191274} // namespace node
0 commit comments