diff --git a/docs/Reference.md b/docs/Reference.md index c3ee74e53..a752b984a 100755 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -852,7 +852,7 @@ a directory content listing. Routes utilizing the directory handler must include a single path parameter at the end of the path string (e.g. _'/path/to/somewhere/{param}'_). The directory handler is an object with the following options: -- `path` - a required path string or function. If the `path` is a string, it is used as the prefix for any resources requested within the route by appending the required route path parameter to the provided string. Alternatively, the `path` can be a function with the signature _'function (request) { return './path'; }'_. The function is passed the request object and must return a string with the relative or absolute path to the static resource. Relative paths are resolved based on the server's `files` option as described in (Files)[#files]. +- `path` - a required path string, an array of strings, or function. If the `path` is a string, it is used as the prefix for any resources requested within the route by appending the required route path parameter to the provided string. An array of string will be attemped in order until a match is found. Alternatively, the `path` can be a function with the signature _'function (request) { return './path'; }'_. The function is passed the request object and must return a string with the relative or absolute path to the static resource. Relative paths are resolved based on the server's `files` option as described in (Files)[#files]. - `index` - optional boolean, determines if 'index.html' will be served if exists in the folder when requesting a directory. 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'_. diff --git a/lib/files.js b/lib/files.js index 57deb1b95..c034ba5ef 100755 --- a/lib/files.js +++ b/lib/files.js @@ -55,7 +55,7 @@ exports.directoryHandler = function (route, options) { Utils.assert(options, 'Options must exist'); Utils.assert(typeof options === 'object' && options.path, 'Options must be an object with a path'); - Utils.assert(typeof options.path === 'function' || typeof options.path === 'string', 'options.path must be a function or a string'); + Utils.assert(typeof options.path === 'function' || typeof options.path === 'string' || options.path instanceof Array, 'options.path must be a function, a string, or an array of strings'); Utils.assert(route.path[route.path.length - 1] === '}', 'The route path must end with a parameter'); Utils.assert(route.params.length === 1, 'The route path must include one and only one parameter'); @@ -65,54 +65,55 @@ exports.directoryHandler = function (route, options) { // Normalize static string path if (typeof settings.path === 'string') { - if (settings.path[settings.path.length - 1] !== '/') { - settings.path += '/'; - } - - if (settings.path[0] !== '/') { - settings.path = absolutePath + '/' + settings.path; - } + settings.path = [settings.path]; } - var handler = function (request) { + var normalize = function (path) { - var path = null; + if (path[path.length - 1] !== '/') { + path += '/'; + } - if (typeof settings.path === 'function') { - path = settings.path(request); + if (path[0] !== '/') { + path = absolutePath + '/' + path; + } + + return path; + }; - // Normalize function string path + var normalized = []; + if (settings.path instanceof Array) { + settings.path.forEach(function (path) { + + Utils.assert(path && typeof path === 'string', 'Directory path array must only contain strings'); + normalized.push(normalize(path)); + }); + } - if (path[path.length - 1] !== '/') { - path += '/'; - } + var handler = function (request) { - if (path[0] !== '/') { - path = absolutePath + '/' + path; - } - } - else { - path = settings.path; + var paths = normalized; + if (typeof settings.path === 'function') { + paths = [normalize(settings.path(request))]; } // Append parameter - var isRoot = true; + var selection = null; if (request._paramsArray[0]) { - if (request._paramsArray[0].indexOf('..') !== -1) { return request.reply(Boom.forbidden()); } - path += request._paramsArray[0]; - isRoot = false; + selection = request._paramsArray[0]; } // Generate response - var response = new Response.Directory(path, { + var response = new Response.Directory(paths, { + resource: request.path, - isRoot: isRoot, + selection: selection, index: settings.index, listing: settings.listing, showHidden: settings.showHidden diff --git a/lib/response/directory.js b/lib/response/directory.js index c32b8974e..9f53eb6ae 100755 --- a/lib/response/directory.js +++ b/lib/response/directory.js @@ -2,6 +2,7 @@ var Fs = require('fs'); var Path = require('path'); +var Async = require('async'); var Cacheable = require('./cacheable'); var Boom = require('boom'); var File = require('./file'); @@ -15,7 +16,7 @@ var internals = {}; // File response (Base -> Generic -> Cacheable -> Directory) -exports = module.exports = internals.Directory = function (path, options) { +exports = module.exports = internals.Directory = function (paths, options) { Utils.assert(this.constructor === internals.Directory, 'Directory must be instantiated using new'); Utils.assert(options, 'Options must exist'); @@ -24,9 +25,9 @@ exports = module.exports = internals.Directory = function (path, options) { this.variety = 'directory'; this.varieties.directory = true; - this._path = Path.normalize(path); + this._paths = paths; this._resource = options.resource; - this._isRoot = options.isRoot; + this._selection = options.selection; this._index = options.index === false ? false : true; // Defaults to true this._listing = !!options.listing; // Defaults to false this._showHidden = !!options.showHidden; // Defaults to false @@ -37,19 +38,47 @@ exports = module.exports = internals.Directory = function (path, options) { Utils.inherits(internals.Directory, Cacheable); -internals.Directory.prototype._prepare = function (request, callback) { +internals.Directory.prototype._prepare = function (request, callback) { var self = this; - this._wasPrepared = true; + this._wasPrepared = true; + + var selection = this._selection || ''; + + Async.forEachSeries(this._paths, function (path, next) { - if (this._hideFile(this._path)) { // Don't serve hidden files when showHidden is disabled - return callback(Boom.notFound()); - } + path = Path.normalize(path + selection); + + if (self._hideFile(path)) { // Don't serve hidden files when showHidden is disabled + return callback(Boom.notFound()); + } + + self._preparePath(path, request, function (response) { + + if (response instanceof Error === false || + response.response.code !== 404) { + + return callback(response); + } + + next(); + }); + }, + function (err) { + + callback(Boom.notFound()); + }); +}; + + +internals.Directory.prototype._preparePath = function (path, request, callback) { + + var self = this; // Lookup file - (new File(this._path))._prepare(request, function (response) { + (new File(path))._prepare(request, function (response) { // File loaded successfully @@ -73,10 +102,10 @@ internals.Directory.prototype._prepare = function (request, callback) { } if (!self._index) { - return self._generateListing(request, callback); + return self._generateListing(path, request, callback); } - var indexFile = Path.normalize(self._path + (self._path[self._path.length - 1] !== '/' ? '/' : '') + 'index.html'); + var indexFile = Path.normalize(path + (path[path.length - 1] !== '/' ? '/' : '') + 'index.html'); (new File(indexFile))._prepare(request, function (indexResponse) { // File loaded successfully @@ -98,17 +127,17 @@ internals.Directory.prototype._prepare = function (request, callback) { return callback(Boom.forbidden()); } - return self._generateListing(request, callback); + return self._generateListing(path, request, callback); }); }); }; -internals.Directory.prototype._generateListing = function (request, callback) { +internals.Directory.prototype._generateListing = function (path, request, callback) { var self = this; - Fs.readdir(self._path, function (err, files) { + Fs.readdir(path, function (err, files) { if (err) { return callback(Boom.internal('Error accessing directory')); @@ -118,7 +147,7 @@ internals.Directory.prototype._generateListing = function (request, callback) { var display = Utils.escapeHtml(self._resource); var html = '