Minor dependency updates
[yaffs-website] / vendor / easyrdf / easyrdf / lib / EasyRdf / Http / Client.php
1 <?php
2
3 /**
4  * EasyRdf
5  *
6  * LICENSE
7  *
8  * Copyright (c) 2009-2013 Nicholas J Humfrey.  All rights reserved.
9  * Copyright (c) 2005-2009 Zend Technologies USA Inc.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright notice,
16  *    this list of conditions and the following disclaimer in the documentation
17  *    and/or other materials provided with the distribution.
18  * 3. The name of the author 'Nicholas J Humfrey" may be used to endorse or
19  *    promote products derived from this software without specific prior
20  *    written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  * POSSIBILITY OF SUCH DAMAGE.
33  *
34  * @package    EasyRdf
35  * @copyright  Copyright (c) 2009-2013 Nicholas J Humfrey
36  *             Copyright (c) 2005-2009 Zend Technologies USA Inc.
37  * @license    http://www.opensource.org/licenses/bsd-license.php
38  */
39
40 /**
41  * This class is an implemetation of an HTTP client in PHP.
42  * It supports basic HTTP 1.0 and 1.1 requests. For a more complete
43  * implementation try Zend_Http_Client.
44  *
45  * @package    EasyRdf
46  * @copyright  Copyright (c) 2009-2013 Nicholas J Humfrey
47  * @license    http://www.opensource.org/licenses/bsd-license.php
48  */
49 class EasyRdf_Http_Client
50 {
51     /**
52      * Configuration array, set using the constructor or using ::setConfig()
53      *
54      * @var array
55      */
56     private $config = array(
57         'maxredirects'    => 5,
58         'useragent'       => 'EasyRdf_Http_Client',
59         'timeout'         => 10
60     );
61
62     /**
63      * Request URI
64      *
65      * @var string
66      */
67     private $uri = null;
68
69     /**
70      * Associative array of request headers
71      *
72      * @var array
73      */
74     private $headers = array();
75
76     /**
77      * HTTP request method
78      *
79      * @var string
80      */
81     private $method = 'GET';
82
83     /**
84      * Associative array of GET parameters
85      *
86      * @var array
87      */
88     private $paramsGet = array();
89
90     /**
91      * The raw post data to send. Could be set by setRawData($data).
92      *
93      * @var string
94      */
95     private $rawPostData = null;
96
97     /**
98      * Redirection counter
99      *
100      * @var int
101      */
102     private $redirectCounter = 0;
103
104     /**
105      * Constructor method. Will create a new HTTP client. Accepts the target
106      * URL and optionally configuration array.
107      *
108      * @param string $uri
109      * @param array $config Configuration key-value pairs.
110      */
111     public function __construct($uri = null, $config = null)
112     {
113         if ($uri !== null) {
114             $this->setUri($uri);
115         }
116         if ($config !== null) {
117             $this->setConfig($config);
118         }
119     }
120
121     /**
122      * Set the URI for the next request
123      *
124      * @param  string $uri
125      * @return EasyRdf_Http_Client
126      */
127     public function setUri($uri)
128     {
129         if (!is_string($uri)) {
130             $uri = strval($uri);
131         }
132
133         if (!preg_match('/^http(s?):/', $uri)) {
134             throw new InvalidArgumentException(
135                 "EasyRdf_Http_Client only supports the 'http' and 'https' schemes."
136             );
137         }
138
139         $this->uri = $uri;
140
141         return $this;
142     }
143
144     /**
145      * Get the URI for the next request
146      *
147      * @return string
148      */
149     public function getUri($asString = true)
150     {
151         return $this->uri;
152     }
153
154     /**
155      * Set configuration parameters for this HTTP client
156      *
157      * @param  array $config
158      * @return EasyRdf_Http_Client
159      * @throws InvalidArgumentException
160      */
161     public function setConfig($config = array())
162     {
163         if ($config == null or !is_array($config)) {
164             throw new InvalidArgumentException(
165                 "\$config should be an array and cannot be null"
166             );
167         }
168
169         foreach ($config as $k => $v) {
170             $this->config[strtolower($k)] = $v;
171         }
172
173         return $this;
174     }
175
176     /**
177      * Set a request header
178      *
179      * @param string $name Header name (e.g. 'Accept')
180      * @param string $value Header value or null
181      * @return EasyRdf_Http_Client
182      */
183     public function setHeaders($name, $value = null)
184     {
185         $normalizedName = strtolower($name);
186
187         // If $value is null or false, unset the header
188         if ($value === null || $value === false) {
189             unset($this->headers[$normalizedName]);
190         } else {
191             // Else, set the header
192             $this->headers[$normalizedName] = array($name, $value);
193         }
194
195         return $this;
196     }
197
198     /**
199      * Set the next request's method
200      *
201      * Validated the passed method and sets it.
202      *
203      * @param string $method
204      * @return EasyRdf_Http_Client
205      * @throws InvalidArgumentException
206      */
207     public function setMethod($method)
208     {
209         if (!is_string($method) or !preg_match('/^[A-Z]+$/', $method)) {
210             throw new InvalidArgumentException("Invalid HTTP request method.");
211         }
212
213         $this->method = $method;
214
215         return $this;
216     }
217
218     /**
219      * Get the method for the next request
220      *
221      * @return string
222      */
223     public function getMethod()
224     {
225         return $this->method;
226     }
227
228     /**
229      * Get the value of a specific header
230      *
231      * Note that if the header has more than one value, an array
232      * will be returned.
233      *
234      * @param string $key
235      * @return string|array|null The header value or null if it is not set
236      */
237     public function getHeader($key)
238     {
239         $key = strtolower($key);
240         if (isset($this->headers[$key])) {
241             return $this->headers[$key][1];
242         } else {
243             return null;
244         }
245     }
246
247     /**
248      * Set a GET parameter for the request.
249      *
250      * @param string $name
251      * @param string $value
252      * @return EasyRdf_Http_Client
253      */
254     public function setParameterGet($name, $value = null)
255     {
256         if ($value === null) {
257             if (isset($this->paramsGet[$name])) {
258                 unset($this->paramsGet[$name]);
259             }
260         } else {
261             $this->paramsGet[$name] = $value;
262         }
263
264         return $this;
265     }
266
267     /**
268      * Get a GET parameter for the request.
269      *
270      * @param string $name
271      * @return string value
272      */
273     public function getParameterGet($name)
274     {
275         if (isset($this->paramsGet[$name])) {
276             return $this->paramsGet[$name];
277         } else {
278             return null;
279         }
280     }
281
282     /**
283      * Get all the GET parameters
284      *
285      * @return array
286      */
287     public function getParametersGet()
288     {
289         return $this->paramsGet;
290     }
291
292     /**
293      * Get the number of redirections done on the last request
294      *
295      * @return int
296      */
297     public function getRedirectionsCount()
298     {
299         return $this->redirectCounter;
300     }
301
302     /**
303      * Set the raw (already encoded) POST data.
304      *
305      * This function is here for two reasons:
306      * 1. For advanced user who would like to set their own data, already encoded
307      * 2. For backwards compatibilty: If someone uses the old post($data) method.
308      *    this method will be used to set the encoded data.
309      *
310      * $data can also be stream (such as file) from which the data will be read.
311      *
312      * @param string|resource $data
313      * @return Zend_Http_Client
314      */
315     public function setRawData($data)
316     {
317         $this->rawPostData = $data;
318         return $this;
319     }
320
321     /**
322      * Get the raw (already encoded) POST data.
323      *
324      * @return string
325      */
326     public function getRawData()
327     {
328         return $this->rawPostData;
329     }
330
331     /**
332      * Clear all GET and POST parameters
333      *
334      * Should be used to reset the request parameters if the client is
335      * used for several concurrent requests.
336      *
337      * clearAll parameter controls if we clean just parameters or also
338      * headers
339      *
340      * @param bool $clearAll Should all data be cleared?
341      * @return EasyRdf_Http_Client
342      */
343     public function resetParameters($clearAll = false)
344     {
345         // Reset parameter data
346         $this->paramsGet   = array();
347         $this->rawPostData = null;
348         $this->method      = 'GET';
349
350         if ($clearAll) {
351             $this->headers = array();
352         } else {
353             // Clear outdated headers
354             if (isset($this->headers['content-type'])) {
355                 unset($this->headers['content-type']);
356             }
357             if (isset($this->headers['content-length'])) {
358                 unset($this->headers['content-length']);
359             }
360         }
361
362         return $this;
363     }
364
365     /**
366      * Send the HTTP request and return an HTTP response object
367      *
368      * @return EasyRdf_Http_Response
369      * @throws EasyRdf_Exception
370      */
371     public function request($method = null)
372     {
373         if (!$this->uri) {
374             throw new EasyRdf_Exception(
375                 "Set URI before calling EasyRdf_Http_Client->request()"
376             );
377         }
378
379         if ($method) {
380             $this->setMethod($method);
381         }
382         $this->redirectCounter = 0;
383         $response = null;
384
385         // Send the first request. If redirected, continue.
386         do {
387             // Clone the URI and add the additional GET parameters to it
388             $uri = parse_url($this->uri);
389             if ($uri['scheme'] === 'http') {
390                 $host = $uri['host'];
391             } elseif ($uri['scheme'] === 'https') {
392                 $host = 'ssl://'.$uri['host'];
393             } else {
394                 throw new EasyRdf_Exception(
395                     "Unsupported URI scheme: ".$uri['scheme']
396                 );
397             }
398
399             if (isset($uri['port'])) {
400                 $port = $uri['port'];
401             } else {
402                 if ($uri['scheme'] === 'https') {
403                     $port = 443;
404                 } else {
405                     $port = 80;
406                 }
407             }
408
409             if (!empty($this->paramsGet)) {
410                 if (!empty($uri['query'])) {
411                     $uri['query'] .= '&';
412                 } else {
413                     $uri['query'] = '';
414                 }
415                 $uri['query'] .= http_build_query($this->paramsGet, null, '&');
416             }
417
418             $headers = $this->prepareHeaders($uri['host'], $port);
419
420             // Open socket to remote server
421             $socket = @fsockopen($host, $port, $errno, $errstr, $this->config['timeout']);
422             if (!$socket) {
423                 throw new EasyRdf_Exception("Unable to connect to $host:$port ($errstr)");
424             }
425             stream_set_timeout($socket, $this->config['timeout']);
426             $info = stream_get_meta_data($socket);
427
428             // Write the request
429             $path = $uri['path'];
430             if (empty($path)) {
431                 $path = '/';
432             }
433             if (isset($uri['query'])) {
434                 $path .= '?' . $uri['query'];
435             }
436             fwrite($socket, "{$this->method} {$path} HTTP/1.1\r\n");
437             foreach ($headers as $k => $v) {
438                 if (is_string($k)) {
439                     $v = ucfirst($k) . ": $v";
440                 }
441                 fwrite($socket, "$v\r\n");
442             }
443             fwrite($socket, "\r\n");
444
445             // Send the request body, if there is one set
446             if (isset($this->rawPostData)) {
447                 fwrite($socket, $this->rawPostData);
448             }
449
450             // Read in the response
451             $content = '';
452             while (!feof($socket) && !$info['timed_out']) {
453                 $content .= fgets($socket);
454                 $info = stream_get_meta_data($socket);
455             }
456
457             if ($info['timed_out']) {
458                 throw new EasyRdf_Exception("Request to $host:$port timed out");
459             }
460
461             // FIXME: support HTTP/1.1 100 Continue
462
463             // Close the socket
464             @fclose($socket);
465
466             // Parse the response string
467             $response = EasyRdf_Http_Response::fromString($content);
468
469             // If we got redirected, look for the Location header
470             if ($response->isRedirect() &&
471                    ($location = $response->getHeader('location'))
472                ) {
473
474                 // Avoid problems with buggy servers that add whitespace at the
475                 // end of some headers (See ZF-11283)
476                 $location = trim($location);
477
478                 // Some servers return relative URLs in the location header
479                 // resolve it in relation to previous request
480                 $baseUri = new EasyRdf_ParsedUri($this->uri);
481                 $location = $baseUri->resolve($location)->toString();
482
483                 // If it is a 303 then drop the parameters and send a GET request
484                 if ($response->getStatus() == 303) {
485                     $this->resetParameters();
486                     $this->setMethod('GET');
487                 }
488
489                 // If we got a well formed absolute URI
490                 if (parse_url($location)) {
491                     $this->setHeaders('host', null);
492                     $this->setUri($location);
493                 } else {
494                     throw new EasyRdf_Exception(
495                         "Failed to parse Location header returned by ".
496                         $this->uri
497                     );
498                 }
499                 ++$this->redirectCounter;
500
501             } else {
502                 // If we didn't get any location, stop redirecting
503                 break;
504             }
505
506
507         } while ($this->redirectCounter < $this->config['maxredirects']);
508
509         return $response;
510     }
511
512     /**
513      * Prepare the request headers
514      *
515      * @ignore
516      * @return array
517      */
518     protected function prepareHeaders($host, $port)
519     {
520         $headers = array();
521
522         // Set the host header
523         if (! isset($this->headers['host'])) {
524             // If the port is not default, add it
525             if ($port !== 80 and $port !== 443) {
526                 $host .= ':' . $port;
527             }
528             $headers[] = "Host: {$host}";
529         }
530
531         // Set the connection header
532         if (! isset($this->headers['connection'])) {
533             $headers[] = "Connection: close";
534         }
535
536         // Set the user agent header
537         if (! isset($this->headers['user-agent'])) {
538             $headers[] = "User-Agent: {$this->config['useragent']}";
539         }
540
541         // If we have rawPostData set, set the content-length header
542         if (isset($this->rawPostData)) {
543             $headers[] = "Content-Length: ".strlen($this->rawPostData);
544         }
545
546         // Add all other user defined headers
547         foreach ($this->headers as $header) {
548             list($name, $value) = $header;
549             if (is_array($value)) {
550                 $value = implode(', ', $value);
551             }
552
553             $headers[] = "$name: $value";
554         }
555
556         return $headers;
557     }
558 }