This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Nette HTTP Component - A standalone PHP library providing HTTP abstraction for request/response handling, URL manipulation, session management, and file uploads. Part of the Nette Framework ecosystem but usable independently.
- PHP Version: 8.2 - 8.5
- Package:
nette/http
# Run all tests
vendor/bin/tester tests -s
# Run specific test file
php tests/Http/Request.files.phpt
# Run tests in specific directory
vendor/bin/tester tests/Http -s# Run PHPStan
composer phpstan
# Or directly
vendor/bin/phpstan analyseThe codebase uses a sophisticated immutability strategy:
Request- Immutable HTTP request with single wither methodwithUrl()Response- Mutable for managing response state (headers, cookies, status)UrlImmutable- Immutable URL with wither methods (withHost(),withPath(), etc.)Url- Mutable URL with setters for flexible buildingUrlScript- Extends UrlImmutable with script path information
Design principle: Data objects (Request) are immutable for integrity; state managers (Response, Session) are mutable for practical management.
Input sanitization is mandatory, not optional:
-
RequestFactory sanitizes ALL input:
- Removes invalid UTF-8 sequences
- Strips control characters (except tab, newline, carriage return)
- Validates with regex:
[\x09\x0A\x0D\x20-\x7E\xA0-\x{10FFFF}]
-
Secure defaults everywhere:
- Cookies are
httpOnlyby default SameSite=Laxby default- Session uses strict mode and one-time cookies only
- HTTPS auto-detection via proxy configuration
- Cookies are
-
FileUpload security:
- Content-based MIME detection (not client-provided)
getSanitizedName()removes dangerous characters- Documentation warns against trusting
getUntrustedName()
- Immutable HTTP request representation
- Type-safe access to GET/POST/COOKIE/FILES/headers
- AJAX detection, same-site checking (
_nsscookie, formerlynette-samesite), language detection - Sanitized by RequestFactory before construction
- Origin detection:
getOrigin()returns scheme + host + port for CORS validation - Basic Auth:
getBasicCredentials()returns[user, password]array - File access:
getFile(['my-form', 'details', 'avatar'])accepts array of keys for nested structures - Warning: Browsers don't send URL fragments to the server (
$url->getFragment()returns empty string)
- Mutable HTTP response management
- Header manipulation (set/add/delete)
- Cookie handling with security defaults (use
Response::SameSiteLax,SameSiteStrict,SameSiteNoneconstants) - Redirect, cache control, content-type helpers
- Download support:
sendAsFile('invoice.pdf')triggers browser download dialog - Checks
isSent()to prevent modification after output starts - Cookie domain: If specified, includes subdomains; if omitted, excludes subdomains
- Creates Request from PHP superglobals (
$_GET,$_POST, etc.) - Configurable proxy support (RFC 7239 Forwarded header + X-Forwarded-*)
- URL filters for cleaning malformed URLs
- File upload normalization into FileUpload objects
Url- Mutable URL builder with setters, supportsappendQuery()to add parametersUrlImmutable- Immutable URL with wither methodsresolve($reference)- Resolves relative URLs like a browser (v3.3.2+)withoutUserInfo()- Removes user and password
UrlScript- Request URL with virtual components:baseUrl- Base URL including domain and path to app rootbasePath- Path to application root directoryscriptPath- Path to current scriptrelativePath- Script name relative to basePathrelativeUrl- Everything after baseUrl (query + fragment)pathInfo- Rarely used part after script name
- Static helpers:
Url::isAbsolute($url)- Checks if URL has scheme (v3.3.2+)Url::removeDotSegments($path)- Normalizes path by removing.and..(v3.3.2+)
- All support IDN (via
ext-intl), canonicalization, query manipulation
- Auto-start modes:
smart- Start only when session data is accessed (default)always- Start immediately with applicationnever- Manual start required
- Namespaced sections to prevent naming conflicts
- SessionSection API: Use explicit methods instead of magic properties:
$section->set('userName', 'john')- Write variable$section->get('userName')- Read variable (returns null if missing)$section->remove('userName')- Delete variable$section->set('flash', $message, '30 seconds')- Third parameter sets expiration
- Per-section or per-variable expiration
- Custom session handler support
- Events:
$onStart,$onBeforeWrite- Callbacks invoked after session starts or before write to disk - Session ID management:
regenerateId()generates new ID (e.g., after login for security)
- Safe file upload handling
- Content-based MIME detection (requires
ext-fileinfo) - Image validation and conversion (supports JPEG, PNG, GIF, WebP, AVIF)
- Sanitized filename generation
- Filename methods:
getUntrustedName()- Original browser-provided name (⚠️ never trust!)getSanitizedName()- Safe ASCII-only name[a-zA-Z0-9.-]with correct extensiongetSuggestedExtension()- Extension based on MIME type (v3.2.4+)getUntrustedFullPath()- Full path for directory uploads (PHP 8.1+,⚠️ never trust!)
Two extensions provide auto-wiring:
-
HttpExtension (
src/Bridges/HttpDI/HttpExtension.php)- Registers:
http.requestFactory,http.request,http.response - Configuration: proxy IPs, headers, CSP, X-Frame-Options, cookie defaults
- CSP with nonce: Automatically generates nonce for inline scripts
Use in templates:
http: csp: script-src: [nonce, strict-dynamic, self]
<script n:nonce>...</script>- nonce filled automatically - Cookie defaults:
cookiePath,cookieDomain: domain(includes subdomains),cookieSecure: auto - X-Frame-Options:
frames: SAMEORIGIN(default) orframes: trueto allow all
- Registers:
-
SessionExtension (
src/Bridges/HttpDI/SessionExtension.php)- Registers:
session.session - Configuration:
autoStart: smart|always|never, expiration, handler, all PHPsession.*directives in camelCase - Tracy debugger panel: Enable with
debugger: truein config - Session cookie: Configure separately with
cookieDomain,cookieSamesite: Strict|Lax|None
- Registers:
Every file must start with:
declare(strict_types=1);Heavily uses PHP 8.1+ features:
// Constructor property promotion with readonly
public function __construct(
private readonly UrlScript $url,
private readonly array $post = [],
private readonly string $method = 'GET',
) {
}
// Named arguments
setcookie($name, $value, [
'expires' => $expire ? (int) DateTime::from($expire)->format('U') : 0,
'httponly' => $httpOnly ?? true,
'samesite' => $sameSite ?? self::SameSiteLax,
]);
// First-class callables
Nette\Utils\Callback::invokeSafe(
'session_start',
[['read_and_close' => $this->readAndClose]],
fn(string $message) => throw new Exception($message)
);Uses @property-read magic properties with Nette's SmartObject trait:
/**
* @property-read UrlScript $url
* @property-read array $query
* @property-read string $method
*/
class Request implements IRequest
{
use Nette\SmartObject;
}Test files use .phpt extension and follow this pattern:
<?php
declare(strict_types=1);
use Nette\Http\Url;
use Tester\Assert;
require __DIR__ . '/../bootstrap.php';
test('Url canonicalization removes duplicate slashes', function () {
$url = new Url('http://example.com/path//to/../file.txt');
$url->canonicalize();
Assert::same('http://example.com/path/file.txt', (string) $url);
});
test('Url handles IDN domains', function () {
$url = new Url('https://xn--tst-qla.de/');
$url->canonicalize();
Assert::same('https://täst.de/', (string) $url);
});The test() helper is defined in tests/bootstrap.php.
- Read existing code first - Understand patterns before modifying
- Security first - Consider injection, XSS, CSRF implications
- Maintain immutability contracts - Don't add setters to immutable classes
- Test thoroughly - Add
.phpttest files intests/Http/ - Check Windows compatibility - Tests run on Windows in CI
Proxy detection - Use RequestFactory's setProxy() for trusted proxy IPs:
$factory->setProxy(['127.0.0.1', '::1']);URL filtering - Clean malformed URLs via urlFilters:
// Remove spaces from path
$factory->urlFilters['path']['%20'] = '';
// Remove trailing punctuation
$factory->urlFilters['url']['[.,)]$'] = '';Cookie security - Response uses secure defaults:
// httpOnly=true, sameSite=Lax by default
$response->setCookie('name', 'value', '1 hour');Session sections - Namespace session data with explicit methods:
$section = $session->getSection('cart');
$section->set('items', []);
$section->setExpiration('20 minutes');
// Per-variable expiration
$section->set('flash', $message, '30 seconds');
// Events
$session->onBeforeWrite[] = function () use ($section) {
$section->set('lastSaved', time());
};GitHub Actions runs:
-
Tests (
.github/workflows/tests.yml):- Matrix: Ubuntu/Windows/macOS × PHP 8.1-8.5 × php/php-cgi
- Lowest dependencies test
- Code coverage with Coveralls
-
Static Analysis (
.github/workflows/static-analysis.yml):- PHPStan level 5 (informative only)
-
Coding Style (
.github/workflows/coding-style.yml):- Nette Coding Standard (PSR-12 based)
- Single Responsibility - Each class has one clear purpose
- Dependency Injection - Constructor injection, no service locators
- Type Safety - Everything typed (properties, parameters, returns)
- Fail Fast - Validation at boundaries, exceptions for errors
- Framework Optional - Works standalone or with Nette DI
- Browser behavior - Browsers don't send URL fragments or Origin header for same-origin GET requests
- Proxy configuration critical - Required for correct IP detection and HTTPS detection
- Session auto-start modes:
smart- Starts only when$section->get()/set()is called (default)always- Starts immediately on application bootstrapnever- Must call$session->start()manually
- URL encoding nuances - Respects PHP's
arg_separator.inputfor query parsing - FileUpload validation - Always check
hasFile()andisOk()before processing - UrlScript virtual components - Generated by RequestFactory, understand baseUrl vs basePath distinction
- CSP nonce in templates - Use
<script n:nonce>for automatic nonce insertion with CSP headers