<?php
/**
 * CDN / 反代场景下解析访客真实 IP
 */
class ClientIpResolver
{
    /**
     * @return string
     */
    public static function resolve()
    {
        static $resolved = null;
        if ($resolved !== null) {
            return $resolved;
        }

        $candidates = [];

        if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) {
            $candidates[] = (string) $_SERVER['HTTP_CF_CONNECTING_IP'];
        }
        if (!empty($_SERVER['HTTP_TRUE_CLIENT_IP'])) {
            $candidates[] = (string) $_SERVER['HTTP_TRUE_CLIENT_IP'];
        }
        if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
            $candidates[] = (string) $_SERVER['HTTP_X_REAL_IP'];
        }
        if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            foreach (explode(',', (string) $_SERVER['HTTP_X_FORWARDED_FOR']) as $part) {
                $part = trim($part);
                if ($part !== '' && strcasecmp($part, 'unknown') !== 0) {
                    $candidates[] = $part;
                }
            }
        }
        if (!empty($_SERVER['REMOTE_ADDR'])) {
            $candidates[] = (string) $_SERVER['REMOTE_ADDR'];
        }

        foreach ($candidates as $candidate) {
            $ip = self::normalizeIp($candidate);
            if ($ip === null) {
                continue;
            }
            if (self::isPublicIp($ip)) {
                $resolved = $ip;
                return $resolved;
            }
        }

        foreach ($candidates as $candidate) {
            $ip = self::normalizeIp($candidate);
            if ($ip !== null) {
                $resolved = $ip;
                return $resolved;
            }
        }

        $resolved = '0.0.0.0';
        return $resolved;
    }

    /**
     * @param string $ip
     */
    public static function normalizeIp($ip)
    {
        $ip = trim($ip);
        if ($ip === '' || strcasecmp($ip, 'unknown') === 0) {
            return null;
        }
        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6) === false) {
            return null;
        }
        return $ip;
    }

    /**
     * @param string $ip
     */
    private static function isPublicIp($ip)
    {
        return filter_var(
            $ip,
            FILTER_VALIDATE_IP,
            FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
        ) !== false;
    }
}
