1<?php
2
3/**
4 * This module contains the plain non-curl HTTP fetcher
5 * implementation.
6 *
7 * PHP versions 4 and 5
8 *
9 * LICENSE: See the COPYING file included in this distribution.
10 *
11 * @package OpenID
12 * @author JanRain, Inc. <openid@janrain.com>
13 * @copyright 2005-2008 Janrain, Inc.
14 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
15 */
16
17/**
18 * Interface import
19 */
20require_once "Auth/Yadis/HTTPFetcher.php";
21
22/**
23 * This class implements a plain, hand-built socket-based fetcher
24 * which will be used in the event that CURL is unavailable.
25 *
26 * @package OpenID
27 */
28class Auth_Yadis_PlainHTTPFetcher extends Auth_Yadis_HTTPFetcher {
29    /**
30     * Does this fetcher support SSL URLs?
31     */
32    function supportsSSL()
33    {
34        return function_exists('openssl_open');
35    }
36
37    /**
38     * @param string $url
39     * @param array|null $extra_headers
40     * @return Auth_Yadis_HTTPResponse|null|bool
41     */
42    function get($url, $extra_headers = null)
43    {
44        if (!$this->canFetchURL($url)) {
45            return null;
46        }
47
48        $redir = true;
49
50        $stop = time() + $this->timeout;
51        $off = $this->timeout;
52        $headers = [];
53        $code = '';
54        $body = '';
55
56        while ($redir && ($off > 0)) {
57
58            $parts = parse_url($url);
59
60            $specify_port = true;
61
62            // Set a default port.
63            if (!array_key_exists('port', $parts)) {
64                $specify_port = false;
65                if ($parts['scheme'] == 'http') {
66                    $parts['port'] = 80;
67                } elseif ($parts['scheme'] == 'https') {
68                    $parts['port'] = 443;
69                } else {
70                    return null;
71                }
72            }
73
74            if (!array_key_exists('path', $parts)) {
75                $parts['path'] = '/';
76            }
77
78            $host = $parts['host'];
79
80            if ($parts['scheme'] == 'https') {
81                $host = 'ssl://' . $host;
82            }
83
84            $user_agent = Auth_OpenID_USER_AGENT;
85
86            $headers = [
87                "GET " . $parts['path'] .
88                (array_key_exists('query', $parts) ?
89                    "?" . $parts['query'] : "") .
90                " HTTP/1.0",
91                "User-Agent: $user_agent",
92                "Host: " . $parts['host'] .
93                ($specify_port ? ":" . $parts['port'] : ""),
94                "Port: " . $parts['port'],
95            ];
96
97            $errno = 0;
98            $errstr = '';
99
100            if ($extra_headers) {
101                foreach ($extra_headers as $h) {
102                    $headers[] = $h;
103                }
104            }
105
106            @$sock = fsockopen($host, $parts['port'], $errno, $errstr,
107                               $this->timeout);
108            if ($sock === false) {
109                return false;
110            }
111
112            stream_set_timeout($sock, $this->timeout);
113
114            fputs($sock, implode("\r\n", $headers) . "\r\n\r\n");
115
116            $data = "";
117            $kilobytes = 0;
118            while (!feof($sock) &&
119                   $kilobytes < Auth_OpenID_FETCHER_MAX_RESPONSE_KB ) {
120                $data .= fgets($sock, 1024);
121                $kilobytes += 1;
122            }
123
124            fclose($sock);
125
126            // Split response into header and body sections
127            list($headers, $body) = explode("\r\n\r\n", $data, 2);
128            $headers = explode("\r\n", $headers);
129
130            $http_code = explode(" ", $headers[0]);
131            $code = $http_code[1];
132
133            if (in_array($code, ['301', '302'])) {
134                $url = $this->_findRedirect($headers, $url);
135                $redir = true;
136            } else {
137                $redir = false;
138            }
139
140            $off = $stop - time();
141        }
142
143        $new_headers = [];
144
145        foreach ($headers as $header) {
146            if (preg_match("/:/", $header)) {
147                $parts = explode(": ", $header, 2);
148
149                if (count($parts) == 2) {
150                    list($name, $value) = $parts;
151                    $new_headers[$name] = $value;
152                }
153            }
154
155        }
156
157        return new Auth_Yadis_HTTPResponse($url, $code, $new_headers, $body);
158    }
159
160    function post($url, $body, $extra_headers = null)
161    {
162        if (!$this->canFetchURL($url)) {
163            return null;
164        }
165
166        $parts = parse_url($url);
167
168        $headers = [];
169
170        $post_path = $parts['path'];
171        if (isset($parts['query'])) {
172            $post_path .= '?' . $parts['query'];
173        }
174
175        $headers[] = "POST ".$post_path." HTTP/1.0";
176        $headers[] = "Host: " . $parts['host'];
177        $headers[] = "Content-type: application/x-www-form-urlencoded";
178        $headers[] = "Content-length: " . strval(strlen($body));
179
180        if ($extra_headers &&
181            is_array($extra_headers)) {
182            $headers = array_merge($headers, $extra_headers);
183        }
184
185        // Join all headers together.
186        $all_headers = implode("\r\n", $headers);
187
188        // Add headers, two newlines, and request body.
189        $request = $all_headers . "\r\n\r\n" . $body;
190
191        // Set a default port.
192        if (!array_key_exists('port', $parts)) {
193            if ($parts['scheme'] == 'http') {
194                $parts['port'] = 80;
195            } elseif ($parts['scheme'] == 'https') {
196                $parts['port'] = 443;
197            } else {
198                return null;
199            }
200        }
201
202        if ($parts['scheme'] == 'https') {
203            $parts['host'] = sprintf("ssl://%s", $parts['host']);
204        }
205
206        // Connect to the remote server.
207        $errno = 0;
208        $errstr = '';
209
210        $sock = fsockopen($parts['host'], $parts['port'], $errno, $errstr,
211                          $this->timeout);
212
213        if ($sock === false) {
214            return null;
215        }
216
217        stream_set_timeout($sock, $this->timeout);
218
219        // Write the POST request.
220        fputs($sock, $request);
221
222        // Get the response from the server.
223        $response = "";
224        while (!feof($sock)) {
225            if ($data = fgets($sock, 128)) {
226                $response .= $data;
227            } else {
228                break;
229            }
230        }
231
232        // Split the request into headers and body.
233        list($headers, $response_body) = explode("\r\n\r\n", $response, 2);
234
235        $headers = explode("\r\n", $headers);
236
237        // Expect the first line of the headers data to be something
238        // like HTTP/1.1 200 OK.  Split the line on spaces and take
239        // the second token, which should be the return code.
240        $http_code = explode(" ", $headers[0]);
241        $code = $http_code[1];
242
243        $new_headers = [];
244
245        foreach ($headers as $header) {
246            if (preg_match("/:/", $header)) {
247                list($name, $value) = explode(": ", $header, 2);
248                $new_headers[$name] = $value;
249            }
250
251        }
252
253        return new Auth_Yadis_HTTPResponse($url, $code,
254                                           $new_headers, $response_body);
255    }
256}
257
258