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
- Use only http:// or https:// absolute URLs, or send relative paths.
- Strip or reject non-http(s) schemes on the client before submission.
- 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
- Restrict to http/https absolute URLs or relative paths.
- Sanitize content before persisting to avoid ftp/file/javascript URLs reaching the matcher.
- Reject non-http(s) schemes at the form/input layer.
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
- urls must not be empty.
- url must not be blank.
- Required url is missing.
- Policy name must not be blank
- Invalid part of file
AI-assisted analysis of halo-dev/halo@d2f5165f9c (2026-08-14).
Data as JSON: /api/errors/cbeb500c5c8f54db.
Report an issue: GitHub.