Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
util: fix parseEnv incorrectly splitting multiple ‘=‘ in value
Previously, parseEnv would create multiple environment
variables if a single line contained multiple ‘=‘ characters
(e.g. A=B=C would become { A: ‘B=C’, B: ‘C’ }).
This commit ensures that only the first ‘=‘ is used as
the key-value delimiter, and the rest of the line is treated
as the value.

Fixes: #57411
  • Loading branch information
rayark1 committed Mar 12, 2025
commit be4ee4d27853249264ca4fd458e85e60cc9a9f7c
3 changes: 2 additions & 1 deletion benchmark/fixtures/valid.env
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ BASIC=basic

# previous line intentionally left blank
AFTER_LINE=after_line
A=B=C
EMPTY=
EMPTY_SINGLE_QUOTES=''
EMPTY_DOUBLE_QUOTES=""
Expand Down Expand Up @@ -64,4 +65,4 @@ export EXPORT_EXAMPLE = ignore export

MULTI_NOT_VALID_QUOTE="
MULTI_NOT_VALID=THIS
IS NOT MULTILINE
IS NOT MULTILINE
Comment thread
rayark1 marked this conversation as resolved.
Outdated
33 changes: 27 additions & 6 deletions src/node_dotenv.cc
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,13 @@ void Dotenv::ParseContent(const std::string_view input) {
// If there is no equal character, then ignore everything
auto equal = content.find('=');
if (equal == std::string_view::npos) {
break;
auto newline = content.find('\n');
if (newline != std::string_view::npos) {
content.remove_prefix(newline + 1);
} else {
content.remove_prefix(content.size());
Comment thread
rayark1 marked this conversation as resolved.
Outdated
}
continue;
Comment thread
anonrig marked this conversation as resolved.
}

key = content.substr(0, equal);
Expand Down Expand Up @@ -195,7 +201,9 @@ void Dotenv::ParseContent(const std::string_view input) {
store_.insert_or_assign(std::string(key), multi_line_value);
auto newline = content.find('\n', closing_quote + 1);
if (newline != std::string_view::npos) {
content.remove_prefix(newline);
content.remove_prefix(newline + 1);
} else {
content.remove_prefix(content.size());
Comment thread
rayark1 marked this conversation as resolved.
Outdated
}
continue;
}
Expand All @@ -216,7 +224,7 @@ void Dotenv::ParseContent(const std::string_view input) {
if (newline != std::string_view::npos) {
value = content.substr(0, newline);
store_.insert_or_assign(std::string(key), value);
content.remove_prefix(newline);
content.remove_prefix(newline + 1);
Comment thread
rayark1 marked this conversation as resolved.
}
} else {
// Example: KEY="value"
Expand All @@ -226,8 +234,11 @@ void Dotenv::ParseContent(const std::string_view input) {
// since there could be newline characters inside the value.
auto newline = content.find('\n', closing_quote + 1);
if (newline != std::string_view::npos) {
content.remove_prefix(newline);
content.remove_prefix(newline + 1);
} else {
content.remove_prefix(content.size());
}
continue;
Comment thread
anonrig marked this conversation as resolved.
}
} else {
// Regular key value pair.
Expand All @@ -243,11 +254,21 @@ void Dotenv::ParseContent(const std::string_view input) {
if (hash_character != std::string_view::npos) {
value = content.substr(0, hash_character);
}
content.remove_prefix(newline);
value = trim_spaces(value);
store_.insert_or_assign(std::string(key), value);
content.remove_prefix(newline + 1);
} 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);
value = content;
auto hash_char = value.find('#');
if (hash_char != std::string_view::npos) {
value = content.substr(0, hash_char);
}
value = trim_spaces(value);

store_.insert_or_assign(std::string(key), value);
Comment thread
rayark1 marked this conversation as resolved.
Outdated
content.remove_prefix(content.size());
Comment thread
rayark1 marked this conversation as resolved.
Outdated
}

value = trim_spaces(value);
Expand Down
3 changes: 2 additions & 1 deletion test/fixtures/dotenv/valid.env
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ BASIC=basic

# previous line intentionally left blank
AFTER_LINE=after_line
A=B=C
Comment thread
rayark1 marked this conversation as resolved.
Outdated
EMPTY=
EMPTY_SINGLE_QUOTES=''
EMPTY_DOUBLE_QUOTES=""
Expand Down Expand Up @@ -64,4 +65,4 @@ export EXPORT_EXAMPLE = ignore export

MULTI_NOT_VALID_QUOTE="
MULTI_NOT_VALID=THIS
IS NOT MULTILINE
IS NOT MULTILINE
Comment thread
rayark1 marked this conversation as resolved.
Outdated
1 change: 1 addition & 0 deletions test/parallel/test-dotenv.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,4 @@ assert.strictEqual(process.env.DONT_EXPAND_SQUOTED, 'dontexpand\\nnewlines');
assert.strictEqual(process.env.EXPORT_EXAMPLE, 'ignore export');
// Ignore spaces before double quotes to avoid quoted strings as value
assert.strictEqual(process.env.SPACE_BEFORE_DOUBLE_QUOTES, 'space before double quotes');
assert.strictEqual(process.env.A, 'B=C');
Comment thread
rayark1 marked this conversation as resolved.
1 change: 1 addition & 0 deletions test/parallel/test-util-parse-env.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const fs = require('node:fs');
const validContent = fs.readFileSync(validEnvFilePath, 'utf8');

assert.deepStrictEqual(util.parseEnv(validContent), {
A: 'B=C',
AFTER_LINE: 'after_line',
BACKTICKS: 'backticks',
BACKTICKS_INSIDE_DOUBLE: '`backticks` work inside double quotes',
Expand Down
Loading