Skip to content

Commit 9146024

Browse files
zzzsdead-horse
authored andcommitted
feat: response.attachment append a parameter: options from contentDisposition (#1240)
1 parent d32623b commit 9146024

File tree

3 files changed

+139
-4
lines changed

3 files changed

+139
-4
lines changed

docs/api/response.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -271,11 +271,11 @@ ctx.redirect('/cart');
271271
ctx.body = 'Redirecting to shopping cart';
272272
```
273273

274-
### response.attachment([filename])
274+
### response.attachment([filename], [options])
275275

276276
Set `Content-Disposition` to "attachment" to signal the client
277277
to prompt for download. Optionally specify the `filename` of the
278-
download.
278+
download and some [options](https://github.com/jshttp/content-disposition#options).
279279

280280
### response.headerSent
281281

lib/response.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -285,9 +285,9 @@ module.exports = {
285285
* @api public
286286
*/
287287

288-
attachment(filename) {
288+
attachment(filename, options) {
289289
if (filename) this.type = extname(filename);
290-
this.set('Content-Disposition', contentDisposition(filename));
290+
this.set('Content-Disposition', contentDisposition(filename, options));
291291
},
292292

293293
/**

test/response/attachment.js

+135
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,138 @@ describe('ctx.attachment([filename])', () => {
4848
});
4949
});
5050
});
51+
52+
// reference test case of content-disposition module
53+
describe('contentDisposition(filename, options)', () => {
54+
describe('with "fallback" option', () => {
55+
it('should require a string or Boolean', () => {
56+
const ctx = context();
57+
assert.throws(() => { ctx.attachment('plans.pdf', { fallback: 42 }); },
58+
/fallback.*string/);
59+
});
60+
61+
it('should default to true', () => {
62+
const ctx = context();
63+
ctx.attachment('€ rates.pdf');
64+
assert.equal(ctx.response.header['content-disposition'],
65+
'attachment; filename="? rates.pdf"; filename*=UTF-8\'\'%E2%82%AC%20rates.pdf');
66+
});
67+
68+
describe('when "false"', () => {
69+
it('should not generate ISO-8859-1 fallback', () => {
70+
const ctx = context();
71+
ctx.attachment('£ and € rates.pdf', { fallback: false });
72+
assert.equal(ctx.response.header['content-disposition'],
73+
'attachment; filename*=UTF-8\'\'%C2%A3%20and%20%E2%82%AC%20rates.pdf');
74+
});
75+
76+
it('should keep ISO-8859-1 filename', () => {
77+
const ctx = context();
78+
ctx.attachment('£ rates.pdf', { fallback: false });
79+
assert.equal(ctx.response.header['content-disposition'],
80+
'attachment; filename="£ rates.pdf"');
81+
});
82+
});
83+
84+
describe('when "true"', () => {
85+
it('should generate ISO-8859-1 fallback', () => {
86+
const ctx = context();
87+
ctx.attachment('£ and € rates.pdf', { fallback: true });
88+
assert.equal(ctx.response.header['content-disposition'],
89+
'attachment; filename="£ and ? rates.pdf"; filename*=UTF-8\'\'%C2%A3%20and%20%E2%82%AC%20rates.pdf');
90+
});
91+
92+
it('should pass through ISO-8859-1 filename', () => {
93+
const ctx = context();
94+
ctx.attachment('£ rates.pdf', { fallback: true });
95+
assert.equal(ctx.response.header['content-disposition'],
96+
'attachment; filename="£ rates.pdf"');
97+
});
98+
});
99+
100+
describe('when a string', () => {
101+
it('should require an ISO-8859-1 string', () => {
102+
const ctx = context();
103+
assert.throws(() => { ctx.attachment('€ rates.pdf', { fallback: '€ rates.pdf' }); },
104+
/fallback.*iso-8859-1/i);
105+
});
106+
107+
it('should use as ISO-8859-1 fallback', () => {
108+
const ctx = context();
109+
ctx.attachment('£ and € rates.pdf', { fallback: '£ and EURO rates.pdf' });
110+
assert.equal(ctx.response.header['content-disposition'],
111+
'attachment; filename="£ and EURO rates.pdf"; filename*=UTF-8\'\'%C2%A3%20and%20%E2%82%AC%20rates.pdf');
112+
});
113+
114+
it('should use as fallback even when filename is ISO-8859-1', () => {
115+
const ctx = context();
116+
ctx.attachment('"£ rates".pdf', { fallback: '£ rates.pdf' });
117+
assert.equal(ctx.response.header['content-disposition'],
118+
'attachment; filename="£ rates.pdf"; filename*=UTF-8\'\'%22%C2%A3%20rates%22.pdf');
119+
});
120+
121+
it('should do nothing if equal to filename', () => {
122+
const ctx = context();
123+
ctx.attachment('plans.pdf', { fallback: 'plans.pdf' });
124+
assert.equal(ctx.response.header['content-disposition'],
125+
'attachment; filename="plans.pdf"');
126+
});
127+
128+
it('should use the basename of the string', () => {
129+
const ctx = context();
130+
ctx.attachment('€ rates.pdf', { fallback: '/path/to/EURO rates.pdf' });
131+
assert.equal(ctx.response.header['content-disposition'],
132+
'attachment; filename="EURO rates.pdf"; filename*=UTF-8\'\'%E2%82%AC%20rates.pdf');
133+
});
134+
135+
it('should do nothing without filename option', () => {
136+
const ctx = context();
137+
ctx.attachment(undefined, { fallback: 'plans.pdf' });
138+
assert.equal(ctx.response.header['content-disposition'],
139+
'attachment');
140+
});
141+
});
142+
});
143+
144+
describe('with "type" option', () => {
145+
it('should default to attachment', () => {
146+
const ctx = context();
147+
ctx.attachment();
148+
assert.equal(ctx.response.header['content-disposition'],
149+
'attachment');
150+
});
151+
152+
it('should require a string', () => {
153+
const ctx = context();
154+
assert.throws(() => { ctx.attachment(undefined, { type: 42 }); },
155+
/invalid type/);
156+
});
157+
158+
it('should require a valid type', () => {
159+
const ctx = context();
160+
assert.throws(() => { ctx.attachment(undefined, { type: 'invlaid;type' }); },
161+
/invalid type/);
162+
});
163+
164+
it('should create a header with inline type', () => {
165+
const ctx = context();
166+
ctx.attachment(undefined, { type: 'inline' });
167+
assert.equal(ctx.response.header['content-disposition'],
168+
'inline');
169+
});
170+
171+
it('should create a header with inline type & filename', () => {
172+
const ctx = context();
173+
ctx.attachment('plans.pdf', { type: 'inline' });
174+
assert.equal(ctx.response.header['content-disposition'],
175+
'inline; filename="plans.pdf"');
176+
});
177+
178+
it('should normalize type', () => {
179+
const ctx = context();
180+
ctx.attachment(undefined, { type: 'INLINE' });
181+
assert.equal(ctx.response.header['content-disposition'],
182+
'inline');
183+
});
184+
});
185+
});

0 commit comments

Comments
 (0)