{"record":{"id":"04303a219a17419b","repo":"getgrav/grav","slug":"uri-query-string-and-fragment-must-be-a-string","errorCode":null,"errorMessage":"Uri query string and fragment must be a string","messagePattern":"Uri query string and fragment must be a string","errorType":"validation","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"error","filePath":"system/src/Grav/Framework/Uri/UriPartsFilter.php","lineNumber":130,"sourceCode":"\n        return preg_replace_callback(\n            '/(?:[^a-zA-Z0-9_\\-\\.~:@&=\\+\\$,\\/;%]+|%(?![A-Fa-f0-9]{2}))/u',\n            fn($match) => rawurlencode((string) $match[0]),\n            $path\n        ) ?? '';\n    }\n\n    /**\n     * Filters the query string or fragment of a URI.\n     *\n     * @param string $query The raw uri query string.\n     * @return string The percent-encoded query string.\n     * @throws InvalidArgumentException If the query is invalid.\n     */\n    public static function filterQueryOrFragment($query)\n    {\n        if (!is_string($query)) {\n            throw new InvalidArgumentException('Uri query string and fragment 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            $query\n        ) ?? '';\n    }\n}\n","sourceCodeStart":112,"sourceCodeEnd":140,"githubUrl":"https://github.com/getgrav/grav/blob/6040efed04efa69b8209448ed81308e7c24147c2/system/src/Grav/Framework/Uri/UriPartsFilter.php#L112-L140","documentation":"UriPartsFilter::filterQueryOrFragment() percent-encodes a query string or fragment and asserts the input is a string, throwing InvalidArgumentException otherwise. One filter serves both withQuery() and withFragment() as well as Grav\\Common\\Uri's query helper. As with the other filters, the native string parameters on the with*() methods mean this throw appears chiefly on direct calls passing null/array values.","triggerScenarios":"Calling filterQueryOrFragment() directly with `$_GET['q']` when the client sent `?q[]=1` (array); passing null from an optional fragment; feeding an http_build_query() result that was overwritten by an array variable.","commonSituations":"Search/query parameters consumed without type validation; array-parameter injection (`?x[]=`) reaching code that filters the raw value.","solutions":["Validate the raw parameter: `is_string($q) ? ... : ''`","Build query strings with http_build_query() from arrays rather than filtering raw input","Use the typed `$uri->withQuery($query)` / `withFragment($fragment)` APIs"],"exampleFix":"// before\n$query = UriPartsFilter::filterQueryOrFragment($_GET['q']); // ?q[]=x -> array -> throws\n\n// after\n$raw = $_GET['q'] ?? '';\n$query = UriPartsFilter::filterQueryOrFragment(is_string($raw) ? $raw : '');","handlingStrategy":"type-guard","validationCode":"$query = $_GET['q'] ?? '';\nif (!is_string($query)) {\n    $query = ''; // or reject: array syntax (?q[]=) must not reach the filter\n}","typeGuard":"function isQueryStringString(mixed $value): bool\n{\n    return is_string($value);\n}","tryCatchPattern":null,"preventionTips":["Reject array syntax parameters at the request boundary for fields used as query strings","Build queries from arrays with http_build_query() instead of filtering raw input","Use the typed withQuery()/withFragment() APIs"],"tags":["uri","query-string","fragment","type-check","validation"],"backgroundTag":"type-validation-failed","analyzedSha":"6040efed04efa69b8209448ed81308e7c24147c2","analyzedAt":"2026-08-17T05:07:31.593Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}