halo-dev/halo · warning · ServerWebInputException

Unsupported URL protocol: {}

Error message

Unsupported URL protocol: {}

What it means

AttachmentPermalinkMatcher only resolves absolute URIs whose scheme is http or https (SUPPORTED_ABSOLUTE_URI_SCHEMES). createCandidate throws ServerWebInputException (HTTP 400) with the offending value when a candidate is an absolute URI with any other scheme — e.g. ftp://, file://, mailto:, javascript:, data:.

Source

Thrown at application/src/main/java/run/halo/app/core/attachment/AttachmentPermalinkMatcher.java:69

        return urls.stream().map(url -> createCandidate(url, siteUrl)).toList();
    }

    private static Candidate createCandidate(String url, URL siteUrl) {
        if (!StringUtils.hasText(url)) {
            throw new ServerWebInputException("url must not be blank.");
        }
        var value = url.strip();

        URI valueUri;
        try {
            valueUri = URI.create(value);
        } catch (IllegalArgumentException e) {
            var permalinks = new LinkedHashSet<String>();
            permalinks.add(value);
            return new Candidate(url, permalinks);
        }
        if (isUnsupportedAbsoluteUri(valueUri)) {
            throw new ServerWebInputException("Unsupported URL protocol: " + value);
        }

        var permalinks = new LinkedHashSet<String>();
        permalinks.add(value);
        var siteUri = URI.create(siteUrl.toString());
        if (valueUri.isAbsolute()) {
            if (sameAuthority(siteUri, valueUri)) {
                permalinks.add(pathAndQuery(valueUri));
            }
        } else {
            permalinks.add(siteUri.resolve(valueUri).normalize().toString());
        }
        return new Candidate(url, permalinks);
    }

    private static boolean isUnsupportedAbsoluteUri(URI uri) {
        if (!uri.isAbsolute()) {
            return false;

View on GitHub (pinned to d2f5165f9c)

Solutions

  1. Use only http:// or https:// absolute URLs, or send relative paths.
  2. Strip or reject non-http(s) schemes on the client before submission.
  3. If matching legacy ftp/file links is truly required, copy the resource to attachment storage and reference its https permalink instead.

Example fix

// before
urls: ["ftp://host/archive.zip"] // 400

// after
urls: ["https://host/archive.zip"]
Defensive patterns

Strategy: validation

Validate before calling

static final Set<String> OK = Set.of("http", "https");
String scheme;
try { scheme = URI.create(url.trim()).getScheme(); }
catch (IllegalArgumentException e) { scheme = null; }
if (scheme != null && !OK.contains(scheme.toLowerCase(Locale.ROOT))) {
    // reject or convert before calling match()
}

Type guard

static boolean isSupportedUrl(String url) {
    var v = url == null ? null : url.strip();
    if (!StringUtils.hasText(v)) return false;
    URI u;
    try { u = URI.create(v); } catch (IllegalArgumentException e) { return true; }
    return !u.isAbsolute() || Set.of("http", "https")
        .contains(u.getScheme().toLowerCase(Locale.ROOT));
}

Prevention

When it happens

Trigger: Submitting a permalink candidate like ftp://host/file, file:///etc/passwd, mailto:a@b.com, or any custom-scheme absolute URI to the attachment match endpoint. Relative URLs and http/https absolute URLs are accepted.

Common situations: Pasting a file:// link copied from a local file browser; legacy content containing ftp:// downloads; a sanitizer that left a javascript: or data: URL in place; malformed input where a scheme was prepended unintentionally.

Related errors


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