{"record":{"id":"66426eaa8ec50190","repo":"getgrav/grav","slug":"uri-user-info-must-be-a-string","errorCode":null,"errorMessage":"Uri user info must be a string","messagePattern":"Uri user info must be a string","errorType":"validation","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"error","filePath":"system/src/Grav/Framework/Uri/UriPartsFilter.php","lineNumber":48,"sourceCode":"    {\n        if (!is_string($scheme)) {\n            throw new InvalidArgumentException('Uri scheme must be a string');\n        }\n\n        return strtolower($scheme);\n    }\n\n    /**\n     * Filters the user info string.\n     *\n     * @param string $info The raw user or password.\n     * @return string The percent-encoded user or password string.\n     * @throws InvalidArgumentException\n     */\n    public static function filterUserInfo($info)\n    {\n        if (!is_string($info)) {\n            throw new InvalidArgumentException('Uri user info must be a string');\n        }\n\n        return preg_replace_callback(\n            '/(?:[^a-zA-Z0-9_\\-\\.~!\\$&\\'\\(\\)\\*\\+,;=]+|%(?![A-Fa-f0-9]{2}))/u',\n            fn($match) => rawurlencode((string) $match[0]),\n            $info\n        ) ?? '';\n    }\n\n    /**\n     * @param string $host\n     * @return string\n     * @throws InvalidArgumentException If the host is invalid.\n     */\n    public static function filterHost($host)\n    {\n        if (!is_string($host)) {\n            throw new InvalidArgumentException('Uri host must be a string');","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/getgrav/grav/blob/6040efed04efa69b8209448ed81308e7c24147c2/system/src/Grav/Framework/Uri/UriPartsFilter.php#L30-L66","documentation":"UriPartsFilter::filterUserInfo() percent-encodes the user/password component and asserts the input is a string, throwing InvalidArgumentException otherwise. It backs AbstractUri::withUserInfo() and Grav\\Common\\Uri's user-info helper (which already null-guards). Because withUserInfo() declares native string parameters, this exception effectively fires only on direct filterUserInfo() calls with a non-string, non-null value.","triggerScenarios":"Calling UriPartsFilter::filterUserInfo() directly with an int/array (e.g. user id from a database instead of the username string); userinfo extracted from a hand-parsed URL where the user segment was captured as an array by a greedy regex.","commonSituations":"Proxy/auth middleware building a URI from database or header fields where a numeric user id or array sneaks into the username slot.","solutions":["Pass the string form: `(string) $username` after confirming it is scalar","Use the typed `$uri->withUserInfo($user, $pass)` API, which coerces earlier and fails with a clearer TypeError","Null-check before calling if the field is optional"],"exampleFix":"// before\n$user = UriPartsFilter::filterUserInfo($row['user_id']); // int 42 -> throws\n\n// after\n$user = UriPartsFilter::filterUserInfo((string) $row['username']);","handlingStrategy":"type-guard","validationCode":"$user = is_scalar($authUser) ? (string) $authUser : '';","typeGuard":"function isUserInfoString(mixed $value): bool\n{\n    return is_string($value);\n}","tryCatchPattern":null,"preventionTips":["Cast database ids to their string form before using them as URI userinfo","Use the typed withUserInfo() boundary so failures surface as clear TypeErrors","Null-check optional userinfo before filtering"],"tags":["uri","type-check","validation","userinfo"],"backgroundTag":"type-validation-failed","analyzedSha":"6040efed04efa69b8209448ed81308e7c24147c2","analyzedAt":"2026-08-17T05:07:31.593Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}