{"record":{"id":"bf6fa9d4a31b1e5a","repo":"getgrav/grav","slug":"url-must-be-a-string","errorCode":null,"errorMessage":"URL must be a string","messagePattern":"URL must be a string","errorType":"validation","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"error","filePath":"system/src/Grav/Framework/Uri/UriFactory.php","lineNumber":123,"sourceCode":"            'pass' => $pass,\n            'host' => $host,\n            'port' => $port,\n            'path' => $path,\n            'query' => $query\n        ];\n    }\n\n    /**\n     * UTF-8 aware parse_url() implementation.\n     *\n     * @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.","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/getgrav/grav/blob/6040efed04efa69b8209448ed81308e7c24147c2/system/src/Grav/Framework/Uri/UriFactory.php#L105-L141","documentation":"UriFactory::parseUrl() is Grav's UTF-8 aware replacement for parse_url(); it first asserts the input is a PHP string and throws InvalidArgumentException otherwise. It is reached through UriFactory::createFromString() and direct parseUrl() calls. Non-string values (arrays, null, objects) are rejected before any parsing happens.","triggerScenarios":"Passing `$_GET['url']` straight in when the client sends `?url[]=x` (PHP turns that into an array); passing null from an optional parameter default; passing an object (even Stringable — no __toString invocation happens here).","commonSituations":"Endpoints that parse user-supplied redirect targets or link URLs; array-syntax query-parameter injection (`param[]=`) hitting unvalidated code; optional config values that default to null being fed to createFromString().","solutions":["Validate with is_string() before calling parseUrl()/createFromString()","Reject array input explicitly when reading query parameters you intend to use as URLs","Cast known scalar inputs: `(string) $value` — but reject arrays/objects first"],"exampleFix":"// before\n$parts = UriFactory::parseUrl($_GET['redirect'] ?? null); // ?redirect[]=x -> array -> throws\n\n// after\n$raw = $_GET['redirect'] ?? '';\n$parts = is_string($raw) && $raw !== '' ? UriFactory::parseUrl($raw) : null;","handlingStrategy":"type-guard","validationCode":"$url = $_GET['url'] ?? '';\nif (!is_string($url) || $url === '') {\n    throw new \\InvalidArgumentException('url parameter must be a non-empty string');\n}","typeGuard":"/** @param mixed $value */\nfunction isParsableUrlString(mixed $value): bool\n{\n    return is_string($value) && $value !== '';\n}","tryCatchPattern":"try {\n    $uri = Grav\\Framework\\Uri\\UriFactory::createFromString($url);\n} catch (\\InvalidArgumentException $e) {\n    // Covers both 'URL must be a string' and 'Malformed URL'\n    throw new HttpBadRequest($request, 'Invalid URL parameter');\n}","preventionTips":["Validate query parameters with is_string() before using them as URLs (guards ?param[]= injection)","Give optional URL inputs explicit string defaults ('' ) rather than null","Wrap third-party input parsing in try/catch and map InvalidArgumentException to a 400 response"],"tags":["uri","type-check","validation","user-input"],"backgroundTag":"type-validation-failed","analyzedSha":"6040efed04efa69b8209448ed81308e7c24147c2","analyzedAt":"2026-08-17T05:07:31.593Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}