symfony/http-foundation · error · BadRequestException
Invalid URI: A URI must not start nor end with ASCII…
Error message
Invalid URI: A URI must not start nor end with ASCII control characters or spaces.
What it means
Request::create() requires the URI to neither start nor end with an ASCII character <= 32 (control characters or spaces). Leading/trailing whitespace or control bytes usually indicate unsanitized input and can be used to smuggle alternate request lines; the library throws BadRequestException ('Invalid URI: A URI must not start nor end with ASCII control characters or spaces.') producing HTTP 400.
Solutions
- Trim the URI before passing it: Request::create(trim($uri)).
- Validate up front: reject if $uri === trim($uri, " \t\n\r\0\x0B") is false, or if ord($uri[0]) <= 32.
- Fix the source that appends/prepends whitespace (e.g. rtrim() file/DB values, remove quotes from CLI input).
- Catch BadRequestException when the URI is untrusted and return a 400 to the client.
Example fix
// before
$request = Request::create(' http://example.com/path ');
// after
$uri = trim(' http://example.com/path ');
if ('' !== $uri && (ord($uri[0]) <= 32 || ord($uri[-1]) <= 32)) {
throw new \InvalidArgumentException('URI has leading/trailing control chars');
}
$request = Request::create($uri); Defensive patterns
Strategy: validation
Validate before calling
function hasCleanEnds(string $uri): bool
{
return '' === $uri || (ord($uri[0]) > 32 && ord($uri[-1]) > 32);
}
$uri = trim($uri);
if (!hasCleanEnds($uri)) {
throw new \InvalidArgumentException('URI has leading/trailing control chars');
}
$request = Request::create($uri); Type guard
function isTrimmedUri(mixed $uri): bool
{
return \is_string($uri) && $uri === trim($uri, " \t\n\r\0\x0B") && '' !== $uri;
} Try / catch
try {
$request = Request::create($uri);
} catch (BadRequestException $e) {
if (str_contains($e->getMessage(), 'control characters or spaces')) {
$request = Request::create(trim($uri));
} else {
throw $e;
}
} Prevention
- trim() every URI coming from logs, CLI args, emails, or form fields before use
- Store and transport URLs without surrounding whitespace; fix producers, not just consumers
- Check for NUL bytes (ord <= 32) which trim() also removes — belt and suspenders with an explicit check
- Add a normalization helper (trim + control-char scan) applied at the single point where external URIs enter the system
When it happens
Trigger: Calling Request::create() with a URI that begins or ends with a space, NUL, or other byte with ord() <= 32, e.g. ' http://host/' or "http://host/\0" — checked at Request.php:413-415 via ord($uri[0]) <= 32 || ord($uri[-1]) <= 32 on a non-empty URI. Empty-string URIs are exempt from this specific check.
Common situations: URIs pasted with surrounding whitespace from logs, emails, or CLI arguments; values read from files/DB with trailing newline (handled by the previous check) or trailing spaces; fuzzing/security tests prepending NUL bytes or spaces to bypass filters.
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: Host is malformed.
- Invalid URI: A URI cannot contain a backslash.
- Invalid URI: Scheme is malformed.
- Invalid URI.
AI-assisted analysis of symfony/http-foundation@5aea19cd67 (2026-09-13).
Data as JSON: /api/errors/86653833895277a0.
Report an issue: GitHub.
Appendix: source
Thrown at Request.php:415
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;
}
}
if (isset($components['port'])) {View on GitHub (pinned to 5aea19cd67)