100 lines
2.8 KiB
PHP
100 lines
2.8 KiB
PHP
|
|
<?php
|
||
|
|
/**
|
||
|
|
* UrlHelper - URL and domain utilities
|
||
|
|
*
|
||
|
|
* Provides secure URL generation with host validation.
|
||
|
|
*/
|
||
|
|
class UrlHelper {
|
||
|
|
/**
|
||
|
|
* Get the application base URL with validated host
|
||
|
|
*
|
||
|
|
* Uses APP_DOMAIN from config if set, otherwise validates HTTP_HOST
|
||
|
|
* against ALLOWED_HOSTS whitelist.
|
||
|
|
*
|
||
|
|
* @return string Base URL (e.g., "https://example.com")
|
||
|
|
*/
|
||
|
|
public static function getBaseUrl(): string {
|
||
|
|
$protocol = self::getProtocol();
|
||
|
|
$host = self::getValidatedHost();
|
||
|
|
|
||
|
|
return "{$protocol}://{$host}";
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the current protocol (http or https)
|
||
|
|
*
|
||
|
|
* @return string 'https' or 'http'
|
||
|
|
*/
|
||
|
|
public static function getProtocol(): string {
|
||
|
|
if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') {
|
||
|
|
return 'https';
|
||
|
|
}
|
||
|
|
if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
|
||
|
|
return 'https';
|
||
|
|
}
|
||
|
|
if (!empty($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443) {
|
||
|
|
return 'https';
|
||
|
|
}
|
||
|
|
return 'http';
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get validated hostname
|
||
|
|
*
|
||
|
|
* Priority:
|
||
|
|
* 1. APP_DOMAIN from config (if set)
|
||
|
|
* 2. HTTP_HOST if it passes validation
|
||
|
|
* 3. First allowed host as fallback
|
||
|
|
*
|
||
|
|
* @return string Validated hostname
|
||
|
|
*/
|
||
|
|
public static function getValidatedHost(): string {
|
||
|
|
$config = $GLOBALS['config'] ?? [];
|
||
|
|
|
||
|
|
// Use configured APP_DOMAIN if available
|
||
|
|
if (!empty($config['APP_DOMAIN'])) {
|
||
|
|
return $config['APP_DOMAIN'];
|
||
|
|
}
|
||
|
|
|
||
|
|
// Get allowed hosts
|
||
|
|
$allowedHosts = $config['ALLOWED_HOSTS'] ?? ['localhost'];
|
||
|
|
|
||
|
|
// Validate HTTP_HOST against whitelist
|
||
|
|
$httpHost = $_SERVER['HTTP_HOST'] ?? '';
|
||
|
|
|
||
|
|
// Strip port if present for comparison
|
||
|
|
$hostWithoutPort = preg_replace('/:\d+$/', '', $httpHost);
|
||
|
|
|
||
|
|
if (in_array($hostWithoutPort, $allowedHosts, true)) {
|
||
|
|
return $httpHost;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Log suspicious host header
|
||
|
|
if (!empty($httpHost) && $httpHost !== 'localhost') {
|
||
|
|
error_log("UrlHelper: Rejected HTTP_HOST '{$httpHost}' - not in allowed hosts");
|
||
|
|
}
|
||
|
|
|
||
|
|
// Return first allowed host as fallback
|
||
|
|
return $allowedHosts[0] ?? 'localhost';
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Build a full URL for a ticket
|
||
|
|
*
|
||
|
|
* @param string $ticketId Ticket ID
|
||
|
|
* @return string Full ticket URL
|
||
|
|
*/
|
||
|
|
public static function ticketUrl(string $ticketId): string {
|
||
|
|
return self::getBaseUrl() . '/ticket/' . urlencode($ticketId);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Check if the current request is using HTTPS
|
||
|
|
*
|
||
|
|
* @return bool True if HTTPS
|
||
|
|
*/
|
||
|
|
public static function isSecure(): bool {
|
||
|
|
return self::getProtocol() === 'https';
|
||
|
|
}
|
||
|
|
}
|