Skip to content

Commit

Permalink
http: remove prototype primordials
Browse files Browse the repository at this point in the history
Co-authored-by: Yagiz Nizipli <yagiz@nizipli.com>
PR-URL: #53698
Reviewed-By: Yagiz Nizipli <yagiz.nizipli@sentry.io>
Reviewed-By: Robert Nagy <ronagy@icloud.com>
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
  • Loading branch information
aduh95 and anonrig committed Jul 16, 2024
1 parent a9db553 commit 7debb6c
Show file tree
Hide file tree
Showing 10 changed files with 63 additions and 96 deletions.
1 change: 1 addition & 0 deletions doc/contributing/primordials.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ later look these up from the global proxy, which can be mutated by users.
For some area of the codebase, performance and code readability are deemed more
important than reliability against prototype pollution:

* `node:http`
* `node:http2`

Usage of primordials should be preferred for new code in other areas, but
Expand Down
51 changes: 18 additions & 33 deletions lib/_http_agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,10 @@
'use strict';

const {
ArrayPrototypeIncludes,
ArrayPrototypeIndexOf,
ArrayPrototypePop,
ArrayPrototypePush,
ArrayPrototypeShift,
ArrayPrototypeSome,
ArrayPrototypeSplice,
FunctionPrototypeCall,
NumberParseInt,
ObjectKeys,
ObjectSetPrototypeOf,
ObjectValues,
RegExpPrototypeExec,
StringPrototypeIndexOf,
StringPrototypeSplit,
StringPrototypeStartsWith,
StringPrototypeSubstring,
Symbol,
} = primordials;

Expand Down Expand Up @@ -92,7 +79,7 @@ function Agent(options) {
if (!(this instanceof Agent))
return new Agent(options);

FunctionPrototypeCall(EventEmitter, this);
EventEmitter.call(this);

this.defaultPort = 80;
this.protocol = 'http:';
Expand Down Expand Up @@ -139,7 +126,7 @@ function Agent(options) {

const requests = this.requests[name];
if (requests && requests.length) {
const req = ArrayPrototypeShift(requests);
const req = requests.shift();
const reqAsyncRes = req[kRequestAsyncResource];
if (reqAsyncRes) {
// Run request within the original async context.
Expand Down Expand Up @@ -185,7 +172,7 @@ function Agent(options) {
this.removeSocket(socket, options);

socket.once('error', freeSocketErrorListener);
ArrayPrototypePush(freeSockets, socket);
freeSockets.push(socket);
});

// Don't emit keylog events unless there is a listener for them.
Expand Down Expand Up @@ -264,11 +251,11 @@ Agent.prototype.addRequest = function addRequest(req, options, port/* legacy */,
let socket;
if (freeSockets) {
while (freeSockets.length && freeSockets[0].destroyed) {
ArrayPrototypeShift(freeSockets);
freeSockets.shift();
}
socket = this.scheduling === 'fifo' ?
ArrayPrototypeShift(freeSockets) :
ArrayPrototypePop(freeSockets);
freeSockets.shift() :
freeSockets.pop();
if (!freeSockets.length)
delete this.freeSockets[name];
}
Expand All @@ -280,7 +267,7 @@ Agent.prototype.addRequest = function addRequest(req, options, port/* legacy */,
asyncResetHandle(socket);
this.reuseSocket(socket, req);
setRequestSocket(this, req, socket);
ArrayPrototypePush(this.sockets[name], socket);
this.sockets[name].push(socket);
} else if (sockLen < this.maxSockets &&
this.totalSocketCount < this.maxTotalSockets) {
debug('call onSocket', sockLen, freeLen);
Expand All @@ -303,7 +290,7 @@ Agent.prototype.addRequest = function addRequest(req, options, port/* legacy */,
// Used to capture the original async context.
req[kRequestAsyncResource] = new AsyncResource('QueuedRequest');

ArrayPrototypePush(this.requests[name], req);
this.requests[name].push(req);
}
};

Expand All @@ -326,7 +313,7 @@ Agent.prototype.createSocket = function createSocket(req, options, cb) {
if (!this.sockets[name]) {
this.sockets[name] = [];
}
ArrayPrototypePush(this.sockets[name], s);
this.sockets[name].push(s);
this.totalSocketCount++;
debug('sockets', name, this.sockets[name].length, this.totalSocketCount);
installListeners(this, s, options);
Expand Down Expand Up @@ -357,16 +344,16 @@ function calculateServerName(options, req) {
// abc:123 => abc
// [::1] => ::1
// [::1]:123 => ::1
if (StringPrototypeStartsWith(hostHeader, '[')) {
const index = StringPrototypeIndexOf(hostHeader, ']');
if (hostHeader.startsWith('[')) {
const index = hostHeader.indexOf(']');
if (index === -1) {
// Leading '[', but no ']'. Need to do something...
servername = hostHeader;
} else {
servername = StringPrototypeSubstring(hostHeader, 1, index);
servername = hostHeader.substring(1, index);
}
} else {
servername = StringPrototypeSplit(hostHeader, ':', 1)[0];
servername = hostHeader.split(':', 1)[0];
}
}
// Don't implicitly set invalid (IP) servernames.
Expand Down Expand Up @@ -398,9 +385,7 @@ function installListeners(agent, s, options) {
// Destroy if in free list.
// TODO(ronag): Always destroy, even if not in free list.
const sockets = agent.freeSockets;
if (ArrayPrototypeSome(ObjectKeys(sockets), (name) =>
ArrayPrototypeIncludes(sockets[name], s),
)) {
if (ObjectKeys(sockets).some((name) => sockets[name].includes(s))) {
return s.destroy();
}
}
Expand Down Expand Up @@ -432,15 +417,15 @@ Agent.prototype.removeSocket = function removeSocket(s, options) {

// If the socket was destroyed, remove it from the free buffers too.
if (!s.writable)
ArrayPrototypePush(sets, this.freeSockets);
sets.push(this.freeSockets);

for (let sk = 0; sk < sets.length; sk++) {
const sockets = sets[sk];

if (sockets[name]) {
const index = ArrayPrototypeIndexOf(sockets[name], s);
const index = sockets[name].indexOf(s);
if (index !== -1) {
ArrayPrototypeSplice(sockets[name], index, 1);
sockets[name].splice(index, 1);
// Don't leak
if (sockets[name].length === 0)
delete sockets[name];
Expand Down Expand Up @@ -493,7 +478,7 @@ Agent.prototype.keepSocketAlive = function keepSocketAlive(socket) {
const keepAliveHint = socket._httpMessage.res.headers['keep-alive'];

if (keepAliveHint) {
const hint = RegExpPrototypeExec(/^timeout=(\d+)/, keepAliveHint)?.[1];
const hint = /^timeout=(\d+)/.exec(keepAliveHint)?.[1];

if (hint) {
const serverHintTimeout = NumberParseInt(hint) * 1000;
Expand Down
23 changes: 8 additions & 15 deletions lib/_http_client.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,13 @@ const {
ArrayIsArray,
Boolean,
Error,
FunctionPrototypeCall,
NumberIsFinite,
ObjectAssign,
ObjectKeys,
ObjectSetPrototypeOf,
ReflectApply,
RegExpPrototypeExec,
String,
StringPrototypeCharCodeAt,
StringPrototypeIncludes,
StringPrototypeIndexOf,
StringPrototypeToUpperCase,
Symbol,
TypedArrayPrototypeSlice,
} = primordials;

const net = require('net');
Expand Down Expand Up @@ -130,7 +123,7 @@ class HTTPClientAsyncResource {
}

function ClientRequest(input, options, cb) {
FunctionPrototypeCall(OutgoingMessage, this);
OutgoingMessage.call(this);

if (typeof input === 'string') {
const urlStr = input;
Expand Down Expand Up @@ -175,7 +168,7 @@ function ClientRequest(input, options, cb) {

if (options.path) {
const path = String(options.path);
if (RegExpPrototypeExec(INVALID_PATH_REGEX, path) !== null) {
if (INVALID_PATH_REGEX.test(path)) {
debug('Path contains unescaped characters: "%s"', path);
throw new ERR_UNESCAPED_CHARACTERS('Request path');
}
Expand Down Expand Up @@ -216,7 +209,7 @@ function ClientRequest(input, options, cb) {
if (!checkIsHttpToken(method)) {
throw new ERR_INVALID_HTTP_TOKEN('Method', method);
}
method = this.method = StringPrototypeToUpperCase(method);
method = this.method = method.toUpperCase();
} else {
method = this.method = 'GET';
}
Expand Down Expand Up @@ -298,10 +291,10 @@ function ClientRequest(input, options, cb) {
// For the Host header, ensure that IPv6 addresses are enclosed
// in square brackets, as defined by URI formatting
// https://tools.ietf.org/html/rfc3986#section-3.2.2
const posColon = StringPrototypeIndexOf(hostHeader, ':');
const posColon = hostHeader.indexOf(':');
if (posColon !== -1 &&
StringPrototypeIncludes(hostHeader, ':', posColon + 1) &&
StringPrototypeCharCodeAt(hostHeader, 0) !== 91/* '[' */) {
hostHeader.includes(':', posColon + 1) &&
hostHeader.charCodeAt(0) !== 91/* '[' */) {
hostHeader = `[${hostHeader}]`;
}

Expand Down Expand Up @@ -374,7 +367,7 @@ ObjectSetPrototypeOf(ClientRequest.prototype, OutgoingMessage.prototype);
ObjectSetPrototypeOf(ClientRequest, OutgoingMessage);

ClientRequest.prototype._finish = function _finish() {
FunctionPrototypeCall(OutgoingMessage.prototype._finish, this);
OutgoingMessage.prototype._finish.call(this);
if (hasObserver('http')) {
startPerf(this, kClientRequestStatistics, {
type: 'http',
Expand Down Expand Up @@ -565,7 +558,7 @@ function socketOnData(d) {
parser.finish();
freeParser(parser, req, socket);

const bodyHead = TypedArrayPrototypeSlice(d, bytesParsed, d.length);
const bodyHead = d.slice(bytesParsed, d.length);

const eventName = req.method === 'CONNECT' ? 'connect' : 'upgrade';
if (req.listenerCount(eventName) > 0) {
Expand Down
5 changes: 2 additions & 3 deletions lib/_http_common.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@

const {
MathMin,
RegExpPrototypeExec,
Symbol,
} = primordials;
const { setImmediate } = require('timers');
Expand Down Expand Up @@ -210,7 +209,7 @@ const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/;
* See https://tools.ietf.org/html/rfc7230#section-3.2.6
*/
function checkIsHttpToken(val) {
return RegExpPrototypeExec(tokenRegExp, val) !== null;
return tokenRegExp.test(val);
}

const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/;
Expand All @@ -221,7 +220,7 @@ const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/;
* field-vchar = VCHAR / obs-text
*/
function checkInvalidHeaderChar(val) {
return RegExpPrototypeExec(headerCharRegex, val) !== null;
return headerCharRegex.test(val);
}

function cleanParser(parser) {
Expand Down
11 changes: 4 additions & 7 deletions lib/_http_incoming.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@
const {
ObjectDefineProperty,
ObjectSetPrototypeOf,
StringPrototypeCharCodeAt,
StringPrototypeSlice,
StringPrototypeToLowerCase,
Symbol,
} = primordials;

Expand Down Expand Up @@ -370,7 +367,7 @@ function matchKnownFields(field, lowercased) {
if (lowercased) {
return '\u0000' + field;
}
return matchKnownFields(StringPrototypeToLowerCase(field), true);
return matchKnownFields(field.toLowerCase(), true);
}
// Add the given (field, value) pair to the message
//
Expand All @@ -384,9 +381,9 @@ function matchKnownFields(field, lowercased) {
IncomingMessage.prototype._addHeaderLine = _addHeaderLine;
function _addHeaderLine(field, value, dest) {
field = matchKnownFields(field);
const flag = StringPrototypeCharCodeAt(field, 0);
const flag = field.charCodeAt(0);
if (flag === 0 || flag === 2) {
field = StringPrototypeSlice(field, 1);
field = field.slice(1);
// Make a delimited list
if (typeof dest[field] === 'string') {
dest[field] += (flag === 0 ? ', ' : '; ') + value;
Expand Down Expand Up @@ -418,7 +415,7 @@ function _addHeaderLine(field, value, dest) {

IncomingMessage.prototype._addHeaderLineDistinct = _addHeaderLineDistinct;
function _addHeaderLineDistinct(field, value, dest) {
field = StringPrototypeToLowerCase(field);
field = field.toLowerCase();
if (!dest[field]) {
dest[field] = [value];
} else {
Expand Down
Loading

0 comments on commit 7debb6c

Please sign in to comment.