mirror of
https://github.com/nodejs/node.git
synced 2024-11-30 07:27:22 +01:00
ef3776a785
PR-URL: https://github.com/nodejs/node/pull/16925 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Daniel Bevenius <daniel.bevenius@gmail.com> Reviewed-By: Tobias Nießen <tniessen@tnie.de> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Gireesh Punathil <gpunathi@in.ibm.com> Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com>
369 lines
11 KiB
JavaScript
369 lines
11 KiB
JavaScript
// Copyright Joyent, Inc. and other Node contributors.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
|
// copy of this software and associated documentation files (the
|
|
// "Software"), to deal in the Software without restriction, including
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
// persons to whom the Software is furnished to do so, subject to the
|
|
// following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included
|
|
// in all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
|
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
'use strict';
|
|
|
|
const binding = process.binding('http_parser');
|
|
const { methods, HTTPParser } = binding;
|
|
|
|
const FreeList = require('internal/freelist');
|
|
const { ondrain } = require('internal/http');
|
|
const incoming = require('_http_incoming');
|
|
const { emitDestroy } = require('async_hooks');
|
|
const {
|
|
IncomingMessage,
|
|
readStart,
|
|
readStop
|
|
} = incoming;
|
|
|
|
const debug = require('util').debuglog('http');
|
|
|
|
const kOnHeaders = HTTPParser.kOnHeaders | 0;
|
|
const kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0;
|
|
const kOnBody = HTTPParser.kOnBody | 0;
|
|
const kOnMessageComplete = HTTPParser.kOnMessageComplete | 0;
|
|
const kOnExecute = HTTPParser.kOnExecute | 0;
|
|
|
|
// Only called in the slow case where slow means
|
|
// that the request headers were either fragmented
|
|
// across multiple TCP packets or too large to be
|
|
// processed in a single run. This method is also
|
|
// called to process trailing HTTP headers.
|
|
function parserOnHeaders(headers, url) {
|
|
// Once we exceeded headers limit - stop collecting them
|
|
if (this.maxHeaderPairs <= 0 ||
|
|
this._headers.length < this.maxHeaderPairs) {
|
|
this._headers = this._headers.concat(headers);
|
|
}
|
|
this._url += url;
|
|
}
|
|
|
|
// `headers` and `url` are set only if .onHeaders() has not been called for
|
|
// this request.
|
|
// `url` is not set for response parsers but that's not applicable here since
|
|
// all our parsers are request parsers.
|
|
function parserOnHeadersComplete(versionMajor, versionMinor, headers, method,
|
|
url, statusCode, statusMessage, upgrade,
|
|
shouldKeepAlive) {
|
|
var parser = this;
|
|
|
|
if (!headers) {
|
|
headers = parser._headers;
|
|
parser._headers = [];
|
|
}
|
|
|
|
if (!url) {
|
|
url = parser._url;
|
|
parser._url = '';
|
|
}
|
|
|
|
parser.incoming = new IncomingMessage(parser.socket);
|
|
parser.incoming.httpVersionMajor = versionMajor;
|
|
parser.incoming.httpVersionMinor = versionMinor;
|
|
parser.incoming.httpVersion = `${versionMajor}.${versionMinor}`;
|
|
parser.incoming.url = url;
|
|
|
|
var n = headers.length;
|
|
|
|
// If parser.maxHeaderPairs <= 0 assume that there's no limit.
|
|
if (parser.maxHeaderPairs > 0)
|
|
n = Math.min(n, parser.maxHeaderPairs);
|
|
|
|
parser.incoming._addHeaderLines(headers, n);
|
|
|
|
if (typeof method === 'number') {
|
|
// server only
|
|
parser.incoming.method = methods[method];
|
|
} else {
|
|
// client only
|
|
parser.incoming.statusCode = statusCode;
|
|
parser.incoming.statusMessage = statusMessage;
|
|
}
|
|
|
|
if (upgrade && parser.outgoing !== null && !parser.outgoing.upgrading) {
|
|
// The client made non-upgrade request, and server is just advertising
|
|
// supported protocols.
|
|
//
|
|
// See RFC7230 Section 6.7
|
|
upgrade = false;
|
|
}
|
|
|
|
parser.incoming.upgrade = upgrade;
|
|
|
|
var skipBody = 0; // response to HEAD or CONNECT
|
|
|
|
if (!upgrade) {
|
|
// For upgraded connections and CONNECT method request, we'll emit this
|
|
// after parser.execute so that we can capture the first part of the new
|
|
// protocol.
|
|
skipBody = parser.onIncoming(parser.incoming, shouldKeepAlive);
|
|
}
|
|
|
|
if (typeof skipBody !== 'number')
|
|
return skipBody ? 1 : 0;
|
|
else
|
|
return skipBody;
|
|
}
|
|
|
|
// XXX This is a mess.
|
|
// TODO: http.Parser should be a Writable emits request/response events.
|
|
function parserOnBody(b, start, len) {
|
|
var parser = this;
|
|
var stream = parser.incoming;
|
|
|
|
// if the stream has already been removed, then drop it.
|
|
if (!stream)
|
|
return;
|
|
|
|
var socket = stream.socket;
|
|
|
|
// pretend this was the result of a stream._read call.
|
|
if (len > 0 && !stream._dumped) {
|
|
var slice = b.slice(start, start + len);
|
|
var ret = stream.push(slice);
|
|
if (!ret)
|
|
readStop(socket);
|
|
}
|
|
}
|
|
|
|
function parserOnMessageComplete() {
|
|
var parser = this;
|
|
var stream = parser.incoming;
|
|
|
|
if (stream) {
|
|
stream.complete = true;
|
|
// Emit any trailing headers.
|
|
var headers = parser._headers;
|
|
if (headers) {
|
|
parser.incoming._addHeaderLines(headers, headers.length);
|
|
parser._headers = [];
|
|
parser._url = '';
|
|
}
|
|
|
|
// For emit end event
|
|
stream.push(null);
|
|
}
|
|
|
|
// force to read the next incoming message
|
|
readStart(parser.socket);
|
|
}
|
|
|
|
|
|
var parsers = new FreeList('parsers', 1000, function() {
|
|
var parser = new HTTPParser(HTTPParser.REQUEST);
|
|
|
|
parser._headers = [];
|
|
parser._url = '';
|
|
parser._consumed = false;
|
|
|
|
parser.socket = null;
|
|
parser.incoming = null;
|
|
parser.outgoing = null;
|
|
|
|
// Only called in the slow case where slow means
|
|
// that the request headers were either fragmented
|
|
// across multiple TCP packets or too large to be
|
|
// processed in a single run. This method is also
|
|
// called to process trailing HTTP headers.
|
|
parser[kOnHeaders] = parserOnHeaders;
|
|
parser[kOnHeadersComplete] = parserOnHeadersComplete;
|
|
parser[kOnBody] = parserOnBody;
|
|
parser[kOnMessageComplete] = parserOnMessageComplete;
|
|
parser[kOnExecute] = null;
|
|
|
|
return parser;
|
|
});
|
|
|
|
|
|
// Free the parser and also break any links that it
|
|
// might have to any other things.
|
|
// TODO: All parser data should be attached to a
|
|
// single object, so that it can be easily cleaned
|
|
// up by doing `parser.data = {}`, which should
|
|
// be done in FreeList.free. `parsers.free(parser)`
|
|
// should be all that is needed.
|
|
function freeParser(parser, req, socket) {
|
|
if (parser) {
|
|
parser._headers = [];
|
|
parser.onIncoming = null;
|
|
if (parser._consumed)
|
|
parser.unconsume();
|
|
parser._consumed = false;
|
|
if (parser.socket)
|
|
parser.socket.parser = null;
|
|
parser.socket = null;
|
|
parser.incoming = null;
|
|
parser.outgoing = null;
|
|
parser[kOnExecute] = null;
|
|
if (parsers.free(parser) === false) {
|
|
parser.close();
|
|
} else {
|
|
// Since the Parser destructor isn't going to run the destroy() callbacks
|
|
// it needs to be triggered manually.
|
|
emitDestroy(parser.getAsyncId());
|
|
}
|
|
}
|
|
if (req) {
|
|
req.parser = null;
|
|
}
|
|
if (socket) {
|
|
socket.parser = null;
|
|
}
|
|
}
|
|
|
|
|
|
function httpSocketSetup(socket) {
|
|
socket.removeListener('drain', ondrain);
|
|
socket.on('drain', ondrain);
|
|
}
|
|
|
|
/**
|
|
* Verifies that the given val is a valid HTTP token
|
|
* per the rules defined in RFC 7230
|
|
* See https://tools.ietf.org/html/rfc7230#section-3.2.6
|
|
*
|
|
* Allowed characters in an HTTP token:
|
|
* ^_`a-z 94-122
|
|
* A-Z 65-90
|
|
* - 45
|
|
* 0-9 48-57
|
|
* ! 33
|
|
* #$%&' 35-39
|
|
* *+ 42-43
|
|
* . 46
|
|
* | 124
|
|
* ~ 126
|
|
*
|
|
* This implementation of checkIsHttpToken() loops over the string instead of
|
|
* using a regular expression since the former is up to 180% faster with v8 4.9
|
|
* depending on the string length (the shorter the string, the larger the
|
|
* performance difference)
|
|
*
|
|
* Additionally, checkIsHttpToken() is currently designed to be inlinable by v8,
|
|
* so take care when making changes to the implementation so that the source
|
|
* code size does not exceed v8's default max_inlined_source_size setting.
|
|
**/
|
|
var validTokens = [
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 15
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 31
|
|
0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, // 32 - 47
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 - 63
|
|
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 - 79
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, // 80 - 95
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 - 111
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, // 112 - 127
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 128 ...
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // ... 255
|
|
];
|
|
function checkIsHttpToken(val) {
|
|
if (!validTokens[val.charCodeAt(0)])
|
|
return false;
|
|
if (val.length < 2)
|
|
return true;
|
|
if (!validTokens[val.charCodeAt(1)])
|
|
return false;
|
|
if (val.length < 3)
|
|
return true;
|
|
if (!validTokens[val.charCodeAt(2)])
|
|
return false;
|
|
if (val.length < 4)
|
|
return true;
|
|
if (!validTokens[val.charCodeAt(3)])
|
|
return false;
|
|
for (var i = 4; i < val.length; ++i) {
|
|
if (!validTokens[val.charCodeAt(i)])
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* True if val contains an invalid field-vchar
|
|
* field-value = *( field-content / obs-fold )
|
|
* field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
|
|
* field-vchar = VCHAR / obs-text
|
|
*
|
|
* checkInvalidHeaderChar() is currently designed to be inlinable by v8,
|
|
* so take care when making changes to the implementation so that the source
|
|
* code size does not exceed v8's default max_inlined_source_size setting.
|
|
**/
|
|
var validHdrChars = [
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, // 0 - 15
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 31
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 32 - 47
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 48 - 63
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 - 79
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 80 - 95
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 - 111
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 112 - 127
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 128 ...
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // ... 255
|
|
];
|
|
function checkInvalidHeaderChar(val) {
|
|
val += '';
|
|
if (val.length < 1)
|
|
return false;
|
|
if (!validHdrChars[val.charCodeAt(0)])
|
|
return true;
|
|
if (val.length < 2)
|
|
return false;
|
|
if (!validHdrChars[val.charCodeAt(1)])
|
|
return true;
|
|
if (val.length < 3)
|
|
return false;
|
|
if (!validHdrChars[val.charCodeAt(2)])
|
|
return true;
|
|
if (val.length < 4)
|
|
return false;
|
|
if (!validHdrChars[val.charCodeAt(3)])
|
|
return true;
|
|
for (var i = 4; i < val.length; ++i) {
|
|
if (!validHdrChars[val.charCodeAt(i)])
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
module.exports = {
|
|
_checkInvalidHeaderChar: checkInvalidHeaderChar,
|
|
_checkIsHttpToken: checkIsHttpToken,
|
|
chunkExpression: /(?:^|\W)chunked(?:$|\W)/i,
|
|
continueExpression: /(?:^|\W)100-continue(?:$|\W)/i,
|
|
CRLF: '\r\n',
|
|
debug,
|
|
freeParser,
|
|
httpSocketSetup,
|
|
methods,
|
|
parsers
|
|
};
|