Skip to content

Commit

Permalink
src: expand \n to a newline in double quote string
Browse files Browse the repository at this point in the history
  • Loading branch information
ilyasShabiCS committed Jan 26, 2024
1 parent 4c11ddc commit ecdc5e7
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 12 deletions.
18 changes: 18 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,10 @@ of `--enable-source-maps`.
<!-- YAML
added: v20.6.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/51289
description: Add support to multi-line values.
-->

Loads environment variables from a file relative to the current directory,
Expand Down Expand Up @@ -702,6 +706,20 @@ They are omitted from the values.
USERNAME="nodejs" # will result in `nodejs` as the value.
```

Multi-line values are supported:

```text
MULTI_LINE="THIS IS
A MULTILINE"
# will result in `THIS IS\nA MULTILINE` as the value.
```

Export keyword before a key is ignored:

```text
export USERNAME="nodejs" # will result in `nodejs` as the value.
```

### `-e`, `--eval "script"`

<!-- YAML
Expand Down
16 changes: 8 additions & 8 deletions src/node_dotenv.cc
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ Local<Object> Dotenv::ToObject(Environment* env) {
}

void Dotenv::ParseContent(const std::string_view content) {
using std::string_view_literals::operator""sv;
auto lines = SplitString(content, "\n"sv);
std::string lines = std::string(content);
lines = std::regex_replace(lines, std::regex("\r\n?"), "\n");

std::smatch match;
while (std::regex_search(lines, match, LINE)) {
Expand Down Expand Up @@ -131,7 +131,6 @@ void Dotenv::ParseContent(const std::string_view content) {
store_.insert_or_assign(std::string(key), value);
lines = match.suffix();
}

}

Dotenv::ParseResult Dotenv::ParsePath(const std::string_view path) {
Expand All @@ -151,7 +150,7 @@ Dotenv::ParseResult Dotenv::ParsePath(const std::string_view path) {
uv_fs_req_cleanup(&close_req);
});

std::string lines{};
std::string result{};
char buffer[8192];
uv_buf_t buf = uv_buf_init(buffer, sizeof(buffer));

Expand All @@ -165,11 +164,11 @@ Dotenv::ParseResult Dotenv::ParsePath(const std::string_view path) {
if (r <= 0) {
break;
}
lines.append(buf.base, r);
result.append(buf.base, r);
}

ParseContent(result);
return true;
return ParseResult::Valid;
}

void Dotenv::AssignNodeOptionsIfAvailable(std::string* node_options) {
Expand All @@ -180,9 +179,10 @@ void Dotenv::AssignNodeOptionsIfAvailable(std::string* node_options) {
}
}

std::string Dotenv::trim_quotes(std::string str) {
std::string_view Dotenv::trim_quotes(std::string_view str) {
static const std::unordered_set<char> quotes = {'"', '\'', '`'};
if (str.size() >= 2 && quotes.count(str[0]) && quotes.count(str.back())) {
if (str.size() >= 2 && quotes.count(str.front()) &&
quotes.count(str.back())) {
str = str.substr(1, str.size() - 2);
}
return str;
Expand Down
2 changes: 1 addition & 1 deletion src/node_dotenv.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class Dotenv {

private:
std::map<std::string, std::string> store_;
std::string trim_quotes(std::string str);
std::string_view trim_quotes(std::string_view str);
};

} // namespace node
Expand Down
3 changes: 3 additions & 0 deletions test/fixtures/dotenv/valid.env
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ BACKTICKS_SPACED=` backticks `
DOUBLE_QUOTES_INSIDE_BACKTICKS=`double "quotes" work inside backticks`
SINGLE_QUOTES_INSIDE_BACKTICKS=`single 'quotes' work inside backticks`
DOUBLE_AND_SINGLE_QUOTES_INSIDE_BACKTICKS=`double "quotes" and single 'quotes' work inside backticks`
EXPAND_NEWLINES="expand\nnew\nlines"
DONT_EXPAND_UNQUOTED=dontexpand\nnewlines
DONT_EXPAND_SQUOTED='dontexpand\nnewlines'
# COMMENTS=work
INLINE_COMMENTS=inline comments # work #very #well
INLINE_COMMENTS_SINGLE_QUOTES='inline comments outside of #singlequotes' # work
Expand Down
9 changes: 8 additions & 1 deletion test/parallel/test-dotenv.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ assert.strictEqual(process.env.INLINE_COMMENTS_DOUBLE_QUOTES, 'inline comments o
assert.strictEqual(process.env.INLINE_COMMENTS_BACKTICKS, 'inline comments outside of #backticks');
// Treats # character as start of comment
assert.strictEqual(process.env.INLINE_COMMENTS_SPACE, 'inline comments start with a');
// ignore comment
assert.strictEqual(process.env.COMMENTS, undefined);
// Respects equals signs in values
assert.strictEqual(process.env.EQUAL_SIGNS, 'equals==');
// Retains inner quotes
Expand All @@ -70,10 +72,15 @@ assert.strictEqual(process.env.EMAIL, 'therealnerdybeast@example.tld');
assert.strictEqual(process.env.SPACED_KEY, 'parsed');
// Parse inline comments correctly when multiple quotes
assert.strictEqual(process.env.EDGE_CASE_INLINE_COMMENTS, 'VALUE1');
// Test multiple-line value
// Test multi-line values with line breaks
assert.strictEqual(process.env.MULTI_DOUBLE_QUOTED, 'THIS\nIS\nA\nMULTILINE\nSTRING');
assert.strictEqual(process.env.MULTI_SINGLE_QUOTED, 'THIS\nIS\nA\nMULTILINE\nSTRING');
assert.strictEqual(process.env.MULTI_BACKTICKED, 'THIS\nIS\nA\n"MULTILINE\'S"\nSTRING');
assert.strictEqual(process.env.MULTI_NOT_VALID_QUOTE, '"');
assert.strictEqual(process.env.MULTI_NOT_VALID, 'THIS');
// Test that \n is expanded to a newline in double-quoted string
assert.strictEqual(process.env.EXPAND_NEWLINES, 'expand\nnew\nlines');
assert.strictEqual(process.env.DONT_EXPAND_UNQUOTED, 'dontexpand\\nnewlines');
assert.strictEqual(process.env.DONT_EXPAND_SQUOTED, 'dontexpand\\nnewlines');
// Ignore export before key
assert.strictEqual(process.env.EXAMPLE, 'ignore export');
14 changes: 12 additions & 2 deletions test/parallel/util-parse-env.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,33 @@ const fs = require('node:fs');
BACKTICKS_INSIDE_SINGLE: '`backticks` work inside single quotes',
BACKTICKS_SPACED: ' backticks ',
BASIC: 'basic',
DOUBLE_AND_SINGLE_QUOTES_INSIDE_BACKTICKS: 'double "quotes" and single \'quotes\' work inside backticks',
DONT_EXPAND_SQUOTED: 'dontexpand\\nnewlines',
DONT_EXPAND_UNQUOTED: 'dontexpand\\nnewlines',
DOUBLE_AND_SINGLE_QUOTES_INSIDE_BACKTICKS: "double \"quotes\" and single 'quotes' work inside backticks",
DOUBLE_QUOTES: 'double_quotes',
DOUBLE_QUOTES_INSIDE_BACKTICKS: 'double "quotes" work inside backticks',
DOUBLE_QUOTES_INSIDE_SINGLE: 'double "quotes" work inside single quotes',
DOUBLE_QUOTES_SPACED: ' double quotes ',
DOUBLE_QUOTES_WITH_NO_SPACE_BRACKET: '{ port: $MONGOLAB_PORT}',
EDGE_CASE_INLINE_COMMENTS: 'VALUE1',
EMAIL: 'therealnerdybeast@example.tld',
EMPTY: '',
EMPTY_BACKTICKS: '',
EMPTY_DOUBLE_QUOTES: '',
EMPTY_SINGLE_QUOTES: '',
EQUAL_SIGNS: 'equals==',
EXAMPLE: 'ignore export',
EXPAND_NEWLINES: 'expand\nnew\nlines',
INLINE_COMMENTS: 'inline comments',
INLINE_COMMENTS_BACKTICKS: 'inline comments outside of #backticks',
INLINE_COMMENTS_DOUBLE_QUOTES: 'inline comments outside of #doublequotes',
INLINE_COMMENTS_SINGLE_QUOTES: 'inline comments outside of #singlequotes',
INLINE_COMMENTS_SPACE: 'inline comments start with a',
MULTI_BACKTICKED: 'THIS\nIS\nA\n"MULTILINE\'S"\nSTRING',
MULTI_DOUBLE_QUOTED: 'THIS\nIS\nA\nMULTILINE\nSTRING',
MULTI_NOT_VALID: 'THIS',
MULTI_NOT_VALID_QUOTE: '"',
MULTI_SINGLE_QUOTED: 'THIS\nIS\nA\nMULTILINE\nSTRING',
RETAIN_INNER_QUOTES: '{"foo": "bar"}',
RETAIN_INNER_QUOTES_AS_BACKTICKS: '{"foo": "bar\'s"}',
RETAIN_INNER_QUOTES_AS_STRING: '{"foo": "bar"}',
Expand All @@ -42,7 +52,7 @@ const fs = require('node:fs');
SINGLE_QUOTES_INSIDE_DOUBLE: "single 'quotes' work inside double quotes",
SINGLE_QUOTES_SPACED: ' single quotes ',
SPACED_KEY: 'parsed',
TRIM_SPACE_FROM_UNQUOTED: 'some spaced out string'
TRIM_SPACE_FROM_UNQUOTED: 'some spaced out string',
});
}

Expand Down

0 comments on commit ecdc5e7

Please sign in to comment.