= 0; $i--) { if (!self::isTrusted($ips[$i])) { return $ips[$i]; } } return $ips[0]; // all trusted, use leftmost } /** * Extract the real protocol (http/https). * * @method clientProto * @static * @param {string} $directIp * @param {array} $headers * @param {boolean} $isTls Whether connection is TLS * @return {string} 'http' or 'https' */ static function clientProto($directIp, $headers, $isTls = false) { if ($isTls) return 'https'; if (!self::isTrusted($directIp)) return 'http'; $headerName = strtolower(Q_Config::get( 'Q', 'webserver', 'proxy', 'headers', 'proto', 'x-forwarded-proto' )); $proto = $headers[$headerName] ?? ''; return strtolower($proto) === 'https' ? 'https' : 'http'; } /** * Extract the real host. * * @method clientHost * @static * @param {string} $directIp * @param {array} $headers * @return {string} */ static function clientHost($directIp, $headers) { if (self::isTrusted($directIp)) { $headerName = strtolower(Q_Config::get( 'Q', 'webserver', 'proxy', 'headers', 'host', 'x-forwarded-host' )); $host = $headers[$headerName] ?? ''; if ($host) return $host; } return $headers['host'] ?? 'localhost'; } /** * Check if an IP is a trusted proxy. * * @method isTrusted * @static * @param {string} $ip * @return {boolean} */ static function isTrusted($ip) { if (self::$trusted === null) { self::$trusted = Q_Config::get( 'Q', 'webserver', 'proxy', 'trusted', array('127.0.0.1', '::1') ); } foreach (self::$trusted as $range) { if (strpos($range, '/') !== false) { if (self::ipInCidr($ip, $range)) return true; } else { if ($ip === $range) return true; } } return false; } /** * Check if IP is within a CIDR range. */ static function ipInCidr($ip, $cidr) { list($subnet, $bits) = explode('/', $cidr); $ip = ip2long($ip); $subnet = ip2long($subnet); if ($ip === false || $subnet === false) return false; $mask = -1 << (32 - (int) $bits); return ($ip & $mask) === ($subnet & $mask); } }