{"record":{"id":"933804fcb3a6edeb","repo":"getgrav/grav","slug":"malformed-url-url-933804","errorCode":null,"errorMessage":"Malformed URL: {$url}","messagePattern":"Malformed URL: (.+?)","errorType":"validation","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"error","filePath":"system/src/Grav/Framework/Uri/UriFactory.php","lineNumber":134,"sourceCode":"     * @param string $url\n     * @return array\n     * @throws InvalidArgumentException\n     */\n    public static function parseUrl($url)\n    {\n        if (!is_string($url)) {\n            throw new InvalidArgumentException('URL must be a string');\n        }\n\n        $encodedUrl = preg_replace_callback(\n            '%[^:/@?&=#]+%u',\n            static fn($matches) => rawurlencode((string) $matches[0]),\n            $url\n        );\n\n        $parts = is_string($encodedUrl) ? parse_url($encodedUrl) : false;\n        if ($parts === false) {\n            throw new InvalidArgumentException(\"Malformed URL: {$url}\");\n        }\n\n        return $parts;\n    }\n\n    /**\n     * Parse query string and return it as an array.\n     *\n     * @param string $query\n     * @return mixed\n     */\n    public static function parseQuery($query)\n    {\n        parse_str($query, $params);\n\n        return $params;\n    }\n","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/getgrav/grav/blob/6040efed04efa69b8209448ed81308e7c24147c2/system/src/Grav/Framework/Uri/UriFactory.php#L116-L152","documentation":"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.","triggerScenarios":"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().","commonSituations":"User-submitted link/redirect fields; imported content containing broken URLs; crafted scanner requests hitting an endpoint that parses arbitrary input as a URL.","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"],"exampleFix":"// before\n$uri = UriFactory::createFromString($userUrl); // 'http://:80' -> Malformed URL\n\n// after\nif (!is_string($userUrl) || !filter_var($userUrl, FILTER_VALIDATE_URL)) {\n    throw new HttpBadRequest('Invalid URL');\n}\n$uri = UriFactory::createFromString($userUrl);","handlingStrategy":"validation","validationCode":"if (!is_string($url) || !filter_var($url, FILTER_VALIDATE_URL)) {\n    throw new \\InvalidArgumentException(\"Not a valid URL: \" . print_r($url, true));\n}","typeGuard":null,"tryCatchPattern":"try {\n    $parts = UriFactory::parseUrl($url);\n} catch (\\InvalidArgumentException $e) {\n    // 'Malformed URL: ...' — reject the input, do not retry\n    return $response->withStatus(400)->write('Invalid URL');\n}","preventionTips":["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"],"tags":["uri","validation","parse-url","user-input"],"backgroundTag":"malformed-url","analyzedSha":"6040efed04efa69b8209448ed81308e7c24147c2","analyzedAt":"2026-08-17T05:07:31.593Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}