Spaces:
Sleeping
Sleeping
; | |
/** | |
* Module dependencies. | |
*/ | |
const URL = require('url').URL; | |
const net = require('net'); | |
const accepts = require('accepts'); | |
const contentType = require('content-type'); | |
const stringify = require('url').format; | |
const parse = require('parseurl'); | |
const qs = require('querystring'); | |
const typeis = require('type-is'); | |
const fresh = require('fresh'); | |
const only = require('only'); | |
const util = require('util'); | |
const IP = Symbol('context#ip'); | |
/** | |
* Prototype. | |
*/ | |
module.exports = { | |
/** | |
* Return request header. | |
* | |
* @return {Object} | |
* @api public | |
*/ | |
get header() { | |
return this.req.headers; | |
}, | |
/** | |
* Set request header. | |
* | |
* @api public | |
*/ | |
set header(val) { | |
this.req.headers = val; | |
}, | |
/** | |
* Return request header, alias as request.header | |
* | |
* @return {Object} | |
* @api public | |
*/ | |
get headers() { | |
return this.req.headers; | |
}, | |
/** | |
* Set request header, alias as request.header | |
* | |
* @api public | |
*/ | |
set headers(val) { | |
this.req.headers = val; | |
}, | |
/** | |
* Get request URL. | |
* | |
* @return {String} | |
* @api public | |
*/ | |
get url() { | |
return this.req.url; | |
}, | |
/** | |
* Set request URL. | |
* | |
* @api public | |
*/ | |
set url(val) { | |
this.req.url = val; | |
}, | |
/** | |
* Get origin of URL. | |
* | |
* @return {String} | |
* @api public | |
*/ | |
get origin() { | |
return `${this.protocol}://${this.host}`; | |
}, | |
/** | |
* Get full request URL. | |
* | |
* @return {String} | |
* @api public | |
*/ | |
get href() { | |
// support: `GET http://example.com/foo` | |
if (/^https?:\/\//i.test(this.originalUrl)) return this.originalUrl; | |
return this.origin + this.originalUrl; | |
}, | |
/** | |
* Get request method. | |
* | |
* @return {String} | |
* @api public | |
*/ | |
get method() { | |
return this.req.method; | |
}, | |
/** | |
* Set request method. | |
* | |
* @param {String} val | |
* @api public | |
*/ | |
set method(val) { | |
this.req.method = val; | |
}, | |
/** | |
* Get request pathname. | |
* | |
* @return {String} | |
* @api public | |
*/ | |
get path() { | |
return parse(this.req).pathname; | |
}, | |
/** | |
* Set pathname, retaining the query string when present. | |
* | |
* @param {String} path | |
* @api public | |
*/ | |
set path(path) { | |
const url = parse(this.req); | |
if (url.pathname === path) return; | |
url.pathname = path; | |
url.path = null; | |
this.url = stringify(url); | |
}, | |
/** | |
* Get parsed query string. | |
* | |
* @return {Object} | |
* @api public | |
*/ | |
get query() { | |
const str = this.querystring; | |
const c = this._querycache = this._querycache || {}; | |
return c[str] || (c[str] = qs.parse(str)); | |
}, | |
/** | |
* Set query string as an object. | |
* | |
* @param {Object} obj | |
* @api public | |
*/ | |
set query(obj) { | |
this.querystring = qs.stringify(obj); | |
}, | |
/** | |
* Get query string. | |
* | |
* @return {String} | |
* @api public | |
*/ | |
get querystring() { | |
if (!this.req) return ''; | |
return parse(this.req).query || ''; | |
}, | |
/** | |
* Set query string. | |
* | |
* @param {String} str | |
* @api public | |
*/ | |
set querystring(str) { | |
const url = parse(this.req); | |
if (url.search === `?${str}`) return; | |
url.search = str; | |
url.path = null; | |
this.url = stringify(url); | |
}, | |
/** | |
* Get the search string. Same as the query string | |
* except it includes the leading ?. | |
* | |
* @return {String} | |
* @api public | |
*/ | |
get search() { | |
if (!this.querystring) return ''; | |
return `?${this.querystring}`; | |
}, | |
/** | |
* Set the search string. Same as | |
* request.querystring= but included for ubiquity. | |
* | |
* @param {String} str | |
* @api public | |
*/ | |
set search(str) { | |
this.querystring = str; | |
}, | |
/** | |
* Parse the "Host" header field host | |
* and support X-Forwarded-Host when a | |
* proxy is enabled. | |
* | |
* @return {String} hostname:port | |
* @api public | |
*/ | |
get host() { | |
const proxy = this.app.proxy; | |
let host = proxy && this.get('X-Forwarded-Host'); | |
if (!host) { | |
if (this.req.httpVersionMajor >= 2) host = this.get(':authority'); | |
if (!host) host = this.get('Host'); | |
} | |
if (!host) return ''; | |
return host.split(/\s*,\s*/, 1)[0]; | |
}, | |
/** | |
* Parse the "Host" header field hostname | |
* and support X-Forwarded-Host when a | |
* proxy is enabled. | |
* | |
* @return {String} hostname | |
* @api public | |
*/ | |
get hostname() { | |
const host = this.host; | |
if (!host) return ''; | |
if ('[' === host[0]) return this.URL.hostname || ''; // IPv6 | |
return host.split(':', 1)[0]; | |
}, | |
/** | |
* Get WHATWG parsed URL. | |
* Lazily memoized. | |
* | |
* @return {URL|Object} | |
* @api public | |
*/ | |
get URL() { | |
/* istanbul ignore else */ | |
if (!this.memoizedURL) { | |
const originalUrl = this.originalUrl || ''; // avoid undefined in template string | |
try { | |
this.memoizedURL = new URL(`${this.origin}${originalUrl}`); | |
} catch (err) { | |
this.memoizedURL = Object.create(null); | |
} | |
} | |
return this.memoizedURL; | |
}, | |
/** | |
* Check if the request is fresh, aka | |
* Last-Modified and/or the ETag | |
* still match. | |
* | |
* @return {Boolean} | |
* @api public | |
*/ | |
get fresh() { | |
const method = this.method; | |
const s = this.ctx.status; | |
// GET or HEAD for weak freshness validation only | |
if ('GET' !== method && 'HEAD' !== method) return false; | |
// 2xx or 304 as per rfc2616 14.26 | |
if ((s >= 200 && s < 300) || 304 === s) { | |
return fresh(this.header, this.response.header); | |
} | |
return false; | |
}, | |
/** | |
* Check if the request is stale, aka | |
* "Last-Modified" and / or the "ETag" for the | |
* resource has changed. | |
* | |
* @return {Boolean} | |
* @api public | |
*/ | |
get stale() { | |
return !this.fresh; | |
}, | |
/** | |
* Check if the request is idempotent. | |
* | |
* @return {Boolean} | |
* @api public | |
*/ | |
get idempotent() { | |
const methods = ['GET', 'HEAD', 'PUT', 'DELETE', 'OPTIONS', 'TRACE']; | |
return !!~methods.indexOf(this.method); | |
}, | |
/** | |
* Return the request socket. | |
* | |
* @return {Connection} | |
* @api public | |
*/ | |
get socket() { | |
return this.req.socket; | |
}, | |
/** | |
* Get the charset when present or undefined. | |
* | |
* @return {String} | |
* @api public | |
*/ | |
get charset() { | |
try { | |
const { parameters } = contentType.parse(this.req); | |
return parameters.charset || ''; | |
} catch (e) { | |
return ''; | |
} | |
}, | |
/** | |
* Return parsed Content-Length when present. | |
* | |
* @return {Number} | |
* @api public | |
*/ | |
get length() { | |
const len = this.get('Content-Length'); | |
if (len === '') return; | |
return ~~len; | |
}, | |
/** | |
* Return the protocol string "http" or "https" | |
* when requested with TLS. When the proxy setting | |
* is enabled the "X-Forwarded-Proto" header | |
* field will be trusted. If you're running behind | |
* a reverse proxy that supplies https for you this | |
* may be enabled. | |
* | |
* @return {String} | |
* @api public | |
*/ | |
get protocol() { | |
if (this.socket.encrypted) return 'https'; | |
if (!this.app.proxy) return 'http'; | |
const proto = this.get('X-Forwarded-Proto'); | |
return proto ? proto.split(/\s*,\s*/, 1)[0] : 'http'; | |
}, | |
/** | |
* Shorthand for: | |
* | |
* this.protocol == 'https' | |
* | |
* @return {Boolean} | |
* @api public | |
*/ | |
get secure() { | |
return 'https' === this.protocol; | |
}, | |
/** | |
* When `app.proxy` is `true`, parse | |
* the "X-Forwarded-For" ip address list. | |
* | |
* For example if the value was "client, proxy1, proxy2" | |
* you would receive the array `["client", "proxy1", "proxy2"]` | |
* where "proxy2" is the furthest down-stream. | |
* | |
* @return {Array} | |
* @api public | |
*/ | |
get ips() { | |
const proxy = this.app.proxy; | |
const val = this.get(this.app.proxyIpHeader); | |
let ips = proxy && val | |
? val.split(/\s*,\s*/) | |
: []; | |
if (this.app.maxIpsCount > 0) { | |
ips = ips.slice(-this.app.maxIpsCount); | |
} | |
return ips; | |
}, | |
/** | |
* Return request's remote address | |
* When `app.proxy` is `true`, parse | |
* the "X-Forwarded-For" ip address list and return the first one | |
* | |
* @return {String} | |
* @api public | |
*/ | |
get ip() { | |
if (!this[IP]) { | |
this[IP] = this.ips[0] || this.socket.remoteAddress || ''; | |
} | |
return this[IP]; | |
}, | |
set ip(_ip) { | |
this[IP] = _ip; | |
}, | |
/** | |
* Return subdomains as an array. | |
* | |
* Subdomains are the dot-separated parts of the host before the main domain | |
* of the app. By default, the domain of the app is assumed to be the last two | |
* parts of the host. This can be changed by setting `app.subdomainOffset`. | |
* | |
* For example, if the domain is "tobi.ferrets.example.com": | |
* If `app.subdomainOffset` is not set, this.subdomains is | |
* `["ferrets", "tobi"]`. | |
* If `app.subdomainOffset` is 3, this.subdomains is `["tobi"]`. | |
* | |
* @return {Array} | |
* @api public | |
*/ | |
get subdomains() { | |
const offset = this.app.subdomainOffset; | |
const hostname = this.hostname; | |
if (net.isIP(hostname)) return []; | |
return hostname | |
.split('.') | |
.reverse() | |
.slice(offset); | |
}, | |
/** | |
* Get accept object. | |
* Lazily memoized. | |
* | |
* @return {Object} | |
* @api private | |
*/ | |
get accept() { | |
return this._accept || (this._accept = accepts(this.req)); | |
}, | |
/** | |
* Set accept object. | |
* | |
* @param {Object} | |
* @api private | |
*/ | |
set accept(obj) { | |
this._accept = obj; | |
}, | |
/** | |
* Check if the given `type(s)` is acceptable, returning | |
* the best match when true, otherwise `false`, in which | |
* case you should respond with 406 "Not Acceptable". | |
* | |
* The `type` value may be a single mime type string | |
* such as "application/json", the extension name | |
* such as "json" or an array `["json", "html", "text/plain"]`. When a list | |
* or array is given the _best_ match, if any is returned. | |
* | |
* Examples: | |
* | |
* // Accept: text/html | |
* this.accepts('html'); | |
* // => "html" | |
* | |
* // Accept: text/*, application/json | |
* this.accepts('html'); | |
* // => "html" | |
* this.accepts('text/html'); | |
* // => "text/html" | |
* this.accepts('json', 'text'); | |
* // => "json" | |
* this.accepts('application/json'); | |
* // => "application/json" | |
* | |
* // Accept: text/*, application/json | |
* this.accepts('image/png'); | |
* this.accepts('png'); | |
* // => false | |
* | |
* // Accept: text/*;q=.5, application/json | |
* this.accepts(['html', 'json']); | |
* this.accepts('html', 'json'); | |
* // => "json" | |
* | |
* @param {String|Array} type(s)... | |
* @return {String|Array|false} | |
* @api public | |
*/ | |
accepts(...args) { | |
return this.accept.types(...args); | |
}, | |
/** | |
* Return accepted encodings or best fit based on `encodings`. | |
* | |
* Given `Accept-Encoding: gzip, deflate` | |
* an array sorted by quality is returned: | |
* | |
* ['gzip', 'deflate'] | |
* | |
* @param {String|Array} encoding(s)... | |
* @return {String|Array} | |
* @api public | |
*/ | |
acceptsEncodings(...args) { | |
return this.accept.encodings(...args); | |
}, | |
/** | |
* Return accepted charsets or best fit based on `charsets`. | |
* | |
* Given `Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5` | |
* an array sorted by quality is returned: | |
* | |
* ['utf-8', 'utf-7', 'iso-8859-1'] | |
* | |
* @param {String|Array} charset(s)... | |
* @return {String|Array} | |
* @api public | |
*/ | |
acceptsCharsets(...args) { | |
return this.accept.charsets(...args); | |
}, | |
/** | |
* Return accepted languages or best fit based on `langs`. | |
* | |
* Given `Accept-Language: en;q=0.8, es, pt` | |
* an array sorted by quality is returned: | |
* | |
* ['es', 'pt', 'en'] | |
* | |
* @param {String|Array} lang(s)... | |
* @return {Array|String} | |
* @api public | |
*/ | |
acceptsLanguages(...args) { | |
return this.accept.languages(...args); | |
}, | |
/** | |
* Check if the incoming request contains the "Content-Type" | |
* header field and if it contains any of the given mime `type`s. | |
* If there is no request body, `null` is returned. | |
* If there is no content type, `false` is returned. | |
* Otherwise, it returns the first `type` that matches. | |
* | |
* Examples: | |
* | |
* // With Content-Type: text/html; charset=utf-8 | |
* this.is('html'); // => 'html' | |
* this.is('text/html'); // => 'text/html' | |
* this.is('text/*', 'application/json'); // => 'text/html' | |
* | |
* // When Content-Type is application/json | |
* this.is('json', 'urlencoded'); // => 'json' | |
* this.is('application/json'); // => 'application/json' | |
* this.is('html', 'application/*'); // => 'application/json' | |
* | |
* this.is('html'); // => false | |
* | |
* @param {String|String[]} [type] | |
* @param {String[]} [types] | |
* @return {String|false|null} | |
* @api public | |
*/ | |
is(type, ...types) { | |
return typeis(this.req, type, ...types); | |
}, | |
/** | |
* Return the request mime type void of | |
* parameters such as "charset". | |
* | |
* @return {String} | |
* @api public | |
*/ | |
get type() { | |
const type = this.get('Content-Type'); | |
if (!type) return ''; | |
return type.split(';')[0]; | |
}, | |
/** | |
* Return request header. | |
* | |
* The `Referrer` header field is special-cased, | |
* both `Referrer` and `Referer` are interchangeable. | |
* | |
* Examples: | |
* | |
* this.get('Content-Type'); | |
* // => "text/plain" | |
* | |
* this.get('content-type'); | |
* // => "text/plain" | |
* | |
* this.get('Something'); | |
* // => '' | |
* | |
* @param {String} field | |
* @return {String} | |
* @api public | |
*/ | |
get(field) { | |
const req = this.req; | |
switch (field = field.toLowerCase()) { | |
case 'referer': | |
case 'referrer': | |
return req.headers.referrer || req.headers.referer || ''; | |
default: | |
return req.headers[field] || ''; | |
} | |
}, | |
/** | |
* Inspect implementation. | |
* | |
* @return {Object} | |
* @api public | |
*/ | |
inspect() { | |
if (!this.req) return; | |
return this.toJSON(); | |
}, | |
/** | |
* Return JSON representation. | |
* | |
* @return {Object} | |
* @api public | |
*/ | |
toJSON() { | |
return only(this, [ | |
'method', | |
'url', | |
'header' | |
]); | |
} | |
}; | |
/** | |
* Custom inspection implementation for newer Node.js versions. | |
* | |
* @return {Object} | |
* @api public | |
*/ | |
/* istanbul ignore else */ | |
if (util.inspect.custom) { | |
module.exports[util.inspect.custom] = module.exports.inspect; | |
} | |