{"record":{"id":"295ebe58948456f6","repo":"Wei-Shaw/sub2api","slug":"profile-avatar-invalidtype","errorCode":null,"errorMessage":"profile.avatar.invalidType","messagePattern":"profile\\.avatar\\.invalidType","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"frontend/src/components/user/profile/ProfileAvatarCard.vue","lineNumber":193,"sourceCode":"    canvas.height = height\n    ctx.clearRect(0, 0, width, height)\n    ctx.drawImage(image, 0, 0, width, height)\n\n    for (const quality of avatarQualitySteps) {\n      const blob = await canvasToBlob(canvas, 'image/webp', quality)\n      if (blob.size <= targetAvatarUploadBytes) {\n        const fileName = file.name.replace(/\\.[^.]+$/, '') || 'avatar'\n        return new File([blob], `${fileName}.webp`, { type: 'image/webp' })\n      }\n    }\n  }\n\n  throw new Error(t('profile.avatar.compressTooLarge'))\n}\n\nasync function prepareAvatarUpload(file: File): Promise<File> {\n  if (!file.type.startsWith('image/')) {\n    throw new Error(t('profile.avatar.invalidType'))\n  }\n  if (file.type === 'image/gif') {\n    if (file.size > targetAvatarUploadBytes) {\n      throw new Error(t('profile.avatar.gifTooLarge'))\n    }\n    return file\n  }\n  if (file.size <= targetAvatarUploadBytes) {\n    return file\n  }\n  return compressAvatarFile(file)\n}\n\nasync function handleAvatarFileChange(event: Event) {\n  const input = event.target as HTMLInputElement | null\n  const file = input?.files?.[0]\n  if (input) {\n    input.value = ''","sourceCodeStart":175,"sourceCodeEnd":211,"githubUrl":"https://github.com/Wei-Shaw/sub2api/blob/073e92d17178a1ccdb0a27017f572f10c9c7ab62/frontend/src/components/user/profile/ProfileAvatarCard.vue#L175-L211","documentation":"In frontend/src/components/user/profile/ProfileAvatarCard.vue:193, prepareAvatarUpload() throws the localized 'profile.avatar.invalidType' when file.type does not start with 'image/'. It is the first validation gate: only image MIME types are accepted for avatars. file.type comes from the OS/browser MIME sniffing of the file picker selection.","triggerScenarios":"User selects a non-image file (PDF, zip, video) through the avatar file input; a file with an empty or application/* MIME type (common for extensionless files or files from cloud drives); drag-drop of a file whose type the browser reports as blank.","commonSituations":"File inputs with accept filters not applied (or accept attribute missing so any file is selectable); Android content-provider URIs sometimes report empty MIME types; users renaming files to .jpg without real image content still pass this check but fail server-side — pair with server validation.","solutions":["Add accept=\"image/*\" to the file input so the picker filters non-images upfront.","Also treat an empty file.type as invalid (or sniff magic bytes) since empty types slip past startsWith.","Show the localized invalidType message next to the picker so the user immediately understands.","Keep server-side MIME/extension validation as the authoritative check."],"exampleFix":"// before\nif (!file.type.startsWith('image/')) {\n  throw new Error(t('profile.avatar.invalidType'))\n}\n\n// after\nif (!file.type.startsWith('image/')) {\n  throw new Error(t('profile.avatar.invalidType'))\n}\n// template: <input type=\"file\" accept=\"image/gif,image/jpeg,image/png,image/webp\" @change=\"handleAvatarFileChange\">","handlingStrategy":"validation","validationCode":"export function isUploadableImage(file: File): boolean {\n  return file.type.startsWith('image/') && file.type !== '';\n}\n// in the change handler:\nif (!isUploadableImage(file)) { showInvalidTypeError(); return; }","typeGuard":"function isImageFile(file: File): file is File & { type: `image/${string}` } {\n  return file.type.startsWith('image/');\n}","tryCatchPattern":null,"preventionTips":["Set accept=\"image/*\" (or an explicit list) on the file input","Treat empty MIME type as invalid, since sniffing can return blank strings","Keep server-side MIME validation as the authoritative check"],"tags":["validation","file-upload","mime-type","avatar","frontend"],"backgroundTag":null,"analyzedSha":"073e92d17178a1ccdb0a27017f572f10c9c7ab62","analyzedAt":"2026-08-15T14:33:00.750Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}