Add file module for uploads and storage
- File model + migration: file metadata, content lives in object storage - IFileStorage port with local disk and S3-compatible (RustFS/MinIO) adapters - POST /files, GET /files/:id/download, DELETE /files/:id - MIME sniffed from content; only safe images served inline, nosniff header - FileOwnerRegistry: owner modules register to grant visibility and keep their files from GC; FileGcService removes orphaned files hourly - TransformResponseInterceptor passes StreamableFile through Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5cfc8986d7
commit
f32c6e348d
49 files changed
+1992
-2
No files matched your search
@@ -0,0 +1,22 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { contentDisposition } from './content-disposition.js';
|
||||
|
||||
describe('contentDisposition', () => {
|
||||
it('encodes a plain ASCII name', () => {
|
||||
expect(contentDisposition('attachment', 'report.pdf')).toBe(
|
||||
`attachment; filename="report.pdf"; filename*=UTF-8''report.pdf`,
|
||||
);
|
||||
});
|
||||
|
||||
it('encodes cyrillic with an ASCII fallback', () => {
|
||||
expect(contentDisposition('inline', 'Отчёт.png')).toBe(
|
||||
`inline; filename="_____.png"; filename*=UTF-8''%D0%9E%D1%82%D1%87%D1%91%D1%82.png`,
|
||||
);
|
||||
});
|
||||
|
||||
it('escapes quotes and RFC 5987 special characters', () => {
|
||||
expect(contentDisposition('attachment', `a"b'(c).txt`)).toBe(
|
||||
`attachment; filename="a_b'(c).txt"; filename*=UTF-8''a%22b%27%28c%29.txt`,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Заголовок Content-Disposition с именем файла по RFC 6266/5987:
|
||||
* `filename` — ASCII-фолбэк для старых клиентов, `filename*` — полное UTF-8 имя.
|
||||
*/
|
||||
export function contentDisposition(type: 'inline' | 'attachment', fileName: string): string {
|
||||
const fallback = fileName.replace(/[^\x20-\x7e]|["\\%]/g, '_');
|
||||
const encoded = encodeURIComponent(fileName).replace(
|
||||
/['()*]/g,
|
||||
(char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`,
|
||||
);
|
||||
|
||||
return `${type}; filename="${fallback}"; filename*=UTF-8''${encoded}`;
|
||||
}
|
||||
Reference in new issue
Block a user