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

  1. Trim and strip control characters from the URI before use: trim($uri), or reject if preg_match('/[\r\n\t]/', $uri).
  2. URL-encode dynamic parts (rawurlencode) so CR/LF/TAB become %0D/%0A/%09 instead of raw bytes.
  3. Fix the upstream data source that carries trailing newlines (e.g. rtrim() values read from files or DB rows).
  4. 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

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


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)