1<?php
2/**
3 * Zend Framework (http://framework.zend.com/)
4 *
5 * @link      http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license   http://framework.zend.com/license/new-bsd New BSD License
8 */
9
10namespace Zend\Http\PhpEnvironment;
11
12/**
13 * Functionality for determining client IP address.
14 */
15class RemoteAddress
16{
17    /**
18     * Whether to use proxy addresses or not.
19     *
20     * As default this setting is disabled - IP address is mostly needed to increase
21     * security. HTTP_* are not reliable since can easily be spoofed. It can be enabled
22     * just for more flexibility, but if user uses proxy to connect to trusted services
23     * it's his/her own risk, only reliable field for IP address is $_SERVER['REMOTE_ADDR'].
24     *
25     * @var bool
26     */
27    protected $useProxy = false;
28
29    /**
30     * List of trusted proxy IP addresses
31     *
32     * @var array
33     */
34    protected $trustedProxies = array();
35
36    /**
37     * HTTP header to introspect for proxies
38     *
39     * @var string
40     */
41    protected $proxyHeader = 'HTTP_X_FORWARDED_FOR';
42
43
44    /**
45     * Changes proxy handling setting.
46     *
47     * This must be static method, since validators are recovered automatically
48     * at session read, so this is the only way to switch setting.
49     *
50     * @param  bool  $useProxy Whether to check also proxied IP addresses.
51     * @return RemoteAddress
52     */
53    public function setUseProxy($useProxy = true)
54    {
55        $this->useProxy = $useProxy;
56        return $this;
57    }
58
59    /**
60     * Checks proxy handling setting.
61     *
62     * @return bool Current setting value.
63     */
64    public function getUseProxy()
65    {
66        return $this->useProxy;
67    }
68
69    /**
70     * Set list of trusted proxy addresses
71     *
72     * @param  array $trustedProxies
73     * @return RemoteAddress
74     */
75    public function setTrustedProxies(array $trustedProxies)
76    {
77        $this->trustedProxies = $trustedProxies;
78        return $this;
79    }
80
81    /**
82     * Set the header to introspect for proxy IPs
83     *
84     * @param  string $header
85     * @return RemoteAddress
86     */
87    public function setProxyHeader($header = 'X-Forwarded-For')
88    {
89        $this->proxyHeader = $this->normalizeProxyHeader($header);
90        return $this;
91    }
92
93    /**
94     * Returns client IP address.
95     *
96     * @return string IP address.
97     */
98    public function getIpAddress()
99    {
100        $ip = $this->getIpAddressFromProxy();
101        if ($ip) {
102            return $ip;
103        }
104
105        // direct IP address
106        if (isset($_SERVER['REMOTE_ADDR'])) {
107            return $_SERVER['REMOTE_ADDR'];
108        }
109
110        return '';
111    }
112
113    /**
114     * Attempt to get the IP address for a proxied client
115     *
116     * @see http://tools.ietf.org/html/draft-ietf-appsawg-http-forwarded-10#section-5.2
117     * @return false|string
118     */
119    protected function getIpAddressFromProxy()
120    {
121        if (!$this->useProxy
122            || (isset($_SERVER['REMOTE_ADDR']) && !in_array($_SERVER['REMOTE_ADDR'], $this->trustedProxies))
123        ) {
124            return false;
125        }
126
127        $header = $this->proxyHeader;
128        if (!isset($_SERVER[$header]) || empty($_SERVER[$header])) {
129            return false;
130        }
131
132        // Extract IPs
133        $ips = explode(',', $_SERVER[$header]);
134        // trim, so we can compare against trusted proxies properly
135        $ips = array_map('trim', $ips);
136        // remove trusted proxy IPs
137        $ips = array_diff($ips, $this->trustedProxies);
138
139        // Any left?
140        if (empty($ips)) {
141            return false;
142        }
143
144        // Since we've removed any known, trusted proxy servers, the right-most
145        // address represents the first IP we do not know about -- i.e., we do
146        // not know if it is a proxy server, or a client. As such, we treat it
147        // as the originating IP.
148        // @see http://en.wikipedia.org/wiki/X-Forwarded-For
149        $ip = array_pop($ips);
150        return $ip;
151    }
152
153    /**
154     * Normalize a header string
155     *
156     * Normalizes a header string to a format that is compatible with
157     * $_SERVER
158     *
159     * @param  string $header
160     * @return string
161     */
162    protected function normalizeProxyHeader($header)
163    {
164        $header = strtoupper($header);
165        $header = str_replace('-', '_', $header);
166        if (0 !== strpos($header, 'HTTP_')) {
167            $header = 'HTTP_' . $header;
168        }
169        return $header;
170    }
171}
172