| 35 |
|| ---- || |
| 36 |
|| === Constructor === || |
| 37 |
|| %%php |
| 38 |
public function __construct(&$db) |
| 39 |
%% || |
| 40 |
|| **Purpose:** Initializes the Http object and sets up HTTP session handling. || |
| 41 |
|| **Parameters:** || |
| 42 |
|| - ##$db## - Database object reference || |
| 43 |
|| **Initialization Steps:** || |
| 44 |
|| 1. Stores database reference || |
| 45 |
|| 2. Extracts and normalizes REQUEST_URI || |
| 46 |
|| 3. Detects TLS/HTTPS session status || |
| 47 |
|| 4. Determines client's real IP address || |
| 48 |
|| 5. Sets up TLS mark cookie name || |
| 49 |
|| 6. Enforces TLS session upgrade if needed || |
| 50 |
|| **Example:** || |
| 51 |
|| %%php |
| 52 |
$http = new Http($db); |
| 53 |
%% || |
| 54 |
|| ---- || |
| 55 |
|| === Core Methods === || |
| 56 |
|| ==== Session Management ==== || |
| 57 |
|| ===== ##session($route): void## ===== || |
| 58 |
|| Initializes the session handler (file-based or database-based). || |
| 59 |
|| **Parameters:** || |
| 60 |
|| - ##$route## (int) - Routing flag: || |
| 61 |
|| - Bit 2 (##$route & 2##): Enable static mode for files/freecap (disables replay prevention and ID regeneration) || |
| 62 |
|| **Features:** || |
| 63 |
|| - Selects storage backend (file or database) || |
| 64 |
|| - Configures cookie settings (security, path, httponly) || |
| 65 |
|| - Binds IP and TLS validation || |
| 66 |
|| - Recovers diagnostic logs from previous session || |
| 67 |
|| **Example:** || |
| 68 |
|| %%php |
| 69 |
$http->session(0); // Normal session |
| 70 |
$http->session(2); // Static file serving mode |
| 71 |
%% || |
| 72 |
|| ---- || |
| 73 |
|| ==== Caching System ==== || |
| 74 |
|| ===== ##check_cache($page, $method): void## ===== || |
| 75 |
|| Determines if a page can be cached and prepares the cache check. || |
| 76 |
|| **Parameters:** || |
| 77 |
|| - ##$page## (string) - Page name to cache || |
| 78 |
|| - ##$method## (string) - Request method/action (e.g., 'show', 'edit') || |
| 79 |
|| **Caching Rules:** || |
| 80 |
|| - ✅ Enabled for GET requests only || |
| 81 |
|| - ✅ Disabled for POST requests || |
| 82 |
|| - ❌ Never cached for 'edit' or 'watch' methods || |
| 83 |
|| - ✅ Only cached for anonymous users (no logged-in users) || |
| 84 |
|| **Example:** || |
| 85 |
|| %%php |
| 86 |
$http->check_cache('HomePage', 'show'); |
| 87 |
%% || |
| 88 |
|| ---- || |
| 89 |
|| ===== ##store_cache(): void## ===== || |
| 90 |
|| Saves the generated page content to cache file. || |
| 91 |
|| **Features:** || |
| 92 |
|| - Retrieves output buffer content || |
| 93 |
|| - Saves to cache file with proper permissions || |
| 94 |
|| - Records cache metadata in database || |
| 95 |
|| - Only executes if caching flag is set and user is anonymous || |
| 96 |
|| **Example:** || |
| 97 |
|| %%php |
| 98 |
// Called at end of page rendering |
| 99 |
$http->store_cache(); |
| 100 |
%% || |
| 101 |
|| ---- || |
| 102 |
|| ===== ##invalidate_page($page): int## ===== || |
| 103 |
|| Invalidates all cached versions of a page. || |
| 104 |
|| **Parameters:** || |
| 105 |
|| - ##$page## (string) - Page name to invalidate || |
| 106 |
|| **Returns:** || |
| 107 |
|| - Number of cache entries invalidated || |
| 108 |
|| **Process:** || |
| 109 |
|| 1. Finds all cached versions (different methods/languages) || |
| 110 |
|| 2. Touches files to past timestamp (faster than deletion) || |
| 111 |
|| 3. Removes entries from cache metadata table || |
| 112 |
|| 4. Returns count of invalidated caches || |
| 113 |
|| **Example:** || |
| 114 |
|| %%php |
| 115 |
$count = $http->invalidate_page('HomePage'); |
| 116 |
echo "Invalidated $count cache entries"; |
| 117 |
%% || |
| 118 |
|| ---- || |
| 119 |
|| ==== TLS/HTTPS Security ==== || |
| 120 |
|| ===== ##secure_base_url(): void## ===== || |
| 121 |
|| Switches base URL from HTTP to HTTPS. || |
| 122 |
|| **Purpose:** || |
| 123 |
|| - Ensures all subsequent URLs use HTTPS || |
| 124 |
|| - Stores original HTTP URL for fallback || |
| 125 |
|| - Called when TLS session is detected || |
| 126 |
|| **Example:** || |
| 127 |
|| %%php |
| 128 |
$http->secure_base_url(); |
| 129 |
// $db->base_url now uses https:// |
| 130 |
%% || |
| 131 |
|| ---- || |
| 132 |
|| ===== ##ensure_tls($url): void## ===== || |
| 133 |
|| Enforces HTTPS for a specific URL and redirects if necessary. || |
| 134 |
|| **Parameters:** || |
| 135 |
|| - ##$url## (string) - URL to secure || |
| 136 |
|| **Behavior:** || |
| 137 |
|| - If not already HTTPS and TLS is enabled, forces HTTPS redirect || |
| 138 |
|| - Handles both relative and absolute URLs || |
| 139 |
|| - Converts relative URLs using current server name || |
| 140 |
|| **Example:** || |
| 141 |
|| %%php |
| 142 |
$http->ensure_tls('/secure/payment'); |
| 143 |
%% || |
| 144 |
|| ---- || |
| 145 |
|| ==== IP Address Detection ==== || |
| 146 |
|| ===== ##real_ip(): string## (Private) ===== || |
| 147 |
|| Detects client's real IP address accounting for proxies. || |
| 148 |
|| **Proxy Headers Checked (in order):** || |
| 149 |
|| 1. ##HTTP_X_CLUSTER_CLIENT_IP## || |
| 150 |
|| 2. ##HTTP_X_FORWARDED_FOR## (or custom header) || |
| 151 |
|| 3. ##HTTP_CLIENT_IP## || |
| 152 |
|| 4. ##HTTP_X_REMOTE_ADDR## || |
| 153 |
|| 5. ##REMOTE_ADDR## (fallback) || |
| 154 |
|| **Features:** || |
| 155 |
|| - Filters out private/reserved IP ranges || |
| 156 |
|| - Respects configured reverse proxy addresses || |
| 157 |
|| - Returns ##'0.0.0.0'## as fallback || |
| 158 |
|| **Configuration in Database:** || |
| 159 |
|| - ##reverse_proxy_addresses## - Comma/space-separated proxy IPs || |
| 160 |
|| - ##reverse_proxy_header## - Custom header name (default: ##X-Forwarded-For##) || |
| 161 |
|| **Example:** || |
| 162 |
|| %%php |
| 163 |
$client_ip = $http->ip; // e.g., "203.0.113.42" |
| 164 |
%% || |
| 165 |
|| ---- || |
| 166 |
|| ==== HTTPS Detection ==== || |
| 167 |
|| ===== ##tls_session(): bool## (Private) ===== || |
| 168 |
|| Detects if current connection uses HTTPS/TLS. || |
| 169 |
|| **Checks (any being true = HTTPS):** || |
| 170 |
|| - ##$_SERVER['HTTPS']## is 'on' || |
| 171 |
|| - ##$_SERVER['SERVER_PORT']## is 443 || |
| 172 |
|| - ##$_SERVER['HTTP_X_FORWARDED_PROTO']## is 'https' || |
| 173 |
|| - ##$_SERVER['HTTP_X_FORWARDED_SSL']## is 'on' || |
| 174 |
|| - ##$_SERVER['HTTP_X_FORWARDED_PORT']## is 443 || |
| 175 |
|| ---- || |
| 176 |
|| ==== Security Headers ==== || |
| 177 |
|| ===== ##http_security_headers(): void## ===== || |
| 178 |
|| Sets security-related HTTP headers. || |
| 179 |
|| **Headers Set:** || |
| 180 |
|| Header | Purpose | Config Key || |
| 181 |
|| -------- | --------- | ------------ || |
| 182 |
|| Content-Security-Policy | XSS/injection protection | ##csp## || |
| 183 |
|| Permissions-Policy | Control browser features | ##permissions_policy## || |
| 184 |
|| Referrer-Policy | Control referrer information | ##referrer_policy## || |
| 185 |
|| Strict-Transport-Security | Force HTTPS | Auto (TLS only) || |
| 186 |
|| X-Frame-Options | Clickjacking protection | Hardcoded: ##SAMEORIGIN## || |
| 187 |
|| X-Content-Type-Options | MIME sniffing prevention | Hardcoded: ##nosniff## || |
| 188 |
|| **CSP Configuration Options:** || |
| 189 |
|| - ##0## - Disabled || |
| 190 |
|| - ##1## - Default policy (from ##csp.conf##) || |
| 191 |
|| - ##2## - Custom policy (from ##csp_custom.conf##) || |
| 192 |
|| **Example:** || |
| 193 |
|| %%php |
| 194 |
$http->http_security_headers(); |
| 195 |
%% || |
| 196 |
|| ---- || |
| 197 |
|| ==== HTTP Methods ==== || |
| 198 |
|| ===== ##redirect($url, $permanent = false): void## ===== || |
| 199 |
|| Performs an HTTP redirect. || |
| 200 |
|| **Parameters:** || |
| 201 |
|| - ##$url## (string) - Target URL || |
| 202 |
|| - ##$permanent## (bool) - Use 301 (permanent) vs 302 (temporary) || |
| 203 |
|| **Features:** || |
| 204 |
|| - Decodes ##&## entities to prevent broken redirects || |
| 205 |
|| - Only works if headers not yet sent || |
| 206 |
|| - Uses output buffering to work anywhere in page processing || |
| 207 |
|| **Example:** || |
| 208 |
|| %%php |
| 209 |
$http->redirect('http://example.com/new-page', true); // 301 |
| 210 |
$http->redirect('/wiki/HomePage'); // 302 |
| 211 |
%% || |
| 212 |
|| ---- || |
| 213 |
|| ===== ##terminate(): void## ===== || |
| 214 |
|| Safe exit/die with cleanup. || |
| 215 |
|| **Cleanup Operations:** || |
| 216 |
|| - Saves diagnostic logs to session flash data || |
| 217 |
|| - Ends script execution || |
| 218 |
|| **Example:** || |
| 219 |
|| %%php |
| 220 |
$http->terminate(); |
| 221 |
%% || |
| 222 |
|| ---- || |
| 223 |
|| ===== ##status($code): void## ===== || |
| 224 |
|| Sets HTTP response status code. || |
| 225 |
|| **Supported Status Codes:** || |
| 226 |
|| %%php |
| 227 |
200 => 'OK' |
| 228 |
206 => 'Partial Content' |
| 229 |
301 => 'Moved Permanently' |
| 230 |
302 => 'Moved Temporarily' |
| 231 |
304 => 'Not Modified' |
| 232 |
400 => 'Bad Request' |
| 233 |
401 => 'Unauthorized' |
| 234 |
403 => 'Forbidden' |
| 235 |
404 => 'Not Found' |
| 236 |
405 => 'Method Not Allowed' |
| 237 |
409 => 'Conflict' |
| 238 |
410 => 'Gone' |
| 239 |
416 => 'Requested Range Not Satisfiable' |
| 240 |
500 => 'Internal Server Error' |
| 241 |
501 => 'Not Implemented' |
| 242 |
503 => 'Service Unavailable' |
| 243 |
%% || |
| 244 |
|| **Example:** || |
| 245 |
|| %%php |
| 246 |
$http->status(404); // Send 404 Not Found |
| 247 |
%% || |
| 248 |
|| ---- || |
| 249 |
|| ==== Caching Control ==== || |
| 250 |
|| ===== ##no_cache($client_only = true): void## ===== || |
| 251 |
|| Disables caching of the current page. || |
| 252 |
|| **Parameters:** || |
| 253 |
|| - ##$client_only## (bool, default: TRUE) || |
| 254 |
|| - ##TRUE##: Disable browser cache only || |
| 255 |
|| - ##FALSE##: Disable both browser and server cache || |
| 256 |
|| **Headers Set:** || |
| 257 |
|| - ##Last-Modified: <current-time>## (always fresh) || |
| 258 |
|| - ##Cache-Control: no-store## || |
| 259 |
|| **Example:** || |
| 260 |
|| %%php |
| 261 |
$http->no_cache(); // Client-side only |
| 262 |
$http->no_cache(false); // Both client & server |
| 263 |
%% || |
| 264 |
|| ---- || |
| 265 |
|| ===== ##cache_promisc(): void## ===== || |
| 266 |
|| Marks page as publicly cacheable. || |
| 267 |
|| **Headers Set:** || |
| 268 |
|| - ##Cache-Control: public## || |
| 269 |
|| **Example:** || |
| 270 |
|| %%php |
| 271 |
$http->cache_promisc(); |
| 272 |
%% || |
| 273 |
|| ---- || |
| 274 |
|| ==== Language Negotiation ==== || |
| 275 |
|| ===== ##user_agent_language(): string## ===== || |
| 276 |
|| Determines best language based on browser preferences. || |
| 277 |
|| **Features:** || |
| 278 |
|| - Follows RFC 9110 section 12.5.4 (HTTP Accept-Language) || |
| 279 |
|| - Parses ##Accept-Language## header with quality factors || |
| 280 |
|| - Attempts exact match first, then language fallback || |
| 281 |
|| - Falls back to default system language || |
| 282 |
|| **Example Header:** || |
| 283 |
|| %% |
| 284 |
Accept-Language: en-US,en;q=0.9,de;q=0.8 |
| 285 |
%% || |
| 286 |
|| **Returns:** || |
| 287 |
|| - Language code (e.g., 'en', 'en-US', 'de') || |
| 288 |
|| ---- || |
| 289 |
|| ===== ##available_languages($subset = true): array## ===== || |
| 290 |
|| Returns list of available language translations. || |
| 291 |
|| **Parameters:** || |
| 292 |
|| - ##$subset## (bool, default: TRUE) || |
| 293 |
|| - ##TRUE##: Only allowed languages || |
| 294 |
|| - ##FALSE##: All available languages || |
| 295 |
|| **Features:** || |
| 296 |
|| - Scans ##LANG_DIR## for language files || |
| 297 |
|| - Filters by ##allowed_languages## config if set || |
| 298 |
|| - Caches result in session || |
| 299 |
|| - System language always included || |
| 300 |
|| **Returns:** || |
| 301 |
|| - Associative array: ##['en' => 'en', 'de' => 'de', ...]## || |
| 302 |
|| **Example:** || |
| 303 |
|| %%php |
| 304 |
$all_langs = $http->available_languages(false); |
| 305 |
$allowed = $http->available_languages(true); |
| 306 |
%% || |
| 307 |
|| ---- || |
| 308 |
|| ==== File Serving ==== || |
| 309 |
|| ===== ##sendfile($path, $filename = null, $age = null): void## ===== || |
| 310 |
|| Serves files with proper HTTP headers and caching. || |
| 311 |
|| **Parameters:** || |
| 312 |
|| - ##$path## (string) - File path (or HTTP_XXX constant for error pages) || |
| 313 |
|| - ##$filename## (string, optional) - Custom download filename || |
| 314 |
|| - ##$age## (int, optional) - Cache age in days || |
| 315 |
|| **Features:** || |
| 316 |
|| - HTTP range request support (partial file downloads) || |
| 317 |
|| - ETag and Last-Modified conditional requests || |
| 318 |
|| - Proper MIME type detection || |
| 319 |
|| - Content-Security-Policy for special file types || |
| 320 |
|| - Streaming for large files || |
| 321 |
|| - GZip compression for text files || |
| 322 |
|| **Special Paths:** || |
| 323 |
|| %%php |
| 324 |
$http->sendfile(404); // Serves file defined by HTTP_404 constant |
| 325 |
$http->sendfile(403); // Serves file defined by HTTP_403 constant |
| 326 |
%% || |
| 327 |
|| **Example:** || |
| 328 |
|| %%php |
| 329 |
$http->sendfile('uploads/document.pdf', 'my-document.pdf', 30); |
| 330 |
%% || |
| 331 |
|| ---- || |
| 332 |
|| ===== ##mime_type($path): string## ===== || |
| 333 |
|| Returns MIME type for a file. || |
| 334 |
|| **Returns:** || |
| 335 |
|| - MIME type string (e.g., 'application/pdf') || |
| 336 |
|| - Default: ##'application/octet-stream'## || |
| 337 |
|| **Example:** || |
| 338 |
|| %%php |
| 339 |
$mime = $http->mime_type('file.pdf'); // 'application/pdf' |
| 340 |
%% || |
| 341 |
|| ---- || |
| 342 |
|| ===== ##mime_types(): array## (Private) ===== || |
| 343 |
|| Loads and caches MIME types from configuration. || |
| 344 |
|| **Features:** || |
| 345 |
|| - Reads from ##config/mime.types## || |
| 346 |
|| - Caches to ##cache/config/mime.types## || |
| 347 |
|| - Reloads if config is updated || |
| 348 |
|| ---- || |
| 349 |
|| ==== Compression ==== || |
| 350 |
|| ===== ##gzip(): void## ===== || |
| 351 |
|| Compresses HTTP response with gzip/x-gzip. || |
| 352 |
|| **Features:** || |
| 353 |
|| - Manually implements gzip (not relying on zlib.output_compression) || |
| 354 |
|| - Produces correct ##Content-Length## header || |
| 355 |
|| - Only compresses if: || |
| 356 |
|| - 860 bytes < content < 1 MB || |
| 357 |
|| - Client accepts compression || |
| 358 |
|| - Headers not already sent || |
| 359 |
|| **Example:** || |
| 360 |
|| %%php |
| 361 |
$http->gzip(); |
| 362 |
%% || |
| 363 |
|| ---- || |
| 364 |
|| ==== Utility Methods ==== || |
| 365 |
|| ===== ##parse_str($str): array## (Private) ===== || |
| 366 |
|| Parses URL-encoded strings with special character handling. || |
| 367 |
|| **Purpose:** || |
| 368 |
|| - Safely handles special characters in query/form data || |
| 369 |
|| - Converts encoding properly || |
| 370 |
|| **Example:** || |
| 371 |
|| %%php |
| 372 |
$data = $http->parse_str('name=John&age=30'); |
| 373 |
%% || |
| 374 |
|| ---- || |
| 375 |
|| ===== ##request_uri(): string## (Private) ===== || |
| 376 |
|| Extracts and normalizes REQUEST_URI from server. || |
| 377 |
|| **Normalization:** || |
| 378 |
|| - Removes base URL prefix || |
| 379 |
|| - Removes spaces || |
| 380 |
|| - Collapses multiple slashes || |
| 381 |
|| - Removes ##..## path traversal attempts || |
| 382 |
|| - Removes leading/trailing slashes || |
| 383 |
|| ---- || |
| 384 |
|| ===== ##cut_prefix($prefix, $path): string## (Private) ===== || |
| 385 |
|| Removes prefix from path (case-insensitive). || |
| 386 |
|| ---- || |
| 387 |
|| ===== ##get_header_conf($file_name): string## (Private) ===== || |
| 388 |
|| Loads security header configuration from files. || |
| 389 |
|| **Files Supported:** || |
| 390 |
|| - ##csp.conf## / ##csp_custom.conf## || |
| 391 |
|| - ##permissions_policy.conf## / ##permissions_policy_custom.conf## || |
| 392 |
|| ---- || |
| 393 |
|| === Configuration Dependencies === || |
| 394 |
|| The class relies on these database configuration settings: || |
| 395 |
|| Setting | Type | Purpose || |
| 396 |
|| --------- | ------ | --------- || |
| 397 |
|| ##base_url## | string | Wiki's base URL || |
| 398 |
|| ##tls## | bool | Enable HTTPS enforcement || |
| 399 |
|| ##cache## | bool | Enable page caching || |
| 400 |
|| ##cache_ttl## | int | Cache lifetime in seconds || |
| 401 |
|| ##session_store## | int | 1=File, 0=Database || |
| 402 |
|| ##system_seed_hash## | string | Session encryption seed || |
| 403 |
|| ##cookie_prefix## | string | Session cookie prefix || |
| 404 |
|| ##cookie_path## | string | Cookie path || |
| 405 |
|| ##allow_persistent_cookie## | bool | Allow persistent login || |
| 406 |
|| ##session_length## | int | Session lifetime in seconds || |
| 407 |
|| ##reverse_proxy_addresses## | string | Comma/space-separated proxy IPs || |
| 408 |
|| ##reverse_proxy_header## | string | Custom X-Forwarded header || |
| 409 |
|| ##language## | string | Default language code || |
| 410 |
|| ##multilanguage## | bool | Enable language negotiation || |
| 411 |
|| ##allowed_languages## | string | Comma/space-separated allowed langs || |
| 412 |
|| ##enable_security_headers## | bool | Send security headers || |
| 413 |
|| ##csp## | int | CSP setting (0/1/2) || |
| 414 |
|| ##permissions_policy## | int | Permissions-Policy setting (0/1/2) || |
| 415 |
|| ##referrer_policy## | int | Referrer-Policy setting (0-8) || |
| 416 |
|| ---- || |
| 417 |
|| === Constants Used === || |
| 418 |
|| Constant | Type | Purpose || |
| 419 |
|| ---------- | ------ | --------- || |
| 420 |
|| ##IN_WACKO## | bool | Security check (exit if not defined) || |
| 421 |
|| ##CHMOD_SAFE## | int | File permissions for cache files || |
| 422 |
|| ##CHMOD_FILE## | int | File permissions for config cache || |
| 423 |
|| ##CACHE_PAGE_DIR## | string | Page cache directory || |
| 424 |
|| ##CACHE_SESSION_DIR## | string | Session cache directory || |
| 425 |
|| ##CACHE_CONFIG_DIR## | string | Config cache directory || |
| 426 |
|| ##CONFIG_DIR## | string | Configuration directory || |
| 427 |
|| ##LANG_DIR## | string | Language files directory || |
| 428 |
|| ##DAYSECS## | int | Seconds in a day (86400) || |
| 429 |
|| ##HTTP_404## | string | Path to 404 error page || |
| 430 |
|| ##HTTP_403## | string | Path to 403 error page || |
| 431 |
|# |
| 432 |
|
| 433 |
---- |
| 434 |
|
| 435 |
=== Workflow Examples === |
| 436 |
|
| 437 |
==== Example 1: Handling a GET Request ==== |
| 438 |
|
| 439 |
%%php |
| 440 |
// In main wiki entry point |
| 441 |
$http = new Http($db); |
| 442 |
$http->session(0); // Start session |
| 443 |
|
| 444 |
// Check if page can be served from cache |
| 445 |
$http->check_cache('HomePage', 'show'); |
| 446 |
|
| 447 |
// ... render page content ... |
| 448 |
|
| 449 |
// Store rendered page in cache if applicable |
| 450 |
$http->store_cache(); |
| 451 |
|
| 452 |
// Send security headers |
| 453 |
$http->http_security_headers(); |
| 454 |
|
| 455 |
// Possibly compress output |
| 456 |
$http->gzip(); |
| 457 |
%% |
| 458 |
|
| 459 |
==== Example 2: Handling TLS/HTTPS Upgrade ==== |
| 460 |
|
| 461 |
%%php |
| 462 |
$http = new Http($db); // Constructor detects TLS requirement |
| 463 |
// If TLS is enabled and user wasn't in TLS before: |
| 464 |
// - Sets TLS session flag |
| 465 |
// - Marks session with TLS cookie |
| 466 |
// - Redirects to HTTPS version |
| 467 |
%% |
| 468 |
|
| 469 |
==== Example 3: Invalidating Cache After Page Edit ==== |
| 470 |
|
| 471 |
%%php |
| 472 |
// User edits a page |
| 473 |
$http = new Http($db); |
| 474 |
$count = $http->invalidate_page('HomePage'); |
| 475 |
// All cached versions (different languages, methods) are invalidated |
| 476 |
%% |
| 477 |
|
| 478 |
==== Example 4: Serving a File ==== |
| 479 |
|
| 480 |
%%php |
| 481 |
$http = new Http($db); |
| 482 |
$http->session(2); // Static file mode - no session replay prevention |
| 483 |
|
| 484 |
// Serve with 30-day cache |
| 485 |
$http->sendfile('uploads/manual.pdf', 'user-manual.pdf', 30); |
| 486 |
%% |
| 487 |
|
| 488 |
---- |
| 489 |
|