Kareadita/Kavita · warning · KavitaException
url-malformed
Error message
url-malformed
What it means
Thrown by UrlValidationService.ValidateUrlAsync when Uri.TryCreate(url, UriKind.Absolute, ...) fails — the input is not a parseable absolute URI. ValidateUrlAsync is Kavita's SSRF pre-flight gate invoked before fetching any user-supplied URL (cover images via CoverDbService/UploadController, favicons, Google Fonts, CBL upload and CBL sync). It is a localized KavitaException surfaced as HTTP 500; some callers (UploadController, CBLController) catch it and return 400 instead.
Source
Thrown at Kavita.Services/UrlValidationService.cs:17
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using Kavita.API.Services;
using Kavita.Common;
using Kavita.Common.Helpers;
namespace Kavita.Services;
public class UrlValidationService(ILocalizationService localizationService) : IUrlValidationService
{
public async Task ValidateUrlAsync(string url)
{
if (!Uri.TryCreate(url, UriKind.Absolute, out var uri))
{
throw new KavitaException(await localizationService.TranslateAsync("url-malformed"));
}
if (!string.Equals(uri.Scheme, "https", StringComparison.OrdinalIgnoreCase))
{
throw new KavitaException(await localizationService.TranslateAsync("url-https-only"));
}
IPAddress[] addresses;
try
{
addresses = await Dns.GetHostAddressesAsync(uri.Host);
}
catch (SocketException)
{
throw new KavitaException(await localizationService.TranslateAsync("url-unable-to-resolve"));
}
if (addresses.Length == 0)View on GitHub (pinned to 9c3e540000)
Solutions
- Trim and encode the URL, then ensure it starts with https:// before submitting.
- Validate client-side with `new URL(url)` (JS) / `Uri.IsWellFormedUriString(url, Absolute)` before calling the API.
- If only a host is available, prefix 'https://' on the client so the value is absolute.
Example fix
// before
uploadByUrl(url: string) { return this.http.post('upload/upload-by-url', { url }); }
// after
uploadByUrl(raw: string) {
const url = raw.trim();
try { if (new URL(url).protocol !== 'https:') throw 0; }
catch { return throwError(() => new Error('A valid https:// URL is required')); }
return this.http.post('upload/upload-by-url', { url });
} Defensive patterns
Strategy: validation
Validate before calling
function isAbsoluteUrl(url: string): boolean {
try { const u = new URL(url.trim()); return u.protocol === 'http:' || u.protocol === 'https:'; }
catch { return false; }
} Type guard
function isAbsoluteHttpsCandidate(s: unknown): s is string {
return typeof s === 'string' && isAbsoluteUrl(s);
} Try / catch
try { await svc.fetchFromUrl(url); } catch (e) { if (/malformed/i.test(e.message)) showUser('Enter a valid https:// URL'); else throw e; } Prevention
- Trim whitespace/newlines from pasted URLs before submitting.
- Always prefix a scheme; bare hosts are rejected.
- Validate with `new URL(url)` on the client before the API call.
When it happens
Trigger: Any code path that calls ValidateUrlAsync with a value that is not an absolute URI: relative paths, strings with unencoded spaces, missing scheme (e.g. 'example.com/foo'), or garbage. Reachable via upload-by-url, CBL upload-cbl-file, cover-from-url, favicon fetch, font download, and CBL URL sync.
Common situations: User pastes a bare domain without https://; a cover URL is copied with a trailing space or newline; a relative '/covers/x.png' is stored where an absolute URL was expected; clipboard copy loses the scheme.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- url-https-only
- url-unable-to-resolve
- url-blocked-address
- url-blocked-address
- {comparison} is not applicable for {fieldName}
AI-assisted analysis of Kareadita/Kavita@9c3e540000 (2026-08-13).
Data as JSON: /api/errors/fdedf1d9232338fd.
Report an issue: GitHub.