Files

100 lines
2.8 KiB
PHP
Raw Permalink Normal View History

<?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';
}
}