Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multi directory path #590

Merged
merged 4 commits into from
Feb 27, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -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'_.
Expand Down
57 changes: 29 additions & 28 deletions lib/files.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand All @@ -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
Expand Down
59 changes: 44 additions & 15 deletions lib/response/directory.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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');
Expand All @@ -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
Expand All @@ -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

Expand All @@ -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
Expand All @@ -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'));
Expand All @@ -118,7 +147,7 @@ internals.Directory.prototype._generateListing = function (request, callback) {
var display = Utils.escapeHtml(self._resource);
var html = '<html><head><title>' + display + '</title></head><body><h1>Directory: ' + display + '</h1><ul>';

if (!self._isRoot) {
if (self._selection) {
separator = '/';
var parent = self._resource.substring(0, self._resource.lastIndexOf('/')) || '/';
html += '<li><a href="' + internals.pathEncode(parent) + '">Parent Directory</a></li>';
Expand Down
15 changes: 15 additions & 0 deletions test/integration/response.js
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,7 @@ describe('Response', function () {
server.route({ method: 'GET', path: '/noshowhidden/{path*}', handler: { directory: { path: './', listing: true } } });
server.route({ method: 'GET', path: '/{path*}', handler: { directory: { path: './', index: true, listing: true } } });
server.route({ method: 'GET', path: '/showindex/{path*}', handler: { directory: { path: './', index: true, listing: true } } });
server.route({ method: 'GET', path: '/multiple/{path*}', handler: { directory: { path: ['./', '../'], listing: true } } });

it('returns a 403 when no index exists and listing is disabled', function (done) {

Expand Down Expand Up @@ -841,6 +842,20 @@ describe('Response', function () {
});
});

it('returns a file when requesting a file from multi directory setup', function (done) {

server.start(function () {

Request.get(server.settings.uri + '/multiple/unit/response/directory.js', function (err, res, body) {

expect(err).to.not.exist;
expect(res.statusCode).to.equal(200);
expect(body).to.contain('no_such_path');
done();
});
});
});

it('returns the correct file when requesting a file from a child directory', function (done) {

server.start(function () {
Expand Down
4 changes: 2 additions & 2 deletions test/unit/response/directory.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ describe('Response', function () {

it('returns an error when reading an invalid directory', function (done) {

var dir = new Hapi.response.Directory('no_such_path', {});
dir._generateListing(null, function (response) {
var dir = new Hapi.response.Directory(['no_such_path'], {});
dir._generateListing('no_such_path', null, function (response) {

expect(response.response.code).to.equal(500);
done();
Expand Down