halo-dev/halo · error · ServerWebInputException

Only support file with extension

Error message

Only support file with extension: {}

What it means

Thrown as a ServerWebInputException (HTTP 400) by AvatarUploadRequest.getFile() when the uploaded file's extension is not in ALLOWED_AVATAR_EXTENSIONS. The message interpolates the allowed list (e.g. png, jpg, jpeg, gif, webp) so the caller knows what is accepted.

Solutions

  1. Convert the image to one of the allowed formats (png/jpg/jpeg/gif/webp) before uploading.
  2. Rename the file to use an allowed extension only if the actual content matches that format.
  3. If a new format is genuinely required, extend ALLOWED_AVATAR_EXTENSIONS and ensure the storage/attachment policy supports it.

Example fix

// before: upload avatar.bmp
// after:  convert to png, upload avatar.png
Defensive patterns

Strategy: validation

Validate before calling

// check extension against the allow-list before upload
Set<String> allowed = Set.of("png", "jpg", "jpeg", "gif", "webp");
String ext = name.substring(name.lastIndexOf('.') + 1).toLowerCase(Locale.ROOT);
if (!allowed.contains(ext)) {
    showUserError("Allowed: " + allowed);
    return;
}

Prevention

When it happens

Trigger: POST to the avatar upload endpoint with a file whose extension is not in the allow-list — e.g. .bmp, .svg, .tif, .heic, or a file with no extension. The check is a simple endsWith so a missing dot also fails.

Common situations: User picks a HEIC photo from a phone; SVG intended as a logo; screenshot saved as .bmp; filename lost its extension during transfer; admin wants a format not in the default allow-list.

Related errors


AI-assisted analysis of halo-dev/halo@d2f5165f9c (2026-08-14). Data as JSON: /api/errors/a74ee347b772a2bb. Report an issue: GitHub.

Appendix: source

Thrown at application/src/main/java/run/halo/app/core/endpoint/console/UserEndpoint.java:412

        FilePart getFile();
    }

    public record AvatarUploadRequest(MultiValueMap<String, Part> formData) {
        public FilePart getFile() {
            Part file = formData.getFirst("file");
            if (file == null) {
                throw new ServerWebInputException("No file part found in the request");
            }

            if (!(file instanceof FilePart filePart)) {
                throw new ServerWebInputException("Invalid part of file");
            }

            boolean isNoneExt = Arrays.stream(ALLOWED_AVATAR_EXTENSIONS)
                    .noneMatch(ext -> filePart.filename().endsWith("." + ext));

            if (isNoneExt) {
                throw new ServerWebInputException(
                        "Only support file with extension: " + String.join(", ", ALLOWED_AVATAR_EXTENSIONS));
            }
            return filePart;
        }
    }

    private Mono<Attachment> uploadAvatar(AvatarUploadRequest uploadRequest) {
        var fallbackSetting = environmentFetcher
                .fetch(SystemSetting.User.GROUP, SystemSetting.User.class)
                .mapNotNull(SystemSetting.User::getAvatarPolicy)
                .filter(StringUtils::isNotBlank);
        var getAvatarPolicy = environmentFetcher
                .fetch(SystemSetting.Attachment.GROUP, SystemSetting.Attachment.class)
                .mapNotNull(SystemSetting.Attachment::avatar)
                .mapNotNull(UploadOptions::policyName)
                .filter(StringUtils::isNotBlank)
                .switchIfEmpty(fallbackSetting)
                .defaultIfEmpty(DEFAULT_USER_AVATAR_ATTACHMENT_POLICY_NAME);

View on GitHub (pinned to d2f5165f9c)