1040 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			1040 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | /*! | ||
|  |  * express | ||
|  |  * Copyright(c) 2009-2013 TJ Holowaychuk | ||
|  |  * Copyright(c) 2014-2015 Douglas Christopher Wilson | ||
|  |  * MIT Licensed | ||
|  |  */ | ||
|  | 
 | ||
|  | 'use strict'; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Module dependencies. | ||
|  |  * @private | ||
|  |  */ | ||
|  | 
 | ||
|  | var contentDisposition = require('content-disposition'); | ||
|  | var createError = require('http-errors') | ||
|  | var encodeUrl = require('encodeurl'); | ||
|  | var escapeHtml = require('escape-html'); | ||
|  | var http = require('node:http'); | ||
|  | var onFinished = require('on-finished'); | ||
|  | var mime = require('mime-types') | ||
|  | var path = require('node:path'); | ||
|  | var pathIsAbsolute = require('node:path').isAbsolute; | ||
|  | var statuses = require('statuses') | ||
|  | 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 resolve = path.resolve; | ||
|  | var vary = require('vary'); | ||
|  | 
 | ||
|  | /** | ||
|  |  * Response prototype. | ||
|  |  * @public | ||
|  |  */ | ||
|  | 
 | ||
|  | var res = Object.create(http.ServerResponse.prototype) | ||
|  | 
 | ||
|  | /** | ||
|  |  * Module exports. | ||
|  |  * @public | ||
|  |  */ | ||
|  | 
 | ||
|  | module.exports = res | ||
|  | 
 | ||
|  | /** | ||
|  |  * Set the HTTP status code for the response. | ||
|  |  * | ||
|  |  * Expects an integer value between 100 and 999 inclusive. | ||
|  |  * Throws an error if the provided status code is not an integer or if it's outside the allowable range. | ||
|  |  * | ||
|  |  * @param {number} code - The HTTP status code to set. | ||
|  |  * @return {ServerResponse} - Returns itself for chaining methods. | ||
|  |  * @throws {TypeError} If `code` is not an integer. | ||
|  |  * @throws {RangeError} If `code` is outside the range 100 to 999. | ||
|  |  * @public | ||
|  |  */ | ||
|  | 
 | ||
|  | res.status = function status(code) { | ||
|  |   // Check if the status code is not an integer
 | ||
|  |   if (!Number.isInteger(code)) { | ||
|  |     throw new TypeError(`Invalid status code: ${JSON.stringify(code)}. Status code must be an integer.`); | ||
|  |   } | ||
|  |   // Check if the status code is outside of Node's valid range
 | ||
|  |   if (code < 100 || code > 999) { | ||
|  |     throw new RangeError(`Invalid status code: ${JSON.stringify(code)}. Status code must be greater than 99 and less than 1000.`); | ||
|  |   } | ||
|  | 
 | ||
|  |   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', | ||
|  |  *      pages: [ | ||
|  |  *        'http://api.example.com/users?page=1', | ||
|  |  *        'http://api.example.com/users?page=2' | ||
|  |  *      ] | ||
|  |  *    }); | ||
|  |  * | ||
|  |  * @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) { | ||
|  |     // Allow multiple links if links[rel] is an array
 | ||
|  |     if (Array.isArray(links[rel])) { | ||
|  |       return links[rel].map(function (singleLink) { | ||
|  |         return `<${singleLink}>; rel="${rel}"`; | ||
|  |       }).join(', '); | ||
|  |     } else { | ||
|  |       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; | ||
|  | 
 | ||
|  |   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 (ArrayBuffer.isView(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.status(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) { | ||
|  |   // 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(obj, 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) { | ||
|  |   // 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(obj, 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.status(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 && !pathIsAbsolute(path)) { | ||
|  |     throw new TypeError('path must be absolute or specify root to res.sendFile'); | ||
|  |   } | ||
|  | 
 | ||
|  |   // create file stream
 | ||
|  |   var pathname = encodeURI(path); | ||
|  | 
 | ||
|  |   // wire application etag option to send
 | ||
|  |   opts.etag = this.app.enabled('etag'); | ||
|  |   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` 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.contentType()` | ||
|  |  * when it does not contain "/", or set the Content-Type to `type` otherwise. | ||
|  |  * When no mapping is found though `mime.contentType()`, the type is set to | ||
|  |  * "application/octet-stream". | ||
|  |  * | ||
|  |  * 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.contentType(type) || 'application/octet-stream') | ||
|  |     : 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()`. | ||
|  |  * | ||
|  |  * When the set header is "Content-Type", the type is expanded to include | ||
|  |  * the charset if not present using `mime.contentType()`. | ||
|  |  * | ||
|  |  * @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'); | ||
|  |       } | ||
|  |       value = mime.contentType(value) | ||
|  |     } | ||
|  | 
 | ||
|  |     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) { | ||
|  |   // Force cookie expiration by setting expires to the past
 | ||
|  |   const opts = { path: '/', ...options, expires: new Date(1)}; | ||
|  |   // ensure maxAge is not passed
 | ||
|  |   delete opts.maxAge | ||
|  | 
 | ||
|  |   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 = { ...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) { | ||
|  |   return this.set('Location', encodeUrl(url)); | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Redirect to the given `url` with optional response `status` | ||
|  |  * defaulting to 302. | ||
|  |  * | ||
|  |  * 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) { | ||
|  |     status = arguments[0] | ||
|  |     address = 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 ' + u + '</p>' | ||
|  |     }, | ||
|  | 
 | ||
|  |     default: function(){ | ||
|  |       body = ''; | ||
|  |     } | ||
|  |   }); | ||
|  | 
 | ||
|  |   // Respond
 | ||
|  |   this.status(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){ | ||
|  |   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 | ||
|  | } |