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    function Auth_Yadis_ParanoidHTTPFetcher()
31    {
32        $this->reset();
33    }
34
35    function reset()
36    {
37        $this->headers = array();
38        $this->data = "";
39    }
40
41    /**
42     * @access private
43     */
44    function _writeHeader($ch, $header)
45    {
46        array_push($this->headers, rtrim($header));
47        return strlen($header);
48    }
49
50    /**
51     * @access private
52     */
53    function _writeData($ch, $data)
54    {
55        if (strlen($this->data) > 1024*Auth_OpenID_FETCHER_MAX_RESPONSE_KB) {
56            return 0;
57        } else {
58            $this->data .= $data;
59            return strlen($data);
60        }
61    }
62
63    /**
64     * Does this fetcher support SSL URLs?
65     */
66    function supportsSSL()
67    {
68        $v = curl_version();
69        if(is_array($v)) {
70            return in_array('https', $v['protocols']);
71        } elseif (is_string($v)) {
72            return preg_match('/OpenSSL/i', $v);
73        } else {
74            return 0;
75        }
76    }
77
78    function get($url, $extra_headers = null)
79    {
80        if (!$this->canFetchURL($url)) {
81            return null;
82        }
83
84        $stop = time() + $this->timeout;
85        $off = $this->timeout;
86
87        $redir = true;
88
89        while ($redir && ($off > 0)) {
90            $this->reset();
91
92            $c = curl_init();
93
94            if ($c === false) {
95                Auth_OpenID::log(
96                    "curl_init returned false; could not " .
97                    "initialize for URL '%s'", $url);
98                return null;
99            }
100
101            if (defined('CURLOPT_NOSIGNAL')) {
102                curl_setopt($c, CURLOPT_NOSIGNAL, true);
103            }
104
105            if (!$this->allowedURL($url)) {
106                Auth_OpenID::log("Fetching URL not allowed: %s",
107                                 $url);
108                return null;
109            }
110
111            curl_setopt($c, CURLOPT_WRITEFUNCTION,
112                        array($this, "_writeData"));
113            curl_setopt($c, CURLOPT_HEADERFUNCTION,
114                        array($this, "_writeHeader"));
115
116            if ($extra_headers) {
117                curl_setopt($c, CURLOPT_HTTPHEADER, $extra_headers);
118            }
119
120            $cv = curl_version();
121            if(is_array($cv)) {
122              $curl_user_agent = 'curl/'.$cv['version'];
123            } else {
124              $curl_user_agent = $cv;
125            }
126            curl_setopt($c, CURLOPT_USERAGENT,
127                        Auth_OpenID_USER_AGENT.' '.$curl_user_agent);
128            curl_setopt($c, CURLOPT_TIMEOUT, $off);
129            curl_setopt($c, CURLOPT_URL, $url);
130
131            if (defined('Auth_OpenID_VERIFY_HOST')) {
132                // set SSL verification options only if Auth_OpenID_VERIFY_HOST
133                // is explicitly set, otherwise use system default.
134                if (Auth_OpenID_VERIFY_HOST) {
135                    curl_setopt($c, CURLOPT_SSL_VERIFYPEER, true);
136                    curl_setopt($c, CURLOPT_SSL_VERIFYHOST, 2);
137                    if (defined('Auth_OpenID_CAINFO')) {
138                        curl_setopt($c, CURLOPT_CAINFO, Auth_OpenID_CAINFO);
139                    }
140                } else {
141                    curl_setopt($c, CURLOPT_SSL_VERIFYPEER, false);
142                }
143            }
144
145            curl_exec($c);
146
147            $code = curl_getinfo($c, CURLINFO_HTTP_CODE);
148            $body = $this->data;
149            $headers = $this->headers;
150
151            if (!$code) {
152                Auth_OpenID::log("Got no response code when fetching %s", $url);
153                Auth_OpenID::log("CURL error (%s): %s",
154                                 curl_errno($c), curl_error($c));
155                return null;
156            }
157
158            if (in_array($code, array(301, 302, 303, 307))) {
159                $url = $this->_findRedirect($headers, $url);
160                $redir = true;
161            } else {
162                $redir = false;
163                curl_close($c);
164
165                if (defined('Auth_OpenID_VERIFY_HOST') &&
166                    Auth_OpenID_VERIFY_HOST == true &&
167                    $this->isHTTPS($url)) {
168                    Auth_OpenID::log('OpenID: Verified SSL host %s using '.
169                                     'curl/get', $url);
170                }
171                $new_headers = array();
172
173                foreach ($headers as $header) {
174                    if (strpos($header, ': ')) {
175                        list($name, $value) = explode(': ', $header, 2);
176                        $new_headers[$name] = $value;
177                    }
178                }
179
180                Auth_OpenID::log(
181                    "Successfully fetched '%s': GET response code %s",
182                    $url, $code);
183
184                return new Auth_Yadis_HTTPResponse($url, $code,
185                                                    $new_headers, $body);
186            }
187
188            $off = $stop - time();
189        }
190
191        return null;
192    }
193
194    function post($url, $body, $extra_headers = null)
195    {
196        if (!$this->canFetchURL($url)) {
197            return null;
198        }
199
200        $this->reset();
201
202        $c = curl_init();
203
204        if (defined('CURLOPT_NOSIGNAL')) {
205            curl_setopt($c, CURLOPT_NOSIGNAL, true);
206        }
207
208        curl_setopt($c, CURLOPT_POST, true);
209        curl_setopt($c, CURLOPT_POSTFIELDS, $body);
210        curl_setopt($c, CURLOPT_TIMEOUT, $this->timeout);
211        curl_setopt($c, CURLOPT_URL, $url);
212        curl_setopt($c, CURLOPT_WRITEFUNCTION,
213                    array($this, "_writeData"));
214
215        if (defined('Auth_OpenID_VERIFY_HOST')) {
216            // set SSL verification options only if Auth_OpenID_VERIFY_HOST
217            // is explicitly set, otherwise use system default.
218            if (Auth_OpenID_VERIFY_HOST) {
219                curl_setopt($c, CURLOPT_SSL_VERIFYPEER, true);
220                curl_setopt($c, CURLOPT_SSL_VERIFYHOST, 2);
221                if (defined('Auth_OpenID_CAINFO')) {
222                    curl_setopt($c, CURLOPT_CAINFO, Auth_OpenID_CAINFO);
223                }
224            } else {
225                curl_setopt($c, CURLOPT_SSL_VERIFYPEER, false);
226            }
227        }
228
229        curl_exec($c);
230
231        $code = curl_getinfo($c, CURLINFO_HTTP_CODE);
232
233        if (!$code) {
234            Auth_OpenID::log("Got no response code when fetching %s", $url);
235            Auth_OpenID::log("CURL error (%s): %s",
236                             curl_errno($c), curl_error($c));
237            return null;
238        }
239
240        if (defined('Auth_OpenID_VERIFY_HOST') &&
241            Auth_OpenID_VERIFY_HOST == true &&
242            $this->isHTTPS($url)) {
243            Auth_OpenID::log('OpenID: Verified SSL host %s using '.
244                             'curl/post', $url);
245        }
246        $body = $this->data;
247
248        curl_close($c);
249
250        $new_headers = $extra_headers;
251
252        foreach ($this->headers as $header) {
253            if (strpos($header, ': ')) {
254                list($name, $value) = explode(': ', $header, 2);
255                $new_headers[$name] = $value;
256            }
257
258        }
259
260        Auth_OpenID::log("Successfully fetched '%s': POST response code %s",
261                         $url, $code);
262
263        return new Auth_Yadis_HTTPResponse($url, $code,
264                                           $new_headers, $body);
265    }
266}
267
268