// 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 Stream = require('stream'); function readStart(socket) { if (socket && !socket._paused && socket.readable) socket.resume(); } function readStop(socket) { if (socket) socket.pause(); } /* Abstract base class for ServerRequest and ClientResponse. */ function IncomingMessage(socket) { Stream.Readable.call(this); this._readableState.readingMore = true; this.socket = socket; this.connection = socket; this.httpVersionMajor = null; this.httpVersionMinor = null; this.httpVersion = null; this.complete = false; this.headers = {}; this.rawHeaders = []; this.trailers = {}; this.rawTrailers = []; this.readable = true; this.aborted = false; this.upgrade = null; // request (server) only this.url = ''; this.method = null; // response (client) only this.statusCode = null; this.statusMessage = null; this.client = socket; this._consuming = false; // Flag for when we decide that this message cannot possibly be // read by the user, so there's no point continuing to handle it. this._dumped = false; } Object.setPrototypeOf(IncomingMessage.prototype, Stream.Readable.prototype); Object.setPrototypeOf(IncomingMessage, Stream.Readable); IncomingMessage.prototype.setTimeout = function setTimeout(msecs, callback) { if (callback) this.on('timeout', callback); this.socket.setTimeout(msecs); return this; }; IncomingMessage.prototype._read = function _read(n) { if (!this._consuming) { this._readableState.readingMore = false; this._consuming = true; } // We actually do almost nothing here, because the parserOnBody // function fills up our internal buffer directly. However, we // do need to unpause the underlying socket so that it flows. if (this.socket.readable) readStart(this.socket); }; // It's possible that the socket will be destroyed, and removed from // any messages, before ever calling this. In that case, just skip // it, since something else is destroying this connection anyway. IncomingMessage.prototype.destroy = function destroy(error) { if (this.socket) this.socket.destroy(error); }; IncomingMessage.prototype._addHeaderLines = _addHeaderLines; function _addHeaderLines(headers, n) { if (headers && headers.length) { var dest; if (this.complete) { this.rawTrailers = headers; dest = this.trailers; } else { this.rawHeaders = headers; dest = this.headers; } for (var i = 0; i < n; i += 2) { this._addHeaderLine(headers[i], headers[i + 1], dest); } } } // This function is used to help avoid the lowercasing of a field name if it // matches a 'traditional cased' version of a field name. It then returns the // lowercased name to both avoid calling toLowerCase() a second time and to // indicate whether the field was a 'no duplicates' field. If a field is not a // 'no duplicates' field, a `0` byte is prepended as a flag. The one exception // to this is the Set-Cookie header which is indicated by a `1` byte flag, since // it is an 'array' field and thus is treated differently in _addHeaderLines(). // TODO: perhaps http_parser could be returning both raw and lowercased versions // of known header names to avoid us having to call toLowerCase() for those // headers. // 'array' header list is taken from: // https://mxr.mozilla.org/mozilla/source/netwerk/protocol/http/src/nsHttpHeaderArray.cpp function matchKnownFields(field) { var low = false; while (true) { switch (field) { case 'Content-Type': case 'content-type': return 'content-type'; case 'Content-Length': case 'content-length': return 'content-length'; case 'User-Agent': case 'user-agent': return 'user-agent'; case 'Referer': case 'referer': return 'referer'; case 'Host': case 'host': return 'host'; case 'Authorization': case 'authorization': return 'authorization'; case 'Proxy-Authorization': case 'proxy-authorization': return 'proxy-authorization'; case 'If-Modified-Since': case 'if-modified-since': return 'if-modified-since'; case 'If-Unmodified-Since': case 'if-unmodified-since': return 'if-unmodified-since'; case 'From': case 'from': return 'from'; case 'Location': case 'location': return 'location'; case 'Max-Forwards': case 'max-forwards': return 'max-forwards'; case 'Retry-After': case 'retry-after': return 'retry-after'; case 'ETag': case 'etag': return 'etag'; case 'Last-Modified': case 'last-modified': return 'last-modified'; case 'Server': case 'server': return 'server'; case 'Age': case 'age': return 'age'; case 'Expires': case 'expires': return 'expires'; case 'Set-Cookie': case 'set-cookie': return '\u0001'; case 'Cookie': case 'cookie': return '\u0002cookie'; // The fields below are not used in _addHeaderLine(), but they are common // headers where we can avoid toLowerCase() if the mixed or lower case // versions match the first time through. case 'Transfer-Encoding': case 'transfer-encoding': return '\u0000transfer-encoding'; case 'Date': case 'date': return '\u0000date'; case 'Connection': case 'connection': return '\u0000connection'; case 'Cache-Control': case 'cache-control': return '\u0000cache-control'; case 'Vary': case 'vary': return '\u0000vary'; case 'Content-Encoding': case 'content-encoding': return '\u0000content-encoding'; case 'Origin': case 'origin': return '\u0000origin'; case 'Upgrade': case 'upgrade': return '\u0000upgrade'; case 'Expect': case 'expect': return '\u0000expect'; case 'If-Match': case 'if-match': return '\u0000if-match'; case 'If-None-Match': case 'if-none-match': return '\u0000if-none-match'; case 'Accept': case 'accept': return '\u0000accept'; case 'Accept-Encoding': case 'accept-encoding': return '\u0000accept-encoding'; case 'Accept-Language': case 'accept-language': return '\u0000accept-language'; case 'X-Forwarded-For': case 'x-forwarded-for': return '\u0000x-forwarded-for'; case 'X-Forwarded-Host': case 'x-forwarded-host': return '\u0000x-forwarded-host'; case 'X-Forwarded-Proto': case 'x-forwarded-proto': return '\u0000x-forwarded-proto'; default: if (low) return '\u0000' + field; field = field.toLowerCase(); low = true; } } } // Add the given (field, value) pair to the message // // Per RFC2616, section 4.2 it is acceptable to join multiple instances of the // same header with a ', ' if the header in question supports specification of // multiple values this way. The one exception to this is the Cookie header, // which has multiple values joined with a '; ' instead. If a header's values // cannot be joined in either of these ways, we declare the first instance the // winner and drop the second. Extended header fields (those beginning with // 'x-') are always joined. IncomingMessage.prototype._addHeaderLine = _addHeaderLine; function _addHeaderLine(field, value, dest) { field = matchKnownFields(field); var flag = field.charCodeAt(0); if (flag === 0 || flag === 2) { field = field.slice(1); // Make a delimited list if (typeof dest[field] === 'string') { dest[field] += (flag === 0 ? ', ' : '; ') + value; } else { dest[field] = value; } } else if (flag === 1) { // Array header -- only Set-Cookie at the moment if (dest['set-cookie'] !== undefined) { dest['set-cookie'].push(value); } else { dest['set-cookie'] = [value]; } } else if (dest[field] === undefined) { // Drop duplicates dest[field] = value; } } // Call this instead of resume() if we want to just // dump all the data to /dev/null IncomingMessage.prototype._dump = function _dump() { if (!this._dumped) { this._dumped = true; // If there is buffered data, it may trigger 'data' events. // Remove 'data' event listeners explicitly. this.removeAllListeners('data'); this.resume(); } }; module.exports = { IncomingMessage, readStart, readStop };