1<?php
2/**
3 * PEAR_Proxy
4 *
5 * HTTP Proxy handling
6 *
7 * @category   pear
8 * @package    PEAR
9 * @author     Nico Boehr
10 * @copyright  1997-2009 The Authors
11 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
12 * @link       http://pear.php.net/package/PEAR
13 */
14
15class PEAR_Proxy
16{
17    var $config = null;
18
19    /**
20     * @access private
21     */
22    var $proxy_host;
23    /**
24     * @access private
25     */
26    var $proxy_port;
27    /**
28     * @access private
29     */
30    var $proxy_user;
31    /**
32     * @access private
33     */
34    var $proxy_pass;
35    /**
36     * @access private
37     */
38    var $proxy_schema;
39
40    function __construct($config = null)
41    {
42        $this->config = $config;
43        $this->_parseProxyInfo();
44    }
45
46    /**
47     * @access private
48     */
49    function _parseProxyInfo()
50    {
51        $this->proxy_host = $this->proxy_port = $this->proxy_user = $this->proxy_pass = '';
52        if ($this->config->get('http_proxy')&&
53              $proxy = parse_url($this->config->get('http_proxy'))
54        ) {
55            $this->proxy_host = isset($proxy['host']) ? $proxy['host'] : null;
56
57            $this->proxy_port   = isset($proxy['port']) ? $proxy['port'] : 8080;
58            $this->proxy_user   = isset($proxy['user']) ? urldecode($proxy['user']) : null;
59            $this->proxy_pass   = isset($proxy['pass']) ? urldecode($proxy['pass']) : null;
60            $this->proxy_schema = (isset($proxy['scheme']) && $proxy['scheme'] == 'https') ? 'https' : 'http';
61        }
62    }
63
64    /**
65     * @access private
66     */
67    function _httpConnect($fp, $host, $port)
68    {
69        fwrite($fp, "CONNECT $host:$port HTTP/1.1\r\n");
70        fwrite($fp, "Host: $host:$port\r\n");
71        if ($this->getProxyAuth()) {
72            fwrite($fp, 'Proxy-Authorization: Basic ' . $this->getProxyAuth() . "\r\n");
73        }
74        fwrite($fp, "\r\n");
75
76        while ($line = trim(fgets($fp, 1024))) {
77            if (preg_match('|^HTTP/1.[01] ([0-9]{3}) |', $line, $matches)) {
78                $code = (int)$matches[1];
79
80                /* as per RFC 2817 */
81                if ($code < 200 || $code >= 300) {
82                    return PEAR::raiseError("Establishing a CONNECT tunnel through proxy failed with response code $code");
83                }
84            }
85        }
86
87        // connection was successful -- establish SSL through
88        // the tunnel
89        $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
90
91        if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
92            $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
93            $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
94        }
95
96        // set the correct hostname for working hostname
97        // verification
98        stream_context_set_option($fp, 'ssl', 'peer_name', $host);
99
100        // blocking socket needed for
101        // stream_socket_enable_crypto()
102        // see
103        // <http://php.net/manual/en/function.stream-socket-enable-crypto.php>
104        stream_set_blocking ($fp, true);
105        $crypto_res = stream_socket_enable_crypto($fp, true, $crypto_method);
106        if (!$crypto_res) {
107            return PEAR::raiseError("Could not establish SSL connection through proxy $proxy_host:$proxy_port: $crypto_res");
108        }
109
110        return true;
111    }
112
113    /**
114     * get the authorization information for the proxy, encoded to be
115     * passed in the Proxy-Authentication HTTP header.
116     * @return null|string the encoded authentication information if a
117     *                     proxy and authentication is configured, null
118     *                     otherwise.
119     */
120    function getProxyAuth()
121    {
122        if ($this->isProxyConfigured() && $this->proxy_user != '') {
123            return base64_encode($this->proxy_user . ':' . $this->proxy_pass);
124        }
125        return null;
126    }
127
128    function getProxyUser()
129    {
130        return $this->proxy_user;
131    }
132
133    /**
134     * Check if we are configured to use a proxy.
135     *
136     * @return boolean true if we are configured to use a proxy, false
137     *                 otherwise.
138     * @access public
139     */
140    function isProxyConfigured()
141    {
142        return $this->proxy_host != '';
143    }
144
145    /**
146     * Open a socket to a remote server, possibly involving a HTTP
147     * proxy.
148     *
149     * If an HTTP proxy has been configured (http_proxy PEAR_Config
150     * setting), the proxy will be used.
151     *
152     * @param string $host    the host to connect to
153     * @param string $port    the port to connect to
154     * @param boolean $secure if true, establish a secure connection
155     *                        using TLS.
156     * @access public
157     */
158    function openSocket($host, $port, $secure = false)
159    {
160        if ($this->isProxyConfigured()) {
161            $fp = @fsockopen(
162                $this->proxy_host, $this->proxy_port,
163                $errno, $errstr, 15
164            );
165
166            if (!$fp) {
167                return PEAR::raiseError("Connection to `$proxy_host:$proxy_port' failed: $errstr", -9276);
168            }
169
170            /* HTTPS is to be used and we have a proxy, use CONNECT verb */
171            if ($secure) {
172                $res = $this->_httpConnect($fp, $host, $port);
173
174                if (PEAR::isError($res)) {
175                    return $res;
176                }
177            }
178        } else {
179            if ($secure) {
180                $host = 'ssl://' . $host;
181            }
182
183            $fp = @fsockopen($host, $port, $errno, $errstr);
184            if (!$fp) {
185                return PEAR::raiseError("Connection to `$host:$port' failed: $errstr", $errno);
186            }
187        }
188
189        return $fp;
190    }
191}
192