Skip to content

Commit 1397f5d

Browse files
aduh95anonrig
andcommitted
http2: remove prototype primordials
Co-authored-by: Yagiz Nizipli <[email protected]> PR-URL: #53696 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Marco Ippolito <[email protected]> Reviewed-By: Moshe Atlow <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: Yagiz Nizipli <[email protected]> Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: Chemi Atlow <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Rafael Gonzaga <[email protected]>
1 parent 1300169 commit 1397f5d

File tree

5 files changed

+105
-125
lines changed

5 files changed

+105
-125
lines changed

doc/contributing/primordials.md

+7-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,13 @@ The file `lib/internal/per_context/primordials.js` subclasses and stores the JS
44
built-ins that come from the VM so that Node.js built-in modules do not need to
55
later look these up from the global proxy, which can be mutated by users.
66

7-
Usage of primordials should be preferred for any new code, but replacing current
8-
code with primordials should be
7+
For some area of the codebase, performance and code readability are deemed more
8+
important than reliability against prototype pollution:
9+
10+
* `node:http2`
11+
12+
Usage of primordials should be preferred for new code in other areas, but
13+
replacing current code with primordials should be
914
[done with care](#primordials-with-known-performance-issues). It is highly
1015
recommended to ping the relevant team when reviewing a pull request that touches
1116
one of the subsystems they "own".

lib/eslint.config_partial.mjs

+42-25
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,32 @@ import {
55
noRestrictedSyntaxCommonLib,
66
} from '../tools/eslint/eslint.config_utils.mjs';
77

8+
const noRestrictedSyntax = [
9+
'error',
10+
...noRestrictedSyntaxCommonAll,
11+
...noRestrictedSyntaxCommonLib,
12+
{
13+
selector: "CallExpression[callee.object.name='assert']:not([callee.property.name='ok']):not([callee.property.name='fail']):not([callee.property.name='ifError'])",
14+
message: 'Please only use simple assertions in ./lib',
15+
},
16+
{
17+
selector: 'NewExpression[callee.name=/Error$/]:not([callee.name=/^(AssertionError|NghttpError|AbortError|NodeAggregateError)$/])',
18+
message: 'Use an error exported by the internal/errors module.',
19+
},
20+
{
21+
selector: "CallExpression[callee.object.name='Error'][callee.property.name='captureStackTrace']",
22+
message: "Please use `require('internal/errors').hideStackFrames()` instead.",
23+
},
24+
{
25+
selector: "AssignmentExpression:matches([left.object.name='Error']):matches([left.name='prepareStackTrace'], [left.property.name='prepareStackTrace'])",
26+
message: "Use 'overrideStackTrace' from 'lib/internal/errors.js' instead of 'Error.prepareStackTrace'.",
27+
},
28+
{
29+
selector: "ThrowStatement > NewExpression[callee.name=/^ERR_[A-Z_]+$/] > ObjectExpression:first-child:not(:has([key.name='message']):has([key.name='code']):has([key.name='syscall']))",
30+
message: 'The context passed into SystemError constructor must have .code, .syscall and .message.',
31+
},
32+
];
33+
834
export default [
935
{
1036
files: ['lib/**/*.js'],
@@ -22,31 +48,7 @@ export default [
2248
rules: {
2349
'prefer-object-spread': 'error',
2450
'no-buffer-constructor': 'error',
25-
'no-restricted-syntax': [
26-
'error',
27-
...noRestrictedSyntaxCommonAll,
28-
...noRestrictedSyntaxCommonLib,
29-
{
30-
selector: "CallExpression[callee.object.name='assert']:not([callee.property.name='ok']):not([callee.property.name='fail']):not([callee.property.name='ifError'])",
31-
message: 'Please only use simple assertions in ./lib',
32-
},
33-
{
34-
selector: 'NewExpression[callee.name=/Error$/]:not([callee.name=/^(AssertionError|NghttpError|AbortError|NodeAggregateError)$/])',
35-
message: 'Use an error exported by the internal/errors module.',
36-
},
37-
{
38-
selector: "CallExpression[callee.object.name='Error'][callee.property.name='captureStackTrace']",
39-
message: "Please use `require('internal/errors').hideStackFrames()` instead.",
40-
},
41-
{
42-
selector: "AssignmentExpression:matches([left.object.name='Error']):matches([left.name='prepareStackTrace'], [left.property.name='prepareStackTrace'])",
43-
message: "Use 'overrideStackTrace' from 'lib/internal/errors.js' instead of 'Error.prepareStackTrace'.",
44-
},
45-
{
46-
selector: "ThrowStatement > NewExpression[callee.name=/^ERR_[A-Z_]+$/] > ObjectExpression:first-child:not(:has([key.name='message']):has([key.name='code']):has([key.name='syscall']))",
47-
message: 'The context passed into SystemError constructor must have .code, .syscall and .message.',
48-
},
49-
],
51+
'no-restricted-syntax': noRestrictedSyntax,
5052
'no-restricted-globals': [
5153
'error',
5254
{
@@ -483,4 +485,19 @@ export default [
483485
'node-core/set-proto-to-null-in-object': 'error',
484486
},
485487
},
488+
{
489+
files: [
490+
'lib/internal/http2/*.js',
491+
'lib/http2.js',
492+
],
493+
rules: {
494+
'no-restricted-syntax': [
495+
...noRestrictedSyntax,
496+
{
497+
selector: 'VariableDeclarator:has(.init[name="primordials"]) Identifier[name=/Prototype/]:not([name=/^(Object|Reflect)(Get|Set)PrototypeOf$/])',
498+
message: 'We do not use prototype primordials in this file',
499+
},
500+
],
501+
},
502+
},
486503
];

lib/internal/http2/compat.js

+15-22
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,13 @@
22

33
const {
44
ArrayIsArray,
5-
ArrayPrototypePush,
65
Boolean,
7-
FunctionPrototypeBind,
86
ObjectAssign,
7+
ObjectHasOwn,
98
ObjectKeys,
10-
ObjectPrototypeHasOwnProperty,
119
Proxy,
1210
ReflectApply,
1311
ReflectGetPrototypeOf,
14-
SafeArrayIterator,
15-
StringPrototypeIncludes,
16-
StringPrototypeToLowerCase,
17-
StringPrototypeTrim,
1812
Symbol,
1913
} = primordials;
2014

@@ -89,7 +83,7 @@ let statusConnectionHeaderWarned = false;
8983
const assertValidHeader = hideStackFrames((name, value) => {
9084
if (name === '' ||
9185
typeof name !== 'string' ||
92-
StringPrototypeIncludes(name, ' ')) {
86+
name.includes(' ')) {
9387
throw new ERR_INVALID_HTTP_TOKEN.HideStackFramesError('Header name', name);
9488
}
9589
if (isPseudoHeader(name)) {
@@ -153,8 +147,7 @@ function onStreamTrailers(trailers, flags, rawTrailers) {
153147
const request = this[kRequest];
154148
if (request !== undefined) {
155149
ObjectAssign(request[kTrailers], trailers);
156-
ArrayPrototypePush(request[kRawTrailers],
157-
...new SafeArrayIterator(rawTrailers));
150+
request[kRawTrailers].push(...rawTrailers);
158151
}
159152
}
160153

@@ -216,7 +209,7 @@ const proxySocketHandler = {
216209
case 'end':
217210
case 'emit':
218211
case 'destroy':
219-
return FunctionPrototypeBind(stream[prop], stream);
212+
return stream[prop].bind(stream);
220213
case 'writable':
221214
case 'destroyed':
222215
return stream[prop];
@@ -229,8 +222,8 @@ const proxySocketHandler = {
229222
case 'setTimeout': {
230223
const session = stream.session;
231224
if (session !== undefined)
232-
return FunctionPrototypeBind(session.setTimeout, session);
233-
return FunctionPrototypeBind(stream.setTimeout, stream);
225+
return session.setTimeout.bind(session);
226+
return stream.setTimeout.bind(stream);
234227
}
235228
case 'write':
236229
case 'read':
@@ -242,7 +235,7 @@ const proxySocketHandler = {
242235
stream.session[kSocket] : stream;
243236
const value = ref[prop];
244237
return typeof value === 'function' ?
245-
FunctionPrototypeBind(value, ref) :
238+
value.bind(ref) :
246239
value;
247240
}
248241
}
@@ -417,7 +410,7 @@ class Http2ServerRequest extends Readable {
417410

418411
set method(method) {
419412
validateString(method, 'method');
420-
if (StringPrototypeTrim(method) === '')
413+
if (method.trim() === '')
421414
throw new ERR_INVALID_ARG_VALUE('method', method);
422415

423416
this[kHeaders][HTTP2_HEADER_METHOD] = method;
@@ -578,7 +571,7 @@ class Http2ServerResponse extends Stream {
578571

579572
setTrailer(name, value) {
580573
validateString(name, 'name');
581-
name = StringPrototypeToLowerCase(StringPrototypeTrim(name));
574+
name = name.trim().toLowerCase();
582575
assertValidHeader(name, value);
583576
this[kTrailers][name] = value;
584577
}
@@ -594,7 +587,7 @@ class Http2ServerResponse extends Stream {
594587

595588
getHeader(name) {
596589
validateString(name, 'name');
597-
name = StringPrototypeToLowerCase(StringPrototypeTrim(name));
590+
name = name.trim().toLowerCase();
598591
return this[kHeaders][name];
599592
}
600593

@@ -609,16 +602,16 @@ class Http2ServerResponse extends Stream {
609602

610603
hasHeader(name) {
611604
validateString(name, 'name');
612-
name = StringPrototypeToLowerCase(StringPrototypeTrim(name));
613-
return ObjectPrototypeHasOwnProperty(this[kHeaders], name);
605+
name = name.trim().toLowerCase();
606+
return ObjectHasOwn(this[kHeaders], name);
614607
}
615608

616609
removeHeader(name) {
617610
validateString(name, 'name');
618611
if (this[kStream].headersSent)
619612
throw new ERR_HTTP2_HEADERS_SENT();
620613

621-
name = StringPrototypeToLowerCase(StringPrototypeTrim(name));
614+
name = name.trim().toLowerCase();
622615

623616
if (name === 'date') {
624617
this[kState].sendDate = false;
@@ -638,7 +631,7 @@ class Http2ServerResponse extends Stream {
638631
}
639632

640633
[kSetHeader](name, value) {
641-
name = StringPrototypeToLowerCase(StringPrototypeTrim(name));
634+
name = name.trim().toLowerCase();
642635
assertValidHeader(name, value);
643636

644637
if (!isConnectionHeaderAllowed(name, value)) {
@@ -662,7 +655,7 @@ class Http2ServerResponse extends Stream {
662655
}
663656

664657
[kAppendHeader](name, value) {
665-
name = StringPrototypeToLowerCase(StringPrototypeTrim(name));
658+
name = name.trim().toLowerCase();
666659
assertValidHeader(name, value);
667660

668661
if (!isConnectionHeaderAllowed(name, value)) {

0 commit comments

Comments
 (0)