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} = primordials; 28const { setImmediate } = require('timers'); 29 30const { methods, HTTPParser } = internalBinding('http_parser'); 31const { getOptionValue } = require('internal/options'); 32const insecureHTTPParser = getOptionValue('--insecure-http-parser'); 33 34const FreeList = require('internal/freelist'); 35const incoming = require('_http_incoming'); 36const { 37 IncomingMessage, 38 readStart, 39 readStop 40} = incoming; 41 42let debug = require('internal/util/debuglog').debuglog('http', (fn) => { 43 debug = fn; 44}); 45 46const kIncomingMessage = Symbol('IncomingMessage'); 47const kRequestTimeout = Symbol('RequestTimeout'); 48const kOnMessageBegin = HTTPParser.kOnMessageBegin | 0; 49const kOnHeaders = HTTPParser.kOnHeaders | 0; 50const kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0; 51const kOnBody = HTTPParser.kOnBody | 0; 52const kOnMessageComplete = HTTPParser.kOnMessageComplete | 0; 53const kOnExecute = HTTPParser.kOnExecute | 0; 54const kOnTimeout = HTTPParser.kOnTimeout | 0; 55 56const MAX_HEADER_PAIRS = 2000; 57 58// Only called in the slow case where slow means 59// that the request headers were either fragmented 60// across multiple TCP packets or too large to be 61// processed in a single run. This method is also 62// called to process trailing HTTP headers. 63function parserOnHeaders(headers, url) { 64 // Once we exceeded headers limit - stop collecting them 65 if (this.maxHeaderPairs <= 0 || 66 this._headers.length < this.maxHeaderPairs) { 67 this._headers = this._headers.concat(headers); 68 } 69 this._url += url; 70} 71 72// `headers` and `url` are set only if .onHeaders() has not been called for 73// this request. 74// `url` is not set for response parsers but that's not applicable here since 75// all our parsers are request parsers. 76function parserOnHeadersComplete(versionMajor, versionMinor, headers, method, 77 url, statusCode, statusMessage, upgrade, 78 shouldKeepAlive) { 79 const parser = this; 80 const { socket } = parser; 81 82 if (headers === undefined) { 83 headers = parser._headers; 84 parser._headers = []; 85 } 86 87 if (url === undefined) { 88 url = parser._url; 89 parser._url = ''; 90 } 91 92 // Parser is also used by http client 93 const ParserIncomingMessage = (socket && socket.server && 94 socket.server[kIncomingMessage]) || 95 IncomingMessage; 96 97 const incoming = parser.incoming = new ParserIncomingMessage(socket); 98 incoming.httpVersionMajor = versionMajor; 99 incoming.httpVersionMinor = versionMinor; 100 incoming.httpVersion = `${versionMajor}.${versionMinor}`; 101 incoming.url = url; 102 incoming.upgrade = upgrade; 103 104 if (socket) { 105 debug('requestTimeout timer moved to req'); 106 incoming[kRequestTimeout] = incoming.socket[kRequestTimeout]; 107 incoming.socket[kRequestTimeout] = undefined; 108 } 109 110 let n = headers.length; 111 112 // If parser.maxHeaderPairs <= 0 assume that there's no limit. 113 if (parser.maxHeaderPairs > 0) 114 n = MathMin(n, parser.maxHeaderPairs); 115 116 incoming._addHeaderLines(headers, n); 117 118 if (typeof method === 'number') { 119 // server only 120 incoming.method = methods[method]; 121 } else { 122 // client only 123 incoming.statusCode = statusCode; 124 incoming.statusMessage = statusMessage; 125 } 126 127 return parser.onIncoming(incoming, shouldKeepAlive); 128} 129 130function parserOnBody(b, start, len) { 131 const stream = this.incoming; 132 133 // If the stream has already been removed, then drop it. 134 if (stream === null) 135 return; 136 137 // Pretend this was the result of a stream._read call. 138 if (len > 0 && !stream._dumped) { 139 const slice = b.slice(start, start + len); 140 const ret = stream.push(slice); 141 if (!ret) 142 readStop(this.socket); 143 } 144} 145 146function parserOnMessageComplete() { 147 const parser = this; 148 const stream = parser.incoming; 149 150 if (stream !== null) { 151 stream.complete = true; 152 // Emit any trailing headers. 153 const headers = parser._headers; 154 if (headers.length) { 155 stream._addHeaderLines(headers, headers.length); 156 parser._headers = []; 157 parser._url = ''; 158 } 159 160 // For emit end event 161 stream.push(null); 162 } 163 164 // Force to read the next incoming message 165 readStart(parser.socket); 166} 167 168 169const parsers = new FreeList('parsers', 1000, function parsersCb() { 170 const parser = new HTTPParser(); 171 172 cleanParser(parser); 173 174 parser[kOnHeaders] = parserOnHeaders; 175 parser[kOnHeadersComplete] = parserOnHeadersComplete; 176 parser[kOnBody] = parserOnBody; 177 parser[kOnMessageComplete] = parserOnMessageComplete; 178 179 return parser; 180}); 181 182function closeParserInstance(parser) { parser.close(); } 183 184// Free the parser and also break any links that it 185// might have to any other things. 186// TODO: All parser data should be attached to a 187// single object, so that it can be easily cleaned 188// up by doing `parser.data = {}`, which should 189// be done in FreeList.free. `parsers.free(parser)` 190// should be all that is needed. 191function freeParser(parser, req, socket) { 192 if (parser) { 193 if (parser._consumed) 194 parser.unconsume(); 195 cleanParser(parser); 196 if (parsers.free(parser) === false) { 197 // Make sure the parser's stack has unwound before deleting the 198 // corresponding C++ object through .close(). 199 setImmediate(closeParserInstance, parser); 200 } else { 201 // Since the Parser destructor isn't going to run the destroy() callbacks 202 // it needs to be triggered manually. 203 parser.free(); 204 } 205 } 206 if (req) { 207 req.parser = null; 208 } 209 if (socket) { 210 socket.parser = null; 211 } 212} 213 214const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/; 215/** 216 * Verifies that the given val is a valid HTTP token 217 * per the rules defined in RFC 7230 218 * See https://tools.ietf.org/html/rfc7230#section-3.2.6 219 */ 220function checkIsHttpToken(val) { 221 return tokenRegExp.test(val); 222} 223 224const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/; 225/** 226 * True if val contains an invalid field-vchar 227 * field-value = *( field-content / obs-fold ) 228 * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] 229 * field-vchar = VCHAR / obs-text 230 */ 231function checkInvalidHeaderChar(val) { 232 return headerCharRegex.test(val); 233} 234 235function cleanParser(parser) { 236 parser._headers = []; 237 parser._url = ''; 238 parser.socket = null; 239 parser.incoming = null; 240 parser.outgoing = null; 241 parser.maxHeaderPairs = MAX_HEADER_PAIRS; 242 parser[kOnMessageBegin] = null; 243 parser[kOnExecute] = null; 244 parser[kOnTimeout] = null; 245 parser._consumed = false; 246 parser.onIncoming = null; 247} 248 249function prepareError(err, parser, rawPacket) { 250 err.rawPacket = rawPacket || parser.getCurrentBuffer(); 251 if (typeof err.reason === 'string') 252 err.message = `Parse Error: ${err.reason}`; 253} 254 255let warnedLenient = false; 256 257function isLenient() { 258 if (insecureHTTPParser && !warnedLenient) { 259 warnedLenient = true; 260 process.emitWarning('Using insecure HTTP parsing'); 261 } 262 return insecureHTTPParser; 263} 264 265module.exports = { 266 _checkInvalidHeaderChar: checkInvalidHeaderChar, 267 _checkIsHttpToken: checkIsHttpToken, 268 chunkExpression: /(?:^|\W)chunked(?:$|\W)/i, 269 continueExpression: /(?:^|\W)100-continue(?:$|\W)/i, 270 CRLF: '\r\n', 271 debug, 272 freeParser, 273 methods, 274 parsers, 275 kIncomingMessage, 276 kRequestTimeout, 277 HTTPParser, 278 isLenient, 279 prepareError, 280}; 281