getgrav/grav · error · InvalidArgumentException
Malformed URL: {$url}
Error message
Malformed URL: {$url} What it means
UriFactory::parseUrl() percent-encodes multibyte characters, then hands the result to PHP's parse_url(); if parse_url() still returns false, the string is not a parseable URL at all and InvalidArgumentException ('Malformed URL: ...') is thrown with the offending input embedded. This catches strings that are structurally broken, not merely unusual.
Source
Thrown at system/src/Grav/Framework/Uri/UriFactory.php:134
* @param string $url
* @return array
* @throws InvalidArgumentException
*/
public static function parseUrl($url)
{
if (!is_string($url)) {
throw new InvalidArgumentException('URL must be a string');
}
$encodedUrl = preg_replace_callback(
'%[^:/@?&=#]+%u',
static fn($matches) => rawurlencode((string) $matches[0]),
$url
);
$parts = is_string($encodedUrl) ? parse_url($encodedUrl) : false;
if ($parts === false) {
throw new InvalidArgumentException("Malformed URL: {$url}");
}
return $parts;
}
/**
* Parse query string and return it as an array.
*
* @param string $query
* @return mixed
*/
public static function parseQuery($query)
{
parse_str($query, $params);
return $params;
}
View on GitHub (pinned to 6040efed04)
Solutions
- Pre-validate with `filter_var($url, FILTER_VALIDATE_URL)` before parsing
- Reject empty/hostless strings early instead of relying on parse_url
- Wrap parseUrl()/createFromString() in try/catch and treat failure as invalid input (400), not a 500
Example fix
// before
$uri = UriFactory::createFromString($userUrl); // 'http://:80' -> Malformed URL
// after
if (!is_string($userUrl) || !filter_var($userUrl, FILTER_VALIDATE_URL)) {
throw new HttpBadRequest('Invalid URL');
}
$uri = UriFactory::createFromString($userUrl); Defensive patterns
Strategy: validation
Validate before calling
if (!is_string($url) || !filter_var($url, FILTER_VALIDATE_URL)) {
throw new \InvalidArgumentException("Not a valid URL: " . print_r($url, true));
} Try / catch
try {
$parts = UriFactory::parseUrl($url);
} catch (\InvalidArgumentException $e) {
// 'Malformed URL: ...' — reject the input, do not retry
return $response->withStatus(400)->write('Invalid URL');
} Prevention
- Run FILTER_VALIDATE_URL before parse_url-based APIs
- Reject structurally empty inputs (no scheme/host) before concatenating partial strings
- Never surface the embedded URL back to end users unescaped — the message contains raw input
When it happens
Trigger: Strings like 'http://:80', '////', 'http://' with empty host, URLs containing raw control characters, or multibyte garbage that remains unparseable after encoding; feeding concatenated user input (e.g. `$base . $path` where $base is empty) into createFromString().
Common situations: User-submitted link/redirect fields; imported content containing broken URLs; crafted scanner requests hitting an endpoint that parses arbitrary input as a URL.
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 must be a string
- Uri scheme must be a string
- Uri user info must be a string
- Uri host must be a string
- Uri host name validation failed
AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17).
Data as JSON: /api/errors/933804fcb3a6edeb.
Report an issue: GitHub.