From 9146024e1094e8bb871ab15d1b7fc556a710732f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E9=9B=B7?= <863837949@qq.com> Date: Mon, 8 Oct 2018 22:43:11 +0800 Subject: [PATCH] feat: response.attachment append a parameter: options from contentDisposition (#1240) --- docs/api/response.md | 4 +- lib/response.js | 4 +- test/response/attachment.js | 135 ++++++++++++++++++++++++++++++++++++ 3 files changed, 139 insertions(+), 4 deletions(-) diff --git a/docs/api/response.md b/docs/api/response.md index c535e3b..602bc14 100644 --- a/docs/api/response.md +++ b/docs/api/response.md @@ -271,11 +271,11 @@ ctx.redirect('/cart'); ctx.body = 'Redirecting to shopping cart'; ``` -### response.attachment([filename]) +### response.attachment([filename], [options]) Set `Content-Disposition` to "attachment" to signal the client to prompt for download. Optionally specify the `filename` of the - download. + download and some [options](https://github.com/jshttp/content-disposition#options). ### response.headerSent diff --git a/lib/response.js b/lib/response.js index 8092c7a..371875f 100644 --- a/lib/response.js +++ b/lib/response.js @@ -285,9 +285,9 @@ module.exports = { * @api public */ - attachment(filename) { + attachment(filename, options) { if (filename) this.type = extname(filename); - this.set('Content-Disposition', contentDisposition(filename)); + this.set('Content-Disposition', contentDisposition(filename, options)); }, /** diff --git a/test/response/attachment.js b/test/response/attachment.js index 5e0f732..7ee16bd 100644 --- a/test/response/attachment.js +++ b/test/response/attachment.js @@ -48,3 +48,138 @@ describe('ctx.attachment([filename])', () => { }); }); }); + +// reference test case of content-disposition module +describe('contentDisposition(filename, options)', () => { + describe('with "fallback" option', () => { + it('should require a string or Boolean', () => { + const ctx = context(); + assert.throws(() => { ctx.attachment('plans.pdf', { fallback: 42 }); }, + /fallback.*string/); + }); + + it('should default to true', () => { + const ctx = context(); + ctx.attachment('€ rates.pdf'); + assert.equal(ctx.response.header['content-disposition'], + 'attachment; filename="? rates.pdf"; filename*=UTF-8\'\'%E2%82%AC%20rates.pdf'); + }); + + describe('when "false"', () => { + it('should not generate ISO-8859-1 fallback', () => { + const ctx = context(); + ctx.attachment('£ and € rates.pdf', { fallback: false }); + assert.equal(ctx.response.header['content-disposition'], + 'attachment; filename*=UTF-8\'\'%C2%A3%20and%20%E2%82%AC%20rates.pdf'); + }); + + it('should keep ISO-8859-1 filename', () => { + const ctx = context(); + ctx.attachment('£ rates.pdf', { fallback: false }); + assert.equal(ctx.response.header['content-disposition'], + 'attachment; filename="£ rates.pdf"'); + }); + }); + + describe('when "true"', () => { + it('should generate ISO-8859-1 fallback', () => { + const ctx = context(); + ctx.attachment('£ and € rates.pdf', { fallback: true }); + assert.equal(ctx.response.header['content-disposition'], + 'attachment; filename="£ and ? rates.pdf"; filename*=UTF-8\'\'%C2%A3%20and%20%E2%82%AC%20rates.pdf'); + }); + + it('should pass through ISO-8859-1 filename', () => { + const ctx = context(); + ctx.attachment('£ rates.pdf', { fallback: true }); + assert.equal(ctx.response.header['content-disposition'], + 'attachment; filename="£ rates.pdf"'); + }); + }); + + describe('when a string', () => { + it('should require an ISO-8859-1 string', () => { + const ctx = context(); + assert.throws(() => { ctx.attachment('€ rates.pdf', { fallback: '€ rates.pdf' }); }, + /fallback.*iso-8859-1/i); + }); + + it('should use as ISO-8859-1 fallback', () => { + const ctx = context(); + ctx.attachment('£ and € rates.pdf', { fallback: '£ and EURO rates.pdf' }); + assert.equal(ctx.response.header['content-disposition'], + 'attachment; filename="£ and EURO rates.pdf"; filename*=UTF-8\'\'%C2%A3%20and%20%E2%82%AC%20rates.pdf'); + }); + + it('should use as fallback even when filename is ISO-8859-1', () => { + const ctx = context(); + ctx.attachment('"£ rates".pdf', { fallback: '£ rates.pdf' }); + assert.equal(ctx.response.header['content-disposition'], + 'attachment; filename="£ rates.pdf"; filename*=UTF-8\'\'%22%C2%A3%20rates%22.pdf'); + }); + + it('should do nothing if equal to filename', () => { + const ctx = context(); + ctx.attachment('plans.pdf', { fallback: 'plans.pdf' }); + assert.equal(ctx.response.header['content-disposition'], + 'attachment; filename="plans.pdf"'); + }); + + it('should use the basename of the string', () => { + const ctx = context(); + ctx.attachment('€ rates.pdf', { fallback: '/path/to/EURO rates.pdf' }); + assert.equal(ctx.response.header['content-disposition'], + 'attachment; filename="EURO rates.pdf"; filename*=UTF-8\'\'%E2%82%AC%20rates.pdf'); + }); + + it('should do nothing without filename option', () => { + const ctx = context(); + ctx.attachment(undefined, { fallback: 'plans.pdf' }); + assert.equal(ctx.response.header['content-disposition'], + 'attachment'); + }); + }); + }); + + describe('with "type" option', () => { + it('should default to attachment', () => { + const ctx = context(); + ctx.attachment(); + assert.equal(ctx.response.header['content-disposition'], + 'attachment'); + }); + + it('should require a string', () => { + const ctx = context(); + assert.throws(() => { ctx.attachment(undefined, { type: 42 }); }, + /invalid type/); + }); + + it('should require a valid type', () => { + const ctx = context(); + assert.throws(() => { ctx.attachment(undefined, { type: 'invlaid;type' }); }, + /invalid type/); + }); + + it('should create a header with inline type', () => { + const ctx = context(); + ctx.attachment(undefined, { type: 'inline' }); + assert.equal(ctx.response.header['content-disposition'], + 'inline'); + }); + + it('should create a header with inline type & filename', () => { + const ctx = context(); + ctx.attachment('plans.pdf', { type: 'inline' }); + assert.equal(ctx.response.header['content-disposition'], + 'inline; filename="plans.pdf"'); + }); + + it('should normalize type', () => { + const ctx = context(); + ctx.attachment(undefined, { type: 'INLINE' }); + assert.equal(ctx.response.header['content-disposition'], + 'inline'); + }); + }); +});