rc@73: /** rc@73: * Module dependencies. rc@73: */ rc@73: rc@73: var accepts = require('accepts'); rc@73: var typeis = require('type-is'); rc@73: var http = require('http'); rc@73: var fresh = require('fresh'); rc@73: var parseRange = require('range-parser'); rc@73: var parse = require('parseurl'); rc@73: var proxyaddr = require('proxy-addr'); rc@73: rc@73: /** rc@73: * Request prototype. rc@73: */ rc@73: rc@73: var req = exports = module.exports = { rc@73: __proto__: http.IncomingMessage.prototype rc@73: }; rc@73: rc@73: /** rc@73: * Return request header. rc@73: * rc@73: * The `Referrer` header field is special-cased, rc@73: * both `Referrer` and `Referer` are interchangeable. rc@73: * rc@73: * Examples: rc@73: * rc@73: * req.get('Content-Type'); rc@73: * // => "text/plain" rc@73: * rc@73: * req.get('content-type'); rc@73: * // => "text/plain" rc@73: * rc@73: * req.get('Something'); rc@73: * // => undefined rc@73: * rc@73: * Aliased as `req.header()`. rc@73: * rc@73: * @param {String} name rc@73: * @return {String} rc@73: * @api public rc@73: */ rc@73: rc@73: req.get = rc@73: req.header = function(name){ rc@73: switch (name = name.toLowerCase()) { rc@73: case 'referer': rc@73: case 'referrer': rc@73: return this.headers.referrer rc@73: || this.headers.referer; rc@73: default: rc@73: return this.headers[name]; rc@73: } rc@73: }; rc@73: rc@73: /** rc@73: * To do: update docs. rc@73: * rc@73: * Check if the given `type(s)` is acceptable, returning rc@73: * the best match when true, otherwise `undefined`, in which rc@73: * case you should respond with 406 "Not Acceptable". rc@73: * rc@73: * The `type` value may be a single mime type string rc@73: * such as "application/json", the extension name rc@73: * such as "json", a comma-delimted list such as "json, html, text/plain", rc@73: * an argument list such as `"json", "html", "text/plain"`, rc@73: * or an array `["json", "html", "text/plain"]`. When a list rc@73: * or array is given the _best_ match, if any is returned. rc@73: * rc@73: * Examples: rc@73: * rc@73: * // Accept: text/html rc@73: * req.accepts('html'); rc@73: * // => "html" rc@73: * rc@73: * // Accept: text/*, application/json rc@73: * req.accepts('html'); rc@73: * // => "html" rc@73: * req.accepts('text/html'); rc@73: * // => "text/html" rc@73: * req.accepts('json, text'); rc@73: * // => "json" rc@73: * req.accepts('application/json'); rc@73: * // => "application/json" rc@73: * rc@73: * // Accept: text/*, application/json rc@73: * req.accepts('image/png'); rc@73: * req.accepts('png'); rc@73: * // => undefined rc@73: * rc@73: * // Accept: text/*;q=.5, application/json rc@73: * req.accepts(['html', 'json']); rc@73: * req.accepts('html', 'json'); rc@73: * req.accepts('html, json'); rc@73: * // => "json" rc@73: * rc@73: * @param {String|Array} type(s) rc@73: * @return {String} rc@73: * @api public rc@73: */ rc@73: rc@73: req.accepts = function(){ rc@73: var accept = accepts(this); rc@73: return accept.types.apply(accept, arguments); rc@73: }; rc@73: rc@73: /** rc@73: * Check if the given `encoding` is accepted. rc@73: * rc@73: * @param {String} encoding rc@73: * @return {Boolean} rc@73: * @api public rc@73: */ rc@73: rc@73: req.acceptsEncoding = // backwards compatibility rc@73: req.acceptsEncodings = function(){ rc@73: var accept = accepts(this); rc@73: return accept.encodings.apply(accept, arguments); rc@73: }; rc@73: rc@73: /** rc@73: * To do: update docs. rc@73: * rc@73: * Check if the given `charset` is acceptable, rc@73: * otherwise you should respond with 406 "Not Acceptable". rc@73: * rc@73: * @param {String} charset rc@73: * @return {Boolean} rc@73: * @api public rc@73: */ rc@73: rc@73: req.acceptsCharset = // backwards compatibility rc@73: req.acceptsCharsets = function(){ rc@73: var accept = accepts(this); rc@73: return accept.charsets.apply(accept, arguments); rc@73: }; rc@73: rc@73: /** rc@73: * To do: update docs. rc@73: * rc@73: * Check if the given `lang` is acceptable, rc@73: * otherwise you should respond with 406 "Not Acceptable". rc@73: * rc@73: * @param {String} lang rc@73: * @return {Boolean} rc@73: * @api public rc@73: */ rc@73: rc@73: req.acceptsLanguage = // backwards compatibility rc@73: req.acceptsLanguages = function(){ rc@73: var accept = accepts(this); rc@73: return accept.languages.apply(accept, arguments); rc@73: }; rc@73: rc@73: /** rc@73: * Parse Range header field, rc@73: * capping to the given `size`. rc@73: * rc@73: * Unspecified ranges such as "0-" require rc@73: * knowledge of your resource length. In rc@73: * the case of a byte range this is of course rc@73: * the total number of bytes. If the Range rc@73: * header field is not given `null` is returned, rc@73: * `-1` when unsatisfiable, `-2` when syntactically invalid. rc@73: * rc@73: * NOTE: remember that ranges are inclusive, so rc@73: * for example "Range: users=0-3" should respond rc@73: * with 4 users when available, not 3. rc@73: * rc@73: * @param {Number} size rc@73: * @return {Array} rc@73: * @api public rc@73: */ rc@73: rc@73: req.range = function(size){ rc@73: var range = this.get('Range'); rc@73: if (!range) return; rc@73: return parseRange(size, range); rc@73: }; rc@73: rc@73: /** rc@73: * Return the value of param `name` when present or `defaultValue`. rc@73: * rc@73: * - Checks route placeholders, ex: _/user/:id_ rc@73: * - Checks body params, ex: id=12, {"id":12} rc@73: * - Checks query string params, ex: ?id=12 rc@73: * rc@73: * To utilize request bodies, `req.body` rc@73: * should be an object. This can be done by using rc@73: * the `bodyParser()` middleware. rc@73: * rc@73: * @param {String} name rc@73: * @param {Mixed} [defaultValue] rc@73: * @return {String} rc@73: * @api public rc@73: */ rc@73: rc@73: req.param = function(name, defaultValue){ rc@73: var params = this.params || {}; rc@73: var body = this.body || {}; rc@73: var query = this.query || {}; rc@73: if (null != params[name] && params.hasOwnProperty(name)) return params[name]; rc@73: if (null != body[name]) return body[name]; rc@73: if (null != query[name]) return query[name]; rc@73: return defaultValue; rc@73: }; rc@73: rc@73: /** rc@73: * Check if the incoming request contains the "Content-Type" rc@73: * header field, and it contains the give mime `type`. rc@73: * rc@73: * Examples: rc@73: * rc@73: * // With Content-Type: text/html; charset=utf-8 rc@73: * req.is('html'); rc@73: * req.is('text/html'); rc@73: * req.is('text/*'); rc@73: * // => true rc@73: * rc@73: * // When Content-Type is application/json rc@73: * req.is('json'); rc@73: * req.is('application/json'); rc@73: * req.is('application/*'); rc@73: * // => true rc@73: * rc@73: * req.is('html'); rc@73: * // => false rc@73: * rc@73: * @param {String} type rc@73: * @return {Boolean} rc@73: * @api public rc@73: */ rc@73: rc@73: req.is = function(types){ rc@73: if (!Array.isArray(types)) types = [].slice.call(arguments); rc@73: return typeis(this, types); rc@73: }; rc@73: rc@73: /** rc@73: * Return the protocol string "http" or "https" rc@73: * when requested with TLS. When the "trust proxy" rc@73: * setting trusts the socket address, the rc@73: * "X-Forwarded-Proto" header field will be trusted. rc@73: * If you're running behind a reverse proxy that rc@73: * supplies https for you this may be enabled. rc@73: * rc@73: * @return {String} rc@73: * @api public rc@73: */ rc@73: rc@73: req.__defineGetter__('protocol', function(){ rc@73: var trust = this.app.get('trust proxy fn'); rc@73: rc@73: if (!trust(this.connection.remoteAddress)) { rc@73: return this.connection.encrypted rc@73: ? 'https' rc@73: : 'http'; rc@73: } rc@73: rc@73: // Note: X-Forwarded-Proto is normally only ever a rc@73: // single value, but this is to be safe. rc@73: var proto = this.get('X-Forwarded-Proto') || 'http'; rc@73: return proto.split(/\s*,\s*/)[0]; rc@73: }); rc@73: rc@73: /** rc@73: * Short-hand for: rc@73: * rc@73: * req.protocol == 'https' rc@73: * rc@73: * @return {Boolean} rc@73: * @api public rc@73: */ rc@73: rc@73: req.__defineGetter__('secure', function(){ rc@73: return 'https' == this.protocol; rc@73: }); rc@73: rc@73: /** rc@73: * Return the remote address from the trusted proxy. rc@73: * rc@73: * The is the remote address on the socket unless rc@73: * "trust proxy" is set. rc@73: * rc@73: * @return {String} rc@73: * @api public rc@73: */ rc@73: rc@73: req.__defineGetter__('ip', function(){ rc@73: var trust = this.app.get('trust proxy fn'); rc@73: return proxyaddr(this, trust); rc@73: }); rc@73: rc@73: /** rc@73: * When "trust proxy" is set, trusted proxy addresses + client. rc@73: * rc@73: * For example if the value were "client, proxy1, proxy2" rc@73: * you would receive the array `["client", "proxy1", "proxy2"]` rc@73: * where "proxy2" is the furthest down-stream and "proxy1" and rc@73: * "proxy2" were trusted. rc@73: * rc@73: * @return {Array} rc@73: * @api public rc@73: */ rc@73: rc@73: req.__defineGetter__('ips', function(){ rc@73: var trust = this.app.get('trust proxy fn'); rc@73: var addrs = proxyaddr.all(this, trust); rc@73: return addrs.slice(1).reverse(); rc@73: }); rc@73: rc@73: /** rc@73: * Return subdomains as an array. rc@73: * rc@73: * Subdomains are the dot-separated parts of the host before the main domain of rc@73: * the app. By default, the domain of the app is assumed to be the last two rc@73: * parts of the host. This can be changed by setting "subdomain offset". rc@73: * rc@73: * For example, if the domain is "tobi.ferrets.example.com": rc@73: * If "subdomain offset" is not set, req.subdomains is `["ferrets", "tobi"]`. rc@73: * If "subdomain offset" is 3, req.subdomains is `["tobi"]`. rc@73: * rc@73: * @return {Array} rc@73: * @api public rc@73: */ rc@73: rc@73: req.__defineGetter__('subdomains', function(){ rc@73: var offset = this.app.get('subdomain offset'); rc@73: return (this.host || '') rc@73: .split('.') rc@73: .reverse() rc@73: .slice(offset); rc@73: }); rc@73: rc@73: /** rc@73: * Short-hand for `url.parse(req.url).pathname`. rc@73: * rc@73: * @return {String} rc@73: * @api public rc@73: */ rc@73: rc@73: req.__defineGetter__('path', function(){ rc@73: return parse(this).pathname; rc@73: }); rc@73: rc@73: /** rc@73: * Parse the "Host" header field hostname. rc@73: * rc@73: * When the "trust proxy" setting trusts the socket rc@73: * address, the "X-Forwarded-Host" header field will rc@73: * be trusted. rc@73: * rc@73: * @return {String} rc@73: * @api public rc@73: */ rc@73: rc@73: req.__defineGetter__('host', function(){ rc@73: var trust = this.app.get('trust proxy fn'); rc@73: var host = this.get('X-Forwarded-Host'); rc@73: rc@73: if (!host || !trust(this.connection.remoteAddress)) { rc@73: host = this.get('Host'); rc@73: } rc@73: rc@73: if (!host) return; rc@73: rc@73: // IPv6 literal support rc@73: var offset = host[0] === '[' rc@73: ? host.indexOf(']') + 1 rc@73: : 0; rc@73: var index = host.indexOf(':', offset); rc@73: rc@73: return ~index rc@73: ? host.substring(0, index) rc@73: : host; rc@73: }); rc@73: rc@73: /** rc@73: * Check if the request is fresh, aka rc@73: * Last-Modified and/or the ETag rc@73: * still match. rc@73: * rc@73: * @return {Boolean} rc@73: * @api public rc@73: */ rc@73: rc@73: req.__defineGetter__('fresh', function(){ rc@73: var method = this.method; rc@73: var s = this.res.statusCode; rc@73: rc@73: // GET or HEAD for weak freshness validation only rc@73: if ('GET' != method && 'HEAD' != method) return false; rc@73: rc@73: // 2xx or 304 as per rfc2616 14.26 rc@73: if ((s >= 200 && s < 300) || 304 == s) { rc@73: return fresh(this.headers, this.res._headers); rc@73: } rc@73: rc@73: return false; rc@73: }); rc@73: rc@73: /** rc@73: * Check if the request is stale, aka rc@73: * "Last-Modified" and / or the "ETag" for the rc@73: * resource has changed. rc@73: * rc@73: * @return {Boolean} rc@73: * @api public rc@73: */ rc@73: rc@73: req.__defineGetter__('stale', function(){ rc@73: return !this.fresh; rc@73: }); rc@73: rc@73: /** rc@73: * Check if the request was an _XMLHttpRequest_. rc@73: * rc@73: * @return {Boolean} rc@73: * @api public rc@73: */ rc@73: rc@73: req.__defineGetter__('xhr', function(){ rc@73: var val = this.get('X-Requested-With') || ''; rc@73: return 'xmlhttprequest' == val.toLowerCase(); rc@73: });