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 ObjectSetPrototypeOf, 26} = primordials; 27 28const Stream = require('stream'); 29 30function readStart(socket) { 31 if (socket && !socket._paused && socket.readable) 32 socket.resume(); 33} 34 35function readStop(socket) { 36 if (socket) 37 socket.pause(); 38} 39 40/* Abstract base class for ServerRequest and ClientResponse. */ 41function IncomingMessage(socket) { 42 let streamOptions; 43 44 if (socket) { 45 streamOptions = { 46 highWaterMark: socket.readableHighWaterMark 47 }; 48 } 49 50 Stream.Readable.call(this, streamOptions); 51 52 this._readableState.readingMore = true; 53 54 this.socket = socket; 55 this.connection = socket; 56 57 this.httpVersionMajor = null; 58 this.httpVersionMinor = null; 59 this.httpVersion = null; 60 this.complete = false; 61 this.headers = {}; 62 this.rawHeaders = []; 63 this.trailers = {}; 64 this.rawTrailers = []; 65 66 this.readable = true; 67 68 this.aborted = false; 69 70 this.upgrade = null; 71 72 // request (server) only 73 this.url = ''; 74 this.method = null; 75 76 // response (client) only 77 this.statusCode = null; 78 this.statusMessage = null; 79 this.client = socket; 80 81 this._consuming = false; 82 // Flag for when we decide that this message cannot possibly be 83 // read by the user, so there's no point continuing to handle it. 84 this._dumped = false; 85} 86ObjectSetPrototypeOf(IncomingMessage.prototype, Stream.Readable.prototype); 87ObjectSetPrototypeOf(IncomingMessage, Stream.Readable); 88 89IncomingMessage.prototype.setTimeout = function setTimeout(msecs, callback) { 90 if (callback) 91 this.on('timeout', callback); 92 this.socket.setTimeout(msecs); 93 return this; 94}; 95 96 97IncomingMessage.prototype._read = function _read(n) { 98 if (!this._consuming) { 99 this._readableState.readingMore = false; 100 this._consuming = true; 101 } 102 103 // We actually do almost nothing here, because the parserOnBody 104 // function fills up our internal buffer directly. However, we 105 // do need to unpause the underlying socket so that it flows. 106 if (this.socket.readable) 107 readStart(this.socket); 108}; 109 110 111// It's possible that the socket will be destroyed, and removed from 112// any messages, before ever calling this. In that case, just skip 113// it, since something else is destroying this connection anyway. 114IncomingMessage.prototype.destroy = function destroy(error) { 115 if (this.socket) 116 this.socket.destroy(error); 117 return this; 118}; 119 120 121IncomingMessage.prototype._addHeaderLines = _addHeaderLines; 122function _addHeaderLines(headers, n) { 123 if (headers && headers.length) { 124 let dest; 125 if (this.complete) { 126 this.rawTrailers = headers; 127 dest = this.trailers; 128 } else { 129 this.rawHeaders = headers; 130 dest = this.headers; 131 } 132 133 for (let i = 0; i < n; i += 2) { 134 this._addHeaderLine(headers[i], headers[i + 1], dest); 135 } 136 } 137} 138 139 140// This function is used to help avoid the lowercasing of a field name if it 141// matches a 'traditional cased' version of a field name. It then returns the 142// lowercased name to both avoid calling toLowerCase() a second time and to 143// indicate whether the field was a 'no duplicates' field. If a field is not a 144// 'no duplicates' field, a `0` byte is prepended as a flag. The one exception 145// to this is the Set-Cookie header which is indicated by a `1` byte flag, since 146// it is an 'array' field and thus is treated differently in _addHeaderLines(). 147// TODO: perhaps http_parser could be returning both raw and lowercased versions 148// of known header names to avoid us having to call toLowerCase() for those 149// headers. 150function matchKnownFields(field, lowercased) { 151 switch (field.length) { 152 case 3: 153 if (field === 'Age' || field === 'age') return 'age'; 154 break; 155 case 4: 156 if (field === 'Host' || field === 'host') return 'host'; 157 if (field === 'From' || field === 'from') return 'from'; 158 if (field === 'ETag' || field === 'etag') return 'etag'; 159 if (field === 'Date' || field === 'date') return '\u0000date'; 160 if (field === 'Vary' || field === 'vary') return '\u0000vary'; 161 break; 162 case 6: 163 if (field === 'Server' || field === 'server') return 'server'; 164 if (field === 'Cookie' || field === 'cookie') return '\u0002cookie'; 165 if (field === 'Origin' || field === 'origin') return '\u0000origin'; 166 if (field === 'Expect' || field === 'expect') return '\u0000expect'; 167 if (field === 'Accept' || field === 'accept') return '\u0000accept'; 168 break; 169 case 7: 170 if (field === 'Referer' || field === 'referer') return 'referer'; 171 if (field === 'Expires' || field === 'expires') return 'expires'; 172 if (field === 'Upgrade' || field === 'upgrade') return '\u0000upgrade'; 173 break; 174 case 8: 175 if (field === 'Location' || field === 'location') 176 return 'location'; 177 if (field === 'If-Match' || field === 'if-match') 178 return '\u0000if-match'; 179 break; 180 case 10: 181 if (field === 'User-Agent' || field === 'user-agent') 182 return 'user-agent'; 183 if (field === 'Set-Cookie' || field === 'set-cookie') 184 return '\u0001'; 185 if (field === 'Connection' || field === 'connection') 186 return '\u0000connection'; 187 break; 188 case 11: 189 if (field === 'Retry-After' || field === 'retry-after') 190 return 'retry-after'; 191 break; 192 case 12: 193 if (field === 'Content-Type' || field === 'content-type') 194 return 'content-type'; 195 if (field === 'Max-Forwards' || field === 'max-forwards') 196 return 'max-forwards'; 197 break; 198 case 13: 199 if (field === 'Authorization' || field === 'authorization') 200 return 'authorization'; 201 if (field === 'Last-Modified' || field === 'last-modified') 202 return 'last-modified'; 203 if (field === 'Cache-Control' || field === 'cache-control') 204 return '\u0000cache-control'; 205 if (field === 'If-None-Match' || field === 'if-none-match') 206 return '\u0000if-none-match'; 207 break; 208 case 14: 209 if (field === 'Content-Length' || field === 'content-length') 210 return 'content-length'; 211 break; 212 case 15: 213 if (field === 'Accept-Encoding' || field === 'accept-encoding') 214 return '\u0000accept-encoding'; 215 if (field === 'Accept-Language' || field === 'accept-language') 216 return '\u0000accept-language'; 217 if (field === 'X-Forwarded-For' || field === 'x-forwarded-for') 218 return '\u0000x-forwarded-for'; 219 break; 220 case 16: 221 if (field === 'Content-Encoding' || field === 'content-encoding') 222 return '\u0000content-encoding'; 223 if (field === 'X-Forwarded-Host' || field === 'x-forwarded-host') 224 return '\u0000x-forwarded-host'; 225 break; 226 case 17: 227 if (field === 'If-Modified-Since' || field === 'if-modified-since') 228 return 'if-modified-since'; 229 if (field === 'Transfer-Encoding' || field === 'transfer-encoding') 230 return '\u0000transfer-encoding'; 231 if (field === 'X-Forwarded-Proto' || field === 'x-forwarded-proto') 232 return '\u0000x-forwarded-proto'; 233 break; 234 case 19: 235 if (field === 'Proxy-Authorization' || field === 'proxy-authorization') 236 return 'proxy-authorization'; 237 if (field === 'If-Unmodified-Since' || field === 'if-unmodified-since') 238 return 'if-unmodified-since'; 239 break; 240 } 241 if (lowercased) { 242 return '\u0000' + field; 243 } 244 return matchKnownFields(field.toLowerCase(), true); 245} 246// Add the given (field, value) pair to the message 247// 248// Per RFC2616, section 4.2 it is acceptable to join multiple instances of the 249// same header with a ', ' if the header in question supports specification of 250// multiple values this way. The one exception to this is the Cookie header, 251// which has multiple values joined with a '; ' instead. If a header's values 252// cannot be joined in either of these ways, we declare the first instance the 253// winner and drop the second. Extended header fields (those beginning with 254// 'x-') are always joined. 255IncomingMessage.prototype._addHeaderLine = _addHeaderLine; 256function _addHeaderLine(field, value, dest) { 257 field = matchKnownFields(field); 258 const flag = field.charCodeAt(0); 259 if (flag === 0 || flag === 2) { 260 field = field.slice(1); 261 // Make a delimited list 262 if (typeof dest[field] === 'string') { 263 dest[field] += (flag === 0 ? ', ' : '; ') + value; 264 } else { 265 dest[field] = value; 266 } 267 } else if (flag === 1) { 268 // Array header -- only Set-Cookie at the moment 269 if (dest['set-cookie'] !== undefined) { 270 dest['set-cookie'].push(value); 271 } else { 272 dest['set-cookie'] = [value]; 273 } 274 } else if (dest[field] === undefined) { 275 // Drop duplicates 276 dest[field] = value; 277 } 278} 279 280 281// Call this instead of resume() if we want to just 282// dump all the data to /dev/null 283IncomingMessage.prototype._dump = function _dump() { 284 if (!this._dumped) { 285 this._dumped = true; 286 // If there is buffered data, it may trigger 'data' events. 287 // Remove 'data' event listeners explicitly. 288 this.removeAllListeners('data'); 289 this.resume(); 290 } 291}; 292 293module.exports = { 294 IncomingMessage, 295 readStart, 296 readStop 297}; 298