52 lines
2.0 KiB
TypeScript
52 lines
2.0 KiB
TypeScript
import { describe, test, expect } from 'bun:test';
|
|
import { attachmentContentDisposition } from '../src/lib/server/content-disposition';
|
|
|
|
/**
|
|
* The download filename is derived from a user-influenced path, so it must never
|
|
* land raw in the Content-Disposition header (quote breakout / CRLF injection).
|
|
*/
|
|
describe('attachmentContentDisposition', () => {
|
|
test('always disposition=attachment with both filename and filename*', () => {
|
|
const v = attachmentContentDisposition('config.yaml');
|
|
expect(v.startsWith('attachment;')).toBe(true);
|
|
expect(v).toContain('filename="config.yaml"');
|
|
expect(v).toContain("filename*=UTF-8''config.yaml");
|
|
});
|
|
|
|
test('strips double-quote (no breakout of the quoted form)', () => {
|
|
const v = attachmentContentDisposition('evil".pdf');
|
|
// the fallback must not contain a bare quote that closes filename="
|
|
expect(v).toContain('filename="evil.pdf"');
|
|
// the encoded copy percent-encodes it instead
|
|
expect(v).toContain('%22');
|
|
});
|
|
|
|
test('strips CR/LF so no header injection is possible', () => {
|
|
const v = attachmentContentDisposition('a\r\nSet-Cookie: x=1');
|
|
expect(v).not.toContain('\r');
|
|
expect(v).not.toContain('\n');
|
|
// CRLF is percent-encoded in the filename* copy
|
|
expect(v).toContain('%0D%0A');
|
|
});
|
|
|
|
test('strips backslash', () => {
|
|
const v = attachmentContentDisposition('back\\slash.log');
|
|
expect(v).toContain('filename="backslash.log"');
|
|
});
|
|
|
|
test('handles non-ASCII via filename*', () => {
|
|
const v = attachmentContentDisposition('résumé.txt');
|
|
expect(v).toContain("filename*=UTF-8''r%C3%A9sum%C3%A9.txt");
|
|
});
|
|
|
|
test('empty / whitespace falls back to "download"', () => {
|
|
expect(attachmentContentDisposition('')).toContain('filename="download"');
|
|
expect(attachmentContentDisposition(' ')).toContain('filename="download"');
|
|
});
|
|
|
|
test('control characters are stripped from the fallback', () => {
|
|
const v = attachmentContentDisposition('a\x00\x07\x1fb.txt');
|
|
expect(v).toContain('filename="ab.txt"');
|
|
});
|
|
});
|