Wei-Shaw/sub2api · warning

profile.avatar.invalidType

Error message

profile.avatar.invalidType

What it means

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.

Source

Thrown at frontend/src/components/user/profile/ProfileAvatarCard.vue:193

    canvas.height = height
    ctx.clearRect(0, 0, width, height)
    ctx.drawImage(image, 0, 0, width, height)

    for (const quality of avatarQualitySteps) {
      const blob = await canvasToBlob(canvas, 'image/webp', quality)
      if (blob.size <= targetAvatarUploadBytes) {
        const fileName = file.name.replace(/\.[^.]+$/, '') || 'avatar'
        return new File([blob], `${fileName}.webp`, { type: 'image/webp' })
      }
    }
  }

  throw new Error(t('profile.avatar.compressTooLarge'))
}

async function prepareAvatarUpload(file: File): Promise<File> {
  if (!file.type.startsWith('image/')) {
    throw new Error(t('profile.avatar.invalidType'))
  }
  if (file.type === 'image/gif') {
    if (file.size > targetAvatarUploadBytes) {
      throw new Error(t('profile.avatar.gifTooLarge'))
    }
    return file
  }
  if (file.size <= targetAvatarUploadBytes) {
    return file
  }
  return compressAvatarFile(file)
}

async function handleAvatarFileChange(event: Event) {
  const input = event.target as HTMLInputElement | null
  const file = input?.files?.[0]
  if (input) {
    input.value = ''

View on GitHub (pinned to 073e92d171)

Solutions

  1. Add accept="image/*" to the file input so the picker filters non-images upfront.
  2. Also treat an empty file.type as invalid (or sniff magic bytes) since empty types slip past startsWith.
  3. Show the localized invalidType message next to the picker so the user immediately understands.
  4. Keep server-side MIME/extension validation as the authoritative check.

Example fix

// before
if (!file.type.startsWith('image/')) {
  throw new Error(t('profile.avatar.invalidType'))
}

// after
if (!file.type.startsWith('image/')) {
  throw new Error(t('profile.avatar.invalidType'))
}
// template: <input type="file" accept="image/gif,image/jpeg,image/png,image/webp" @change="handleAvatarFileChange">
Defensive patterns

Strategy: validation

Validate before calling

export function isUploadableImage(file: File): boolean {
  return file.type.startsWith('image/') && file.type !== '';
}
// in the change handler:
if (!isUploadableImage(file)) { showInvalidTypeError(); return; }

Type guard

function isImageFile(file: File): file is File & { type: `image/${string}` } {
  return file.type.startsWith('image/');
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of Wei-Shaw/sub2api@073e92d171 (2026-08-15). Data as JSON: /api/errors/295ebe58948456f6. Report an issue: GitHub.