+105
| // Load modules | ||
| var Code = require('code'); | ||
| var Hapi = require('hapi'); | ||
| var Hoek = require('hoek'); | ||
| var Inert = require('..'); | ||
| var Lab = require('lab'); | ||
| // Declare internals | ||
| var internals = {}; | ||
| // Test shortcuts | ||
| var lab = exports.lab = Lab.script(); | ||
| var describe = lab.describe; | ||
| var it = lab.it; | ||
| var expect = Code.expect; | ||
| describe('security', function () { | ||
| var provisionServer = function () { | ||
| var server = new Hapi.Server(); | ||
| server.connection(); | ||
| server.register(Inert, Hoek.ignore); | ||
| return server; | ||
| }; | ||
| it('blocks path traversal to files outside of hosted directory is not allowed with null byte injection', function (done) { | ||
| var server = provisionServer(); | ||
| server.connection(); | ||
| server.route({ method: 'GET', path: '/{path*}', handler: { directory: { path: './directory' } } }); | ||
| server.inject('/%00/../security.js', function (res) { | ||
| expect(res.statusCode).to.equal(403); | ||
| done(); | ||
| }); | ||
| }); | ||
| it('blocks path traversal to files outside of hosted directory is not allowed', function (done) { | ||
| var server = provisionServer(); | ||
| server.route({ method: 'GET', path: '/{path*}', handler: { directory: { path: './directory' } } }); | ||
| server.inject('/../security.js', function (res) { | ||
| expect(res.statusCode).to.equal(403); | ||
| done(); | ||
| }); | ||
| }); | ||
| it('blocks path traversal to files outside of hosted directory is not allowed with encoded slash', function (done) { | ||
| var server = provisionServer(); | ||
| server.route({ method: 'GET', path: '/{path*}', handler: { directory: { path: './directory' } } }); | ||
| server.inject('/..%2Fsecurity.js', function (res) { | ||
| expect(res.statusCode).to.equal(403); | ||
| done(); | ||
| }); | ||
| }); | ||
| it('blocks path traversal to files outside of hosted directory is not allowed with double encoded slash', function (done) { | ||
| var server = provisionServer(); | ||
| server.route({ method: 'GET', path: '/{path*}', handler: { directory: { path: './directory' } } }); | ||
| server.inject('/..%252Fsecurity.js', function (res) { | ||
| expect(res.statusCode).to.equal(403); | ||
| done(); | ||
| }); | ||
| }); | ||
| it('blocks path traversal to files outside of hosted directory is not allowed with unicode encoded slash', function (done) { | ||
| var server = provisionServer(); | ||
| server.route({ method: 'GET', path: '/{path*}', handler: { directory: { path: './directory' } } }); | ||
| server.inject('/..\u2216security.js', function (res) { | ||
| expect(res.statusCode).to.equal(403); | ||
| done(); | ||
| }); | ||
| }); | ||
| it('blocks null byte injection when serving a file', function (done) { | ||
| var server = provisionServer(); | ||
| server.route({ method: 'GET', path: '/{path*}', handler: { directory: { path: './directory' } } }); | ||
| server.inject('/index%00.html', function (res) { | ||
| expect(res.statusCode).to.equal(404); | ||
| done(); | ||
| }); | ||
| }); | ||
| }); |
+1
-0
@@ -135,2 +135,3 @@ // Load modules | ||
| if (!response.source.settings.lookupCompressed || | ||
| !response.request.connection.settings.compression || | ||
| response.request.info.acceptEncoding !== 'gzip') { | ||
@@ -137,0 +138,0 @@ |
+2
-2
| { | ||
| "name": "inert", | ||
| "description": "Static file and directory handlers plugin for hapi.js", | ||
| "version": "3.0.0", | ||
| "version": "3.0.1", | ||
| "repository": "git://github.com/hapijs/inert", | ||
@@ -27,3 +27,3 @@ "main": "lib/index.js", | ||
| "code": "1.x.x", | ||
| "hapi": "8.x.x", | ||
| "hapi": "9.x.x", | ||
| "lab": "5.x.x" | ||
@@ -30,0 +30,0 @@ }, |
+220
-1
@@ -1,2 +0,2 @@ | ||
| #inert | ||
| # inert | ||
@@ -8,1 +8,220 @@ Static file and directory handlers plugin for hapi.js. | ||
| Lead Maintainer - [Gil Pedersen](https://github.com/kanongil) | ||
| **inert** provides new [handler](https://github.com/hapijs/hapi/blob/master/API.md#serverhandlername-method) | ||
| methods for serving static files and directories, as well as decorating the [reply](https://github.com/hapijs/hapi/blob/master/API.md#reply-interface) | ||
| interface with a `file` method for serving file based resources. | ||
| #### Features | ||
| * Files are served with cache friendly `last-modified` and `etag` headers. | ||
| * Generated file listings and custom indexes. | ||
| * Precompressed file support for `content-encoding: gzip` responses. | ||
| * File attachment support using `content-disposition` header. | ||
| ## Index | ||
| - [Examples](#examples) | ||
| - [Static file server](#static-file-server) | ||
| - [Serving a single file](#serving-a-single-file) | ||
| - [Customized file response](#customized-file-response) | ||
| - [Usage](#usage) | ||
| - [Registration options](#registration-options) | ||
| - [`reply.file(path, [options])`](#replyfilepath-options) | ||
| - [The `file` handler](#the-file-handler) | ||
| - [The `directory` handler](#the-directory-handler) | ||
| ## Examples | ||
| **inert** enables a number of common use cases for serving static assets. | ||
| ### Static file server | ||
| The following creates a basic static file server that can be used to serve html content from the | ||
| `public` directory on port 3000: | ||
| ```js | ||
| var Path = require('path'); | ||
| var Hapi = require('hapi'); | ||
| var Inert = require('inert'); | ||
| var server = new Hapi.Server({ | ||
| connections: { | ||
| routes: { | ||
| files: { | ||
| relativeTo: Path.join(__dirname, 'public') | ||
| } | ||
| } | ||
| } | ||
| }); | ||
| server.connection({ port: 3000 }); | ||
| server.register(Inert, function () {}); | ||
| server.route({ | ||
| method: 'GET', | ||
| path: '/{param*}', | ||
| handler: { | ||
| directory: { | ||
| path: '.', | ||
| redirectToSlash: true, | ||
| index: true | ||
| } | ||
| } | ||
| }); | ||
| server.start(function (err) { | ||
| if (err) { | ||
| throw err; | ||
| } | ||
| console.log('Server running at:', server.info.uri); | ||
| }); | ||
| ``` | ||
| ### Serving a single file | ||
| You can serve specific files using the `file` handler: | ||
| ```js | ||
| server.route({ | ||
| method: 'GET', | ||
| path: '/{path*}', | ||
| handler: { | ||
| file: 'page.html' | ||
| } | ||
| }); | ||
| ``` | ||
| ### Customized file response | ||
| If you need more control, the `reply.file()` method is available to use inside handlers: | ||
| ```js | ||
| server.route({ | ||
| method: 'GET', | ||
| path: '/file', | ||
| handler: function (request, reply) { | ||
| var path = 'plain.txt'; | ||
| if (request.headers['x-magic'] === 'sekret') { | ||
| path = 'awesome.png'; | ||
| } | ||
| return reply.file(path).vary('x-magic'); | ||
| } | ||
| }); | ||
| server.ext('onPostHandler', function (request, reply) { | ||
| var response = request.response; | ||
| if (response.isBoom && | ||
| response.output.statusCode === 404) { | ||
| return reply.file('404.html').code(404); | ||
| } | ||
| return reply.continue(); | ||
| }); | ||
| ``` | ||
| Note that paths for files served using the `reply.file()` handler are **NOT** guarded against | ||
| access outside the `files.relativeTo` directory, so be careful to guard against malevolent user | ||
| input. | ||
| ## Usage | ||
| After registration, this plugin adds a new method to the `reply` object and exposes the `'file'` | ||
| and `'directory'` route handlers. | ||
| ### Registration options | ||
| **inert** accepts the following registration options: | ||
| - `etagsCacheMaxSize` - sets the maximum number of file etag hash values stored in the | ||
| etags cache. Defaults to `10000`. | ||
| Note that **inert** uses the custom `'file'` `variety` to signal that the response is a static | ||
| file generated by this plugin. | ||
| ### `reply.file(path, [options])` | ||
| Transmits a file from the file system. The 'Content-Type' header defaults to the matching mime | ||
| type based on filename extension.: | ||
| - `path` - the file path. | ||
| - `options` - optional settings: | ||
| - `filename` - an optional filename to specify if sending a 'Content-Disposition' header, | ||
| defaults to the basename of `path` | ||
| - `mode` - specifies whether to include the 'Content-Disposition' header with the response. | ||
| Available values: | ||
| - `false` - header is not included. This is the default value. | ||
| - `'attachment'` | ||
| - `'inline'` | ||
| - `lookupCompressed` - if `true`, looks for the same filename with the '.gz' suffix for a | ||
| pre-compressed version of the file to serve if the request supports content encoding. | ||
| Defaults to `false`. | ||
| Returns a standard [response](https://github.com/hapijs/hapi/blob/master/API.md#response-object) object. | ||
| The [response flow control rules](https://github.com/hapijs/hapi/blob/master/API.md#flow-control) **do not** apply. | ||
| ### The `file` handler | ||
| Generates a static file endpoint for serving a single file. `file` can be set to: | ||
| - a relative or absolute file path string (relative paths are resolved based on the | ||
| route [`files`](https://github.com/hapijs/hapi/blob/master/API.md#route.config.files) | ||
| configuration). | ||
| - a function with the signature `function(request)` which returns the relative or absolute | ||
| file path. | ||
| - an object with one or more of the following options: | ||
| - `path` - a path string or function as described above (required). | ||
| - `filename` - an optional filename to specify if sending a 'Content-Disposition' | ||
| header, defaults to the basename of `path` | ||
| - `mode` - specifies whether to include the 'Content-Disposition' header with the | ||
| response. Available values: | ||
| - `false` - header is not included. This is the default value. | ||
| - `'attachment'` | ||
| - `'inline'` | ||
| - `lookupCompressed` - if `true`, looks for the same filename with the '.gz' suffix | ||
| for a pre-compressed version of the file to serve if the request supports content | ||
| encoding. Defaults to `false`. | ||
| ### The `directory` handler | ||
| Generates a directory endpoint for serving static content from a directory. | ||
| Routes using the directory handler must include a path parameter at the end of the path | ||
| string (e.g. `/path/to/somewhere/{param}` where the parameter name does not matter). The | ||
| path parameter can use any of the parameter options (e.g. `{param}` for one level files | ||
| only, `{param?}` for one level files or the directory root, `{param*}` for any level, or | ||
| `{param*3}` for a specific level). If additional path parameters are present, they are | ||
| ignored for the purpose of selecting the file system resource. The directory handler is an | ||
| object with the following options: | ||
| - `path` - (required) the directory root path (relative paths are resolved based on the | ||
| route [`files`](https://github.com/hapijs/hapi/blob/master/API.md#route.config.files) | ||
| configuration). Value can be: | ||
| - a single path string used as the prefix for any resources requested by appending the | ||
| request path parameter to the provided string. | ||
| - an array of path strings. Each path will be attempted in order until a match is | ||
| found (by following the same process as the single path string). | ||
| - a function with the signature `function(request)` which returns the path string or | ||
| an array of path strings. If the function returns an error, the error is passed back | ||
| to the client in the response. | ||
| - `index` - optional boolean|string|string[], determines if an index file will be served | ||
| if found in the folder when requesting a directory. The given string or strings specify | ||
| the name(s) of the index file to look for. If `true`, looks for 'index.html'. Any falsy | ||
| value disables index file lookup. Defaults to `true`. | ||
| - `listing` - optional boolean, determines if directory listing is generated when a | ||
| directory is requested without an index document. | ||
| Defaults to `false`. | ||
| - `showHidden` - optional boolean, determines if hidden files will be shown and served. | ||
| Defaults to `false`. | ||
| - `redirectToSlash` - optional boolean, determines if requests for a directory without a | ||
| trailing slash are redirected to the same path with the missing slash. Useful for | ||
| ensuring relative links inside the response are resolved correctly. Disabled when the | ||
| server config `router.stripTrailingSlash` is `true. `Defaults to `false`. | ||
| - `lookupCompressed` - optional boolean, instructs the file processor to look for the same | ||
| filename with the '.gz' suffix for a pre-compressed version of the file to serve if the | ||
| request supports content encoding. Defaults to `false`. | ||
| - `defaultExtension` - optional string, appended to file requests if the requested file is | ||
| not found. Defaults to no extension. |
@@ -33,3 +33,3 @@ // Load modules | ||
| var server = new Hapi.Server({ minimal: true, debug: debug }); | ||
| var server = new Hapi.Server({ debug: debug }); | ||
| server.connection(connection || { routes: { files: { relativeTo: __dirname } }, router: { stripTrailingSlash: false } }); | ||
@@ -36,0 +36,0 @@ server.register(Inert, Hoek.ignore); |
+36
-21
@@ -32,6 +32,6 @@ // Load modules | ||
| var provisionServer = function (relativeTo, etagsCacheMaxSize) { | ||
| var provisionServer = function (connection, etagsCacheMaxSize) { | ||
| var server = new Hapi.Server({ minimal: true }); | ||
| server.connection({ routes: { files: { relativeTo: relativeTo } } }); | ||
| var server = new Hapi.Server(); | ||
| server.connection(connection || {}); | ||
| server.register(etagsCacheMaxSize !== undefined ? { register: Inert, options: { etagsCacheMaxSize: etagsCacheMaxSize } } : Inert, Hoek.ignore); | ||
@@ -43,3 +43,3 @@ return server; | ||
| var server = provisionServer(__dirname); | ||
| var server = provisionServer({ routes: { files: { relativeTo: __dirname } } }); | ||
| var handler = function (request, reply) { | ||
@@ -97,3 +97,3 @@ | ||
| var server = provisionServer('./'); | ||
| var server = provisionServer({ routes: { files: { relativeTo: './' } } }); | ||
| server.route({ method: 'GET', path: '/', handler: { file: { path: './package.json', mode: 'inline' } } }); | ||
@@ -113,3 +113,3 @@ | ||
| var server = provisionServer('./'); | ||
| var server = provisionServer({ routes: { files: { relativeTo: './' } } }); | ||
| server.route({ method: 'GET', path: '/', handler: { file: { path: './package.json', mode: 'inline', filename: 'attachment.json' } } }); | ||
@@ -174,3 +174,3 @@ | ||
| var server = provisionServer(__dirname); | ||
| var server = provisionServer({ routes: { files: { relativeTo: __dirname } } }); | ||
| var handler = function (request, reply) { | ||
@@ -195,3 +195,3 @@ | ||
| var server = provisionServer(__dirname); | ||
| var server = provisionServer({ routes: { files: { relativeTo: __dirname } } }); | ||
| var handler = function (request, reply) { | ||
@@ -216,3 +216,3 @@ | ||
| var server = provisionServer(__dirname); | ||
| var server = provisionServer({ routes: { files: { relativeTo: __dirname } } }); | ||
| var handler = function (request, reply) { | ||
@@ -237,3 +237,3 @@ | ||
| var server = provisionServer(__dirname); | ||
| var server = provisionServer({ routes: { files: { relativeTo: __dirname } } }); | ||
| var handler = function (request, reply) { | ||
@@ -258,3 +258,3 @@ | ||
| var server = provisionServer('/no/such/path/x1'); | ||
| var server = provisionServer({ routes: { files: { relativeTo: '/no/such/path/x1' } } }); | ||
@@ -285,3 +285,3 @@ server.route({ method: 'GET', path: '/filenotfound', handler: { file: 'nopes' } }); | ||
| var server = provisionServer(__dirname); | ||
| var server = provisionServer({ routes: { files: { relativeTo: __dirname } } }); | ||
| server.route({ method: 'GET', path: '/staticfile', handler: { file: Path.join(__dirname, '..', 'package.json') } }); | ||
@@ -305,3 +305,3 @@ | ||
| var server = provisionServer(__dirname); | ||
| var server = provisionServer({ routes: { files: { relativeTo: __dirname } } }); | ||
| server.route({ method: 'GET', path: '/filefn/{file}', handler: { file: filenameFn } }); | ||
@@ -320,3 +320,3 @@ | ||
| var server = provisionServer('.'); | ||
| var server = provisionServer({ routes: { files: { relativeTo: '.' } } }); | ||
| var relativeHandler = function (request, reply) { | ||
@@ -340,3 +340,3 @@ | ||
| var server = provisionServer(__dirname); | ||
| var server = provisionServer({ routes: { files: { relativeTo: __dirname } } }); | ||
| server.route({ method: 'GET', path: '/relativestaticfile', handler: { file: '../package.json' } }); | ||
@@ -367,3 +367,3 @@ | ||
| var server = provisionServer(__dirname); | ||
| var server = provisionServer({ routes: { files: { relativeTo: __dirname } } }); | ||
| var handler = function (request, reply) { | ||
@@ -385,3 +385,3 @@ | ||
| var server = provisionServer(__dirname, 0); | ||
| var server = provisionServer({ routes: { files: { relativeTo: __dirname } } }, 0); | ||
| server.route({ method: 'GET', path: '/note', handler: { file: './file/note.txt' } }); | ||
@@ -407,3 +407,3 @@ | ||
| var server = provisionServer(__dirname); | ||
| var server = provisionServer({ routes: { files: { relativeTo: __dirname } } }); | ||
@@ -743,3 +743,3 @@ server.route({ method: 'GET', path: '/note', handler: { file: './file/note.txt' } }); | ||
| var server = provisionServer(__dirname); | ||
| var server = provisionServer({ routes: { files: { relativeTo: __dirname } } }); | ||
| var handler = function (request, reply) { | ||
@@ -764,3 +764,3 @@ | ||
| var server = provisionServer(__dirname); | ||
| var server = provisionServer({ routes: { files: { relativeTo: __dirname } } }); | ||
| var handler = function (request, reply) { | ||
@@ -785,3 +785,3 @@ | ||
| var server = provisionServer(__dirname); | ||
| var server = provisionServer({ routes: { files: { relativeTo: __dirname } } }); | ||
| var handler = function (request, reply) { | ||
@@ -858,2 +858,3 @@ | ||
| expect(res.headers['content-type']).to.equal('image/png'); | ||
| expect(res.headers['content-encoding']).to.not.exist(); | ||
| expect(res.payload).to.exist(); | ||
@@ -864,2 +865,16 @@ done(); | ||
| it('ignores precompressed file when connection compression is disabled', function (done) { | ||
| var server = provisionServer({ compression: false }); | ||
| server.route({ method: 'GET', path: '/file', handler: { file: { path: './test/file/image.png', lookupCompressed: true } } }); | ||
| server.inject({ url: '/file', headers: { 'accept-encoding': 'gzip' } }, function (res) { | ||
| expect(res.headers['content-type']).to.equal('image/png'); | ||
| expect(res.headers['content-encoding']).to.not.exist(); | ||
| expect(res.payload).to.exist(); | ||
| done(); | ||
| }); | ||
| }); | ||
| it('does not throw an error when adding a route with a parameter and function path', function (done) { | ||
@@ -866,0 +881,0 @@ |
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
381236
3.53%26
4%1940
4.41%227
2737.5%