1<?php
2
3/**
4 * This module contains the CURL-based HTTP fetcher implementation.
5 *
6 * PHP versions 4 and 5
7 *
8 * LICENSE: See the COPYING file included in this distribution.
9 *
10 * @package OpenID
11 * @author JanRain, Inc. <openid@janrain.com>
12 * @copyright 2005-2008 Janrain, Inc.
13 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
14 */
15
16/**
17 * Interface import
18 */
19require_once "Auth/Yadis/HTTPFetcher.php";
20
21require_once "Auth/OpenID.php";
22
23/**
24 * A paranoid {@link Auth_Yadis_HTTPFetcher} class which uses CURL
25 * for fetching.
26 *
27 * @package OpenID
28 */
29class Auth_Yadis_ParanoidHTTPFetcher extends Auth_Yadis_HTTPFetcher {
30
31    private $headers = [];
32    private $data = '';
33
34    function __construct()
35    {
36        $this->reset();
37    }
38
39    function reset()
40    {
41        $this->headers = [];
42        $this->data = "";
43    }
44
45    /**
46     * @access private
47     * @param string $ch
48     * @param string $header
49     * @return int
50     */
51    function _writeHeader($ch, $header)
52    {
53        array_push($this->headers, rtrim($header));
54        return strlen($header);
55    }
56
57    /**
58     * @access private
59     * @param string $ch
60     * @param string $data
61     * @return int
62     */
63    function _writeData($ch, $data)
64    {
65        if (strlen($this->data) > 1024*Auth_OpenID_FETCHER_MAX_RESPONSE_KB) {
66            return 0;
67        } else {
68            $this->data .= $data;
69            return strlen($data);
70        }
71    }
72
73    /**
74     * Does this fetcher support SSL URLs?
75     */
76    function supportsSSL()
77    {
78        $v = curl_version();
79        if(is_array($v)) {
80            return in_array('https', $v['protocols']);
81        } elseif (is_string($v)) {
82            return preg_match('/OpenSSL/i', $v);
83        } else {
84            return 0;
85        }
86    }
87
88    /**
89     * @param string $url
90     * @param array|null $extra_headers
91     * @return Auth_Yadis_HTTPResponse|null
92     */
93    function get($url, $extra_headers = null)
94    {
95        if (!$this->canFetchURL($url)) {
96            return null;
97        }
98
99        $stop = time() + $this->timeout;
100        $off = $this->timeout;
101
102        $redir = true;
103
104        while ($redir && ($off > 0)) {
105            $this->reset();
106
107            $c = curl_init();
108            if (defined('Auth_OpenID_DISABLE_SSL_VERIFYPEER')
109                    && Auth_OpenID_DISABLE_SSL_VERIFYPEER === true) {
110                trigger_error(
111                    'You have disabled SSL verifcation, this is a TERRIBLE ' .
112                    'idea in almost all cases. Set Auth_OpenID_DISABLE_SSL_' .
113                    'VERIFYPEER to false if you want to be safe again',
114                    E_USER_WARNING);
115                curl_setopt($c, CURLOPT_SSL_VERIFYPEER, false);
116            }
117
118            if ($c === false) {
119                Auth_OpenID::log(
120                    "curl_init returned false; could not " .
121                    "initialize for URL '%s'", $url);
122                return null;
123            }
124
125            if (defined('CURLOPT_NOSIGNAL')) {
126                curl_setopt($c, CURLOPT_NOSIGNAL, true);
127            }
128
129            if (!$this->allowedURL($url)) {
130                Auth_OpenID::log("Fetching URL not allowed: %s",
131                                 $url);
132                return null;
133            }
134
135            curl_setopt($c, CURLOPT_WRITEFUNCTION,
136                        [$this, "_writeData"]);
137            curl_setopt($c, CURLOPT_HEADERFUNCTION,
138                        [$this, "_writeHeader"]);
139
140            if ($extra_headers) {
141                curl_setopt($c, CURLOPT_HTTPHEADER, $extra_headers);
142            }
143
144            $cv = curl_version();
145            if(is_array($cv)) {
146              $curl_user_agent = 'curl/'.$cv['version'];
147            } else {
148              $curl_user_agent = $cv;
149            }
150            curl_setopt($c, CURLOPT_USERAGENT,
151                        Auth_OpenID_USER_AGENT.' '.$curl_user_agent);
152            curl_setopt($c, CURLOPT_TIMEOUT, $off);
153            curl_setopt($c, CURLOPT_URL, $url);
154
155            if (defined('Auth_OpenID_VERIFY_HOST')) {
156                // set SSL verification options only if Auth_OpenID_VERIFY_HOST
157                // is explicitly set, otherwise use system default.
158                if (Auth_OpenID_VERIFY_HOST) {
159                    curl_setopt($c, CURLOPT_SSL_VERIFYPEER, true);
160                    curl_setopt($c, CURLOPT_SSL_VERIFYHOST, 2);
161                    if (defined('Auth_OpenID_CAINFO')) {
162                        curl_setopt($c, CURLOPT_CAINFO, Auth_OpenID_CAINFO);
163                    }
164                } else {
165                    curl_setopt($c, CURLOPT_SSL_VERIFYPEER, false);
166                }
167            }
168            if (defined('Auth_OpenID_HTTP_PROXY')) {
169                curl_setopt($c, CURLOPT_PROXY, Auth_OpenID_HTTP_PROXY);
170            }
171
172            curl_exec($c);
173
174            $code = curl_getinfo($c, CURLINFO_HTTP_CODE);
175            $body = $this->data;
176            $headers = $this->headers;
177
178            if (!$code) {
179                Auth_OpenID::log("Got no response code when fetching %s", $url);
180                Auth_OpenID::log("CURL error (%s): %s",
181                                 curl_errno($c), curl_error($c));
182                return null;
183            }
184
185            if (in_array($code, [301, 302, 303, 307])) {
186                $url = $this->_findRedirect($headers, $url);
187                $redir = true;
188            } else {
189                $redir = false;
190                curl_close($c);
191
192                if (defined('Auth_OpenID_VERIFY_HOST') &&
193                    Auth_OpenID_VERIFY_HOST == true &&
194                    $this->isHTTPS($url)) {
195                    Auth_OpenID::log('OpenID: Verified SSL host %s using '.
196                                     'curl/get', $url);
197                }
198                $new_headers = [];
199
200                foreach ($headers as $header) {
201                    if (strpos($header, ': ')) {
202                        list($name, $value) = explode(': ', $header, 2);
203                        $new_headers[$name] = $value;
204                    }
205                }
206
207                return new Auth_Yadis_HTTPResponse($url, $code,
208                                                    $new_headers, $body);
209            }
210
211            $off = $stop - time();
212        }
213
214        return null;
215    }
216
217    function post($url, $body, $extra_headers = null)
218    {
219        if (!$this->canFetchURL($url)) {
220            return null;
221        }
222
223        $this->reset();
224
225        $c = curl_init();
226
227        if (defined('CURLOPT_NOSIGNAL')) {
228            curl_setopt($c, CURLOPT_NOSIGNAL, true);
229        }
230
231        if (defined('Auth_OpenID_HTTP_PROXY')) {
232            curl_setopt($c, CURLOPT_PROXY, Auth_OpenID_HTTP_PROXY);
233        }
234
235        curl_setopt($c, CURLOPT_POST, true);
236        curl_setopt($c, CURLOPT_POSTFIELDS, $body);
237        curl_setopt($c, CURLOPT_TIMEOUT, $this->timeout);
238        curl_setopt($c, CURLOPT_URL, $url);
239        curl_setopt($c, CURLOPT_WRITEFUNCTION,
240                    [$this, "_writeData"]);
241
242        if (defined('Auth_OpenID_VERIFY_HOST')) {
243            // set SSL verification options only if Auth_OpenID_VERIFY_HOST
244            // is explicitly set, otherwise use system default.
245            if (Auth_OpenID_VERIFY_HOST) {
246                curl_setopt($c, CURLOPT_SSL_VERIFYPEER, true);
247                curl_setopt($c, CURLOPT_SSL_VERIFYHOST, 2);
248                if (defined('Auth_OpenID_CAINFO')) {
249                    curl_setopt($c, CURLOPT_CAINFO, Auth_OpenID_CAINFO);
250                }
251            } else {
252                curl_setopt($c, CURLOPT_SSL_VERIFYPEER, false);
253            }
254        }
255
256        curl_exec($c);
257
258        $code = curl_getinfo($c, CURLINFO_HTTP_CODE);
259
260        if (!$code) {
261            Auth_OpenID::log("Got no response code when fetching %s", $url);
262            Auth_OpenID::log("CURL error (%s): %s",
263                             curl_errno($c), curl_error($c));
264            return null;
265        }
266
267        if (defined('Auth_OpenID_VERIFY_HOST') &&
268            Auth_OpenID_VERIFY_HOST == true &&
269            $this->isHTTPS($url)) {
270            Auth_OpenID::log('OpenID: Verified SSL host %s using '.
271                             'curl/post', $url);
272        }
273        $body = $this->data;
274
275        curl_close($c);
276
277        $new_headers = $extra_headers;
278
279        foreach ($this->headers as $header) {
280            if (strpos($header, ': ')) {
281                list($name, $value) = explode(': ', $header, 2);
282                $new_headers[$name] = $value;
283            }
284
285        }
286
287        return new Auth_Yadis_HTTPResponse($url, $code,
288                                           $new_headers, $body);
289    }
290}
291
292