mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-22 05:19:54 +08:00
fix(agiloft): resolve attachment MIME type instead of trusting the header (#6573)
Agiloft labels most attachments application/octet-stream whatever they actually are, so a downstream consumer keyed on the header mis-handles them. Resolve the type through resolveEffectiveMimeType, the shared helper the other tool routes already use, which prefers a header that names a real format and otherwise falls back to the filename Agiloft sends in Content-Disposition.
This commit is contained in:
@@ -140,6 +140,35 @@ describe('POST /api/tools/agiloft/retrieve', () => {
|
||||
expect(inputValidationMockFns.mockValidateUrlWithDNS).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('resolves the real type when Agiloft labels an attachment octet-stream', async () => {
|
||||
const fileBytes = Buffer.from('PKdocx-bytes', 'utf-8')
|
||||
|
||||
inputValidationMockFns.mockSecureFetchWithPinnedIP.mockResolvedValueOnce(
|
||||
mockSecureFetchResponse({
|
||||
arrayBuffer: fileBytes.buffer.slice(
|
||||
fileBytes.byteOffset,
|
||||
fileBytes.byteOffset + fileBytes.byteLength
|
||||
) as ArrayBuffer,
|
||||
headers: new Headers({
|
||||
'content-type': 'application/octet-stream',
|
||||
'content-disposition': 'attachment; filename="Master Agreement.docx"',
|
||||
}),
|
||||
})
|
||||
)
|
||||
|
||||
const response = await POST(createMockRequest('POST', baseBody))
|
||||
const data = (await response.json()) as { output: { file: { mimeType: string } } }
|
||||
|
||||
/**
|
||||
* Agiloft labels most attachments octet-stream whatever they are. The
|
||||
* filename disambiguates what the leading bytes cannot: a ZIP header is
|
||||
* equally a .docx, .xlsx or a plain archive.
|
||||
*/
|
||||
expect(data.output.file.mimeType).toBe(
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
||||
)
|
||||
})
|
||||
|
||||
it('propagates upstream errors', async () => {
|
||||
inputValidationMockFns.mockSecureFetchWithPinnedIP.mockResolvedValueOnce(
|
||||
mockSecureFetchResponse({ ok: false, status: 404, text: 'Record not found' })
|
||||
|
||||
@@ -7,6 +7,7 @@ import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { secureFetchWithPinnedIP } from '@/lib/core/security/input-validation.server'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
|
||||
import { resolveEffectiveMimeType } from '@/lib/uploads/utils/file-utils'
|
||||
import { isEwRestBody } from '@/tools/agiloft/ewrest'
|
||||
import {
|
||||
AGILOFT_MAX_ATTACHMENT_BYTES,
|
||||
@@ -128,10 +129,17 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Agiloft labels most attachments application/octet-stream whatever they
|
||||
* are, so downstream consumers keyed on the header mis-handle them. The
|
||||
* filename Agiloft sends in Content-Disposition carries the real type.
|
||||
*/
|
||||
const mimeType = resolveEffectiveMimeType(contentType, fileName)
|
||||
|
||||
logger.info(`[${requestId}] Attachment downloaded successfully`, {
|
||||
name: fileName,
|
||||
size: fileBuffer.length,
|
||||
mimeType: contentType,
|
||||
mimeType,
|
||||
})
|
||||
|
||||
const base64Data = fileBuffer.toString('base64')
|
||||
@@ -141,7 +149,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
|
||||
output: {
|
||||
file: {
|
||||
name: fileName,
|
||||
mimeType: contentType,
|
||||
mimeType,
|
||||
data: base64Data,
|
||||
size: fileBuffer.length,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user