1// Copyright Joyent, Inc. and other Node contributors. 2// 3// Permission is hereby granted, free of charge, to any person obtaining a 4// copy of this software and associated documentation files (the 5// "Software"), to deal in the Software without restriction, including 6// without limitation the rights to use, copy, modify, merge, publish, 7// distribute, sublicense, and/or sell copies of the Software, and to permit 8// persons to whom the Software is furnished to do so, subject to the 9// following conditions: 10// 11// The above copyright notice and this permission notice shall be included 12// in all copies or substantial portions of the Software. 13// 14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20// USE OR OTHER DEALINGS IN THE SOFTWARE. 21 22'use strict'; 23 24const { 25 MathMin, 26 Symbol, 27 RegExpPrototypeExec, 28} = primordials; 29const { setImmediate } = require('timers'); 30 31const { methods, HTTPParser } = internalBinding('http_parser'); 32const { getOptionValue } = require('internal/options'); 33const insecureHTTPParser = getOptionValue('--insecure-http-parser'); 34 35const FreeList = require('internal/freelist'); 36const incoming = require('_http_incoming'); 37const { 38 IncomingMessage, 39 readStart, 40 readStop, 41} = incoming; 42 43const kIncomingMessage = Symbol('IncomingMessage'); 44const kOnMessageBegin = HTTPParser.kOnMessageBegin | 0; 45const kOnHeaders = HTTPParser.kOnHeaders | 0; 46const kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0; 47const kOnBody = HTTPParser.kOnBody | 0; 48const kOnMessageComplete = HTTPParser.kOnMessageComplete | 0; 49const kOnExecute = HTTPParser.kOnExecute | 0; 50const kOnTimeout = HTTPParser.kOnTimeout | 0; 51 52const MAX_HEADER_PAIRS = 2000; 53 54// Only called in the slow case where slow means 55// that the request headers were either fragmented 56// across multiple TCP packets or too large to be 57// processed in a single run. This method is also 58// called to process trailing HTTP headers. 59function parserOnHeaders(headers, url) { 60 // Once we exceeded headers limit - stop collecting them 61 if (this.maxHeaderPairs <= 0 || 62 this._headers.length < this.maxHeaderPairs) { 63 this._headers.push(...headers); 64 } 65 this._url += url; 66} 67 68// `headers` and `url` are set only if .onHeaders() has not been called for 69// this request. 70// `url` is not set for response parsers but that's not applicable here since 71// all our parsers are request parsers. 72function parserOnHeadersComplete(versionMajor, versionMinor, headers, method, 73 url, statusCode, statusMessage, upgrade, 74 shouldKeepAlive) { 75 const parser = this; 76 const { socket } = parser; 77 78 if (headers === undefined) { 79 headers = parser._headers; 80 parser._headers = []; 81 } 82 83 if (url === undefined) { 84 url = parser._url; 85 parser._url = ''; 86 } 87 88 // Parser is also used by http client 89 const ParserIncomingMessage = (socket && socket.server && 90 socket.server[kIncomingMessage]) || 91 IncomingMessage; 92 93 const incoming = parser.incoming = new ParserIncomingMessage(socket); 94 incoming.httpVersionMajor = versionMajor; 95 incoming.httpVersionMinor = versionMinor; 96 incoming.httpVersion = `${versionMajor}.${versionMinor}`; 97 incoming.joinDuplicateHeaders = socket?.server?.joinDuplicateHeaders || 98 parser.joinDuplicateHeaders; 99 incoming.url = url; 100 incoming.upgrade = upgrade; 101 102 let n = headers.length; 103 104 // If parser.maxHeaderPairs <= 0 assume that there's no limit. 105 if (parser.maxHeaderPairs > 0) 106 n = MathMin(n, parser.maxHeaderPairs); 107 108 incoming._addHeaderLines(headers, n); 109 110 if (typeof method === 'number') { 111 // server only 112 incoming.method = methods[method]; 113 } else { 114 // client only 115 incoming.statusCode = statusCode; 116 incoming.statusMessage = statusMessage; 117 } 118 119 return parser.onIncoming(incoming, shouldKeepAlive); 120} 121 122function parserOnBody(b) { 123 const stream = this.incoming; 124 125 // If the stream has already been removed, then drop it. 126 if (stream === null) 127 return; 128 129 // Pretend this was the result of a stream._read call. 130 if (!stream._dumped) { 131 const ret = stream.push(b); 132 if (!ret) 133 readStop(this.socket); 134 } 135} 136 137function parserOnMessageComplete() { 138 const parser = this; 139 const stream = parser.incoming; 140 141 if (stream !== null) { 142 stream.complete = true; 143 // Emit any trailing headers. 144 const headers = parser._headers; 145 if (headers.length) { 146 stream._addHeaderLines(headers, headers.length); 147 parser._headers = []; 148 parser._url = ''; 149 } 150 151 // For emit end event 152 stream.push(null); 153 } 154 155 // Force to read the next incoming message 156 readStart(parser.socket); 157} 158 159 160const parsers = new FreeList('parsers', 1000, function parsersCb() { 161 const parser = new HTTPParser(); 162 163 cleanParser(parser); 164 165 parser[kOnHeaders] = parserOnHeaders; 166 parser[kOnHeadersComplete] = parserOnHeadersComplete; 167 parser[kOnBody] = parserOnBody; 168 parser[kOnMessageComplete] = parserOnMessageComplete; 169 170 return parser; 171}); 172 173function closeParserInstance(parser) { parser.close(); } 174 175// Free the parser and also break any links that it 176// might have to any other things. 177// TODO: All parser data should be attached to a 178// single object, so that it can be easily cleaned 179// up by doing `parser.data = {}`, which should 180// be done in FreeList.free. `parsers.free(parser)` 181// should be all that is needed. 182function freeParser(parser, req, socket) { 183 if (parser) { 184 if (parser._consumed) 185 parser.unconsume(); 186 cleanParser(parser); 187 parser.remove(); 188 if (parsers.free(parser) === false) { 189 // Make sure the parser's stack has unwound before deleting the 190 // corresponding C++ object through .close(). 191 setImmediate(closeParserInstance, parser); 192 } else { 193 // Since the Parser destructor isn't going to run the destroy() callbacks 194 // it needs to be triggered manually. 195 parser.free(); 196 } 197 } 198 if (req) { 199 req.parser = null; 200 } 201 if (socket) { 202 socket.parser = null; 203 } 204} 205 206const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/; 207/** 208 * Verifies that the given val is a valid HTTP token 209 * per the rules defined in RFC 7230 210 * See https://tools.ietf.org/html/rfc7230#section-3.2.6 211 */ 212function checkIsHttpToken(val) { 213 return RegExpPrototypeExec(tokenRegExp, val) !== null; 214} 215 216const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/; 217/** 218 * True if val contains an invalid field-vchar 219 * field-value = *( field-content / obs-fold ) 220 * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] 221 * field-vchar = VCHAR / obs-text 222 */ 223function checkInvalidHeaderChar(val) { 224 return RegExpPrototypeExec(headerCharRegex, val) !== null; 225} 226 227function cleanParser(parser) { 228 parser._headers = []; 229 parser._url = ''; 230 parser.socket = null; 231 parser.incoming = null; 232 parser.outgoing = null; 233 parser.maxHeaderPairs = MAX_HEADER_PAIRS; 234 parser[kOnMessageBegin] = null; 235 parser[kOnExecute] = null; 236 parser[kOnTimeout] = null; 237 parser._consumed = false; 238 parser.onIncoming = null; 239 parser.joinDuplicateHeaders = null; 240} 241 242function prepareError(err, parser, rawPacket) { 243 err.rawPacket = rawPacket || parser.getCurrentBuffer(); 244 if (typeof err.reason === 'string') 245 err.message = `Parse Error: ${err.reason}`; 246} 247 248let warnedLenient = false; 249 250function isLenient() { 251 if (insecureHTTPParser && !warnedLenient) { 252 warnedLenient = true; 253 process.emitWarning('Using insecure HTTP parsing'); 254 } 255 return insecureHTTPParser; 256} 257 258module.exports = { 259 _checkInvalidHeaderChar: checkInvalidHeaderChar, 260 _checkIsHttpToken: checkIsHttpToken, 261 chunkExpression: /(?:^|\W)chunked(?:$|\W)/i, 262 continueExpression: /(?:^|\W)100-continue(?:$|\W)/i, 263 CRLF: '\r\n', // TODO: Deprecate this. 264 freeParser, 265 methods, 266 parsers, 267 kIncomingMessage, 268 HTTPParser, 269 isLenient, 270 prepareError, 271}; 272