'use strict'; const FreeList = require('internal/freelist').FreeList; const HTTPParser = process.binding('http_parser').HTTPParser; const incoming = require('_http_incoming'); const IncomingMessage = incoming.IncomingMessage; const readStart = incoming.readStart; const readStop = incoming.readStop; const debug = require('util').debuglog('http'); exports.debug = debug; exports.CRLF = '\r\n'; exports.chunkExpression = /chunk/i; exports.continueExpression = /100-continue/i; exports.methods = HTTPParser.methods; 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 = HTTPParser.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; }); exports.parsers = parsers; // 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(); parser = null; } if (req) { req.parser = null; } if (socket) { socket.parser = null; } } exports.freeParser = freeParser; function ondrain() { if (this._httpMessage) this._httpMessage.emit('drain'); } function httpSocketSetup(socket) { socket.removeListener('drain', ondrain); socket.on('drain', ondrain); } exports.httpSocketSetup = httpSocketSetup; /** * 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 * * 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) **/ function checkIsHttpToken(val) { if (typeof val !== 'string' || val.length === 0) return false; for (var i = 0, len = val.length; i < len; i++) { var ch = val.charCodeAt(i); if (ch >= 65 && ch <= 90) // A-Z continue; if (ch >= 97 && ch <= 122) // a-z continue; // ^ => 94 // _ => 95 // ` => 96 // | => 124 // ~ => 126 if (ch === 94 || ch === 95 || ch === 96 || ch === 124 || ch === 126) continue; if (ch >= 48 && ch <= 57) // 0-9 continue; // ! => 33 // # => 35 // $ => 36 // % => 37 // & => 38 // ' => 39 // * => 42 // + => 43 // - => 45 // . => 46 if (ch >= 33 && ch <= 46) { if (ch === 34 || ch === 40 || ch === 41 || ch === 44) return false; continue; } return false; } return true; } exports._checkIsHttpToken = checkIsHttpToken; /** * 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 **/ function checkInvalidHeaderChar(val) { val = '' + val; for (var i = 0; i < val.length; i++) { const ch = val.charCodeAt(i); if (ch === 9) continue; if (ch <= 31 || ch > 255 || ch === 127) return true; } return false; } exports._checkInvalidHeaderChar = checkInvalidHeaderChar;