Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion assistant/src/__tests__/attachment-content-route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ describe('handleGetAttachmentContent — file-backed', () => {
expect(Buffer.from(body).toString()).toBe('fghij');
});

test('returns 416 for unsatisfiable range', () => {
test('returns 416 when start is beyond file size', () => {
const filePath = join(testDir, 'test-416.mp4');
const content = Buffer.from('short');
writeFileSync(filePath, content);
Expand All @@ -156,6 +156,32 @@ describe('handleGetAttachmentContent — file-backed', () => {
expect(res.status).toBe(416);
});

test('clamps oversized end to fileSize-1 per RFC 7233', async () => {
const filePath = join(testDir, 'test-clamp.mp4');
const content = Buffer.from('0123456789');
writeFileSync(filePath, content);

const attachment = createFileBackedAttachment({
filename: 'test-clamp.mp4',
mimeType: 'video/mp4',
sizeBytes: content.length,
filePath,
});

// Request bytes=5-999 on a 10-byte file; end should be clamped to 9
const req = new Request('http://localhost/v1/attachments/' + attachment.id + '/content', {
headers: { Range: 'bytes=5-999' },
});
const res = handleGetAttachmentContent(attachment.id, req);

expect(res.status).toBe(206);
expect(res.headers.get('Content-Range')).toBe(`bytes 5-9/${content.length}`);
expect(res.headers.get('Content-Length')).toBe('5');

const body = await res.arrayBuffer();
expect(Buffer.from(body).toString()).toBe('56789');
});

test('returns 404 when file is missing from disk', () => {
const attachment = createFileBackedAttachment({
filename: 'missing.mp4',
Expand Down
12 changes: 9 additions & 3 deletions assistant/src/memory/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1672,12 +1672,18 @@ function migrateRemoveAssistantIdColumns(database: ReturnType<typeof drizzle<typ
data_base64 TEXT NOT NULL,
content_hash TEXT,
thumbnail_base64 TEXT,
created_at INTEGER NOT NULL
created_at INTEGER NOT NULL,
storage_kind TEXT DEFAULT 'inline_base64',
Comment thread
Jasonnnz marked this conversation as resolved.
file_path TEXT,
sha256 TEXT,
expires_at INTEGER
)
`);
raw.exec(/*sql*/ `
INSERT INTO attachments_new (id, original_filename, mime_type, size_bytes, kind, data_base64, content_hash, thumbnail_base64, created_at)
SELECT id, original_filename, mime_type, size_bytes, kind, data_base64, content_hash, thumbnail_base64, created_at FROM attachments
INSERT INTO attachments_new (id, original_filename, mime_type, size_bytes, kind, data_base64, content_hash, thumbnail_base64, created_at, storage_kind, file_path, sha256, expires_at)
SELECT id, original_filename, mime_type, size_bytes, kind, data_base64, content_hash, thumbnail_base64, created_at,
COALESCE(storage_kind, 'inline_base64'), file_path, sha256, expires_at
FROM attachments
`);
raw.exec(/*sql*/ `DROP TABLE attachments`);
raw.exec(/*sql*/ `ALTER TABLE attachments_new RENAME TO attachments`);
Expand Down
4 changes: 2 additions & 2 deletions assistant/src/runtime/routes/attachment-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,9 +206,9 @@ function handleFileContent(
}

const start = parseInt(rangeMatch[1], 10);
const end = rangeMatch[2] ? parseInt(rangeMatch[2], 10) : fileSize - 1;
const end = rangeMatch[2] ? Math.min(parseInt(rangeMatch[2], 10), fileSize - 1) : fileSize - 1;

if (start >= fileSize || end >= fileSize || start > end) {
if (start >= fileSize || start > end) {
return new Response('Range not satisfiable', {
status: 416,
headers: { 'Content-Range': `bytes */${fileSize}` },
Expand Down
Loading