send
Advanced tools
Comparing version 0.13.2 to 0.14.0
@@ -0,1 +1,26 @@ | ||
0.14.0 / 2016-06-06 | ||
=================== | ||
* Add `acceptRanges` option | ||
* Add `cacheControl` option | ||
* Attempt to combine multiple ranges into single range | ||
* Correctly inherit from `Stream` class | ||
* Fix `Content-Range` header in 416 responses when using `start`/`end` options | ||
* Fix `Content-Range` header missing from default 416 responses | ||
* Ignore non-byte `Range` headers | ||
* deps: http-errors@~1.5.0 | ||
- Add `HttpError` export, for `err instanceof createError.HttpError` | ||
- Support new code `421 Misdirected Request` | ||
- Use `setprototypeof` module to replace `__proto__` setting | ||
- deps: inherits@2.0.1 | ||
- deps: statuses@'>= 1.3.0 < 2' | ||
- perf: enable strict mode | ||
* deps: range-parser@~1.2.0 | ||
- Fix incorrectly returning -1 when there is at least one valid range | ||
- perf: remove internal function | ||
* deps: statuses@~1.3.0 | ||
- Add `421 Misdirected Request` | ||
- perf: enable strict mode | ||
* perf: remove argument reassignment | ||
0.13.2 / 2016-03-05 | ||
@@ -2,0 +27,0 @@ =================== |
528
index.js
/*! | ||
* send | ||
* Copyright(c) 2012 TJ Holowaychuk | ||
* Copyright(c) 2014-2015 Douglas Christopher Wilson | ||
* Copyright(c) 2014-2016 Douglas Christopher Wilson | ||
* MIT Licensed | ||
@@ -20,27 +20,48 @@ */ | ||
var escapeHtml = require('escape-html') | ||
, parseRange = require('range-parser') | ||
, Stream = require('stream') | ||
, mime = require('mime') | ||
, fresh = require('fresh') | ||
, path = require('path') | ||
, fs = require('fs') | ||
, normalize = path.normalize | ||
, join = path.join | ||
var etag = require('etag') | ||
var EventEmitter = require('events').EventEmitter; | ||
var ms = require('ms'); | ||
var EventEmitter = require('events').EventEmitter | ||
var fresh = require('fresh') | ||
var fs = require('fs') | ||
var mime = require('mime') | ||
var ms = require('ms') | ||
var onFinished = require('on-finished') | ||
var parseRange = require('range-parser') | ||
var path = require('path') | ||
var statuses = require('statuses') | ||
var Stream = require('stream') | ||
var util = require('util') | ||
/** | ||
* Variables. | ||
* Path function references. | ||
* @private | ||
*/ | ||
var extname = path.extname | ||
var maxMaxAge = 60 * 60 * 24 * 365 * 1000; // 1 year | ||
var join = path.join | ||
var normalize = path.normalize | ||
var resolve = path.resolve | ||
var sep = path.sep | ||
var toString = Object.prototype.toString | ||
var upPathRegexp = /(?:^|[\\\/])\.\.(?:[\\\/]|$)/ | ||
/** | ||
* Regular expression for identifying a bytes Range header. | ||
* @private | ||
*/ | ||
var BYTES_RANGE_REGEXP = /^ *bytes=/ | ||
/** | ||
* Maximum value allowed for the max age. | ||
* @private | ||
*/ | ||
var MAX_MAXAGE = 60 * 60 * 24 * 365 * 1000 // 1 year | ||
/** | ||
* Regular expression to match a path with a directory up component. | ||
* @private | ||
*/ | ||
var UP_PATH_REGEXP = /(?:^|[\\\/])\.\.(?:[\\\/]|$)/ | ||
/** | ||
* Module exports. | ||
@@ -58,4 +79,4 @@ * @public | ||
/* istanbul ignore next */ | ||
var listenerCount = EventEmitter.listenerCount | ||
|| function(emitter, type){ return emitter.listeners(type).length; }; | ||
var listenerCount = EventEmitter.listenerCount || | ||
function (emitter, type) { return emitter.listeners(type).length } | ||
@@ -72,4 +93,4 @@ /** | ||
function send(req, path, options) { | ||
return new SendStream(req, path, options); | ||
function send (req, path, options) { | ||
return new SendStream(req, path, options) | ||
} | ||
@@ -86,3 +107,5 @@ | ||
function SendStream(req, path, options) { | ||
function SendStream (req, path, options) { | ||
Stream.call(this) | ||
var opts = options || {} | ||
@@ -94,2 +117,10 @@ | ||
this._acceptRanges = opts.acceptRanges !== undefined | ||
? Boolean(opts.acceptRanges) | ||
: true | ||
this._cacheControl = opts.cacheControl !== undefined | ||
? Boolean(opts.cacheControl) | ||
: true | ||
this._etag = opts.etag !== undefined | ||
@@ -135,3 +166,3 @@ ? Boolean(opts.etag) | ||
this._maxage = !isNaN(this._maxage) | ||
? Math.min(Math.max(0, this._maxage), maxMaxAge) | ||
? Math.min(Math.max(0, this._maxage), MAX_MAXAGE) | ||
: 0 | ||
@@ -149,6 +180,6 @@ | ||
/** | ||
* Inherits from `Stream.prototype`. | ||
* Inherits from `Stream`. | ||
*/ | ||
SendStream.prototype.__proto__ = Stream.prototype; | ||
util.inherits(SendStream, Stream) | ||
@@ -163,8 +194,7 @@ /** | ||
SendStream.prototype.etag = deprecate.function(function etag(val) { | ||
val = Boolean(val); | ||
debug('etag %s', val); | ||
this._etag = val; | ||
return this; | ||
}, 'send.etag: pass etag as option'); | ||
SendStream.prototype.etag = deprecate.function(function etag (val) { | ||
this._etag = Boolean(val) | ||
debug('etag %s', this._etag) | ||
return this | ||
}, 'send.etag: pass etag as option') | ||
@@ -179,9 +209,8 @@ /** | ||
SendStream.prototype.hidden = deprecate.function(function hidden(val) { | ||
val = Boolean(val); | ||
debug('hidden %s', val); | ||
this._hidden = val; | ||
SendStream.prototype.hidden = deprecate.function(function hidden (val) { | ||
this._hidden = Boolean(val) | ||
this._dotfiles = undefined | ||
return this; | ||
}, 'send.hidden: use dotfiles option'); | ||
debug('hidden %s', this._hidden) | ||
return this | ||
}, 'send.hidden: use dotfiles option') | ||
@@ -197,8 +226,8 @@ /** | ||
SendStream.prototype.index = deprecate.function(function index(paths) { | ||
var index = !paths ? [] : normalizeList(paths, 'paths argument'); | ||
debug('index %o', paths); | ||
this._index = index; | ||
return this; | ||
}, 'send.index: pass index as option'); | ||
SendStream.prototype.index = deprecate.function(function index (paths) { | ||
var index = !paths ? [] : normalizeList(paths, 'paths argument') | ||
debug('index %o', paths) | ||
this._index = index | ||
return this | ||
}, 'send.index: pass index as option') | ||
@@ -213,13 +242,13 @@ /** | ||
SendStream.prototype.root = function(path){ | ||
path = String(path); | ||
this._root = resolve(path) | ||
return this; | ||
}; | ||
SendStream.prototype.root = function root (path) { | ||
this._root = resolve(String(path)) | ||
debug('root %s', this._root) | ||
return this | ||
} | ||
SendStream.prototype.from = deprecate.function(SendStream.prototype.root, | ||
'send.from: pass root as option'); | ||
'send.from: pass root as option') | ||
SendStream.prototype.root = deprecate.function(SendStream.prototype.root, | ||
'send.root: pass root as option'); | ||
'send.root: pass root as option') | ||
@@ -234,12 +263,12 @@ /** | ||
SendStream.prototype.maxage = deprecate.function(function maxage(maxAge) { | ||
maxAge = typeof maxAge === 'string' | ||
SendStream.prototype.maxage = deprecate.function(function maxage (maxAge) { | ||
this._maxage = typeof maxAge === 'string' | ||
? ms(maxAge) | ||
: Number(maxAge); | ||
if (isNaN(maxAge)) maxAge = 0; | ||
if (Infinity == maxAge) maxAge = 60 * 60 * 24 * 365 * 1000; | ||
debug('max-age %d', maxAge); | ||
this._maxage = maxAge; | ||
return this; | ||
}, 'send.maxage: pass maxAge as option'); | ||
: Number(maxAge) | ||
this._maxage = !isNaN(this._maxage) | ||
? Math.min(Math.max(0, this._maxage), MAX_MAXAGE) | ||
: 0 | ||
debug('max-age %d', this._maxage) | ||
return this | ||
}, 'send.maxage: pass maxAge as option') | ||
@@ -254,3 +283,3 @@ /** | ||
SendStream.prototype.error = function error(status, error) { | ||
SendStream.prototype.error = function error (status, error) { | ||
// emit if listeners instead of responding | ||
@@ -266,5 +295,10 @@ if (listenerCount(this, 'error') !== 0) { | ||
// wipe all existing headers | ||
res._headers = null | ||
// clear existing headers | ||
clearHeaders(res) | ||
// add error headers | ||
if (error && error.headers) { | ||
setHeaders(res, error.headers) | ||
} | ||
// send basic response | ||
@@ -285,5 +319,5 @@ res.statusCode = status | ||
SendStream.prototype.hasTrailingSlash = function(){ | ||
return '/' == this.path[this.path.length - 1]; | ||
}; | ||
SendStream.prototype.hasTrailingSlash = function hasTrailingSlash () { | ||
return this.path[this.path.length - 1] === '/' | ||
} | ||
@@ -297,6 +331,6 @@ /** | ||
SendStream.prototype.isConditionalGET = function(){ | ||
return this.req.headers['if-none-match'] | ||
|| this.req.headers['if-modified-since']; | ||
}; | ||
SendStream.prototype.isConditionalGET = function isConditionalGET () { | ||
return this.req.headers['if-none-match'] || | ||
this.req.headers['if-modified-since'] | ||
} | ||
@@ -309,3 +343,3 @@ /** | ||
SendStream.prototype.removeContentHeaderFields = function removeContentHeaderFields() { | ||
SendStream.prototype.removeContentHeaderFields = function removeContentHeaderFields () { | ||
var res = this.res | ||
@@ -328,9 +362,9 @@ var headers = Object.keys(res._headers || {}) | ||
SendStream.prototype.notModified = function(){ | ||
var res = this.res; | ||
debug('not modified'); | ||
this.removeContentHeaderFields(); | ||
res.statusCode = 304; | ||
res.end(); | ||
}; | ||
SendStream.prototype.notModified = function notModified () { | ||
var res = this.res | ||
debug('not modified') | ||
this.removeContentHeaderFields() | ||
res.statusCode = 304 | ||
res.end() | ||
} | ||
@@ -343,7 +377,7 @@ /** | ||
SendStream.prototype.headersAlreadySent = function headersAlreadySent(){ | ||
var err = new Error('Can\'t set headers after they are sent.'); | ||
debug('headers already sent'); | ||
this.error(500, err); | ||
}; | ||
SendStream.prototype.headersAlreadySent = function headersAlreadySent () { | ||
var err = new Error('Can\'t set headers after they are sent.') | ||
debug('headers already sent') | ||
this.error(500, err) | ||
} | ||
@@ -358,6 +392,7 @@ /** | ||
SendStream.prototype.isCachable = function(){ | ||
var res = this.res; | ||
return (res.statusCode >= 200 && res.statusCode < 300) || 304 == res.statusCode; | ||
}; | ||
SendStream.prototype.isCachable = function isCachable () { | ||
var statusCode = this.res.statusCode | ||
return (statusCode >= 200 && statusCode < 300) || | ||
statusCode === 304 | ||
} | ||
@@ -371,3 +406,3 @@ /** | ||
SendStream.prototype.onStatError = function onStatError(error) { | ||
SendStream.prototype.onStatError = function onStatError (error) { | ||
switch (error.code) { | ||
@@ -392,5 +427,5 @@ case 'ENAMETOOLONG': | ||
SendStream.prototype.isFresh = function(){ | ||
return fresh(this.req.headers, this.res._headers); | ||
}; | ||
SendStream.prototype.isFresh = function isFresh () { | ||
return fresh(this.req.headers, this.res._headers) | ||
} | ||
@@ -404,11 +439,13 @@ /** | ||
SendStream.prototype.isRangeFresh = function isRangeFresh(){ | ||
var ifRange = this.req.headers['if-range']; | ||
SendStream.prototype.isRangeFresh = function isRangeFresh () { | ||
var ifRange = this.req.headers['if-range'] | ||
if (!ifRange) return true; | ||
if (!ifRange) { | ||
return true | ||
} | ||
return ~ifRange.indexOf('"') | ||
? ~ifRange.indexOf(this.res._headers['etag']) | ||
: Date.parse(this.res._headers['last-modified']) <= Date.parse(ifRange); | ||
}; | ||
: Date.parse(this.res._headers['last-modified']) <= Date.parse(ifRange) | ||
} | ||
@@ -422,3 +459,3 @@ /** | ||
SendStream.prototype.redirect = function redirect(path) { | ||
SendStream.prototype.redirect = function redirect (path) { | ||
if (listenerCount(this, 'directory') !== 0) { | ||
@@ -455,16 +492,21 @@ this.emit('directory') | ||
SendStream.prototype.pipe = function(res){ | ||
var self = this | ||
, args = arguments | ||
, root = this._root; | ||
SendStream.prototype.pipe = function pipe (res) { | ||
// root path | ||
var root = this._root | ||
// references | ||
this.res = res; | ||
this.res = res | ||
// decode the path | ||
var path = decode(this.path) | ||
if (path === -1) return this.error(400) | ||
if (path === -1) { | ||
this.error(400) | ||
return res | ||
} | ||
// null byte(s) | ||
if (~path.indexOf('\0')) return this.error(400); | ||
if (~path.indexOf('\0')) { | ||
this.error(400) | ||
return res | ||
} | ||
@@ -474,5 +516,6 @@ var parts | ||
// malicious path | ||
if (upPathRegexp.test(normalize('.' + sep + path))) { | ||
if (UP_PATH_REGEXP.test(normalize('.' + sep + path))) { | ||
debug('malicious path "%s"', path) | ||
return this.error(403) | ||
this.error(403) | ||
return res | ||
} | ||
@@ -488,5 +531,6 @@ | ||
// ".." is malicious without "root" | ||
if (upPathRegexp.test(path)) { | ||
if (UP_PATH_REGEXP.test(path)) { | ||
debug('malicious path "%s"', path) | ||
return this.error(403) | ||
this.error(403) | ||
return res | ||
} | ||
@@ -517,6 +561,8 @@ | ||
case 'deny': | ||
return this.error(403) | ||
this.error(403) | ||
return res | ||
case 'ignore': | ||
default: | ||
return this.error(404) | ||
this.error(404) | ||
return res | ||
} | ||
@@ -527,9 +573,9 @@ } | ||
if (this._index.length && this.path[this.path.length - 1] === '/') { | ||
this.sendIndex(path); | ||
return res; | ||
this.sendIndex(path) | ||
return res | ||
} | ||
this.sendFile(path); | ||
return res; | ||
}; | ||
this.sendFile(path) | ||
return res | ||
} | ||
@@ -543,14 +589,15 @@ /** | ||
SendStream.prototype.send = function(path, stat){ | ||
var len = stat.size; | ||
SendStream.prototype.send = function send (path, stat) { | ||
var len = stat.size | ||
var options = this.options | ||
var opts = {} | ||
var res = this.res; | ||
var req = this.req; | ||
var ranges = req.headers.range; | ||
var offset = options.start || 0; | ||
var res = this.res | ||
var req = this.req | ||
var ranges = req.headers.range | ||
var offset = options.start || 0 | ||
if (res._header) { | ||
// impossible to send now | ||
return this.headersAlreadySent(); | ||
this.headersAlreadySent() | ||
return | ||
} | ||
@@ -561,53 +608,57 @@ | ||
// set header fields | ||
this.setHeader(path, stat); | ||
this.setHeader(path, stat) | ||
// set content-type | ||
this.type(path); | ||
this.type(path) | ||
// conditional GET support | ||
if (this.isConditionalGET() | ||
&& this.isCachable() | ||
&& this.isFresh()) { | ||
return this.notModified(); | ||
if (this.isConditionalGET() && this.isCachable() && this.isFresh()) { | ||
this.notModified() | ||
return | ||
} | ||
// adjust len to start/end options | ||
len = Math.max(0, len - offset); | ||
len = Math.max(0, len - offset) | ||
if (options.end !== undefined) { | ||
var bytes = options.end - offset + 1; | ||
if (len > bytes) len = bytes; | ||
var bytes = options.end - offset + 1 | ||
if (len > bytes) len = bytes | ||
} | ||
// Range support | ||
if (ranges) { | ||
ranges = parseRange(len, ranges); | ||
if (this._acceptRanges && BYTES_RANGE_REGEXP.test(ranges)) { | ||
// parse | ||
ranges = parseRange(len, ranges, { | ||
combine: true | ||
}) | ||
// If-Range support | ||
if (!this.isRangeFresh()) { | ||
debug('range stale'); | ||
ranges = -2; | ||
debug('range stale') | ||
ranges = -2 | ||
} | ||
// unsatisfiable | ||
if (-1 == ranges) { | ||
debug('range unsatisfiable'); | ||
res.setHeader('Content-Range', 'bytes */' + stat.size); | ||
return this.error(416); | ||
if (ranges === -1) { | ||
debug('range unsatisfiable') | ||
// Content-Range | ||
res.setHeader('Content-Range', contentRange('bytes', len)) | ||
// 416 Requested Range Not Satisfiable | ||
return this.error(416, { | ||
headers: {'Content-Range': res.getHeader('Content-Range')} | ||
}) | ||
} | ||
// valid (syntactically invalid/multiple ranges are treated as a regular response) | ||
if (-2 != ranges && ranges.length === 1) { | ||
debug('range %j', ranges); | ||
if (ranges !== -2 && ranges.length === 1) { | ||
debug('range %j', ranges) | ||
// Content-Range | ||
res.statusCode = 206; | ||
res.setHeader('Content-Range', 'bytes ' | ||
+ ranges[0].start | ||
+ '-' | ||
+ ranges[0].end | ||
+ '/' | ||
+ len); | ||
res.statusCode = 206 | ||
res.setHeader('Content-Range', contentRange('bytes', len, ranges[0])) | ||
offset += ranges[0].start; | ||
len = ranges[0].end - ranges[0].start + 1; | ||
// adjust for requested range | ||
offset += ranges[0].start | ||
len = ranges[0].end - ranges[0].start + 1 | ||
} | ||
@@ -626,9 +677,12 @@ } | ||
// content-length | ||
res.setHeader('Content-Length', len); | ||
res.setHeader('Content-Length', len) | ||
// HEAD support | ||
if ('HEAD' == req.method) return res.end(); | ||
if (req.method === 'HEAD') { | ||
res.end() | ||
return | ||
} | ||
this.stream(path, opts) | ||
}; | ||
} | ||
@@ -641,11 +695,9 @@ /** | ||
*/ | ||
SendStream.prototype.sendFile = function sendFile(path) { | ||
SendStream.prototype.sendFile = function sendFile (path) { | ||
var i = 0 | ||
var self = this | ||
debug('stat "%s"', path); | ||
fs.stat(path, function onstat(err, stat) { | ||
if (err && err.code === 'ENOENT' | ||
&& !extname(path) | ||
&& path[path.length - 1] !== sep) { | ||
debug('stat "%s"', path) | ||
fs.stat(path, function onstat (err, stat) { | ||
if (err && err.code === 'ENOENT' && !extname(path) && path[path.length - 1] !== sep) { | ||
// not found, check extensions | ||
@@ -660,3 +712,3 @@ return next(err) | ||
function next(err) { | ||
function next (err) { | ||
if (self._extensions.length <= i) { | ||
@@ -686,25 +738,25 @@ return err | ||
*/ | ||
SendStream.prototype.sendIndex = function sendIndex(path){ | ||
var i = -1; | ||
var self = this; | ||
SendStream.prototype.sendIndex = function sendIndex (path) { | ||
var i = -1 | ||
var self = this | ||
function next(err){ | ||
function next (err) { | ||
if (++i >= self._index.length) { | ||
if (err) return self.onStatError(err); | ||
return self.error(404); | ||
if (err) return self.onStatError(err) | ||
return self.error(404) | ||
} | ||
var p = join(path, self._index[i]); | ||
var p = join(path, self._index[i]) | ||
debug('stat "%s"', p); | ||
fs.stat(p, function(err, stat){ | ||
if (err) return next(err); | ||
if (stat.isDirectory()) return next(); | ||
self.emit('file', p, stat); | ||
self.send(p, stat); | ||
}); | ||
debug('stat "%s"', p) | ||
fs.stat(p, function (err, stat) { | ||
if (err) return next(err) | ||
if (stat.isDirectory()) return next() | ||
self.emit('file', p, stat) | ||
self.send(p, stat) | ||
}) | ||
} | ||
next(); | ||
}; | ||
next() | ||
} | ||
@@ -719,38 +771,37 @@ /** | ||
SendStream.prototype.stream = function(path, options){ | ||
SendStream.prototype.stream = function stream (path, options) { | ||
// TODO: this is all lame, refactor meeee | ||
var finished = false; | ||
var self = this; | ||
var res = this.res; | ||
var req = this.req; | ||
var finished = false | ||
var self = this | ||
var res = this.res | ||
// pipe | ||
var stream = fs.createReadStream(path, options); | ||
this.emit('stream', stream); | ||
stream.pipe(res); | ||
var stream = fs.createReadStream(path, options) | ||
this.emit('stream', stream) | ||
stream.pipe(res) | ||
// response finished, done with the fd | ||
onFinished(res, function onfinished(){ | ||
finished = true; | ||
destroy(stream); | ||
}); | ||
onFinished(res, function onfinished () { | ||
finished = true | ||
destroy(stream) | ||
}) | ||
// error handling code-smell | ||
stream.on('error', function onerror(err){ | ||
stream.on('error', function onerror (err) { | ||
// request already finished | ||
if (finished) return; | ||
if (finished) return | ||
// clean up stream | ||
finished = true; | ||
destroy(stream); | ||
finished = true | ||
destroy(stream) | ||
// error | ||
self.onStatError(err); | ||
}); | ||
self.onStatError(err) | ||
}) | ||
// end | ||
stream.on('end', function onend(){ | ||
self.emit('end'); | ||
}); | ||
}; | ||
stream.on('end', function onend () { | ||
self.emit('end') | ||
}) | ||
} | ||
@@ -765,19 +816,19 @@ /** | ||
SendStream.prototype.type = function type(path) { | ||
var res = this.res; | ||
SendStream.prototype.type = function type (path) { | ||
var res = this.res | ||
if (res.getHeader('Content-Type')) return; | ||
if (res.getHeader('Content-Type')) return | ||
var type = mime.lookup(path); | ||
var type = mime.lookup(path) | ||
if (!type) { | ||
debug('no content-type'); | ||
return; | ||
debug('no content-type') | ||
return | ||
} | ||
var charset = mime.charsets.lookup(type); | ||
var charset = mime.charsets.lookup(type) | ||
debug('content-type %s', type); | ||
res.setHeader('Content-Type', type + (charset ? '; charset=' + charset : '')); | ||
}; | ||
debug('content-type %s', type) | ||
res.setHeader('Content-Type', type + (charset ? '; charset=' + charset : '')) | ||
} | ||
@@ -793,10 +844,18 @@ /** | ||
SendStream.prototype.setHeader = function setHeader(path, stat){ | ||
var res = this.res; | ||
SendStream.prototype.setHeader = function setHeader (path, stat) { | ||
var res = this.res | ||
this.emit('headers', res, path, stat); | ||
this.emit('headers', res, path, stat) | ||
if (!res.getHeader('Accept-Ranges')) res.setHeader('Accept-Ranges', 'bytes'); | ||
if (!res.getHeader('Cache-Control')) res.setHeader('Cache-Control', 'public, max-age=' + Math.floor(this._maxage / 1000)); | ||
if (this._acceptRanges && !res.getHeader('Accept-Ranges')) { | ||
debug('accept ranges') | ||
res.setHeader('Accept-Ranges', 'bytes') | ||
} | ||
if (this._cacheControl && !res.getHeader('Cache-Control')) { | ||
var cacheControl = 'public, max-age=' + Math.floor(this._maxage / 1000) | ||
debug('cache-control %s', cacheControl) | ||
res.setHeader('Cache-Control', cacheControl) | ||
} | ||
if (this._lastModified && !res.getHeader('Last-Modified')) { | ||
@@ -813,5 +872,17 @@ var modified = stat.mtime.toUTCString() | ||
} | ||
}; | ||
} | ||
/** | ||
* Clear all headers from a response. | ||
* | ||
* @param {object} res | ||
* @private | ||
*/ | ||
function clearHeaders (res) { | ||
res._headers = {} | ||
res._headerNames = {} | ||
} | ||
/** | ||
* Determine if path parts contain a dotfile. | ||
@@ -822,3 +893,3 @@ * | ||
function containsDotFile(parts) { | ||
function containsDotFile (parts) { | ||
for (var i = 0; i < parts.length; i++) { | ||
@@ -834,2 +905,14 @@ if (parts[i][0] === '.') { | ||
/** | ||
* Create a Content-Range header. | ||
* | ||
* @param {string} type | ||
* @param {number} size | ||
* @param {array} [range] | ||
*/ | ||
function contentRange (type, size, range) { | ||
return type + ' ' + (range ? range.start + '-' + range.end : '*') + '/' + size | ||
} | ||
/** | ||
* decodeURIComponent. | ||
@@ -844,3 +927,3 @@ * | ||
function decode(path) { | ||
function decode (path) { | ||
try { | ||
@@ -861,3 +944,3 @@ return decodeURIComponent(path) | ||
function normalizeList(val, name) { | ||
function normalizeList (val, name) { | ||
var list = [].concat(val || []) | ||
@@ -873,1 +956,18 @@ | ||
} | ||
/** | ||
* Set an object of headers on a response. | ||
* | ||
* @param {object} res | ||
* @param {object} headers | ||
* @private | ||
*/ | ||
function setHeaders (res, headers) { | ||
var keys = Object.keys(headers) | ||
for (var i = 0; i < keys.length; i++) { | ||
var key = keys[i] | ||
res.setHeader(key, headers[key]) | ||
} | ||
} |
{ | ||
"name": "send", | ||
"description": "Better streaming static file server with Range and conditional-GET support", | ||
"version": "0.13.2", | ||
"version": "0.14.0", | ||
"author": "TJ Holowaychuk <tj@vision-media.ca>", | ||
@@ -23,13 +23,17 @@ "contributors": [ | ||
"fresh": "0.3.0", | ||
"http-errors": "~1.3.1", | ||
"http-errors": "~1.5.0", | ||
"mime": "1.3.4", | ||
"ms": "0.7.1", | ||
"on-finished": "~2.3.0", | ||
"range-parser": "~1.0.3", | ||
"statuses": "~1.2.1" | ||
"range-parser": "~1.2.0", | ||
"statuses": "~1.3.0" | ||
}, | ||
"devDependencies": { | ||
"after": "0.8.1", | ||
"istanbul": "0.4.2", | ||
"mocha": "2.4.5", | ||
"eslint": "2.11.1", | ||
"eslint-config-standard": "5.3.1", | ||
"eslint-plugin-promise": "1.3.1", | ||
"eslint-plugin-standard": "1.3.2", | ||
"istanbul": "0.4.3", | ||
"mocha": "2.5.3", | ||
"supertest": "1.1.0" | ||
@@ -47,2 +51,3 @@ }, | ||
"scripts": { | ||
"lint": "eslint **/*.js", | ||
"test": "mocha --check-leaks --reporter spec --bail", | ||
@@ -49,0 +54,0 @@ "test-ci": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --check-leaks --reporter spec", |
@@ -37,2 +37,13 @@ # send | ||
##### acceptRanges | ||
Enable or disable accepting ranged requests, defaults to true. | ||
Disabling this will not send `Accept-Ranges` and ignore the contents | ||
of the `Range` request header. | ||
##### cacheControl | ||
Enable or disable setting `Cache-Control` response header, defaults to | ||
true. Disabling this will ignore the `maxAge` option. | ||
##### dotfiles | ||
@@ -39,0 +50,0 @@ |
Sorry, the diff of this file is not supported yet
36950
764
246
8
+ Addedhttp-errors@1.5.1(transitive)
+ Addedinherits@2.0.3(transitive)
+ Addedrange-parser@1.2.1(transitive)
+ Addedsetprototypeof@1.0.2(transitive)
+ Addedstatuses@1.3.1(transitive)
- Removedhttp-errors@1.3.1(transitive)
- Removedinherits@2.0.4(transitive)
- Removedrange-parser@1.0.3(transitive)
- Removedstatuses@1.2.1(transitive)
Updatedhttp-errors@~1.5.0
Updatedrange-parser@~1.2.0
Updatedstatuses@~1.3.0