mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-20 09:09:35 +02:00
feat(uploads): prompt uploads take documents
The Attach button, the Compose and paste-dialog pickers, terminal drop and the clipboard now take documents beside images and videos: the office formats pdf/docx/pptx/xlsx/zip and the text family txt/md/csv/tsv/json/log/yaml/yml/xml. A document is admitted by its name's extension (the MIME a phone attaches to a .md is anyone's guess), buffered under the same 50MB image cap, and judged on its bytes by the same fail-closed sniffer: a magic-number signature for the binary formats, UTF-8 with no NUL byte for text. It is saved under a sanitized stem of its name, so Q3 report.pdf is findable as paste-<ts>-<hex>-Q3-report.pdf. sh/py/js/html are refused by name with a clear 400. That is a scope choice, not a security property: file-raw already serves non-media text inert, so this keeps the server from storing what it would serve or an agent could run, and html and svg are the ones deliberately kept out. One client classifier (_promptAttachKind) now returns image, video or document, and the server's dispatch mirrors it: an image MIME wins whatever the name says, a document extension takes the document branch, and a foreign extension with no image MIME is refused only after the image sniff fails, so nothing the image path accepted by its bytes is lost.
This commit is contained in:
1 parent
a8a8bb71a3
commit
244d9a49f1
18 files changed
+382
-96
No files matched your search
@@ -19,7 +19,7 @@ function loadToolbar(sessionId: string | null = 'session-1', localEcho = true, k
|
||||
const dom = new JSDOM(
|
||||
'<!DOCTYPE html><html><body><span class="btn-attach-mobile-wrap">' +
|
||||
'<button type="button" id="attachBtnMobile"></button>' +
|
||||
'<input type="file" id="attachFileInput" accept="image/*,video/*" multiple>' +
|
||||
'<input type="file" id="attachFileInput" accept="image/*,video/*,.pdf,.docx,.pptx,.xlsx,.zip,.txt,.md,.csv,.tsv,.json,.log,.yaml,.yml,.xml" multiple>' +
|
||||
'</span></body></html>'
|
||||
);
|
||||
const { window } = dom;
|
||||
@@ -333,13 +333,13 @@ describe('phone toolbar Attach button', () => {
|
||||
expect(composerBar._insertComposerText).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('rejects a selection with neither image nor video with a toast and no upload', async () => {
|
||||
it('rejects a selection the server would refuse with a toast and no upload', async () => {
|
||||
const { app, window, select } = loadToolbar();
|
||||
select([new window.File(['%PDF-'], 'doc.pdf', { type: 'application/pdf' })]);
|
||||
select([new window.File(['#!/bin/sh'], 'run.sh', { type: 'text/x-shellscript' })]);
|
||||
await Promise.resolve();
|
||||
expect(app._uploadAndInsertImages).not.toHaveBeenCalled();
|
||||
expect(app._sendInputAsync).not.toHaveBeenCalled();
|
||||
expect(app.showToast).toHaveBeenCalledWith('Only image or video files are supported', 'error');
|
||||
expect(app.showToast).toHaveBeenCalledWith('Unsupported file type', 'error');
|
||||
});
|
||||
|
||||
it('classifies a pick by its name when the file manager supplied no MIME type', () => {
|
||||
@@ -348,8 +348,23 @@ describe('phone toolbar Attach button', () => {
|
||||
expect(kind('clip.ogv', '')).toBe('video');
|
||||
expect(kind('IMG_0001.MOV', '')).toBe('video');
|
||||
expect(kind('shot.heic', '')).toBe('image');
|
||||
expect(kind('notes.pdf', '')).toBeNull();
|
||||
expect(kind('clip.ogv', 'image/jpeg')).toBe('image'); // the MIME type wins when present
|
||||
// Documents are known by the name alone: the MIME a file manager gives them varies.
|
||||
expect(kind('notes.pdf', 'application/pdf')).toBe('document');
|
||||
expect(kind('Q3 report.XLSX', '')).toBe('document');
|
||||
expect(kind('todo.md', 'text/plain')).toBe('document');
|
||||
expect(kind('data.csv', 'application/octet-stream')).toBe('document');
|
||||
expect(kind('run.sh', 'text/x-shellscript')).toBeNull();
|
||||
expect(kind('page.html', 'text/html')).toBeNull();
|
||||
expect(kind('noext', '')).toBeNull();
|
||||
});
|
||||
|
||||
it('uploads a document as picked, through the image path (no re-encode, no video flag)', async () => {
|
||||
const { app, window, fetchMock } = loadToolbar();
|
||||
const pdf = new window.File(['%PDF-'], 'notes.pdf', { type: 'application/pdf' });
|
||||
expect(await app._normalizeImageForUpload(pdf)).toBe(pdf);
|
||||
await app._uploadPasteImage('session-1', pdf);
|
||||
expect(fetchMock.mock.calls[0][0]).toBe('/api/sessions/session-1/paste-image');
|
||||
});
|
||||
|
||||
it('flags the upload as a video by name as well as by MIME type', async () => {
|
||||
|
||||
Reference in new issue
Block a user