Spaces:
Runtime error
Runtime error
/*! | |
* express | |
* Copyright(c) 2009-2013 TJ Holowaychuk | |
* Copyright(c) 2014-2015 Douglas Christopher Wilson | |
* MIT Licensed | |
*/ | |
; | |
/** | |
* Module dependencies. | |
* @private | |
*/ | |
var Buffer = require('safe-buffer').Buffer | |
var contentDisposition = require('content-disposition'); | |
var createError = require('http-errors') | |
var deprecate = require('depd')('express'); | |
var encodeUrl = require('encodeurl'); | |
var escapeHtml = require('escape-html'); | |
var http = require('http'); | |
var isAbsolute = require('./utils').isAbsolute; | |
var onFinished = require('on-finished'); | |
var path = require('path'); | |
var statuses = require('statuses') | |
var merge = require('utils-merge'); | |
var sign = require('cookie-signature').sign; | |
var normalizeType = require('./utils').normalizeType; | |
var normalizeTypes = require('./utils').normalizeTypes; | |
var setCharset = require('./utils').setCharset; | |
var cookie = require('cookie'); | |
var send = require('send'); | |
var extname = path.extname; | |
var mime = send.mime; | |
var resolve = path.resolve; | |
var vary = require('vary'); | |
/** | |
* Response prototype. | |
* @public | |
*/ | |
var res = Object.create(http.ServerResponse.prototype) | |
/** | |
* Module exports. | |
* @public | |
*/ | |
module.exports = res | |
/** | |
* Module variables. | |
* @private | |
*/ | |
var charsetRegExp = /;\s*charset\s*=/; | |
/** | |
* Set status `code`. | |
* | |
* @param {Number} code | |
* @return {ServerResponse} | |
* @public | |
*/ | |
res.status = function status(code) { | |
if ((typeof code === 'string' || Math.floor(code) !== code) && code > 99 && code < 1000) { | |
deprecate('res.status(' + JSON.stringify(code) + '): use res.status(' + Math.floor(code) + ') instead') | |
} | |
this.statusCode = code; | |
return this; | |
}; | |
/** | |
* Set Link header field with the given `links`. | |
* | |
* Examples: | |
* | |
* res.links({ | |
* next: 'http://api.example.com/users?page=2', | |
* last: 'http://api.example.com/users?page=5' | |
* }); | |
* | |
* @param {Object} links | |
* @return {ServerResponse} | |
* @public | |
*/ | |
res.links = function(links){ | |
var link = this.get('Link') || ''; | |
if (link) link += ', '; | |
return this.set('Link', link + Object.keys(links).map(function(rel){ | |
return '<' + links[rel] + '>; rel="' + rel + '"'; | |
}).join(', ')); | |
}; | |
/** | |
* Send a response. | |
* | |
* Examples: | |
* | |
* res.send(Buffer.from('wahoo')); | |
* res.send({ some: 'json' }); | |
* res.send('<p>some html</p>'); | |
* | |
* @param {string|number|boolean|object|Buffer} body | |
* @public | |
*/ | |
res.send = function send(body) { | |
var chunk = body; | |
var encoding; | |
var req = this.req; | |
var type; | |
// settings | |
var app = this.app; | |
// allow status / body | |
if (arguments.length === 2) { | |
// res.send(body, status) backwards compat | |
if (typeof arguments[0] !== 'number' && typeof arguments[1] === 'number') { | |
deprecate('res.send(body, status): Use res.status(status).send(body) instead'); | |
this.statusCode = arguments[1]; | |
} else { | |
deprecate('res.send(status, body): Use res.status(status).send(body) instead'); | |
this.statusCode = arguments[0]; | |
chunk = arguments[1]; | |
} | |
} | |
// disambiguate res.send(status) and res.send(status, num) | |
if (typeof chunk === 'number' && arguments.length === 1) { | |
// res.send(status) will set status message as text string | |
if (!this.get('Content-Type')) { | |
this.type('txt'); | |
} | |
deprecate('res.send(status): Use res.sendStatus(status) instead'); | |
this.statusCode = chunk; | |
chunk = statuses.message[chunk] | |
} | |
switch (typeof chunk) { | |
// string defaulting to html | |
case 'string': | |
if (!this.get('Content-Type')) { | |
this.type('html'); | |
} | |
break; | |
case 'boolean': | |
case 'number': | |
case 'object': | |
if (chunk === null) { | |
chunk = ''; | |
} else if (Buffer.isBuffer(chunk)) { | |
if (!this.get('Content-Type')) { | |
this.type('bin'); | |
} | |
} else { | |
return this.json(chunk); | |
} | |
break; | |
} | |
// write strings in utf-8 | |
if (typeof chunk === 'string') { | |
encoding = 'utf8'; | |
type = this.get('Content-Type'); | |
// reflect this in content-type | |
if (typeof type === 'string') { | |
this.set('Content-Type', setCharset(type, 'utf-8')); | |
} | |
} | |
// determine if ETag should be generated | |
var etagFn = app.get('etag fn') | |
var generateETag = !this.get('ETag') && typeof etagFn === 'function' | |
// populate Content-Length | |
var len | |
if (chunk !== undefined) { | |
if (Buffer.isBuffer(chunk)) { | |
// get length of Buffer | |
len = chunk.length | |
} else if (!generateETag && chunk.length < 1000) { | |
// just calculate length when no ETag + small chunk | |
len = Buffer.byteLength(chunk, encoding) | |
} else { | |
// convert chunk to Buffer and calculate | |
chunk = Buffer.from(chunk, encoding) | |
encoding = undefined; | |
len = chunk.length | |
} | |
this.set('Content-Length', len); | |
} | |
// populate ETag | |
var etag; | |
if (generateETag && len !== undefined) { | |
if ((etag = etagFn(chunk, encoding))) { | |
this.set('ETag', etag); | |
} | |
} | |
// freshness | |
if (req.fresh) this.statusCode = 304; | |
// strip irrelevant headers | |
if (204 === this.statusCode || 304 === this.statusCode) { | |
this.removeHeader('Content-Type'); | |
this.removeHeader('Content-Length'); | |
this.removeHeader('Transfer-Encoding'); | |
chunk = ''; | |
} | |
// alter headers for 205 | |
if (this.statusCode === 205) { | |
this.set('Content-Length', '0') | |
this.removeHeader('Transfer-Encoding') | |
chunk = '' | |
} | |
if (req.method === 'HEAD') { | |
// skip body for HEAD | |
this.end(); | |
} else { | |
// respond | |
this.end(chunk, encoding); | |
} | |
return this; | |
}; | |
/** | |
* Send JSON response. | |
* | |
* Examples: | |
* | |
* res.json(null); | |
* res.json({ user: 'tj' }); | |
* | |
* @param {string|number|boolean|object} obj | |
* @public | |
*/ | |
res.json = function json(obj) { | |
var val = obj; | |
// allow status / body | |
if (arguments.length === 2) { | |
// res.json(body, status) backwards compat | |
if (typeof arguments[1] === 'number') { | |
deprecate('res.json(obj, status): Use res.status(status).json(obj) instead'); | |
this.statusCode = arguments[1]; | |
} else { | |
deprecate('res.json(status, obj): Use res.status(status).json(obj) instead'); | |
this.statusCode = arguments[0]; | |
val = arguments[1]; | |
} | |
} | |
// settings | |
var app = this.app; | |
var escape = app.get('json escape') | |
var replacer = app.get('json replacer'); | |
var spaces = app.get('json spaces'); | |
var body = stringify(val, replacer, spaces, escape) | |
// content-type | |
if (!this.get('Content-Type')) { | |
this.set('Content-Type', 'application/json'); | |
} | |
return this.send(body); | |
}; | |
/** | |
* Send JSON response with JSONP callback support. | |
* | |
* Examples: | |
* | |
* res.jsonp(null); | |
* res.jsonp({ user: 'tj' }); | |
* | |
* @param {string|number|boolean|object} obj | |
* @public | |
*/ | |
res.jsonp = function jsonp(obj) { | |
var val = obj; | |
// allow status / body | |
if (arguments.length === 2) { | |
// res.jsonp(body, status) backwards compat | |
if (typeof arguments[1] === 'number') { | |
deprecate('res.jsonp(obj, status): Use res.status(status).jsonp(obj) instead'); | |
this.statusCode = arguments[1]; | |
} else { | |
deprecate('res.jsonp(status, obj): Use res.status(status).jsonp(obj) instead'); | |
this.statusCode = arguments[0]; | |
val = arguments[1]; | |
} | |
} | |
// settings | |
var app = this.app; | |
var escape = app.get('json escape') | |
var replacer = app.get('json replacer'); | |
var spaces = app.get('json spaces'); | |
var body = stringify(val, replacer, spaces, escape) | |
var callback = this.req.query[app.get('jsonp callback name')]; | |
// content-type | |
if (!this.get('Content-Type')) { | |
this.set('X-Content-Type-Options', 'nosniff'); | |
this.set('Content-Type', 'application/json'); | |
} | |
// fixup callback | |
if (Array.isArray(callback)) { | |
callback = callback[0]; | |
} | |
// jsonp | |
if (typeof callback === 'string' && callback.length !== 0) { | |
this.set('X-Content-Type-Options', 'nosniff'); | |
this.set('Content-Type', 'text/javascript'); | |
// restrict callback charset | |
callback = callback.replace(/[^\[\]\w$.]/g, ''); | |
if (body === undefined) { | |
// empty argument | |
body = '' | |
} else if (typeof body === 'string') { | |
// replace chars not allowed in JavaScript that are in JSON | |
body = body | |
.replace(/\u2028/g, '\\u2028') | |
.replace(/\u2029/g, '\\u2029') | |
} | |
// the /**/ is a specific security mitigation for "Rosetta Flash JSONP abuse" | |
// the typeof check is just to reduce client error noise | |
body = '/**/ typeof ' + callback + ' === \'function\' && ' + callback + '(' + body + ');'; | |
} | |
return this.send(body); | |
}; | |
/** | |
* Send given HTTP status code. | |
* | |
* Sets the response status to `statusCode` and the body of the | |
* response to the standard description from node's http.STATUS_CODES | |
* or the statusCode number if no description. | |
* | |
* Examples: | |
* | |
* res.sendStatus(200); | |
* | |
* @param {number} statusCode | |
* @public | |
*/ | |
res.sendStatus = function sendStatus(statusCode) { | |
var body = statuses.message[statusCode] || String(statusCode) | |
this.statusCode = statusCode; | |
this.type('txt'); | |
return this.send(body); | |
}; | |
/** | |
* Transfer the file at the given `path`. | |
* | |
* Automatically sets the _Content-Type_ response header field. | |
* The callback `callback(err)` is invoked when the transfer is complete | |
* or when an error occurs. Be sure to check `res.headersSent` | |
* if you wish to attempt responding, as the header and some data | |
* may have already been transferred. | |
* | |
* Options: | |
* | |
* - `maxAge` defaulting to 0 (can be string converted by `ms`) | |
* - `root` root directory for relative filenames | |
* - `headers` object of headers to serve with file | |
* - `dotfiles` serve dotfiles, defaulting to false; can be `"allow"` to send them | |
* | |
* Other options are passed along to `send`. | |
* | |
* Examples: | |
* | |
* The following example illustrates how `res.sendFile()` may | |
* be used as an alternative for the `static()` middleware for | |
* dynamic situations. The code backing `res.sendFile()` is actually | |
* the same code, so HTTP cache support etc is identical. | |
* | |
* app.get('/user/:uid/photos/:file', function(req, res){ | |
* var uid = req.params.uid | |
* , file = req.params.file; | |
* | |
* req.user.mayViewFilesFrom(uid, function(yes){ | |
* if (yes) { | |
* res.sendFile('/uploads/' + uid + '/' + file); | |
* } else { | |
* res.send(403, 'Sorry! you cant see that.'); | |
* } | |
* }); | |
* }); | |
* | |
* @public | |
*/ | |
res.sendFile = function sendFile(path, options, callback) { | |
var done = callback; | |
var req = this.req; | |
var res = this; | |
var next = req.next; | |
var opts = options || {}; | |
if (!path) { | |
throw new TypeError('path argument is required to res.sendFile'); | |
} | |
if (typeof path !== 'string') { | |
throw new TypeError('path must be a string to res.sendFile') | |
} | |
// support function as second arg | |
if (typeof options === 'function') { | |
done = options; | |
opts = {}; | |
} | |
if (!opts.root && !isAbsolute(path)) { | |
throw new TypeError('path must be absolute or specify root to res.sendFile'); | |
} | |
// create file stream | |
var pathname = encodeURI(path); | |
var file = send(req, pathname, opts); | |
// transfer | |
sendfile(res, file, opts, function (err) { | |
if (done) return done(err); | |
if (err && err.code === 'EISDIR') return next(); | |
// next() all but write errors | |
if (err && err.code !== 'ECONNABORTED' && err.syscall !== 'write') { | |
next(err); | |
} | |
}); | |
}; | |
/** | |
* Transfer the file at the given `path`. | |
* | |
* Automatically sets the _Content-Type_ response header field. | |
* The callback `callback(err)` is invoked when the transfer is complete | |
* or when an error occurs. Be sure to check `res.headersSent` | |
* if you wish to attempt responding, as the header and some data | |
* may have already been transferred. | |
* | |
* Options: | |
* | |
* - `maxAge` defaulting to 0 (can be string converted by `ms`) | |
* - `root` root directory for relative filenames | |
* - `headers` object of headers to serve with file | |
* - `dotfiles` serve dotfiles, defaulting to false; can be `"allow"` to send them | |
* | |
* Other options are passed along to `send`. | |
* | |
* Examples: | |
* | |
* The following example illustrates how `res.sendfile()` may | |
* be used as an alternative for the `static()` middleware for | |
* dynamic situations. The code backing `res.sendfile()` is actually | |
* the same code, so HTTP cache support etc is identical. | |
* | |
* app.get('/user/:uid/photos/:file', function(req, res){ | |
* var uid = req.params.uid | |
* , file = req.params.file; | |
* | |
* req.user.mayViewFilesFrom(uid, function(yes){ | |
* if (yes) { | |
* res.sendfile('/uploads/' + uid + '/' + file); | |
* } else { | |
* res.send(403, 'Sorry! you cant see that.'); | |
* } | |
* }); | |
* }); | |
* | |
* @public | |
*/ | |
res.sendfile = function (path, options, callback) { | |
var done = callback; | |
var req = this.req; | |
var res = this; | |
var next = req.next; | |
var opts = options || {}; | |
// support function as second arg | |
if (typeof options === 'function') { | |
done = options; | |
opts = {}; | |
} | |
// create file stream | |
var file = send(req, path, opts); | |
// transfer | |
sendfile(res, file, opts, function (err) { | |
if (done) return done(err); | |
if (err && err.code === 'EISDIR') return next(); | |
// next() all but write errors | |
if (err && err.code !== 'ECONNABORTED' && err.syscall !== 'write') { | |
next(err); | |
} | |
}); | |
}; | |
res.sendfile = deprecate.function(res.sendfile, | |
'res.sendfile: Use res.sendFile instead'); | |
/** | |
* Transfer the file at the given `path` as an attachment. | |
* | |
* Optionally providing an alternate attachment `filename`, | |
* and optional callback `callback(err)`. The callback is invoked | |
* when the data transfer is complete, or when an error has | |
* occurred. Be sure to check `res.headersSent` if you plan to respond. | |
* | |
* Optionally providing an `options` object to use with `res.sendFile()`. | |
* This function will set the `Content-Disposition` header, overriding | |
* any `Content-Disposition` header passed as header options in order | |
* to set the attachment and filename. | |
* | |
* This method uses `res.sendFile()`. | |
* | |
* @public | |
*/ | |
res.download = function download (path, filename, options, callback) { | |
var done = callback; | |
var name = filename; | |
var opts = options || null | |
// support function as second or third arg | |
if (typeof filename === 'function') { | |
done = filename; | |
name = null; | |
opts = null | |
} else if (typeof options === 'function') { | |
done = options | |
opts = null | |
} | |
// support optional filename, where options may be in it's place | |
if (typeof filename === 'object' && | |
(typeof options === 'function' || options === undefined)) { | |
name = null | |
opts = filename | |
} | |
// set Content-Disposition when file is sent | |
var headers = { | |
'Content-Disposition': contentDisposition(name || path) | |
}; | |
// merge user-provided headers | |
if (opts && opts.headers) { | |
var keys = Object.keys(opts.headers) | |
for (var i = 0; i < keys.length; i++) { | |
var key = keys[i] | |
if (key.toLowerCase() !== 'content-disposition') { | |
headers[key] = opts.headers[key] | |
} | |
} | |
} | |
// merge user-provided options | |
opts = Object.create(opts) | |
opts.headers = headers | |
// Resolve the full path for sendFile | |
var fullPath = !opts.root | |
? resolve(path) | |
: path | |
// send file | |
return this.sendFile(fullPath, opts, done) | |
}; | |
/** | |
* Set _Content-Type_ response header with `type` through `mime.lookup()` | |
* when it does not contain "/", or set the Content-Type to `type` otherwise. | |
* | |
* Examples: | |
* | |
* res.type('.html'); | |
* res.type('html'); | |
* res.type('json'); | |
* res.type('application/json'); | |
* res.type('png'); | |
* | |
* @param {String} type | |
* @return {ServerResponse} for chaining | |
* @public | |
*/ | |
res.contentType = | |
res.type = function contentType(type) { | |
var ct = type.indexOf('/') === -1 | |
? mime.lookup(type) | |
: type; | |
return this.set('Content-Type', ct); | |
}; | |
/** | |
* Respond to the Acceptable formats using an `obj` | |
* of mime-type callbacks. | |
* | |
* This method uses `req.accepted`, an array of | |
* acceptable types ordered by their quality values. | |
* When "Accept" is not present the _first_ callback | |
* is invoked, otherwise the first match is used. When | |
* no match is performed the server responds with | |
* 406 "Not Acceptable". | |
* | |
* Content-Type is set for you, however if you choose | |
* you may alter this within the callback using `res.type()` | |
* or `res.set('Content-Type', ...)`. | |
* | |
* res.format({ | |
* 'text/plain': function(){ | |
* res.send('hey'); | |
* }, | |
* | |
* 'text/html': function(){ | |
* res.send('<p>hey</p>'); | |
* }, | |
* | |
* 'application/json': function () { | |
* res.send({ message: 'hey' }); | |
* } | |
* }); | |
* | |
* In addition to canonicalized MIME types you may | |
* also use extnames mapped to these types: | |
* | |
* res.format({ | |
* text: function(){ | |
* res.send('hey'); | |
* }, | |
* | |
* html: function(){ | |
* res.send('<p>hey</p>'); | |
* }, | |
* | |
* json: function(){ | |
* res.send({ message: 'hey' }); | |
* } | |
* }); | |
* | |
* By default Express passes an `Error` | |
* with a `.status` of 406 to `next(err)` | |
* if a match is not made. If you provide | |
* a `.default` callback it will be invoked | |
* instead. | |
* | |
* @param {Object} obj | |
* @return {ServerResponse} for chaining | |
* @public | |
*/ | |
res.format = function(obj){ | |
var req = this.req; | |
var next = req.next; | |
var keys = Object.keys(obj) | |
.filter(function (v) { return v !== 'default' }) | |
var key = keys.length > 0 | |
? req.accepts(keys) | |
: false; | |
this.vary("Accept"); | |
if (key) { | |
this.set('Content-Type', normalizeType(key).value); | |
obj[key](req, this, next); | |
} else if (obj.default) { | |
obj.default(req, this, next) | |
} else { | |
next(createError(406, { | |
types: normalizeTypes(keys).map(function (o) { return o.value }) | |
})) | |
} | |
return this; | |
}; | |
/** | |
* Set _Content-Disposition_ header to _attachment_ with optional `filename`. | |
* | |
* @param {String} filename | |
* @return {ServerResponse} | |
* @public | |
*/ | |
res.attachment = function attachment(filename) { | |
if (filename) { | |
this.type(extname(filename)); | |
} | |
this.set('Content-Disposition', contentDisposition(filename)); | |
return this; | |
}; | |
/** | |
* Append additional header `field` with value `val`. | |
* | |
* Example: | |
* | |
* res.append('Link', ['<http://localhost/>', '<http://localhost:3000/>']); | |
* res.append('Set-Cookie', 'foo=bar; Path=/; HttpOnly'); | |
* res.append('Warning', '199 Miscellaneous warning'); | |
* | |
* @param {String} field | |
* @param {String|Array} val | |
* @return {ServerResponse} for chaining | |
* @public | |
*/ | |
res.append = function append(field, val) { | |
var prev = this.get(field); | |
var value = val; | |
if (prev) { | |
// concat the new and prev vals | |
value = Array.isArray(prev) ? prev.concat(val) | |
: Array.isArray(val) ? [prev].concat(val) | |
: [prev, val] | |
} | |
return this.set(field, value); | |
}; | |
/** | |
* Set header `field` to `val`, or pass | |
* an object of header fields. | |
* | |
* Examples: | |
* | |
* res.set('Foo', ['bar', 'baz']); | |
* res.set('Accept', 'application/json'); | |
* res.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' }); | |
* | |
* Aliased as `res.header()`. | |
* | |
* @param {String|Object} field | |
* @param {String|Array} val | |
* @return {ServerResponse} for chaining | |
* @public | |
*/ | |
res.set = | |
res.header = function header(field, val) { | |
if (arguments.length === 2) { | |
var value = Array.isArray(val) | |
? val.map(String) | |
: String(val); | |
// add charset to content-type | |
if (field.toLowerCase() === 'content-type') { | |
if (Array.isArray(value)) { | |
throw new TypeError('Content-Type cannot be set to an Array'); | |
} | |
if (!charsetRegExp.test(value)) { | |
var charset = mime.charsets.lookup(value.split(';')[0]); | |
if (charset) value += '; charset=' + charset.toLowerCase(); | |
} | |
} | |
this.setHeader(field, value); | |
} else { | |
for (var key in field) { | |
this.set(key, field[key]); | |
} | |
} | |
return this; | |
}; | |
/** | |
* Get value for header `field`. | |
* | |
* @param {String} field | |
* @return {String} | |
* @public | |
*/ | |
res.get = function(field){ | |
return this.getHeader(field); | |
}; | |
/** | |
* Clear cookie `name`. | |
* | |
* @param {String} name | |
* @param {Object} [options] | |
* @return {ServerResponse} for chaining | |
* @public | |
*/ | |
res.clearCookie = function clearCookie(name, options) { | |
var opts = merge({ expires: new Date(1), path: '/' }, options); | |
return this.cookie(name, '', opts); | |
}; | |
/** | |
* Set cookie `name` to `value`, with the given `options`. | |
* | |
* Options: | |
* | |
* - `maxAge` max-age in milliseconds, converted to `expires` | |
* - `signed` sign the cookie | |
* - `path` defaults to "/" | |
* | |
* Examples: | |
* | |
* // "Remember Me" for 15 minutes | |
* res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true }); | |
* | |
* // same as above | |
* res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true }) | |
* | |
* @param {String} name | |
* @param {String|Object} value | |
* @param {Object} [options] | |
* @return {ServerResponse} for chaining | |
* @public | |
*/ | |
res.cookie = function (name, value, options) { | |
var opts = merge({}, options); | |
var secret = this.req.secret; | |
var signed = opts.signed; | |
if (signed && !secret) { | |
throw new Error('cookieParser("secret") required for signed cookies'); | |
} | |
var val = typeof value === 'object' | |
? 'j:' + JSON.stringify(value) | |
: String(value); | |
if (signed) { | |
val = 's:' + sign(val, secret); | |
} | |
if (opts.maxAge != null) { | |
var maxAge = opts.maxAge - 0 | |
if (!isNaN(maxAge)) { | |
opts.expires = new Date(Date.now() + maxAge) | |
opts.maxAge = Math.floor(maxAge / 1000) | |
} | |
} | |
if (opts.path == null) { | |
opts.path = '/'; | |
} | |
this.append('Set-Cookie', cookie.serialize(name, String(val), opts)); | |
return this; | |
}; | |
/** | |
* Set the location header to `url`. | |
* | |
* The given `url` can also be "back", which redirects | |
* to the _Referrer_ or _Referer_ headers or "/". | |
* | |
* Examples: | |
* | |
* res.location('/foo/bar').; | |
* res.location('http://example.com'); | |
* res.location('../login'); | |
* | |
* @param {String} url | |
* @return {ServerResponse} for chaining | |
* @public | |
*/ | |
res.location = function location(url) { | |
var loc = url; | |
// "back" is an alias for the referrer | |
if (url === 'back') { | |
loc = this.req.get('Referrer') || '/'; | |
} | |
// set location | |
return this.set('Location', encodeUrl(loc)); | |
}; | |
/** | |
* Redirect to the given `url` with optional response `status` | |
* defaulting to 302. | |
* | |
* The resulting `url` is determined by `res.location()`, so | |
* it will play nicely with mounted apps, relative paths, | |
* `"back"` etc. | |
* | |
* Examples: | |
* | |
* res.redirect('/foo/bar'); | |
* res.redirect('http://example.com'); | |
* res.redirect(301, 'http://example.com'); | |
* res.redirect('../login'); // /blog/post/1 -> /blog/login | |
* | |
* @public | |
*/ | |
res.redirect = function redirect(url) { | |
var address = url; | |
var body; | |
var status = 302; | |
// allow status / url | |
if (arguments.length === 2) { | |
if (typeof arguments[0] === 'number') { | |
status = arguments[0]; | |
address = arguments[1]; | |
} else { | |
deprecate('res.redirect(url, status): Use res.redirect(status, url) instead'); | |
status = arguments[1]; | |
} | |
} | |
// Set location header | |
address = this.location(address).get('Location'); | |
// Support text/{plain,html} by default | |
this.format({ | |
text: function(){ | |
body = statuses.message[status] + '. Redirecting to ' + address | |
}, | |
html: function(){ | |
var u = escapeHtml(address); | |
body = '<p>' + statuses.message[status] + '. Redirecting to <a href="' + u + '">' + u + '</a></p>' | |
}, | |
default: function(){ | |
body = ''; | |
} | |
}); | |
// Respond | |
this.statusCode = status; | |
this.set('Content-Length', Buffer.byteLength(body)); | |
if (this.req.method === 'HEAD') { | |
this.end(); | |
} else { | |
this.end(body); | |
} | |
}; | |
/** | |
* Add `field` to Vary. If already present in the Vary set, then | |
* this call is simply ignored. | |
* | |
* @param {Array|String} field | |
* @return {ServerResponse} for chaining | |
* @public | |
*/ | |
res.vary = function(field){ | |
// checks for back-compat | |
if (!field || (Array.isArray(field) && !field.length)) { | |
deprecate('res.vary(): Provide a field name'); | |
return this; | |
} | |
vary(this, field); | |
return this; | |
}; | |
/** | |
* Render `view` with the given `options` and optional callback `fn`. | |
* When a callback function is given a response will _not_ be made | |
* automatically, otherwise a response of _200_ and _text/html_ is given. | |
* | |
* Options: | |
* | |
* - `cache` boolean hinting to the engine it should cache | |
* - `filename` filename of the view being rendered | |
* | |
* @public | |
*/ | |
res.render = function render(view, options, callback) { | |
var app = this.req.app; | |
var done = callback; | |
var opts = options || {}; | |
var req = this.req; | |
var self = this; | |
// support callback function as second arg | |
if (typeof options === 'function') { | |
done = options; | |
opts = {}; | |
} | |
// merge res.locals | |
opts._locals = self.locals; | |
// default callback to respond | |
done = done || function (err, str) { | |
if (err) return req.next(err); | |
self.send(str); | |
}; | |
// render | |
app.render(view, opts, done); | |
}; | |
// pipe the send file stream | |
function sendfile(res, file, options, callback) { | |
var done = false; | |
var streaming; | |
// request aborted | |
function onaborted() { | |
if (done) return; | |
done = true; | |
var err = new Error('Request aborted'); | |
err.code = 'ECONNABORTED'; | |
callback(err); | |
} | |
// directory | |
function ondirectory() { | |
if (done) return; | |
done = true; | |
var err = new Error('EISDIR, read'); | |
err.code = 'EISDIR'; | |
callback(err); | |
} | |
// errors | |
function onerror(err) { | |
if (done) return; | |
done = true; | |
callback(err); | |
} | |
// ended | |
function onend() { | |
if (done) return; | |
done = true; | |
callback(); | |
} | |
// file | |
function onfile() { | |
streaming = false; | |
} | |
// finished | |
function onfinish(err) { | |
if (err && err.code === 'ECONNRESET') return onaborted(); | |
if (err) return onerror(err); | |
if (done) return; | |
setImmediate(function () { | |
if (streaming !== false && !done) { | |
onaborted(); | |
return; | |
} | |
if (done) return; | |
done = true; | |
callback(); | |
}); | |
} | |
// streaming | |
function onstream() { | |
streaming = true; | |
} | |
file.on('directory', ondirectory); | |
file.on('end', onend); | |
file.on('error', onerror); | |
file.on('file', onfile); | |
file.on('stream', onstream); | |
onFinished(res, onfinish); | |
if (options.headers) { | |
// set headers on successful transfer | |
file.on('headers', function headers(res) { | |
var obj = options.headers; | |
var keys = Object.keys(obj); | |
for (var i = 0; i < keys.length; i++) { | |
var k = keys[i]; | |
res.setHeader(k, obj[k]); | |
} | |
}); | |
} | |
// pipe | |
file.pipe(res); | |
} | |
/** | |
* Stringify JSON, like JSON.stringify, but v8 optimized, with the | |
* ability to escape characters that can trigger HTML sniffing. | |
* | |
* @param {*} value | |
* @param {function} replacer | |
* @param {number} spaces | |
* @param {boolean} escape | |
* @returns {string} | |
* @private | |
*/ | |
function stringify (value, replacer, spaces, escape) { | |
// v8 checks arguments.length for optimizing simple call | |
// https://bugs.chromium.org/p/v8/issues/detail?id=4730 | |
var json = replacer || spaces | |
? JSON.stringify(value, replacer, spaces) | |
: JSON.stringify(value); | |
if (escape && typeof json === 'string') { | |
json = json.replace(/[<>&]/g, function (c) { | |
switch (c.charCodeAt(0)) { | |
case 0x3c: | |
return '\\u003c' | |
case 0x3e: | |
return '\\u003e' | |
case 0x26: | |
return '\\u0026' | |
/* istanbul ignore next: unreachable default */ | |
default: | |
return c | |
} | |
}) | |
} | |
return json | |
} | |