Skip to content

src: add support for escaping quotes with escape slash in --env-file #50814

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

Closed
wants to merge 3 commits into from

Conversation

pluris
Copy link
Contributor

@pluris pluris commented Nov 19, 2023

Fixes: #50801

@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run. labels Nov 19, 2023
@pluris pluris force-pushed the fix/dot-env-escape-slash branch 2 times, most recently from c792097 to 3b215fe Compare November 19, 2023 13:40
@pluris pluris force-pushed the fix/dot-env-escape-slash branch from 3b215fe to 09f8165 Compare November 19, 2023 13:41
@tniessen
Copy link
Member

Is this compatible with dotenv? We should not invent a new format. cc @anonrig

@@ -30,6 +30,7 @@ EQUAL_SIGNS=equals==
RETAIN_INNER_QUOTES={"foo": "bar"}
RETAIN_INNER_QUOTES_AS_STRING='{"foo": "bar"}'
RETAIN_INNER_QUOTES_AS_BACKTICKS=`{"foo": "bar's"}`
RETAIN_INNER_QUOTES_AS_ESCAPE_SLASH=`{\"foo\": \"bar\"}`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
RETAIN_INNER_QUOTES_AS_ESCAPE_SLASH=`{\"foo\": \"bar\"}`
RETAIN_INNER_QUOTES_AS_ESCAPE_SLASH="{\"foo\": \"bar\"}"

@@ -52,6 +52,8 @@ void Dotenv::SetEnvironment(node::Environment* env) {
auto existing = env->env_vars()->Get(key.data());

if (existing.IsNothing()) {
// Remove all '\' characters from value
value.erase(std::remove(value.begin(), value.end(), '\\'), value.end());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about escaping backslashes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I received comments and changed the implementation to apply only to \".

@@ -30,6 +30,7 @@ EQUAL_SIGNS=equals==
RETAIN_INNER_QUOTES={"foo": "bar"}
RETAIN_INNER_QUOTES_AS_STRING='{"foo": "bar"}'
RETAIN_INNER_QUOTES_AS_BACKTICKS=`{"foo": "bar's"}`
RETAIN_INNER_QUOTES_AS_ESCAPE_SLASH=`{\"foo\": \"bar\"}`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add e.g. C:\\Windows\\system32 as a test case?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the default behavior of dotenv?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was a problem with the current implementation, so I modified it and added the test you commented on.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pluris Thanks for the quick fix! I think there is still an issue. I would expect the test to read:

assert.strictEqual(process.env.PATH_WINDOWS, 'C:\\Windows\\system32');

instead of the current

assert.strictEqual(process.env.PATH_WINDOWS, 'C:\\\\Windows\\\\system32');

That is, escaping a backslash should result in just one backslash... This is also what dotenv does, @anonrig.

@bricss
Copy link
Contributor

bricss commented Nov 19, 2023

That thing looks like a foot gun 🔫 to me 🫣

@@ -52,6 +52,8 @@ void Dotenv::SetEnvironment(node::Environment* env) {
auto existing = env->env_vars()->Get(key.data());

if (existing.IsNothing()) {
// Remove all '\' characters from value
value.erase(std::remove(value.begin(), value.end(), '\\'), value.end());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be moved to Dotenv::ParseLine ? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your comment.
IMO, Dotenv::ParseLine() is used in Dotenv::ParsePath(), and the current issue does not seem to be about Path.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I checked further and found what you said was correct. I applied it.

@pluris
Copy link
Contributor Author

pluris commented Nov 19, 2023

Thank you for the comments.
It seems that the current modification may cause another error.
Hmm... to fix this issue, how about applying it only to \" instead of all backslashes?
I've updated the PR. Please take a look again.

@pluris pluris force-pushed the fix/dot-env-escape-slash branch from a4c9544 to 690fa43 Compare November 19, 2023 16:39
@anonrig
Copy link
Member

anonrig commented Nov 19, 2023

What happens if you have a value of '\'hello'?

@meyfa
Copy link
Contributor

meyfa commented Nov 19, 2023

Actually, I think my previous assessment 1 was mistaken as I used require('dotenv').parse from within JavaScript, and got confused by the backslash count within the strings. Running it again with files on disk, it seems that there is no escaping going on whatsoever. This is also backed up by the dotenv documentation 2.

Contents of .env:

INNER_QUOTES=foo'bar"baz`qux

DUPLICATE_INNER_QUOTES1='foo''bar'
DUPLICATE_INNER_QUOTES2="foo""bar"

SINGLE_QUOTED_ESCAPE1='\'foo\'bar\''
SINGLE_QUOTED_ESCAPE2='\"foo\"bar\"'

DOUBLE_QUOTED_ESCAPE1="\'foo\'bar\'"
DOUBLE_QUOTED_ESCAPE2="\"foo\"bar\""

ESCAPED_BACKSLASH=\\foo\\bar\\
SINGLE_ESCAPED_BACKSLASH='\\foo\\bar\\'
DOUBLE_ESCAPED_BACKSLASH="\\foo\\bar\\"

SINGLE_QUOTED_SINGLE_QUOTE='foo'bar'
DOUBLE_QUOTED_DOUBLE_QUOTE="foo"bar"
SINGLE_QUOTED_SINGLE_QUOTE_WITH_BACKSLASH='foo\\'bar'
DOUBLE_QUOTED_DOUBLE_QUOTE_WITH_BACKSLASH="foo\\"bar"

Test:

$ node -p "require('dotenv').config().parsed"
{
  INNER_QUOTES: 'foo\'bar"baz`qux',
  DUPLICATE_INNER_QUOTES1: "foo''bar",
  DUPLICATE_INNER_QUOTES2: 'foo""bar',
  SINGLE_QUOTED_ESCAPE1: "\\'foo\\'bar\\'",
  SINGLE_QUOTED_ESCAPE2: '\\"foo\\"bar\\"',
  DOUBLE_QUOTED_ESCAPE1: "\\'foo\\'bar\\'",
  DOUBLE_QUOTED_ESCAPE2: '\\"foo\\"bar\\"',
  ESCAPED_BACKSLASH: '\\\\foo\\\\bar\\\\',
  SINGLE_ESCAPED_BACKSLASH: '\\\\foo\\\\bar\\\\',
  DOUBLE_ESCAPED_BACKSLASH: '\\\\foo\\\\bar\\\\',
  SINGLE_QUOTED_SINGLE_QUOTE: "foo'bar",
  DOUBLE_QUOTED_DOUBLE_QUOTE: 'foo"bar',
  SINGLE_QUOTED_SINGLE_QUOTE_WITH_BACKSLASH: "foo\\\\'bar",
  DOUBLE_QUOTED_DOUBLE_QUOTE_WITH_BACKSLASH: 'foo\\\\"bar'
}

Footnotes

  1. https://github.com/nodejs/node/pull/50814#discussion_r1398439526

  2. https://github.com/motdotla/dotenv#what-rules-does-the-parsing-engine-follow

@@ -52,6 +52,11 @@ void Dotenv::SetEnvironment(node::Environment* env) {
auto existing = env->env_vars()->Get(key.data());

if (existing.IsNothing()) {
size_t found = value.find("\\\"");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be done inside the ParseLine function. Depending on the start character of the value, we need to de-escape it. For ' it is ', or for " it is "

Copy link
Contributor Author

@pluris pluris Nov 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked again and you're right.
I applied it as you said. Please review again.

Copy link
Member

@tniessen tniessen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not familiar with this feature but my understanding of @meyfa's #50814 (comment) is that this is incompatible with dotenv, which is the only reference for the file format as far as I know (other than some loose relation to the ancient INI file format). If this feature begins to deviate from dotenv (in a potentially breaking manner), then the file format needs to be defined precisely and in some user-facing documentation.

@pluris
Copy link
Contributor Author

pluris commented Nov 20, 2023

@tniessen
I agree in that it has different behavior than dotenv.
I'm having a bit of trouble with that part too. I will close the PR.
Thank you for your comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

--env-file does not support escaping quotes
7 participants