From 971f5f54df43b301979b5848d56442fafd1c1bbf Mon Sep 17 00:00:00 2001 From: Shima Ryuhei <65934663+islandryu@users.noreply.github.com> Date: Tue, 26 Nov 2024 19:12:50 +0900 Subject: [PATCH] src: safely remove the last line from dotenv MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refs: https://github.com/nodejs/node/issues/55925 PR-URL: https://github.com/nodejs/node/pull/55982 Reviewed-By: Yagiz Nizipli Reviewed-By: Richard Lau Reviewed-By: Juan José Arboleda --- src/node_dotenv.cc | 10 +++++++-- .../dotenv/no-final-newline-single-quotes.env | 1 + test/fixtures/dotenv/no-final-newline.env | 1 + test/parallel/test-dotenv-edge-cases.js | 22 +++++++++++++++++++ 4 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 test/fixtures/dotenv/no-final-newline-single-quotes.env create mode 100644 test/fixtures/dotenv/no-final-newline.env diff --git a/src/node_dotenv.cc b/src/node_dotenv.cc index f594df875d7a0c..049f5cfcb77b9c 100644 --- a/src/node_dotenv.cc +++ b/src/node_dotenv.cc @@ -182,7 +182,10 @@ void Dotenv::ParseContent(const std::string_view input) { } store_.insert_or_assign(std::string(key), multi_line_value); - content.remove_prefix(content.find('\n', closing_quote + 1)); + auto newline = content.find('\n', closing_quote + 1); + if (newline != std::string_view::npos) { + content.remove_prefix(newline); + } continue; } } @@ -210,7 +213,10 @@ void Dotenv::ParseContent(const std::string_view input) { 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)); + auto newline = content.find('\n', closing_quote + 1); + if (newline != std::string_view::npos) { + content.remove_prefix(newline); + } } } else { // Regular key value pair. diff --git a/test/fixtures/dotenv/no-final-newline-single-quotes.env b/test/fixtures/dotenv/no-final-newline-single-quotes.env new file mode 100644 index 00000000000000..4f1b37d7741e29 --- /dev/null +++ b/test/fixtures/dotenv/no-final-newline-single-quotes.env @@ -0,0 +1 @@ +BASIC='basic' \ No newline at end of file diff --git a/test/fixtures/dotenv/no-final-newline.env b/test/fixtures/dotenv/no-final-newline.env new file mode 100644 index 00000000000000..ef996552bd9c90 --- /dev/null +++ b/test/fixtures/dotenv/no-final-newline.env @@ -0,0 +1 @@ +BASIC="basic" \ No newline at end of file diff --git a/test/parallel/test-dotenv-edge-cases.js b/test/parallel/test-dotenv-edge-cases.js index 30fd9d20618ba0..769d33a13b8ce9 100644 --- a/test/parallel/test-dotenv-edge-cases.js +++ b/test/parallel/test-dotenv-edge-cases.js @@ -8,6 +8,8 @@ const fixtures = require('../common/fixtures'); const validEnvFilePath = '../fixtures/dotenv/valid.env'; const nodeOptionsEnvFilePath = '../fixtures/dotenv/node-options.env'; +const noFinalNewlineEnvFilePath = '../fixtures/dotenv/no-final-newline.env'; +const noFinalNewlineSingleQuotesEnvFilePath = '../fixtures/dotenv/no-final-newline-single-quotes.env'; describe('.env supports edge cases', () => { it('supports multiple declarations, including optional ones', async () => { @@ -148,4 +150,24 @@ describe('.env supports edge cases', () => { assert.strictEqual(child.stderr, ''); assert.strictEqual(child.code, 0); }); + + it('should handle file without a final newline', async () => { + const code = ` + require('assert').strictEqual(process.env.BASIC, 'basic'); + `.trim(); + const child = await common.spawnPromisified( + process.execPath, + [ `--env-file=${path.resolve(__dirname, noFinalNewlineEnvFilePath)}`, '--eval', code ], + ); + + const SingleQuotesChild = await common.spawnPromisified( + process.execPath, + [ `--env-file=${path.resolve(__dirname, noFinalNewlineSingleQuotesEnvFilePath)}`, '--eval', code ], + ); + + assert.strictEqual(child.stderr, ''); + assert.strictEqual(child.code, 0); + assert.strictEqual(SingleQuotesChild.stderr, ''); + assert.strictEqual(SingleQuotesChild.code, 0); + }); });