@@ -12,15 +12,6 @@ using v8::NewStringType;
12
12
using v8::Object;
13
13
using v8::String;
14
14
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
-
24
15
std::vector<std::string> Dotenv::GetPathFromArgs (
25
16
const std::vector<std::string>& args) {
26
17
const auto find_match = [](const std::string& arg) {
@@ -101,35 +92,120 @@ Local<Object> Dotenv::ToObject(Environment* env) {
101
92
return result;
102
93
}
103
94
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;
107
111
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
+ }
111
129
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);
114
133
115
- // Remove leading whitespaces
116
- value.erase (0 , value.find_first_not_of (" \t " ));
134
+ if (key.empty ()) {
135
+ break ;
136
+ }
117
137
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 );
121
142
}
122
143
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 ;
126
147
}
127
148
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
+ auto multi_line_value =
156
+ std::regex_replace (std::string (value), std::regex (" \\\\ n" ), " \n " );
157
+ store_.insert_or_assign (std::string (key), multi_line_value);
158
+ content.remove_prefix (content.find (' \n ' , closing_quote + 1 ));
159
+ continue ;
160
+ }
161
+ }
130
162
131
- store_.insert_or_assign (std::string (key), value);
132
- lines = match.suffix ();
163
+ // Check if the value is wrapped in quotes, single quotes or backticks
164
+ if ((content.front () == ' \' ' || content.front () == ' "' ||
165
+ content.front () == ' `' )) {
166
+ auto closing_quote = content.find (content.front (), 1 );
167
+
168
+ // Check if the closing quote is not found
169
+ // Example: KEY="value
170
+ if (closing_quote == std::string_view::npos) {
171
+ // Check if newline exist. If it does, take the entire line as the value
172
+ // Example: KEY="value\nKEY2=value2
173
+ // The value pair should be `"value`
174
+ auto newline = content.find (' \n ' );
175
+ if (newline != std::string_view::npos) {
176
+ value = content.substr (0 , newline);
177
+ store_.insert_or_assign (std::string (key), value);
178
+ content.remove_prefix (newline);
179
+ }
180
+ } else {
181
+ // Example: KEY="value"
182
+ value = content.substr (1 , closing_quote - 1 );
183
+ store_.insert_or_assign (std::string (key), value);
184
+ // Select the first newline after the closing quotation mark
185
+ // since there could be newline characters inside the value.
186
+ content.remove_prefix (content.find (' \n ' , closing_quote + 1 ));
187
+ }
188
+ } else {
189
+ // Regular key value pair.
190
+ // Example: `KEY=this is value`
191
+ auto newline = content.find (' \n ' );
192
+
193
+ if (newline != std::string_view::npos) {
194
+ value = content.substr (0 , newline);
195
+ auto hash_character = value.find (' #' );
196
+ // Check if there is a comment in the line
197
+ // Example: KEY=value # comment
198
+ // The value pair should be `value`
199
+ if (hash_character != std::string_view::npos) {
200
+ value = content.substr (0 , hash_character);
201
+ }
202
+ value = trim_spaces (value);
203
+ content.remove_prefix (newline);
204
+ store_.insert_or_assign (std::string (key), value);
205
+ } else {
206
+ break ;
207
+ }
208
+ }
133
209
}
134
210
}
135
211
@@ -179,13 +255,4 @@ void Dotenv::AssignNodeOptionsIfAvailable(std::string* node_options) {
179
255
}
180
256
}
181
257
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
-
191
258
} // namespace node
0 commit comments