Comparing version 2.0.0 to 2.1.0
@@ -19,3 +19,3 @@ // Load modules | ||
path: Joi.alternatives(Joi.array().includes(Joi.string()).single(), Joi.func()).required(), | ||
index: Joi.boolean(), | ||
index: Joi.alternatives(Joi.boolean(), Joi.array().includes(Joi.string()).single()).default(true), | ||
listing: Joi.boolean(), | ||
@@ -54,2 +54,4 @@ showHidden: Joi.boolean(), | ||
var indexNames = (settings.index === true) ? ['index.html'] : (settings.index || []); | ||
// Declare handler | ||
@@ -137,4 +139,3 @@ | ||
var index = (settings.index !== false); // Defaults to true | ||
if (!index && | ||
if (indexNames.length === 0 && | ||
!settings.listing) { | ||
@@ -152,24 +153,29 @@ | ||
if (!index) { | ||
return internals.generateListing(path, resource, selection, hasTrailingSlash, settings, request, reply); | ||
} | ||
Items.serial(indexNames, function (indexName, next) { | ||
var indexFile = Path.join(path, 'index.html'); | ||
File.load(indexFile, request, { lookupCompressed: settings.lookupCompressed }, function (indexResponse) { | ||
var indexFile = Path.join(path, indexName); | ||
File.load(indexFile, request, { lookupCompressed: settings.lookupCompressed }, function (indexResponse) { | ||
// File loaded successfully | ||
// File loaded successfully | ||
if (!indexResponse.isBoom) { | ||
return reply(indexResponse); | ||
} | ||
if (!indexResponse.isBoom) { | ||
return reply(indexResponse); | ||
} | ||
// Directory | ||
// Directory | ||
var err = indexResponse; | ||
if (err.output.statusCode !== 404) { | ||
return reply(Boom.badImplementation('index.html is a directory')); | ||
} | ||
var err = indexResponse; | ||
if (err.output.statusCode !== 404) { | ||
return reply(Boom.badImplementation(indexName + ' is a directory')); | ||
} | ||
// Not found | ||
// Not found, try the next one | ||
return next(); | ||
}); | ||
}, | ||
function (/* err */) { | ||
// None of the index files were found | ||
if (!settings.listing) { | ||
@@ -176,0 +182,0 @@ return reply(Boom.forbidden()); |
@@ -6,2 +6,3 @@ // Load modules | ||
var Crypto = require('crypto'); | ||
var Ammo = require('ammo'); | ||
var Boom = require('boom'); | ||
@@ -133,3 +134,3 @@ var Hoek = require('hoek'); | ||
return next(null, internals.openStream(response, response.source.path)); | ||
return internals.openStream(response, response.source.path, next); | ||
} | ||
@@ -141,3 +142,3 @@ | ||
if (err) { | ||
return next(null, internals.openStream(response, response.source.path)); | ||
return internals.openStream(response, response.source.path, next); | ||
} | ||
@@ -152,3 +153,3 @@ | ||
return next(null, internals.openStream(response, gzFile)); | ||
return internals.openStream(response, gzFile, next); | ||
}); | ||
@@ -158,9 +159,47 @@ }; | ||
internals.openStream = function (response, path) { | ||
internals.openStream = function (response, path, next) { | ||
Hoek.assert(response.source.fd !== null, 'file descriptor must be set'); | ||
var fileStream = Fs.createReadStream(path, { fd: response.source.fd }); | ||
// Check for Range request | ||
var request = response.request; | ||
var length = response.headers['content-length']; | ||
var options = { fd: response.source.fd }; | ||
if (request.headers.range && length) { | ||
// Check If-Range | ||
if (!request.headers['if-range'] || | ||
request.headers['if-range'] === response.headers.etag) { // Ignoring last-modified date (weak) | ||
// Parse header | ||
var ranges = Ammo.header(request.headers.range, length); | ||
if (!ranges) { | ||
var error = Boom.rangeNotSatisfiable(); | ||
error.output.headers['content-range'] = 'bytes */' + length; | ||
return next(error); | ||
} | ||
// Prepare transform | ||
if (ranges.length === 1) { // Ignore requests for multiple ranges | ||
var range = ranges[0]; | ||
response.code(206); | ||
response.bytes(range.to - range.from + 1); | ||
response._header('content-range', 'bytes ' + range.from + '-' + range.to + '/' + length); | ||
options.start = range.from; | ||
options.end = range.to; | ||
} | ||
} | ||
} | ||
response._header('accept-ranges', 'bytes'); | ||
var fileStream = Fs.createReadStream(path, options); | ||
response.source.fd = null; // Claim descriptor | ||
return fileStream; | ||
return next(null, fileStream); | ||
}; | ||
@@ -167,0 +206,0 @@ |
{ | ||
"name": "inert", | ||
"description": "Static file and directory handlers plugin for hapi.js", | ||
"version": "2.0.0", | ||
"version": "2.1.0", | ||
"repository": "git://github.com/hapijs/inert", | ||
@@ -18,2 +18,3 @@ "main": "index", | ||
"dependencies": { | ||
"ammo": "1.x.x", | ||
"boom": "2.x.x", | ||
@@ -31,3 +32,4 @@ "hoek": "2.x.x", | ||
"scripts": { | ||
"test": "make test-cov" | ||
"test": "lab -a code -t 100 -L", | ||
"test-cov-html": "lab -a code -r html -o coverage.html" | ||
}, | ||
@@ -34,0 +36,0 @@ "licenses": [ |
@@ -268,3 +268,3 @@ // Load modules | ||
it('returns the index when found', function (done) { | ||
it('returns the "index.html" index file when found and default index enabled', function (done) { | ||
@@ -282,2 +282,52 @@ var server = provisionServer(); | ||
it('returns the index file when found and single custom index file specified', function (done) { | ||
var server = provisionServer(); | ||
server.route({ method: 'GET', path: '/directoryIndex/{path*}', handler: { directory: { path: './directory/', index: 'index.js' } } }); | ||
server.inject('/directoryIndex/', function (res) { | ||
expect(res.statusCode).to.equal(200); | ||
expect(res.payload).to.contain('var isTest = true;'); | ||
done(); | ||
}); | ||
}); | ||
it('returns the first index file found when an array of index files is specified', function (done) { | ||
var server = provisionServer(); | ||
server.route({ method: 'GET', path: '/directoryIndex/{path*}', handler: { directory: { path: './directory/', index: ['default.html', 'index.js', 'non.existing'] } } }); | ||
server.inject('/directoryIndex/', function (res) { | ||
expect(res.statusCode).to.equal(200); | ||
expect(res.payload).to.contain('var isTest = true;'); | ||
done(); | ||
}); | ||
}); | ||
it('returns a 403 when listing is disabled and a custom index file is specified but not found', function (done) { | ||
var server = provisionServer(); | ||
server.route({ method: 'GET', path: '/directoryIndex/{path*}', handler: { directory: { path: './directory/', index: 'default.html' } } }); | ||
server.inject('/directoryIndex/', function (res) { | ||
expect(res.statusCode).to.equal(403); | ||
done(); | ||
}); | ||
}); | ||
it('returns a 403 when listing is disabled and an array of index files is specified but none were found', function (done) { | ||
var server = provisionServer(); | ||
server.route({ method: 'GET', path: '/directoryIndex/{path*}', handler: { directory: { path: './directory/', index: ['default.html', 'non.existing'] } } }); | ||
server.inject('/directoryIndex/', function (res) { | ||
expect(res.statusCode).to.equal(403); | ||
done(); | ||
}); | ||
}); | ||
it('returns the index when served from a hidden folder', function (done) { | ||
@@ -327,2 +377,14 @@ | ||
it('returns a 500 when the custom index is a directory', function (done) { | ||
var server = provisionServer(null, false); | ||
server.route({ method: 'GET', path: '/directoryIndex/{path*}', handler: { directory: { path: './directory/', index: 'misc' } } }); | ||
server.inject('/directoryIndex/invalid/', function (res) { | ||
expect(res.statusCode).to.equal(500); | ||
done(); | ||
}); | ||
}); | ||
it('returns the correct file when using a fn directory handler', function (done) { | ||
@@ -329,0 +391,0 @@ |
198
test/file.js
@@ -939,2 +939,200 @@ // Load modules | ||
describe('response range', function () { | ||
it('returns a subset of a file (start)', function (done) { | ||
var server = provisionServer(); | ||
server.route({ method: 'GET', path: '/file', handler: { file: { path: Path.join(__dirname, 'file/image.png') } } }); | ||
server.inject({ url: '/file', headers: { 'range': 'bytes=0-4' } }, function (res) { | ||
expect(res.statusCode).to.equal(206); | ||
expect(res.headers['content-length']).to.equal(5); | ||
expect(res.headers['content-range']).to.equal('bytes 0-4/42010'); | ||
expect(res.headers['accept-ranges']).to.equal('bytes'); | ||
expect(res.payload).to.equal('\x89PNG\r'); | ||
done(); | ||
}); | ||
}); | ||
it('returns a subset of a file (middle)', function (done) { | ||
var server = provisionServer(); | ||
server.route({ method: 'GET', path: '/file', handler: { file: { path: Path.join(__dirname, 'file/image.png') } } }); | ||
server.inject({ url: '/file', headers: { 'range': 'bytes=1-5' } }, function (res) { | ||
expect(res.statusCode).to.equal(206); | ||
expect(res.headers['content-length']).to.equal(5); | ||
expect(res.headers['content-range']).to.equal('bytes 1-5/42010'); | ||
expect(res.headers['accept-ranges']).to.equal('bytes'); | ||
expect(res.payload).to.equal('PNG\r\n'); | ||
done(); | ||
}); | ||
}); | ||
it('returns a subset of a file (-to)', function (done) { | ||
var server = provisionServer(); | ||
server.route({ method: 'GET', path: '/file', handler: { file: { path: Path.join(__dirname, 'file/image.png') } } }); | ||
server.inject({ url: '/file', headers: { 'range': 'bytes=-5' } }, function (res) { | ||
expect(res.statusCode).to.equal(206); | ||
expect(res.headers['content-length']).to.equal(5); | ||
expect(res.headers['content-range']).to.equal('bytes 42005-42009/42010'); | ||
expect(res.headers['accept-ranges']).to.equal('bytes'); | ||
expect(res.payload).to.equal('D\xAEB\x60\x82'); | ||
done(); | ||
}); | ||
}); | ||
it('returns a subset of a file (from-)', function (done) { | ||
var server = provisionServer(); | ||
server.route({ method: 'GET', path: '/file', handler: { file: { path: Path.join(__dirname, 'file/image.png') } } }); | ||
server.inject({ url: '/file', headers: { 'range': 'bytes=42005-' } }, function (res) { | ||
expect(res.statusCode).to.equal(206); | ||
expect(res.headers['content-length']).to.equal(5); | ||
expect(res.headers['content-range']).to.equal('bytes 42005-42009/42010'); | ||
expect(res.headers['accept-ranges']).to.equal('bytes'); | ||
expect(res.payload).to.equal('D\xAEB\x60\x82'); | ||
done(); | ||
}); | ||
}); | ||
it('returns a subset of a file (beyond end)', function (done) { | ||
var server = provisionServer(); | ||
server.route({ method: 'GET', path: '/file', handler: { file: { path: Path.join(__dirname, 'file/image.png') } } }); | ||
server.inject({ url: '/file', headers: { 'range': 'bytes=42005-42011' } }, function (res) { | ||
expect(res.statusCode).to.equal(206); | ||
expect(res.headers['content-length']).to.equal(5); | ||
expect(res.headers['content-range']).to.equal('bytes 42005-42009/42010'); | ||
expect(res.headers['accept-ranges']).to.equal('bytes'); | ||
expect(res.payload).to.equal('D\xAEB\x60\x82'); | ||
done(); | ||
}); | ||
}); | ||
it('returns a subset of a file (if-range)', function (done) { | ||
var server = provisionServer(); | ||
server.route({ method: 'GET', path: '/file', handler: { file: { path: Path.join(__dirname, 'file/image.png') } } }); | ||
server.inject('/file', function (res) { | ||
server.inject('/file', function (res1) { | ||
server.inject({ url: '/file', headers: { 'range': 'bytes=42005-42011', 'if-range': res1.headers.etag } }, function (res2) { | ||
expect(res2.statusCode).to.equal(206); | ||
expect(res2.headers['content-length']).to.equal(5); | ||
expect(res2.headers['content-range']).to.equal('bytes 42005-42009/42010'); | ||
expect(res2.headers['accept-ranges']).to.equal('bytes'); | ||
expect(res2.payload).to.equal('D\xAEB\x60\x82'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
it('returns 200 on incorrect if-range', function (done) { | ||
var server = provisionServer(); | ||
server.route({ method: 'GET', path: '/file', handler: { file: { path: Path.join(__dirname, 'file/image.png') } } }); | ||
server.inject({ url: '/file', headers: { 'range': 'bytes=42005-42011', 'if-range': 'abc' } }, function (res2) { | ||
expect(res2.statusCode).to.equal(200); | ||
done(); | ||
}); | ||
}); | ||
it('returns 416 on invalid range (unit)', function (done) { | ||
var server = provisionServer(); | ||
server.route({ method: 'GET', path: '/file', handler: { file: { path: Path.join(__dirname, 'file/image.png') } } }); | ||
server.inject({ url: '/file', headers: { 'range': 'horses=1-5' } }, function (res) { | ||
expect(res.statusCode).to.equal(416); | ||
expect(res.headers['content-range']).to.equal('bytes */42010'); | ||
done(); | ||
}); | ||
}); | ||
it('returns 416 on invalid range (inversed)', function (done) { | ||
var server = provisionServer(); | ||
server.route({ method: 'GET', path: '/file', handler: { file: { path: Path.join(__dirname, 'file/image.png') } } }); | ||
server.inject({ url: '/file', headers: { 'range': 'bytes=5-1' } }, function (res) { | ||
expect(res.statusCode).to.equal(416); | ||
expect(res.headers['content-range']).to.equal('bytes */42010'); | ||
done(); | ||
}); | ||
}); | ||
it('returns 416 on invalid range (format)', function (done) { | ||
var server = provisionServer(); | ||
server.route({ method: 'GET', path: '/file', handler: { file: { path: Path.join(__dirname, 'file/image.png') } } }); | ||
server.inject({ url: '/file', headers: { 'range': 'bytes 1-5' } }, function (res) { | ||
expect(res.statusCode).to.equal(416); | ||
expect(res.headers['content-range']).to.equal('bytes */42010'); | ||
done(); | ||
}); | ||
}); | ||
it('returns 416 on invalid range (empty range)', function (done) { | ||
var server = provisionServer(); | ||
server.route({ method: 'GET', path: '/file', handler: { file: { path: Path.join(__dirname, 'file/image.png') } } }); | ||
server.inject({ url: '/file', headers: { 'range': 'bytes=-' } }, function (res) { | ||
expect(res.statusCode).to.equal(416); | ||
expect(res.headers['content-range']).to.equal('bytes */42010'); | ||
done(); | ||
}); | ||
}); | ||
it('returns 200 on multiple ranges', function (done) { | ||
var server = provisionServer(); | ||
server.route({ method: 'GET', path: '/file', handler: { file: { path: Path.join(__dirname, 'file/image.png') } } }); | ||
server.inject({ url: '/file', headers: { 'range': 'bytes=1-5,7-10' } }, function (res) { | ||
expect(res.statusCode).to.equal(200); | ||
expect(res.headers['content-length']).to.equal(42010); | ||
done(); | ||
}); | ||
}); | ||
it('returns a subset of a file using precompressed file', function (done) { | ||
var server = provisionServer(); | ||
server.route({ method: 'GET', path: '/file', handler: { file: { path: Path.join(__dirname, 'file/image.png'), lookupCompressed: true } } }); | ||
server.inject({ url: '/file', headers: { 'range': 'bytes=10-18', 'accept-encoding': 'gzip' } }, function (res) { | ||
expect(res.statusCode).to.equal(206); | ||
expect(res.headers['content-encoding']).to.equal('gzip'); | ||
expect(res.headers['content-length']).to.equal(9); | ||
expect(res.headers['content-range']).to.equal('bytes 10-18/41936'); | ||
expect(res.headers['accept-ranges']).to.equal('bytes'); | ||
expect(res.payload).to.equal('image.png'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
it('has not leaked file descriptors', { skip: process.platform === 'win32' }, function (done) { | ||
@@ -941,0 +1139,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
360535
20173
27
1678
6
+ Addedammo@1.x.x
+ Addedammo@1.0.1(transitive)