Skip to content

Loading…

Adding file mode option for inline, attachment or no content-disposition header #704

Merged
merged 2 commits into from

2 participants

@geek
hapi.js member

Closes #693

Defaults to no content-disposition header

@hueniverse
hapi.js member

Can we expose these options from the route config?

@geek
hapi.js member

Updated... do we want to make a breaking change for the file handler to always be an object with a path property, like we have for directory?

@hueniverse
hapi.js member

no. no disposition as default is good.

@hueniverse hueniverse merged commit 762431e into hapijs:master

1 check passed

Details default The Travis build passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Showing with 131 additions and 7 deletions.
  1. +13 −0 docs/Reference.md
  2. +5 −3 lib/files.js
  3. +6 −2 lib/response/file.js
  4. +107 −2 test/integration/response.js
View
13 docs/Reference.md
@@ -948,6 +948,19 @@ http.route({ method: 'GET', path: '/{path}', handler: { file: filePath } });
http.start();
```
+File handlers also support specifying if the _'Content-Disposition'_ should be sent on the response. The default is to not send this header, however if it needs to be sent there is an optional _'mode'_ property that can be set to _'attachment'_, _'inline'_, or false. Below is an example setting the mode to _'attachment'_.
+
+```javascript
+// Create Hapi server
+var http = new Hapi.Server('0.0.0.0', 8080, { files: { relativeTo: 'cwd' } });
+
+// Serve index.html file up a directory in the public folder
+http.route({ method: 'GET', path: '/', handler: { file: { path: './public/index.html', mode: 'attachment } } });
+
+http.start();
+```
+
+Please note, that when setting _'file'_ to an object, the file path is assigned to the _'path'_ property.
#### Directory
View
8 lib/files.js
@@ -10,8 +10,10 @@ var Response = require('./response');
var internals = {};
-exports.fileHandler = function (route, filePath) {
+exports.fileHandler = function (route, options) {
+ var filePath = (typeof options === 'object') ? options.path : options;
+ var mode = (typeof options === 'object') ? options.mode : false;
Utils.assert(filePath && (typeof filePath === 'function' || typeof filePath === 'string'), 'Invalid file path');
Utils.assert(typeof filePath !== 'string' || route.params.length === 0, 'Route path with static file path cannot contain a parameter');
Utils.assert(typeof filePath !== 'string' || filePath[filePath.length - 1] !== '/', 'File path cannot end with a \'/\'');
@@ -44,8 +46,8 @@ exports.fileHandler = function (route, filePath) {
path = staticPath;
}
- return request.reply(new Response.File(path));
- }
+ return request.reply(new Response.File(path, { mode: mode }));
+ };
return handler;
};
View
8 lib/response/file.js
@@ -19,15 +19,17 @@ var internals = {
// File response (Base -> Generic -> Stream -> File)
-exports = module.exports = internals.File = function (filePath) {
+exports = module.exports = internals.File = function (filePath, options) {
Utils.assert(this.constructor === internals.File, 'File must be instantiated using new');
+ Utils.assert(!options || !options.mode || ['attachment', 'inline'].indexOf(options.mode) !== -1, 'options.mode must be either false, attachment, or inline');
Stream.call(this, null);
this.variety = 'file';
this.varieties.file = true;
this._filePath = Path.normalize(filePath);
+ this._mode = options ? options.mode : false;
return this;
};
@@ -83,7 +85,9 @@ internals.File.prototype._prepare = function (request, callback) {
});
}
- self._headers['Content-Disposition'] = 'inline; filename=' + encodeURIComponent(fileName);
+ if (self._mode) {
+ self._headers['Content-Disposition'] = self._mode + '; filename=' + encodeURIComponent(fileName);
+ }
return Stream.prototype._prepare.call(self, request, callback);
});
View
109 test/integration/response.js
@@ -471,13 +471,13 @@ describe('Response', function () {
expect(body).to.contain('hapi');
expect(res.headers['content-type']).to.equal('application/json');
expect(res.headers['content-length']).to.exist;
- expect(res.headers['content-disposition']).to.equal('inline; filename=package.json');
+ expect(res.headers['content-disposition']).to.not.exist;
done();
});
});
});
- it('returns a file in the response with the correct headers using cwd relative paths', function (done) {
+ it('returns a file in the response with the correct headers using cwd relative paths without content-disposition header', function (done) {
var server = new Hapi.Server(0, { files: { relativeTo: 'cwd' } });
server.route({ method: 'GET', path: '/', handler: { file: './package.json' } });
@@ -490,6 +490,111 @@ describe('Response', function () {
expect(body).to.contain('hapi');
expect(res.headers['content-type']).to.equal('application/json');
expect(res.headers['content-length']).to.exist;
+ expect(res.headers['content-disposition']).to.not.exist;
+ done();
+ });
+ });
+ });
+
+ it('returns a file in the response with the inline content-disposition header when using route config', function (done) {
+
+ var server = new Hapi.Server(0, { files: { relativeTo: 'cwd' } });
+ server.route({ method: 'GET', path: '/', handler: { file: { path: './package.json', mode: 'inline' } }});
+
+ server.start(function () {
+
+ Request.get(server.settings.uri, function (err, res, body) {
+
+ expect(err).to.not.exist;
+ expect(body).to.contain('hapi');
+ expect(res.headers['content-type']).to.equal('application/json');
+ expect(res.headers['content-length']).to.exist;
+ expect(res.headers['content-disposition']).to.equal('inline; filename=package.json');
+ done();
+ });
+ });
+ });
+
+ it('returns a file in the response with the attachment content-disposition header when using route config', function (done) {
+
+ var server = new Hapi.Server(0, { files: { relativeTo: 'cwd' } });
+ server.route({ method: 'GET', path: '/', handler: { file: { path: './package.json', mode: 'attachment' } }});
+
+ server.start(function () {
+
+ Request.get(server.settings.uri, function (err, res, body) {
+
+ expect(err).to.not.exist;
+ expect(body).to.contain('hapi');
+ expect(res.headers['content-type']).to.equal('application/json');
+ expect(res.headers['content-length']).to.exist;
+ expect(res.headers['content-disposition']).to.equal('attachment; filename=package.json');
+ done();
+ });
+ });
+ });
+
+ it('returns a file in the response without the content-disposition header when using route config mode false', function (done) {
+
+ var server = new Hapi.Server(0, { files: { relativeTo: 'cwd' } });
+ server.route({ method: 'GET', path: '/', handler: { file: { path: './package.json', mode: false } }});
+
+ server.start(function () {
+
+ Request.get(server.settings.uri, function (err, res, body) {
+
+ expect(err).to.not.exist;
+ expect(body).to.contain('hapi');
+ expect(res.headers['content-type']).to.equal('application/json');
+ expect(res.headers['content-length']).to.exist;
+ expect(res.headers['content-disposition']).to.not.exist;
+ done();
+ });
+ });
+ });
+
+ it('returns a file with correct headers when using attachment mode', function (done) {
+
+ var server = new Hapi.Server(0, { files: { relativeTo: 'routes' } });
+ var handler = function (request) {
+
+ request.reply(new Hapi.Response.File(__dirname + '/../../package.json', { mode: 'attachment' }));
+ };
+
+ server.route({ method: 'GET', path: '/file', handler: handler });
+
+ server.start(function () {
+
+ Request.get(server.settings.uri + '/file', function (err, res, body) {
+
+ expect(err).to.not.exist;
+ expect(body).to.contain('hapi');
+ expect(res.headers['content-type']).to.equal('application/json');
+ expect(res.headers['content-length']).to.exist;
+ expect(res.headers['content-disposition']).to.equal('attachment; filename=package.json');
+ done();
+ });
+ });
+ });
+
+ it('returns a file with correct headers when using inline mode', function (done) {
+
+ var server = new Hapi.Server(0, { files: { relativeTo: 'routes' } });
+ var handler = function (request) {
+
+ request.reply(new Hapi.Response.File(__dirname + '/../../package.json', { mode: 'inline' }));
+ };
+
+ server.route({ method: 'GET', path: '/file', handler: handler });
+
+ server.start(function () {
+
+ Request.get(server.settings.uri + '/file', function (err, res, body) {
+
+ expect(err).to.not.exist;
+ expect(body).to.contain('hapi');
+ expect(res.headers['content-type']).to.equal('application/json');
+ expect(res.headers['content-length']).to.exist;
expect(res.headers['content-disposition']).to.equal('inline; filename=package.json');
done();
});
Something went wrong with that request. Please try again.