symfony/http-foundation · error · BadRequestException
Invalid URI: A URI cannot contain CR/LF/TAB characters.
Error message
Invalid URI: A URI cannot contain CR/LF/TAB characters.
What it means
Request::create() forbids raw CR (\r), LF (\n) and TAB characters anywhere in the URI. These characters enable HTTP request-splitting/response-splitting (CRLF injection) when the URI is written into the request line or headers, so the library throws BadRequestException ('Invalid URI: A URI cannot contain CR/LF/TAB characters.') — an HTTP 400 — instead of building the Request.
Solutions
- Trim and strip control characters from the URI before use: trim($uri), or reject if preg_match('/[\r\n\t]/', $uri).
- URL-encode dynamic parts (rawurlencode) so CR/LF/TAB become %0D/%0A/%09 instead of raw bytes.
- Fix the upstream data source that carries trailing newlines (e.g. rtrim() values read from files or DB rows).
- Catch BadRequestException for untrusted URLs and return a 400; log the attempt as a likely CRLF-injection probe.
Example fix
// before
$request = Request::create($userProvidedUri); // may contain "\r\n"
// after
if (preg_match('/[\r\n\t]/', $userProvidedUri)) {
throw new \InvalidArgumentException('URI contains control characters');
}
$request = Request::create(trim($userProvidedUri)); Defensive patterns
Strategy: validation
Validate before calling
function isCrlfSafe(string $uri): bool
{
return strlen($uri) === strcspn($uri, "\r\n\t");
}
if (!isCrlfSafe($uri)) {
throw new \InvalidArgumentException('URI contains CR/LF/TAB');
}
$request = Request::create($uri); Type guard
function isSingleLineUri(mixed $uri): bool
{
return \is_string($uri) && '' !== $uri && !preg_match('/[\x00-\x1F]/', $uri);
} Try / catch
try {
$request = Request::create($uri);
} catch (BadRequestException $e) {
if (str_contains($e->getMessage(), 'CR/LF/TAB')) {
// Likely CRLF-injection attempt; do not retry with the same input
return new Response('Invalid characters in URL.', 400);
}
throw $e;
} Prevention
- rtrim()/trim() any URI read from files, databases, or headers before use
- rawurlencode() every user-controlled value interpolated into a URL
- Treat CR/LF in a URL from a client as an injection attempt and log it
- Run header-injection test cases (\r\nHost: evil) against redirect and proxy endpoints
When it happens
Trigger: Calling Request::create() with a URI containing "\r", "\n" or "\t", typically from unchecked user input interpolated into a URL (e.g. '/redirect?url=http://x\r\nHost: evil') — strlen($uri) !== strcspn($uri, "\r\n\t") at Request.php:410-412. Multi-line pasted URLs and log-injected payloads also trigger it.
Common situations: Redirect endpoints taking a full URL from a query parameter; header injection attempts caught by the framework; URLs read from files or databases with trailing newlines; templates that concatenate user data into hrefs that are later fed to Request::create().
Understand the failure class
Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.
Related errors
- Invalid URI: Userinfo is malformed.
- Invalid URI: A URI cannot contain a backslash.
- Invalid URI: Host is malformed.
- Invalid URI: A URI must not start nor end with ASCII…
- Invalid Host " ".
AI-assisted analysis of symfony/http-foundation@5aea19cd67 (2026-09-13).
Data as JSON: /api/errors/bb2c0290e63bc671.
Report an issue: GitHub.
Appendix: source
Thrown at Request.php:412
throw new BadRequestException('Invalid URI: Scheme is malformed.');
}
if (false === $components = parse_url(\strlen($uri) !== strcspn($uri, '?#') ? $uri : $uri.'#')) {
throw new BadRequestException('Invalid URI.');
}
$part = ($components['user'] ?? '').':'.($components['pass'] ?? '');
if (':' !== $part && \strlen($part) !== strcspn($part, '[]')) {
throw new BadRequestException('Invalid URI: Userinfo is malformed.');
}
if (($part = $components['host'] ?? '') && !self::isHostValid($part)) {
throw new BadRequestException('Invalid URI: Host is malformed.');
}
if (false !== ($i = strpos($uri, '\\')) && $i < strcspn($uri, '?#')) {
throw new BadRequestException('Invalid URI: A URI cannot contain a backslash.');
}
if (\strlen($uri) !== strcspn($uri, "\r\n\t")) {
throw new BadRequestException('Invalid URI: A URI cannot contain CR/LF/TAB characters.');
}
if ('' !== $uri && (\ord($uri[0]) <= 32 || \ord($uri[-1]) <= 32)) {
throw new BadRequestException('Invalid URI: A URI must not start nor end with ASCII control characters or spaces.');
}
if (isset($components['host'])) {
$server['SERVER_NAME'] = $components['host'];
$server['HTTP_HOST'] = $components['host'];
}
if (isset($components['scheme'])) {
if ('https' === $components['scheme']) {
$server['HTTPS'] = 'on';
$server['SERVER_PORT'] = 443;
} else {
unset($server['HTTPS']);
$server['SERVER_PORT'] = 80;
}View on GitHub (pinned to 5aea19cd67)