1
1
#include " node_dotenv.h"
2
- #include < regex> // NOLINT(build/c++11)
3
2
#include < unordered_set>
4
3
#include " env-inl.h"
5
4
#include " node_file.h"
@@ -12,15 +11,6 @@ using v8::NewStringType;
12
11
using v8::Object;
13
12
using v8::String;
14
13
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
14
std::vector<std::string> Dotenv::GetPathFromArgs (
25
15
const std::vector<std::string>& args) {
26
16
const auto find_match = [](const std::string& arg) {
@@ -101,35 +91,137 @@ Local<Object> Dotenv::ToObject(Environment* env) {
101
91
return result;
102
92
}
103
93
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
+ }
107
132
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 );
111
136
112
- // Default undefined or null to an empty string
113
- std::string value = match[2 ].str ();
137
+ if (key.empty ()) {
138
+ break ;
139
+ }
114
140
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
+ }
117
146
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 ;
121
153
}
122
154
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
+ }
126
174
}
127
175
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
+ }
130
221
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
+ }
133
225
}
134
226
}
135
227
@@ -179,13 +271,4 @@ void Dotenv::AssignNodeOptionsIfAvailable(std::string* node_options) {
179
271
}
180
272
}
181
273
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
274
} // namespace node
0 commit comments