-
-
Notifications
You must be signed in to change notification settings - Fork 36.7k
src: make parsing compatible with motdotla/dotenv package #54215
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -87,6 +87,17 @@ Local<Object> Dotenv::ToObject(Environment* env) const { | |||||
| return result; | ||||||
| } | ||||||
|
|
||||||
| std::string_view trim_quotes(std::string_view input) { | ||||||
| if (input.empty()) return ""; | ||||||
| auto first = input.front(); | ||||||
| if ((first == '\'' || first == '"' || first == '`') && | ||||||
| input.back() == first) { | ||||||
| input = input.substr(1, input.size() - 2); | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A more readable way: input.remove_prefix(1);
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed |
||||||
| } | ||||||
|
|
||||||
| return input; | ||||||
| } | ||||||
|
|
||||||
| std::string_view trim_spaces(std::string_view input) { | ||||||
| if (input.empty()) return ""; | ||||||
| if (input.front() == ' ') { | ||||||
|
|
@@ -98,127 +109,99 @@ std::string_view trim_spaces(std::string_view input) { | |||||
| return input; | ||||||
| } | ||||||
|
|
||||||
| void Dotenv::ParseContent(const std::string_view input) { | ||||||
| std::string lines(input); | ||||||
| std::string_view parse_key(std::string_view key) { | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add a comment to what this function does?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||||||
| key = trim_spaces(key); | ||||||
| if (key.empty()) return key; | ||||||
|
|
||||||
| // Handle windows newlines "\r\n": remove "\r" and keep only "\n" | ||||||
| lines.erase(std::remove(lines.begin(), lines.end(), '\r'), lines.end()); | ||||||
| if (key.starts_with("export ")) { | ||||||
| key.remove_prefix(7); | ||||||
| } | ||||||
| return key; | ||||||
| } | ||||||
|
|
||||||
| std::string_view content = lines; | ||||||
| content = trim_spaces(content); | ||||||
| std::string parse_value(std::string_view value) { | ||||||
| value = trim_spaces(value); | ||||||
| if (value.empty()) return ""; | ||||||
|
|
||||||
| auto trimmed = trim_quotes(value); | ||||||
| if (value.front() == '\"' && value.back() == '\"') { | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add a documentation to here?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||||||
| // Expand \n to newline in double-quote strings | ||||||
| size_t pos = 0; | ||||||
| auto expanded = std::string(trimmed); | ||||||
| while ((pos = expanded.find("\\n", pos)) != std::string_view::npos) { | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. expanded is a string now.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍Done, thanks |
||||||
| expanded.replace(pos, 2, "\n"); | ||||||
| pos += 1; | ||||||
| } | ||||||
| return expanded; | ||||||
| } else { | ||||||
| return std::string(trimmed); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Parse the content of a .env file. | ||||||
| * We want to be compatible with motdotla/dotenv js package, | ||||||
| * so some edge-cases might be handled differently than you expect. | ||||||
| * | ||||||
| * Check the test cases in test/cctest/test_dotenv.cc for more details. | ||||||
| */ | ||||||
| void Dotenv::ParseContent(const std::string_view input) { | ||||||
| std::string_view key; | ||||||
| std::string_view value; | ||||||
|
|
||||||
| while (!content.empty()) { | ||||||
| // Skip empty lines and comments | ||||||
| if (content.front() == '\n' || content.front() == '#') { | ||||||
| auto newline = content.find('\n'); | ||||||
| if (newline != std::string_view::npos) { | ||||||
| content.remove_prefix(newline + 1); | ||||||
| continue; | ||||||
| char quote = 0; | ||||||
| bool inComment = false; | ||||||
| std::string::size_type start = 0; | ||||||
| std::string::size_type end = 0; | ||||||
|
|
||||||
| for (std::string::size_type i = 0; i < input.size(); i++) { | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You removed all of the comments in this function, and it is a lot less readable right now. I can't review it with the current state.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is basically a new function. Old one was doing a lot of back & forth searching for chars and newlines in string and it was hard for me to fit in a fix. This goes through the content of file only once and parses it char by char. |
||||||
| char c = input[i]; | ||||||
| // Finished parsing a new key | ||||||
| if (!inComment && c == '=' && key.empty()) { | ||||||
| key = parse_key(input.substr(start, i - start)); | ||||||
| while (i + 1 < input.size() && input[i + 1] == ' ') { | ||||||
| // Skip whitespace after key | ||||||
| i++; | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| // If there is no equal character, then ignore everything | ||||||
| auto equal = content.find('='); | ||||||
| if (equal == std::string_view::npos) { | ||||||
| break; | ||||||
| } | ||||||
|
|
||||||
| key = content.substr(0, equal); | ||||||
| content.remove_prefix(equal + 1); | ||||||
| key = trim_spaces(key); | ||||||
| content = trim_spaces(content); | ||||||
|
|
||||||
| if (key.empty()) { | ||||||
| break; | ||||||
| } | ||||||
|
|
||||||
| // Remove export prefix from key | ||||||
| if (key.starts_with("export ")) { | ||||||
| key.remove_prefix(7); | ||||||
| } | ||||||
|
|
||||||
| // SAFETY: Content is guaranteed to have at least one character | ||||||
| if (content.empty()) { | ||||||
| // In case the last line is a single key without value | ||||||
| // Example: KEY= (without a newline at the EOF) | ||||||
| store_.insert_or_assign(std::string(key), ""); | ||||||
| break; | ||||||
| } | ||||||
|
|
||||||
| // Expand new line if \n it's inside double quotes | ||||||
| // Example: EXPAND_NEWLINES = 'expand\nnew\nlines' | ||||||
| if (content.front() == '"') { | ||||||
| auto closing_quote = content.find(content.front(), 1); | ||||||
| if (closing_quote != std::string_view::npos) { | ||||||
| value = content.substr(1, closing_quote - 1); | ||||||
| std::string multi_line_value = std::string(value); | ||||||
|
|
||||||
| size_t pos = 0; | ||||||
| while ((pos = multi_line_value.find("\\n", pos)) != | ||||||
| std::string_view::npos) { | ||||||
| multi_line_value.replace(pos, 2, "\n"); | ||||||
| pos += 1; | ||||||
| } | ||||||
|
|
||||||
| store_.insert_or_assign(std::string(key), multi_line_value); | ||||||
| content.remove_prefix(content.find('\n', closing_quote + 1)); | ||||||
| continue; | ||||||
| start = i + 1; | ||||||
| end = i + 1; | ||||||
| continue; | ||||||
| } else if (!inComment && (c == '"' || c == '\'' || c == '`')) { | ||||||
| if (start == i) { | ||||||
| quote = c; | ||||||
| } else if (quote == c) { | ||||||
| quote = 0; | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| // Check if the value is wrapped in quotes, single quotes or backticks | ||||||
| if ((content.front() == '\'' || content.front() == '"' || | ||||||
| content.front() == '`')) { | ||||||
| auto closing_quote = content.find(content.front(), 1); | ||||||
|
|
||||||
| // Check if the closing quote is not found | ||||||
| // Example: KEY="value | ||||||
| if (closing_quote == std::string_view::npos) { | ||||||
| // Check if newline exist. If it does, take the entire line as the value | ||||||
| // Example: KEY="value\nKEY2=value2 | ||||||
| // The value pair should be `"value` | ||||||
| auto newline = content.find('\n'); | ||||||
| if (newline != std::string_view::npos) { | ||||||
| value = content.substr(0, newline); | ||||||
| store_.insert_or_assign(std::string(key), value); | ||||||
| content.remove_prefix(newline); | ||||||
| } | ||||||
| } else { | ||||||
| // Example: KEY="value" | ||||||
| value = content.substr(1, closing_quote - 1); | ||||||
| store_.insert_or_assign(std::string(key), value); | ||||||
| // Select the first newline after the closing quotation mark | ||||||
| // since there could be newline characters inside the value. | ||||||
| content.remove_prefix(content.find('\n', closing_quote + 1)); | ||||||
| } | ||||||
| } else { | ||||||
| // Regular key value pair. | ||||||
| // Example: `KEY=this is value` | ||||||
| auto newline = content.find('\n'); | ||||||
|
|
||||||
| if (newline != std::string_view::npos) { | ||||||
| value = content.substr(0, newline); | ||||||
| auto hash_character = value.find('#'); | ||||||
| // Check if there is a comment in the line | ||||||
| // Example: KEY=value # comment | ||||||
| // The value pair should be `value` | ||||||
| if (hash_character != std::string_view::npos) { | ||||||
| value = content.substr(0, hash_character); | ||||||
| } | ||||||
| content.remove_prefix(newline); | ||||||
| } else { | ||||||
| // In case the last line is a single key/value pair | ||||||
| // Example: KEY=VALUE (without a newline at the EOF) | ||||||
| value = content.substr(0); | ||||||
| end++; | ||||||
| } else if (!inComment && c == '#' && quote == 0) { | ||||||
| end = i; | ||||||
| inComment = true; | ||||||
| } else if ((c == '\n' || c == '\r') && quote == 0) { | ||||||
| if (!key.empty()) { | ||||||
| auto value_str = parse_value(input.substr(start, end - start)); | ||||||
| store_.insert_or_assign(std::string(key), value_str); | ||||||
| } | ||||||
|
|
||||||
| value = trim_spaces(value); | ||||||
| store_.insert_or_assign(std::string(key), value); | ||||||
| // Skip \n if it is a part of a \r | ||||||
| if (i + 1 < input.size() && input[i + 1] == '\n') { | ||||||
| i++; | ||||||
| } | ||||||
| start = i + 1; | ||||||
| end = start; | ||||||
| value = ""; | ||||||
| key = ""; | ||||||
| quote = 0; | ||||||
| inComment = false; | ||||||
| } else if (!inComment) { | ||||||
| end++; | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| if (!key.empty()) { | ||||||
| auto value_str = parse_value(input.substr(start, end - start)); | ||||||
| store_.insert_or_assign(std::string(key), value_str); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| Dotenv::ParseResult Dotenv::ParsePath(const std::string_view path) { | ||||||
|
|
@@ -267,4 +250,13 @@ void Dotenv::AssignNodeOptionsIfAvailable(std::string* node_options) const { | |||||
| } | ||||||
| } | ||||||
|
|
||||||
| std::optional<std::string> Dotenv::GetValue(const std::string_view key) const { | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can return std::string_view since the underlying store will not be disposed.
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||||||
| auto match = store_.find(key.data()); | ||||||
|
|
||||||
| if (match != store_.end()) { | ||||||
| return std::optional<std::string>{match->second}; | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||||||
| } | ||||||
| return std::nullopt; | ||||||
| } | ||||||
|
|
||||||
| } // namespace node | ||||||
Uh oh!
There was an error while loading. Please reload this page.