Skip to content

Commit 4dbbb34

Browse files
committed
added CLAUDE.md
1 parent c8de0c0 commit 4dbbb34

File tree

1 file changed

+356
-0
lines changed

1 file changed

+356
-0
lines changed

CLAUDE.md

Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
**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.
8+
9+
- **PHP Version**: 8.1 - 8.5
10+
- **Package**: `nette/http`
11+
12+
## Essential Commands
13+
14+
### Running Tests
15+
16+
```bash
17+
# Run all tests
18+
vendor/bin/tester tests -s
19+
20+
# Run specific test file
21+
php tests/Http/Request.files.phpt
22+
23+
# Run tests in specific directory
24+
vendor/bin/tester tests/Http -s
25+
26+
# Run with coverage (requires phpdbg)
27+
vendor/bin/tester tests -p phpdbg --coverage coverage.xml --coverage-src src
28+
```
29+
30+
**IMPORTANT**: On Windows, use `php.bat` instead of `php` command. Avoid bash shell for running tests - use Windows-native execution.
31+
32+
### Static Analysis
33+
34+
```bash
35+
# Run PHPStan
36+
composer phpstan
37+
38+
# Or directly
39+
vendor/bin/phpstan analyse
40+
```
41+
42+
### Development
43+
44+
```bash
45+
# Install dependencies
46+
composer install
47+
48+
# Shortcuts defined in composer.json
49+
composer phpstan
50+
composer tester
51+
```
52+
53+
## Core Architecture
54+
55+
### Immutability Pattern
56+
57+
The codebase uses a sophisticated immutability strategy:
58+
59+
- **`Request`** - Immutable HTTP request with single wither method `withUrl()`
60+
- **`Response`** - Mutable for managing response state (headers, cookies, status)
61+
- **`UrlImmutable`** - Immutable URL with wither methods (`withHost()`, `withPath()`, etc.)
62+
- **`Url`** - Mutable URL with setters for flexible building
63+
- **`UrlScript`** - Extends UrlImmutable with script path information
64+
65+
**Design principle**: Data objects (Request) are immutable for integrity; state managers (Response, Session) are mutable for practical management.
66+
67+
### Security-First Design
68+
69+
Input sanitization is mandatory, not optional:
70+
71+
1. **RequestFactory** sanitizes ALL input:
72+
- Removes invalid UTF-8 sequences
73+
- Strips control characters (except tab, newline, carriage return)
74+
- Validates with regex: `[\x09\x0A\x0D\x20-\x7E\xA0-\x{10FFFF}]`
75+
76+
2. **Secure defaults everywhere**:
77+
- Cookies are `httpOnly` by default
78+
- `SameSite=Lax` by default
79+
- Session uses strict mode and one-time cookies only
80+
- HTTPS auto-detection via proxy configuration
81+
82+
3. **FileUpload** security:
83+
- Content-based MIME detection (not client-provided)
84+
- `getSanitizedName()` removes dangerous characters
85+
- Documentation warns against trusting `getUntrustedName()`
86+
87+
### Key Components
88+
89+
#### Request (`src/Http/Request.php`)
90+
- Immutable HTTP request representation
91+
- Type-safe access to GET/POST/COOKIE/FILES/headers
92+
- AJAX detection, same-site checking (`_nss` cookie, formerly `nette-samesite`), language detection
93+
- Sanitized by RequestFactory before construction
94+
- **Origin detection**: `getOrigin()` returns scheme + host + port for CORS validation
95+
- **Basic Auth**: `getBasicCredentials()` returns `[user, password]` array
96+
- **File access**: `getFile(['my-form', 'details', 'avatar'])` accepts array of keys for nested structures
97+
- **Warning**: Browsers don't send URL fragments to the server (`$url->getFragment()` returns empty string)
98+
99+
#### Response (`src/Http/Response.php`)
100+
- Mutable HTTP response management
101+
- Header manipulation (set/add/delete)
102+
- Cookie handling with security defaults (use `Response::SameSiteLax`, `SameSiteStrict`, `SameSiteNone` constants)
103+
- Redirect, cache control, content-type helpers
104+
- **Download support**: `sendAsFile('invoice.pdf')` triggers browser download dialog
105+
- Checks `isSent()` to prevent modification after output starts
106+
- **Cookie domain**: If specified, includes subdomains; if omitted, excludes subdomains
107+
108+
#### RequestFactory (`src/Http/RequestFactory.php`)
109+
- Creates Request from PHP superglobals (`$_GET`, `$_POST`, etc.)
110+
- Configurable proxy support (RFC 7239 Forwarded header + X-Forwarded-*)
111+
- URL filters for cleaning malformed URLs
112+
- File upload normalization into FileUpload objects
113+
114+
#### URL Classes
115+
- **`Url`** - Mutable URL builder with setters, supports `appendQuery()` to add parameters
116+
- **`UrlImmutable`** - Immutable URL with wither methods
117+
- `resolve($reference)` - Resolves relative URLs like a browser (v3.3.2+)
118+
- `withoutUserInfo()` - Removes user and password
119+
- **`UrlScript`** - Request URL with virtual components:
120+
- `baseUrl` - Base URL including domain and path to app root
121+
- `basePath` - Path to application root directory
122+
- `scriptPath` - Path to current script
123+
- `relativePath` - Script name relative to basePath
124+
- `relativeUrl` - Everything after baseUrl (query + fragment)
125+
- `pathInfo` - Rarely used part after script name
126+
- **Static helpers**:
127+
- `Url::isAbsolute($url)` - Checks if URL has scheme (v3.3.2+)
128+
- `Url::removeDotSegments($path)` - Normalizes path by removing `.` and `..` (v3.3.2+)
129+
- All support IDN (via `ext-intl`), canonicalization, query manipulation
130+
131+
#### Session (`src/Http/Session.php` + `SessionSection.php`)
132+
- **Auto-start modes**:
133+
- `smart` - Start only when session data is accessed (default)
134+
- `always` - Start immediately with application
135+
- `never` - Manual start required
136+
- Namespaced sections to prevent naming conflicts
137+
- **SessionSection API**: Use explicit methods instead of magic properties:
138+
- `$section->set('userName', 'john')` - Write variable
139+
- `$section->get('userName')` - Read variable (returns null if missing)
140+
- `$section->remove('userName')` - Delete variable
141+
- `$section->set('flash', $message, '30 seconds')` - Third parameter sets expiration
142+
- Per-section or per-variable expiration
143+
- Custom session handler support
144+
- **Events**: `$onStart`, `$onBeforeWrite` - Callbacks invoked after session starts or before write to disk
145+
- **Session ID management**: `regenerateId()` generates new ID (e.g., after login for security)
146+
147+
#### FileUpload (`src/Http/FileUpload.php`)
148+
- Safe file upload handling
149+
- Content-based MIME detection (requires `ext-fileinfo`)
150+
- Image validation and conversion (supports JPEG, PNG, GIF, WebP, AVIF)
151+
- Sanitized filename generation
152+
- **Filename methods**:
153+
- `getUntrustedName()` - Original browser-provided name (⚠️ never trust!)
154+
- `getSanitizedName()` - Safe ASCII-only name `[a-zA-Z0-9.-]` with correct extension
155+
- `getSuggestedExtension()` - Extension based on MIME type (v3.2.4+)
156+
- `getUntrustedFullPath()` - Full path for directory uploads (PHP 8.1+, ⚠️ never trust!)
157+
158+
### Nette DI Integration
159+
160+
Two extensions provide auto-wiring:
161+
162+
1. **HttpExtension** (`src/Bridges/HttpDI/HttpExtension.php`)
163+
- **Registers**: `http.requestFactory`, `http.request`, `http.response`
164+
- **Configuration**: proxy IPs, headers, CSP, X-Frame-Options, cookie defaults
165+
- **CSP with nonce**: Automatically generates nonce for inline scripts
166+
```neon
167+
http:
168+
csp:
169+
script-src: [nonce, strict-dynamic, self]
170+
```
171+
Use in templates: `<script n:nonce>...</script>` - nonce filled automatically
172+
- **Cookie defaults**: `cookiePath`, `cookieDomain: domain` (includes subdomains), `cookieSecure: auto`
173+
- **X-Frame-Options**: `frames: SAMEORIGIN` (default) or `frames: true` to allow all
174+
175+
2. **SessionExtension** (`src/Bridges/HttpDI/SessionExtension.php`)
176+
- **Registers**: `session.session`
177+
- **Configuration**: `autoStart: smart|always|never`, expiration, handler, all PHP `session.*` directives in camelCase
178+
- **Tracy debugger panel**: Enable with `debugger: true` in config
179+
- **Session cookie**: Configure separately with `cookieDomain`, `cookieSamesite: Strict|Lax|None`
180+
181+
## Code Conventions
182+
183+
### Strict PHP Standards
184+
185+
Every file must start with:
186+
```php
187+
declare(strict_types=1);
188+
```
189+
190+
### Modern PHP Features
191+
192+
Heavily uses PHP 8.1+ features:
193+
194+
```php
195+
// Constructor property promotion with readonly
196+
public function __construct(
197+
private readonly UrlScript $url,
198+
private readonly array $post = [],
199+
private readonly string $method = 'GET',
200+
) {
201+
}
202+
203+
// Named arguments
204+
setcookie($name, $value, [
205+
'expires' => $expire ? (int) DateTime::from($expire)->format('U') : 0,
206+
'httponly' => $httpOnly ?? true,
207+
'samesite' => $sameSite ?? self::SameSiteLax,
208+
]);
209+
210+
// First-class callables
211+
Nette\Utils\Callback::invokeSafe(
212+
'session_start',
213+
[['read_and_close' => $this->readAndClose]],
214+
fn(string $message) => throw new Exception($message)
215+
);
216+
```
217+
218+
### Property Documentation with SmartObject
219+
220+
Uses `@property-read` magic properties with Nette's SmartObject trait:
221+
222+
```php
223+
/**
224+
* @property-read UrlScript $url
225+
* @property-read array $query
226+
* @property-read string $method
227+
*/
228+
class Request implements IRequest
229+
{
230+
use Nette\SmartObject;
231+
}
232+
```
233+
234+
### Testing with Nette Tester
235+
236+
Test files use `.phpt` extension and follow this pattern:
237+
238+
```php
239+
<?php
240+
241+
declare(strict_types=1);
242+
243+
use Nette\Http\Url;
244+
use Tester\Assert;
245+
246+
require __DIR__ . '/../bootstrap.php';
247+
248+
test('Url canonicalization removes duplicate slashes', function () {
249+
$url = new Url('http://example.com/path//to/../file.txt');
250+
$url->canonicalize();
251+
Assert::same('http://example.com/path/file.txt', (string) $url);
252+
});
253+
254+
test('Url handles IDN domains', function () {
255+
$url = new Url('https://xn--tst-qla.de/');
256+
$url->canonicalize();
257+
Assert::same('https://täst.de/', (string) $url);
258+
});
259+
```
260+
261+
The `test()` helper is defined in `tests/bootstrap.php`.
262+
263+
## Development Guidelines
264+
265+
### When Adding Features
266+
267+
1. **Read existing code first** - Understand patterns before modifying
268+
2. **Security first** - Consider injection, XSS, CSRF implications
269+
3. **Maintain immutability contracts** - Don't add setters to immutable classes
270+
4. **Test thoroughly** - Add `.phpt` test files in `tests/Http/`
271+
5. **Check Windows compatibility** - Tests run on Windows in CI
272+
273+
### Common Patterns
274+
275+
**Proxy detection** - Use RequestFactory's `setProxy()` for trusted proxy IPs:
276+
```php
277+
$factory->setProxy(['127.0.0.1', '::1']);
278+
```
279+
280+
**URL filtering** - Clean malformed URLs via urlFilters:
281+
```php
282+
// Remove spaces from path
283+
$factory->urlFilters['path']['%20'] = '';
284+
285+
// Remove trailing punctuation
286+
$factory->urlFilters['url']['[.,)]$'] = '';
287+
```
288+
289+
**Cookie security** - Response uses secure defaults:
290+
```php
291+
// httpOnly=true, sameSite=Lax by default
292+
$response->setCookie('name', 'value', '1 hour');
293+
```
294+
295+
**Session sections** - Namespace session data with explicit methods:
296+
```php
297+
$section = $session->getSection('cart');
298+
$section->set('items', []);
299+
$section->setExpiration('20 minutes');
300+
301+
// Per-variable expiration
302+
$section->set('flash', $message, '30 seconds');
303+
304+
// Events
305+
$session->onBeforeWrite[] = function () use ($section) {
306+
$section->set('lastSaved', time());
307+
};
308+
```
309+
310+
### Known Issues & Future Plans
311+
312+
See `TODO.txt` for planned breaking changes:
313+
314+
- Remove deprecated `user` and `password` from URL classes
315+
- Rethink UrlScript structure (scriptPath, basePath handling)
316+
- Remove magic properties in favor of explicit methods
317+
- Rename `UrlImmutable``Url`, `Url``UrlBuilder`
318+
- Session improvements: remove `warnOnUndefined`, deprecate side-effect `__get()`
319+
- FileUpload improvements for non-uploaded files
320+
- Middleware support
321+
322+
## CI/CD Pipeline
323+
324+
GitHub Actions runs:
325+
326+
1. **Tests** (`.github/workflows/tests.yml`):
327+
- Matrix: Ubuntu/Windows/macOS × PHP 8.1-8.5 × php/php-cgi
328+
- Lowest dependencies test
329+
- Code coverage with Coveralls
330+
331+
2. **Static Analysis** (`.github/workflows/static-analysis.yml`):
332+
- PHPStan level 5 (informative only)
333+
334+
3. **Coding Style** (`.github/workflows/coding-style.yml`):
335+
- Nette Coding Standard (PSR-12 based)
336+
337+
## Architecture Principles
338+
339+
- **Single Responsibility** - Each class has one clear purpose
340+
- **Dependency Injection** - Constructor injection, no service locators
341+
- **Type Safety** - Everything typed (properties, parameters, returns)
342+
- **Fail Fast** - Validation at boundaries, exceptions for errors
343+
- **Framework Optional** - Works standalone or with Nette DI
344+
345+
## Important Notes
346+
347+
- **Browser behavior** - Browsers don't send URL fragments or Origin header for same-origin GET requests
348+
- **Proxy configuration critical** - Required for correct IP detection and HTTPS detection
349+
- **Session auto-start modes**:
350+
- `smart` - Starts only when `$section->get()`/`set()` is called (default)
351+
- `always` - Starts immediately on application bootstrap
352+
- `never` - Must call `$session->start()` manually
353+
- **URL encoding nuances** - Respects PHP's `arg_separator.input` for query parsing
354+
- **FileUpload validation** - Always check `hasFile()` and `isOk()` before processing
355+
- **UrlScript virtual components** - Generated by RequestFactory, understand baseUrl vs basePath distinction
356+
- **CSP nonce in templates** - Use `<script n:nonce>` for automatic nonce insertion with CSP headers

0 commit comments

Comments
 (0)