Skip to content

Commit 9cf6f55

Browse files
committed
fix(desktop): keep accepting downloads from shells that send no declared length
The truncation check applies whenever a download declares its length. A shell already in use that streams one without a length is still accepted under the byte ceiling. The E2E fixture's import route now refuses a file without a declared length, as Sim's does, so the device is proven to send one.
1 parent 746413a commit 9cf6f55

3 files changed

Lines changed: 16 additions & 19 deletions

File tree

‎apps/desktop/e2e/background-executor.spec.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,16 @@ class FixtureSim {
203203
this.json(response, 404, { error: 'Desktop import not found' })
204204
return
205205
}
206+
// As Sim's route does: a file must declare its length, and arrive whole.
207+
if (query.kind === 'file' && request.headers['content-length'] === undefined) {
208+
this.json(response, 411, { error: 'A file import must declare its length' })
209+
return
210+
}
206211
const content = await this.raw(request)
212+
if (query.kind === 'file' && content.length !== Number(request.headers['content-length'])) {
213+
this.json(response, 400, { error: 'The file did not arrive whole' })
214+
return
215+
}
207216
const entry: ImportedEntry = {
208217
toolCallId: call.toolCallId,
209218
kind: query.kind ?? '',

‎apps/sim/app/api/desktop/tool/file/route.test.ts‎

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -98,15 +98,4 @@ describe('/api/desktop/tool/file', () => {
9898
expect(res.status).toBe(400)
9999
expect(mockSave).not.toHaveBeenCalled()
100100
})
101-
102-
it('refuses a download that does not declare its length', async () => {
103-
const request = new NextRequest(`${URL_BASE}?toolCallId=call-3&name=a.csv`, {
104-
method: 'PUT',
105-
body: new ReadableStream({ start: (controller) => controller.close() }),
106-
duplex: 'half',
107-
})
108-
109-
expect((await PUT(request)).status).toBe(411)
110-
expect(mockSave).not.toHaveBeenCalled()
111-
})
112101
})

‎apps/sim/app/api/desktop/tool/file/route.ts‎

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,13 @@ export const PUT = withRouteHandler(async (request: NextRequest) => {
7777
}
7878
throw error
7979
}
80+
// Shells already in use may send a download without a declared length; that stays accepted.
8081
const lengthHeader = request.headers.get('content-length')
81-
if (lengthHeader === null) {
82-
return NextResponse.json(withRequestId({ error: 'A download must declare its length' }), {
83-
status: 411,
84-
})
85-
}
86-
const declaredLength = Number(lengthHeader)
87-
if (!Number.isFinite(declaredLength) || declaredLength > BROWSER_FILE_TRANSFER_MAX_BYTES) {
82+
const declaredLength = lengthHeader === null ? null : Number(lengthHeader)
83+
if (
84+
declaredLength !== null &&
85+
(!Number.isFinite(declaredLength) || declaredLength > BROWSER_FILE_TRANSFER_MAX_BYTES)
86+
) {
8887
return NextResponse.json(withRequestId({ error: 'Download is too large to save' }), {
8988
status: 413,
9089
})
@@ -115,7 +114,7 @@ export const PUT = withRouteHandler(async (request: NextRequest) => {
115114
throw error
116115
}
117116
// Anything between the device and here that cut the body short must not become a saved file.
118-
if (content.length !== declaredLength) {
117+
if (declaredLength !== null && content.length !== declaredLength) {
119118
return NextResponse.json(
120119
withRequestId({ error: 'The download did not arrive whole; nothing was saved' }),
121120
{ status: 400 }

0 commit comments

Comments
 (0)