Skip to content

Commit de41ff0

Browse files
Merge remote-tracking branch 'origin/staging' into fix/api-route-error-projection
2 parents cd17e88 + 05de463 commit de41ff0

2 files changed

Lines changed: 39 additions & 2 deletions

File tree

apps/sim/app/api/tools/agiloft/retrieve/route.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,35 @@ describe('POST /api/tools/agiloft/retrieve', () => {
140140
expect(inputValidationMockFns.mockValidateUrlWithDNS).toHaveBeenCalledTimes(1)
141141
})
142142

143+
it('resolves the real type when Agiloft labels an attachment octet-stream', async () => {
144+
const fileBytes = Buffer.from('PKdocx-bytes', 'utf-8')
145+
146+
inputValidationMockFns.mockSecureFetchWithPinnedIP.mockResolvedValueOnce(
147+
mockSecureFetchResponse({
148+
arrayBuffer: fileBytes.buffer.slice(
149+
fileBytes.byteOffset,
150+
fileBytes.byteOffset + fileBytes.byteLength
151+
) as ArrayBuffer,
152+
headers: new Headers({
153+
'content-type': 'application/octet-stream',
154+
'content-disposition': 'attachment; filename="Master Agreement.docx"',
155+
}),
156+
})
157+
)
158+
159+
const response = await POST(createMockRequest('POST', baseBody))
160+
const data = (await response.json()) as { output: { file: { mimeType: string } } }
161+
162+
/**
163+
* Agiloft labels most attachments octet-stream whatever they are. The
164+
* filename disambiguates what the leading bytes cannot: a ZIP header is
165+
* equally a .docx, .xlsx or a plain archive.
166+
*/
167+
expect(data.output.file.mimeType).toBe(
168+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
169+
)
170+
})
171+
143172
it('propagates upstream errors', async () => {
144173
inputValidationMockFns.mockSecureFetchWithPinnedIP.mockResolvedValueOnce(
145174
mockSecureFetchResponse({ ok: false, status: 404, text: 'Record not found' })

apps/sim/app/api/tools/agiloft/retrieve/route.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { checkInternalAuth } from '@/lib/auth/hybrid'
77
import { secureFetchWithPinnedIP } from '@/lib/core/security/input-validation.server'
88
import { generateRequestId } from '@/lib/core/utils/request'
99
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
10+
import { resolveEffectiveMimeType } from '@/lib/uploads/utils/file-utils'
1011
import { isEwRestBody } from '@/tools/agiloft/ewrest'
1112
import {
1213
AGILOFT_MAX_ATTACHMENT_BYTES,
@@ -128,10 +129,17 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
128129
)
129130
}
130131

132+
/**
133+
* Agiloft labels most attachments application/octet-stream whatever they
134+
* are, so downstream consumers keyed on the header mis-handle them. The
135+
* filename Agiloft sends in Content-Disposition carries the real type.
136+
*/
137+
const mimeType = resolveEffectiveMimeType(contentType, fileName)
138+
131139
logger.info(`[${requestId}] Attachment downloaded successfully`, {
132140
name: fileName,
133141
size: fileBuffer.length,
134-
mimeType: contentType,
142+
mimeType,
135143
})
136144

137145
const base64Data = fileBuffer.toString('base64')
@@ -141,7 +149,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
141149
output: {
142150
file: {
143151
name: fileName,
144-
mimeType: contentType,
152+
mimeType,
145153
data: base64Data,
146154
size: fileBuffer.length,
147155
},

0 commit comments

Comments
 (0)