| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224 |
- import { appendFile, mkdir, mkdtemp, open, realpath, rm, symlink, writeFile } from 'node:fs/promises'
- import { tmpdir } from 'node:os'
- import { join } from 'node:path'
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
- import { Context } from '@deepseek-ai/cordis'
- import { FsError, FsTargetKey, FsVersion } from '@deepseek-ai/dsh-fs'
- import { LocalFileSystem } from '@deepseek-ai/dsh-fs-local'
- import { SessionMediaReferences } from '../src/media-references.ts'
- const PNG_BYTES = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 1, 2, 3, 4])
- const DEFAULT_LIMIT = 20 * 1024 * 1024
- async function responseBytes(response: Response): Promise<Uint8Array> {
- return new Uint8Array(await response.arrayBuffer())
- }
- describe('SessionMediaReferences /api/file', () => {
- let root: string
- const contexts: Context[] = []
- beforeEach(async () => {
- root = await realpath(await mkdtemp(join(tmpdir(), 'dsh-media-references-')))
- })
- afterEach(async () => {
- await Promise.all(contexts.splice(0).map(ctx => ctx.fiber.dispose()))
- await rm(root, { recursive: true, force: true })
- })
- async function mount(maxBytes = DEFAULT_LIMIT) {
- const ctx = new Context()
- contexts.push(ctx)
- let handler: ((request: Request) => Promise<Response>) | undefined
- const unregister = vi.fn(() => {})
- ctx.provide('connection', {
- fetch: {
- register: (registered: { fetch: (request: Request) => Promise<Response> }) => {
- handler = registered.fetch
- return unregister
- },
- },
- } as never)
- ctx.provide('attachments', { imageLimits: { maxImageBytes: maxBytes } } as never)
- await ctx.plugin(LocalFileSystem, { cwd: root }).await()
- await ctx.plugin(SessionMediaReferences).await()
- const raw = (url: string, init?: RequestInit) => {
- if (handler === undefined) throw new Error('route not registered')
- return handler(new Request(url, init))
- }
- return {
- call: (path: string, init?: RequestInit) => raw(`http://127.0.0.1/api/file?path=${encodeURIComponent(path)}`, init),
- raw,
- fs: ctx.fs as LocalFileSystem,
- unregister,
- dispose: () => ctx.fiber.dispose(),
- }
- }
- it('serves the inclusive image cap and refuses larger images for GET, HEAD and Range', async () => {
- const route = await mount(PNG_BYTES.length)
- const path = join(root, 'bounded.png')
- await writeFile(path, PNG_BYTES)
- expect(await responseBytes(await route.call(path))).toEqual(PNG_BYTES)
- await appendFile(path, new Uint8Array(1))
- expect((await route.call(path)).status).toBe(413)
- expect((await route.call(path, { headers: { range: 'bytes=0-0' } })).status).toBe(413)
- const head = await route.call(path, { method: 'HEAD' })
- expect(head.status).toBe(413)
- expect(head.body).toBeNull()
- })
- it('rejects a sparse 1 GiB image before content I/O', async () => {
- const route = await mount()
- const inspect = vi.fn()
- route.fs.internals.inspectReadBytesAfterStat = inspect
- const path = join(root, 'huge.png')
- const handle = await open(path, 'w')
- try {
- await handle.truncate(1024 * 1024 * 1024)
- } finally {
- await handle.close()
- }
- expect((await route.call(path)).status).toBe(413)
- expect(inspect).not.toHaveBeenCalled()
- })
- it('uses the filesystem byte reader to reject post-stat image growth', async () => {
- const route = await mount(PNG_BYTES.length)
- const path = join(root, 'growing.png')
- await writeFile(path, PNG_BYTES)
- route.fs.internals.inspectReadBytesAfterStat = async () => {
- await appendFile(path, new Uint8Array(1))
- }
- expect((await route.call(path)).status).toBe(413)
- })
- it.each([
- ['png', 'image/png'], ['svg', 'image/svg+xml'], ['mp4', 'video/mp4'], ['mp3', 'audio/mpeg'],
- ['txt', 'text/plain'], ['html', 'text/html'], ['bin', 'application/octet-stream'], ['', 'application/octet-stream'],
- ])('serves .%s files with their MIME type and response protections', async (extension, mediaType) => {
- const route = await mount()
- const path = join(root, `file${extension === '' ? '' : `.${extension}`}`)
- await writeFile(path, PNG_BYTES)
- const response = await route.call(path)
- expect(response.status).toBe(200)
- expect(response.headers.get('content-type')).toBe(mediaType)
- expect(response.headers.get('content-length')).toBe(String(PNG_BYTES.length))
- expect(response.headers.get('cache-control')).toBe('private, no-store')
- expect(response.headers.get('x-content-type-options')).toBe('nosniff')
- expect(response.headers.get('content-security-policy')).toBe("sandbox; default-src 'none'")
- expect(await responseBytes(response)).toEqual(PNG_BYTES)
- })
- it.each(['mp4', 'mp3', 'bin'])('applies the attachment byte cap to .%s files', async (extension) => {
- const route = await mount(PNG_BYTES.length)
- const path = join(root, `file.${extension}`)
- await writeFile(path, PNG_BYTES)
- expect(await responseBytes(await route.call(path))).toEqual(PNG_BYTES)
- await appendFile(path, new Uint8Array(1))
- expect((await route.call(path)).status).toBe(413)
- expect((await route.call(path, { method: 'HEAD' })).status).toBe(413)
- })
- it('ignores Range headers and returns complete bodies without advertising ranges', async () => {
- const route = await mount()
- const path = join(root, 'clip.mp4')
- await writeFile(path, PNG_BYTES)
- for (const range of ['bytes=0-3', 'bytes=-4', 'bytes=999-', 'bytes=abc', 'items=0-0', 'bytes=0-1,3-4']) {
- const response = await route.call(path, { headers: { range } })
- expect(response.status).toBe(200)
- expect(response.headers.get('accept-ranges')).toBeNull()
- expect(response.headers.get('content-range')).toBeNull()
- expect(await responseBytes(response)).toEqual(PNG_BYTES)
- }
- })
- it('answers HEAD without reading content and reports missing and non-regular files', async () => {
- const route = await mount()
- const path = join(root, 'image.png')
- await writeFile(path, PNG_BYTES)
- const read = vi.spyOn(route.fs, 'readBytes')
- const response = await route.call(path, { method: 'HEAD', headers: { range: 'bytes=0-3' } })
- expect(response.status).toBe(200)
- expect(response.headers.get('content-length')).toBe(String(PNG_BYTES.length))
- expect(response.body).toBeNull()
- expect(read).not.toHaveBeenCalled()
- expect((await route.call(join(root, 'missing'), { method: 'HEAD' })).status).toBe(404)
- expect((await route.call(root, { method: 'HEAD' })).status).toBe(403)
- vi.spyOn(route.fs, 'stat').mockResolvedValue({ type: 'file', version: FsVersion('v1') })
- expect((await route.call(path, { method: 'HEAD' })).headers.get('content-length')).toBeNull()
- })
- it('rejects malformed paths, absent files, and directories', async () => {
- const route = await mount()
- expect((await route.raw('http://127.0.0.1/api/file')).status).toBe(400)
- for (const path of ['', 'relative.png', '/a\0b.png']) {
- expect((await route.call(path)).status).toBe(400)
- }
- const head = await route.call('', { method: 'HEAD' })
- expect(head.status).toBe(400)
- expect(head.body).toBeNull()
- expect((await route.call(join(root, 'missing.png'))).status).toBe(404)
- await mkdir(join(root, 'frames.png'))
- expect((await route.call(join(root, 'frames.png'))).status).toBe(403)
- })
- it('reads files and symlink targets outside the default cwd without a workspace registry', async () => {
- const route = await mount()
- const outside = await mkdtemp(join(tmpdir(), 'dsh-media-outside-'))
- try {
- const path = join(outside, 'image.png')
- await writeFile(path, PNG_BYTES)
- expect(await responseBytes(await route.call(path))).toEqual(PNG_BYTES)
- const link = join(root, 'linked.png')
- await symlink(path, link)
- expect(await responseBytes(await route.call(link))).toEqual(PNG_BYTES)
- } finally {
- await rm(outside, { recursive: true, force: true })
- }
- })
- it.skipIf(process.platform === 'win32')('rejects a FIFO before opening it', async () => {
- const route = await mount()
- const path = join(root, 'stream.png')
- const { execFile } = await import('node:child_process')
- const { promisify } = await import('node:util')
- await promisify(execFile)('mkfifo', [path])
- expect((await route.call(path)).status).toBe(403)
- })
- it('reads opaque remote targets through ctx.fs and preserves provider failures', async () => {
- const route = await mount()
- const target = { targetKey: FsTargetKey('opaque-remote-id'), displayPath: '/remote/photo.png' }
- vi.spyOn(route.fs, 'resolve').mockResolvedValue(target)
- const read = vi.spyOn(route.fs, 'readBytes').mockResolvedValue(PNG_BYTES)
- expect(await responseBytes(await route.call('/remote/photo.png'))).toEqual(PNG_BYTES)
- expect(read).toHaveBeenCalledWith(target, expect.any(AbortSignal), DEFAULT_LIMIT)
- for (const [code, status] of [
- ['FS_PERMISSION_DENIED', 403], ['FS_SANDBOX_DENIED', 403], ['FS_NOT_FOUND', 404],
- ['FS_NOT_REGULAR_FILE', 403], ['FS_TOO_LARGE', 413], ['FS_IO_ERROR', 500],
- ] as const) {
- read.mockRejectedValueOnce(new FsError('provider rejected read', code))
- expect((await route.call('/remote/photo.png')).status).toBe(status)
- }
- read.mockRejectedValueOnce(new Error('provider bug'))
- await expect(route.call('/remote/photo.png')).rejects.toThrow('provider bug')
- })
- it('serves an empty file and respects an aborted request', async () => {
- const route = await mount()
- const path = join(root, 'empty.png')
- await writeFile(path, '')
- const response = await route.call(path)
- expect(response.headers.get('content-length')).toBe('0')
- expect(await response.text()).toBe('')
- expect((await route.call(path, { signal: AbortSignal.abort() })).status).toBe(499)
- })
- it('unregisters the route on disposal', async () => {
- const route = await mount()
- await route.dispose()
- expect(route.unregister).toHaveBeenCalledTimes(1)
- })
- })
|