Skip to content

Commit 0f09310

Browse files
waleedlatif1claude
andauthored
fix(files): align upload route image extensions with picker (#4423)
* fix(files): align upload route image extensions with picker * fix(files): widen image mime mapping for chat/profile/logo uploads * fix(files): fall back to extension when MIME is generic for image-only uploads Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(files): add new image extensions to EXTENSION_TO_MIME map Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(files): skip non-Claude image MIMEs in createFileContent Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(files): resolve content type for image-only contexts when MIME is generic Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(files): cover image/jpg in Claude gate, reverse MIME map, audit metadata Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 14c93f6 commit 0f09310

2 files changed

Lines changed: 51 additions & 10 deletions

File tree

apps/sim/app/api/files/upload/route.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@ import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
1313
import { captureServerEvent } from '@/lib/posthog/server'
1414
import type { StorageContext } from '@/lib/uploads/config'
1515
import { generateWorkspaceFileKey } from '@/lib/uploads/contexts/workspace/workspace-file-manager'
16-
import { isImageFileType } from '@/lib/uploads/utils/file-utils'
16+
import { isImageFileType, resolveFileType } from '@/lib/uploads/utils/file-utils'
1717
import {
1818
SUPPORTED_AUDIO_EXTENSIONS,
1919
SUPPORTED_CODE_EXTENSIONS,
2020
SUPPORTED_DOCUMENT_EXTENSIONS,
21+
SUPPORTED_IMAGE_EXTENSIONS,
2122
SUPPORTED_VIDEO_EXTENSIONS,
2223
validateFileType,
2324
} from '@/lib/uploads/utils/validation'
@@ -28,12 +29,10 @@ import {
2829
InvalidRequestError,
2930
} from '@/app/api/files/utils'
3031

31-
const IMAGE_EXTENSIONS = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg'] as const
32-
3332
const ALLOWED_EXTENSIONS = new Set<string>([
3433
...SUPPORTED_DOCUMENT_EXTENSIONS,
3534
...SUPPORTED_CODE_EXTENSIONS,
36-
...IMAGE_EXTENSIONS,
35+
...SUPPORTED_IMAGE_EXTENSIONS,
3736
...SUPPORTED_AUDIO_EXTENSIONS,
3837
...SUPPORTED_VIDEO_EXTENSIONS,
3938
])
@@ -305,10 +304,17 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
305304
context === 'profile-pictures' ||
306305
context === 'workspace-logos'
307306
) {
308-
if (context !== 'copilot' && !isImageFileType(file.type)) {
309-
throw new InvalidRequestError(
310-
`Only image files (JPEG, PNG, GIF, WebP, SVG) are allowed for ${context} uploads`
307+
if (context !== 'copilot') {
308+
const mimeType = file.type
309+
const isGenericMime = !mimeType || mimeType === 'application/octet-stream'
310+
const extension = originalName.split('.').pop()?.toLowerCase() ?? ''
311+
const extensionIsImage = (SUPPORTED_IMAGE_EXTENSIONS as readonly string[]).includes(
312+
extension
311313
)
314+
const isImage = isGenericMime ? extensionIsImage : isImageFileType(mimeType)
315+
if (!isImage) {
316+
throw new InvalidRequestError(`Only image files are allowed for ${context} uploads`)
317+
}
312318
}
313319

314320
if (context === 'workspace-logos') {
@@ -344,6 +350,8 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
344350

345351
logger.info(`Uploading ${context} file: ${originalName}`)
346352

353+
const resolvedContentType = resolveFileType({ type: file.type, name: originalName })
354+
347355
const timestamp = Date.now()
348356
const safeFileName = sanitizeFileName(originalName)
349357
const storageKey = `${context}/${timestamp}-${safeFileName}`
@@ -362,7 +370,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
362370
const fileInfo = await storageService.uploadFile({
363371
file: buffer,
364372
fileName: storageKey,
365-
contentType: file.type,
373+
contentType: resolvedContentType,
366374
context,
367375
preserveKey: true,
368376
customKey: storageKey,
@@ -379,7 +387,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
379387
key: fileInfo.key,
380388
name: originalName,
381389
size: buffer.length,
382-
type: file.type,
390+
type: resolvedContentType,
383391
},
384392
directUploadSupported: false,
385393
}
@@ -400,7 +408,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
400408
fileName: originalName,
401409
fileKey: fileInfo.key,
402410
fileSize: buffer.length,
403-
fileType: file.type,
411+
fileType: resolvedContentType,
404412
},
405413
request,
406414
})

apps/sim/lib/uploads/utils/file-utils.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ export const MIME_TYPE_MAPPING: Record<string, 'image' | 'document' | 'audio' |
3333
'image/gif': 'image',
3434
'image/webp': 'image',
3535
'image/svg+xml': 'image', // SVG upload is allowed; createFileContent handles it separately for Claude API
36+
'image/bmp': 'image',
37+
'image/tiff': 'image',
38+
'image/heic': 'image',
39+
'image/heif': 'image',
40+
'image/avif': 'image',
41+
'image/x-icon': 'image',
42+
'image/vnd.microsoft.icon': 'image',
3643

3744
// Documents
3845
'application/pdf': 'document',
@@ -158,6 +165,10 @@ export function createFileContent(fileBuffer: Buffer, mimeType: string): Message
158165
return null
159166
}
160167

168+
if (contentType === 'image' && !CLAUDE_SUPPORTED_IMAGE_MIME_TYPES.has(mimeType.toLowerCase())) {
169+
return null
170+
}
171+
161172
return {
162173
type: contentType,
163174
source: {
@@ -168,6 +179,14 @@ export function createFileContent(fileBuffer: Buffer, mimeType: string): Message
168179
}
169180
}
170181

182+
const CLAUDE_SUPPORTED_IMAGE_MIME_TYPES = new Set([
183+
'image/jpeg',
184+
'image/jpg',
185+
'image/png',
186+
'image/gif',
187+
'image/webp',
188+
])
189+
171190
/**
172191
* Extract file extension from filename
173192
*/
@@ -184,6 +203,13 @@ const EXTENSION_TO_MIME: Record<string, string> = {
184203
gif: 'image/gif',
185204
webp: 'image/webp',
186205
svg: 'image/svg+xml',
206+
bmp: 'image/bmp',
207+
tif: 'image/tiff',
208+
tiff: 'image/tiff',
209+
heic: 'image/heic',
210+
heif: 'image/heif',
211+
avif: 'image/avif',
212+
ico: 'image/x-icon',
187213

188214
// Documents
189215
pdf: 'application/pdf',
@@ -339,6 +365,13 @@ const MIME_TO_EXTENSION: Record<string, string> = {
339365
'image/gif': 'gif',
340366
'image/webp': 'webp',
341367
'image/svg+xml': 'svg',
368+
'image/bmp': 'bmp',
369+
'image/tiff': 'tiff',
370+
'image/heic': 'heic',
371+
'image/heif': 'heif',
372+
'image/avif': 'avif',
373+
'image/x-icon': 'ico',
374+
'image/vnd.microsoft.icon': 'ico',
342375

343376
// Documents
344377
'application/pdf': 'pdf',

0 commit comments

Comments
 (0)