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\View\Helper;
11
12/**
13 * Helper for returning the current server URL (optionally with request URI)
14 */
15class ServerUrl extends AbstractHelper
16{
17    /**
18     * Host (including port)
19     *
20     * @var string
21     */
22    protected $host;
23
24    /**
25     * Port
26     *
27     * @var int
28     */
29    protected $port;
30
31    /**
32     * Scheme
33     *
34     * @var string
35     */
36    protected $scheme;
37
38    /**
39     * Whether or not to query proxy servers for address
40     *
41     * @var bool
42     */
43    protected $useProxy = false;
44
45    /**
46     * View helper entry point:
47     * Returns the current host's URL like http://site.com
48     *
49     * @param  string|bool $requestUri  [optional] if true, the request URI
50     *                                     found in $_SERVER will be appended
51     *                                     as a path. If a string is given, it
52     *                                     will be appended as a path. Default
53     *                                     is to not append any path.
54     * @return string
55     */
56    public function __invoke($requestUri = null)
57    {
58        if ($requestUri === true) {
59            $path = $_SERVER['REQUEST_URI'];
60        } elseif (is_string($requestUri)) {
61            $path = $requestUri;
62        } else {
63            $path = '';
64        }
65
66        return $this->getScheme() . '://' . $this->getHost() . $path;
67    }
68
69    /**
70     * Detect the host based on headers
71     *
72     * @return void
73     */
74    protected function detectHost()
75    {
76        if ($this->setHostFromProxy()) {
77            return;
78        }
79
80        if (isset($_SERVER['HTTP_HOST']) && !empty($_SERVER['HTTP_HOST'])) {
81            // Detect if the port is set in SERVER_PORT and included in HTTP_HOST
82            if (isset($_SERVER['SERVER_PORT'])
83                && preg_match('/^(?P<host>.*?):(?P<port>\d+)$/', $_SERVER['HTTP_HOST'], $matches)
84            ) {
85                // If they are the same, set the host to just the hostname
86                // portion of the Host header.
87                if ((int) $matches['port'] === (int) $_SERVER['SERVER_PORT']) {
88                    $this->setHost($matches['host']);
89                    return;
90                }
91
92                // At this point, we have a SERVER_PORT that differs from the
93                // Host header, indicating we likely have a port-forwarding
94                // situation. As such, we'll set the host and port from the
95                // matched values.
96                $this->setPort((int) $matches['port']);
97                $this->setHost($matches['host']);
98                return;
99            }
100
101            $this->setHost($_SERVER['HTTP_HOST']);
102
103            return;
104        }
105
106        if (!isset($_SERVER['SERVER_NAME']) || !isset($_SERVER['SERVER_PORT'])) {
107            return;
108        }
109
110        $name = $_SERVER['SERVER_NAME'];
111        $this->setHost($name);
112    }
113
114    /**
115     * Detect the port
116     *
117     * @return null
118     */
119    protected function detectPort()
120    {
121        if ($this->setPortFromProxy()) {
122            return;
123        }
124
125        if (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT']) {
126            if ($this->isReversedProxy()) {
127                $this->setPort(443);
128                return;
129            }
130            $this->setPort($_SERVER['SERVER_PORT']);
131            return;
132        }
133    }
134
135    /**
136     * Detect the scheme
137     *
138     * @return null
139     */
140    protected function detectScheme()
141    {
142        if ($this->setSchemeFromProxy()) {
143            return;
144        }
145
146        switch (true) {
147            case (isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] == 'on' || $_SERVER['HTTPS'] === true)):
148            case (isset($_SERVER['HTTP_SCHEME']) && ($_SERVER['HTTP_SCHEME'] == 'https')):
149            case (443 === $this->getPort()):
150            case $this->isReversedProxy():
151                $scheme = 'https';
152                break;
153            default:
154                $scheme = 'http';
155                break;
156        }
157
158        $this->setScheme($scheme);
159    }
160
161    protected function isReversedProxy()
162    {
163        return isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https';
164    }
165
166    /**
167     * Detect if a proxy is in use, and, if so, set the host based on it
168     *
169     * @return bool
170     */
171    protected function setHostFromProxy()
172    {
173        if (!$this->useProxy) {
174            return false;
175        }
176
177        if (!isset($_SERVER['HTTP_X_FORWARDED_HOST']) || empty($_SERVER['HTTP_X_FORWARDED_HOST'])) {
178            return false;
179        }
180
181        $host = $_SERVER['HTTP_X_FORWARDED_HOST'];
182        if (strpos($host, ',') !== false) {
183            $hosts = explode(',', $host);
184            $host = trim(array_pop($hosts));
185        }
186        if (empty($host)) {
187            return false;
188        }
189        $this->setHost($host);
190
191        return true;
192    }
193
194    /**
195     * Set port based on detected proxy headers
196     *
197     * @return bool
198     */
199    protected function setPortFromProxy()
200    {
201        if (!$this->useProxy) {
202            return false;
203        }
204
205        if (!isset($_SERVER['HTTP_X_FORWARDED_PORT']) || empty($_SERVER['HTTP_X_FORWARDED_PORT'])) {
206            return false;
207        }
208
209        $port = $_SERVER['HTTP_X_FORWARDED_PORT'];
210        $this->setPort($port);
211
212        return true;
213    }
214
215    /**
216     * Set the current scheme based on detected proxy headers
217     *
218     * @return bool
219     */
220    protected function setSchemeFromProxy()
221    {
222        if (!$this->useProxy) {
223            return false;
224        }
225
226        if (isset($_SERVER['SSL_HTTPS'])) {
227            $sslHttps = strtolower($_SERVER['SSL_HTTPS']);
228            if (in_array($sslHttps, array('on', 1))) {
229                $this->setScheme('https');
230                return true;
231            }
232        }
233
234        if (!isset($_SERVER['HTTP_X_FORWARDED_PROTO']) || empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
235            return false;
236        }
237
238        $scheme = trim(strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']));
239        if (empty($scheme)) {
240            return false;
241        }
242
243        $this->setScheme($scheme);
244
245        return true;
246    }
247
248    /**
249     * Sets host
250     *
251     * @param  string $host
252     * @return ServerUrl
253     */
254    public function setHost($host)
255    {
256        $port   = $this->getPort();
257        $scheme = $this->getScheme();
258
259        if (($scheme == 'http' && (null === $port || $port == 80))
260            || ($scheme == 'https' && (null === $port || $port == 443))
261        ) {
262            $this->host = $host;
263            return $this;
264        }
265
266        $this->host = $host . ':' . $port;
267
268        return $this;
269    }
270
271    /**
272     * Returns host
273     *
274     * @return string
275     */
276    public function getHost()
277    {
278        if (null === $this->host) {
279            $this->detectHost();
280        }
281
282        return $this->host;
283    }
284
285    /**
286     * Set server port
287     *
288     * @param  int $port
289     * @return ServerUrl
290     */
291    public function setPort($port)
292    {
293        $this->port = (int) $port;
294
295        return $this;
296    }
297
298    /**
299     * Retrieve the server port
300     *
301     * @return int|null
302     */
303    public function getPort()
304    {
305        if (null === $this->port) {
306            $this->detectPort();
307        }
308
309        return $this->port;
310    }
311
312    /**
313     * Sets scheme (typically http or https)
314     *
315     * @param  string $scheme
316     * @return ServerUrl
317     */
318    public function setScheme($scheme)
319    {
320        $this->scheme = $scheme;
321
322        return $this;
323    }
324
325    /**
326     * Returns scheme (typically http or https)
327     *
328     * @return string
329     */
330    public function getScheme()
331    {
332        if (null === $this->scheme) {
333            $this->detectScheme();
334        }
335
336        return $this->scheme;
337    }
338
339    /**
340     * Set flag indicating whether or not to query proxy servers
341     *
342     * @param  bool $useProxy
343     * @return ServerUrl
344     */
345    public function setUseProxy($useProxy = false)
346    {
347        $this->useProxy = (bool) $useProxy;
348
349        return $this;
350    }
351}
352