Skip to content

Commit 10df38a

Browse files
authored
src: improve performance of dotenv ToObject
Improves the performance of the dotenv parser ToObject method. Also, switch to a null prototype object to avoid potential prototype pollution issues. PR-URL: #60038 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Marco Ippolito <[email protected]> Reviewed-By: Yagiz Nizipli <[email protected]>
1 parent 6f941fc commit 10df38a

File tree

3 files changed

+29
-11
lines changed

3 files changed

+29
-11
lines changed

src/node_dotenv.cc

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ namespace node {
99
using v8::EscapableHandleScope;
1010
using v8::JustVoid;
1111
using v8::Local;
12+
using v8::LocalVector;
1213
using v8::Maybe;
1314
using v8::MaybeLocal;
15+
using v8::Name;
1416
using v8::Nothing;
1517
using v8::Object;
1618
using v8::String;
@@ -86,20 +88,29 @@ Maybe<void> Dotenv::SetEnvironment(node::Environment* env) {
8688

8789
MaybeLocal<Object> Dotenv::ToObject(Environment* env) const {
8890
EscapableHandleScope scope(env->isolate());
89-
Local<Object> result = Object::New(env->isolate());
9091

91-
Local<Value> name;
92-
Local<Value> val;
92+
LocalVector<Name> names(env->isolate(), store_.size());
93+
LocalVector<Value> values(env->isolate(), store_.size());
9394
auto context = env->context();
9495

96+
Local<Value> tmp;
97+
98+
int n = 0;
9599
for (const auto& entry : store_) {
96-
if (!ToV8Value(context, entry.first).ToLocal(&name) ||
97-
!ToV8Value(context, entry.second).ToLocal(&val) ||
98-
result->Set(context, name, val).IsNothing()) {
100+
if (!ToV8Value(context, entry.first).ToLocal(&tmp)) {
101+
return MaybeLocal<Object>();
102+
}
103+
names[n] = tmp.As<Name>();
104+
if (!ToV8Value(context, entry.second).ToLocal(&tmp)) {
99105
return MaybeLocal<Object>();
100106
}
107+
values[n++] = tmp;
101108
}
102-
109+
Local<Object> result = Object::New(env->isolate(),
110+
Null(env->isolate()),
111+
names.data(),
112+
values.data(),
113+
values.size());
103114
return scope.Escape(result);
104115
}
105116

test/parallel/test-dotenv-edge-cases.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ describe('.env supports edge cases', () => {
215215
].join('\n'));
216216

217217
assert.deepStrictEqual(result, {
218+
__proto__: null,
218219
baz: 'whatever',
219220
VALID_AFTER_INVALID: 'test',
220221
ANOTHER_VALID: 'value',
@@ -236,6 +237,7 @@ describe('.env supports edge cases', () => {
236237
].join('\n'));
237238

238239
assert.deepStrictEqual(result, {
240+
__proto__: null,
239241
KEY_WITH_SPACES_BEFORE: 'value_with_spaces_before_and_after',
240242
KEY_WITH_TABS_BEFORE: 'value_with_tabs_before_and_after',
241243
KEY_WITH_SPACES_AND_TABS: 'value_with_spaces_and_tabs',
@@ -255,6 +257,7 @@ describe('.env supports edge cases', () => {
255257
].join('\n'));
256258

257259
assert.deepStrictEqual(result, {
260+
__proto__: null,
258261
KEY_WITH_COMMENT_IN_VALUE: 'value # this is a comment',
259262
});
260263
});

test/parallel/test-util-parse-env.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ const fs = require('node:fs');
1010
const validEnvFilePath = fixtures.path('dotenv/valid.env');
1111
const validContent = fs.readFileSync(validEnvFilePath, 'utf8');
1212

13-
assert.deepStrictEqual(util.parseEnv(validContent), {
13+
const checkObj = {
14+
__proto__: null,
1415
A: 'B=C',
1516
B: 'C=D',
1617
AFTER_LINE: 'after_line',
@@ -56,11 +57,14 @@ const fs = require('node:fs');
5657
SPACED_KEY: 'parsed',
5758
SPACE_BEFORE_DOUBLE_QUOTES: 'space before double quotes',
5859
TRIM_SPACE_FROM_UNQUOTED: 'some spaced out string',
59-
});
60+
};
61+
62+
assert.deepStrictEqual(util.parseEnv(validContent), checkObj);
6063
}
6164

62-
assert.deepStrictEqual(util.parseEnv(''), {});
63-
assert.deepStrictEqual(util.parseEnv('FOO=bar\nFOO=baz\n'), { FOO: 'baz' });
65+
assert.deepStrictEqual(util.parseEnv(''), { __proto__: null });
66+
assert.deepStrictEqual(util.parseEnv('FOO=bar\nFOO=baz\n'),
67+
{ __proto__: null, FOO: 'baz' });
6468

6569
// Test for invalid input.
6670
assert.throws(() => {

0 commit comments

Comments
 (0)