Completed
Pull Request — master (#210)
by Ryan
04:31 queued 01:32
created

Requests::trace()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 1
eloc 2
nc 1
nop 3
dl 0
loc 3
rs 10
c 2
b 1
f 0
1
<?php
2
/**
3
 * Requests for PHP
4
 *
5
 * Inspired by Requests for Python.
6
 *
7
 * Based on concepts from SimplePie_File, RequestCore and WP_Http.
8
 *
9
 * @package Requests
10
 */
11
12
/**
13
 * Requests for PHP
14
 *
15
 * Inspired by Requests for Python.
16
 *
17
 * Based on concepts from SimplePie_File, RequestCore and WP_Http.
18
 *
19
 * @package Requests
20
 */
21
class Requests {
22
	/**
23
	 * POST method
24
	 *
25
	 * @var string
26
	 */
27
	const POST = 'POST';
28
29
	/**
30
	 * PUT method
31
	 *
32
	 * @var string
33
	 */
34
	const PUT = 'PUT';
35
36
	/**
37
	 * GET method
38
	 *
39
	 * @var string
40
	 */
41
	const GET = 'GET';
42
43
	/**
44
	 * HEAD method
45
	 *
46
	 * @var string
47
	 */
48
	const HEAD = 'HEAD';
49
50
	/**
51
	 * DELETE method
52
	 *
53
	 * @var string
54
	 */
55
	const DELETE = 'DELETE';
56
57
	/**
58
	 * OPTIONS method
59
	 *
60
	 * @var string
61
	 */
62
	const OPTIONS = 'OPTIONS';
63
64
	/**
65
	 * TRACE method
66
	 *
67
	 * @var string
68
	 */
69
	const TRACE = 'TRACE';
70
71
	/**
72
	 * PATCH method
73
	 *
74
	 * @link http://tools.ietf.org/html/rfc5789
75
	 * @var string
76
	 */
77
	const PATCH = 'PATCH';
78
79
	/**
80
	 * Default size of buffer size to read streams
81
	 *
82
	 * @var integer
83
	 */
84
	const BUFFER_SIZE = 1160;
85
86
	/**
87
	 * Current version of Requests
88
	 *
89
	 * @var string
90
	 */
91
	const VERSION = '1.6';
92
93
	/**
94
	 * Registered transport classes
95
	 *
96
	 * @var array
97
	 */
98
	protected static $transports = array();
99
100
	/**
101
	 * Selected transport name
102
	 *
103
	 * Use {@see get_transport()} instead
104
	 *
105
	 * @var array
106
	 */
107
	public static $transport = array();
108
109
	/**
110
	 * Default certificate path.
111
	 *
112
	 * @see Requests::get_certificate_path()
113
	 * @see Requests::set_certificate_path()
114
	 *
115
	 * @var string
116
	 */
117
	protected static $certificate_path;
118
119
	/**
120
	 * This is a static class, do not instantiate it
121
	 *
122
	 * @codeCoverageIgnore
123
	 */
124
	private function __construct() {}
125
126
	/**
127
	 * Autoloader for Requests
128
	 *
129
	 * Register this with {@see register_autoloader()} if you'd like to avoid
130
	 * having to create your own.
131
	 *
132
	 * (You can also use `spl_autoload_register` directly if you'd prefer.)
133
	 *
134
	 * @codeCoverageIgnore
135
	 *
136
	 * @param string $class Class name to load
137
	 */
138
	public static function autoloader($class) {
139
		// Check that the class starts with "Requests"
140
		if (strpos($class, 'Requests') !== 0) {
141
			return;
142
		}
143
144
		$file = str_replace('_', '/', $class);
145
		if (file_exists(dirname(__FILE__) . '/' . $file . '.php')) {
146
			require_once(dirname(__FILE__) . '/' . $file . '.php');
147
		}
148
	}
149
150
	/**
151
	 * Register the built-in autoloader
152
	 *
153
	 * @codeCoverageIgnore
154
	 */
155
	public static function register_autoloader() {
156
		spl_autoload_register(array('Requests', 'autoloader'));
157
	}
158
159
	/**
160
	 * Register a transport
161
	 *
162
	 * @param string $transport Transport class to add, must support the Requests_Transport interface
163
	 */
164
	public static function add_transport($transport) {
165
		if (empty(self::$transports)) {
166
			self::$transports = array(
167
				'Requests_Transport_cURL',
168
				'Requests_Transport_fsockopen',
169
			);
170
		}
171
172
		self::$transports = array_merge(self::$transports, array($transport));
173
	}
174
175
	/**
176
	 * Get a working transport
177
	 *
178
	 * @throws Requests_Exception If no valid transport is found (`notransport`)
179
	 * @return Requests_Transport
180
	 */
181
	protected static function get_transport($capabilities = array()) {
182
		// Caching code, don't bother testing coverage
183
		// @codeCoverageIgnoreStart
184
		// array of capabilities as a string to be used as an array key
185
		ksort($capabilities);
186
		$cap_string = serialize($capabilities);
187
188
		// Don't search for a transport if it's already been done for these $capabilities
189
		if (isset(self::$transport[$cap_string]) && self::$transport[$cap_string] !== null) {
190
			return new self::$transport[$cap_string]();
191
		}
192
		// @codeCoverageIgnoreEnd
193
194
		if (empty(self::$transports)) {
195
			self::$transports = array(
196
				'Requests_Transport_cURL',
197
				'Requests_Transport_fsockopen',
198
			);
199
		}
200
201
		// Find us a working transport
202
		foreach (self::$transports as $class) {
203
			if (!class_exists($class)) {
204
				continue;
205
			}
206
207
			$result = call_user_func(array($class, 'test'), $capabilities);
208
			if ($result) {
209
				self::$transport[$cap_string] = $class;
210
				break;
211
			}
212
		}
213
		if (self::$transport[$cap_string] === null) {
214
			throw new Requests_Exception('No working transports found', 'notransport', self::$transports);
215
		}
216
217
		return new self::$transport[$cap_string]();
218
	}
219
220
	/**#@+
221
	 * @see request()
222
	 * @param string $url
223
	 * @param array $headers
224
	 * @param array $options
225
	 * @return Requests_Response
226
	 */
227
	/**
228
	 * Send a GET request
229
	 */
230
	public static function get($url, $headers = array(), $options = array()) {
231
		return self::request($url, $headers, null, self::GET, $options);
232
	}
233
234
	/**
235
	 * Send a HEAD request
236
	 */
237
	public static function head($url, $headers = array(), $options = array()) {
238
		return self::request($url, $headers, null, self::HEAD, $options);
239
	}
240
241
	/**
242
	 * Send a DELETE request
243
	 */
244
	public static function delete($url, $headers = array(), $options = array()) {
245
		return self::request($url, $headers, null, self::DELETE, $options);
246
	}
247
248
	/**
249
	 * Send a TRACE request
250
	 */
251
	public static function trace($url, $headers = array(), $options = array()) {
252
		return self::request($url, $headers, null, self::TRACE, $options);
253
	}
254
	/**#@-*/
255
256
	/**#@+
257
	 * @see request()
258
	 * @param string $url
259
	 * @param array $headers
260
	 * @param array $data
261
	 * @param array $options
262
	 * @return Requests_Response
263
	 */
264
	/**
265
	 * Send a POST request
266
	 */
267
	public static function post($url, $headers = array(), $data = array(), $options = array()) {
268
		return self::request($url, $headers, $data, self::POST, $options);
269
	}
270
	/**
271
	 * Send a PUT request
272
	 */
273
	public static function put($url, $headers = array(), $data = array(), $options = array()) {
274
		return self::request($url, $headers, $data, self::PUT, $options);
275
	}
276
277
	/**
278
	 * Send an OPTIONS request
279
	 */
280
	public static function options($url, $headers = array(), $data = array(), $options = array()) {
281
		return self::request($url, $headers, $data, self::OPTIONS, $options);
282
	}
283
284
	/**
285
	 * Send a PATCH request
286
	 *
287
	 * Note: Unlike {@see post} and {@see put}, `$headers` is required, as the
288
	 * specification recommends that should send an ETag
289
	 *
290
	 * @link http://tools.ietf.org/html/rfc5789
291
	 */
292
	public static function patch($url, $headers, $data = array(), $options = array()) {
293
		return self::request($url, $headers, $data, self::PATCH, $options);
294
	}
295
	/**#@-*/
296
297
	/**
298
	 * Main interface for HTTP requests
299
	 *
300
	 * This method initiates a request and sends it via a transport before
301
	 * parsing.
302
	 *
303
	 * The `$options` parameter takes an associative array with the following
304
	 * options:
305
	 *
306
	 * - `timeout`: How long should we wait for a response?
307
	 *    (float, seconds with a millisecond precision, default: 10, example: 0.01)
308
	 * - `connect_timeout`: How long should we wait while trying to connect?
309
	 *    (float, seconds with a millisecond precision, default: 10, example: 0.01)
310
	 * - `useragent`: Useragent to send to the server
311
	 *    (string, default: php-requests/$version)
312
	 * - `follow_redirects`: Should we follow 3xx redirects?
313
	 *    (boolean, default: true)
314
	 * - `redirects`: How many times should we redirect before erroring?
315
	 *    (integer, default: 10)
316
	 * - `blocking`: Should we block processing on this request?
317
	 *    (boolean, default: true)
318
	 * - `filename`: File to stream the body to instead.
319
	 *    (string|boolean, default: false)
320
	 * - `auth`: Authentication handler or array of user/password details to use
321
	 *    for Basic authentication
322
	 *    (Requests_Auth|array|boolean, default: false)
323
	 * - `proxy`: Proxy details to use for proxy by-passing and authentication
324
	 *    (Requests_Proxy|array|boolean, default: false)
325
	 * - `max_bytes`: Limit for the response body size.
326
	 *    (integer|boolean, default: false)
327
	 * - `idn`: Enable IDN parsing
328
	 *    (boolean, default: true)
329
	 * - `transport`: Custom transport. Either a class name, or a
330
	 *    transport object. Defaults to the first working transport from
331
	 *    {@see getTransport()}
332
	 *    (string|Requests_Transport, default: {@see getTransport()})
333
	 * - `hooks`: Hooks handler.
334
	 *    (Requests_Hooker, default: new Requests_Hooks())
335
	 * - `verify`: Should we verify SSL certificates? Allows passing in a custom
336
	 *    certificate file as a string. (Using true uses the system-wide root
337
	 *    certificate store instead, but this may have different behaviour
338
	 *    across transports.)
339
	 *    (string|boolean, default: library/Requests/Transport/cacert.pem)
340
	 * - `verifyname`: Should we verify the common name in the SSL certificate?
341
	 *    (boolean: default, true)
342
	 * - `data_format`: How should we send the `$data` parameter?
343
	 *    (string, one of 'query' or 'body', default: 'query' for
344
	 *    HEAD/GET/DELETE, 'body' for POST/PUT/OPTIONS/PATCH)
345
	 *
346
	 * @throws Requests_Exception On invalid URLs (`nonhttp`)
347
	 *
348
	 * @param string $url URL to request
349
	 * @param array $headers Extra headers to send with the request
350
	 * @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 121 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
351
	 * @param string $type HTTP request type (use Requests constants)
352
	 * @param array $options Options for the request (see description for more information)
353
	 * @return Requests_Response
354
	 */
355
	public static function request($url, $headers = array(), $data = array(), $type = self::GET, $options = array()) {
356
		if (empty($options['type'])) {
357
			$options['type'] = $type;
358
		}
359
		$options = array_merge(self::get_default_options(), $options);
360
361
		self::set_defaults($url, $headers, $data, $type, $options);
362
363
		$options['hooks']->dispatch('requests.before_request', array(&$url, &$headers, &$data, &$type, &$options));
364
365
		if (!empty($options['transport'])) {
366
			$transport = $options['transport'];
367
368
			if (is_string($options['transport'])) {
369
				$transport = new $transport();
370
			}
371
		}
372
		else {
373
			$need_ssl = (0 === stripos($url, 'https://'));
374
			$capabilities = array('ssl' => $need_ssl);
375
			$transport = self::get_transport($capabilities);
376
		}
377
		$response = $transport->request($url, $headers, $data, $options);
378
379
		$options['hooks']->dispatch('requests.before_parse', array(&$response, $url, $headers, $data, $type, $options));
380
381
		return self::parse_response($response, $url, $headers, $data, $options);
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 355 can also be of type null; however, Requests::parse_response() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
382
	}
383
384
	/**
385
	 * Send multiple HTTP requests simultaneously
386
	 *
387
	 * The `$requests` parameter takes an associative or indexed array of
388
	 * request fields. The key of each request can be used to match up the
389
	 * request with the returned data, or with the request passed into your
390
	 * `multiple.request.complete` callback.
391
	 *
392
	 * The request fields value is an associative array with the following keys:
393
	 *
394
	 * - `url`: Request URL Same as the `$url` parameter to
395
	 *    {@see Requests::request}
396
	 *    (string, required)
397
	 * - `headers`: Associative array of header fields. Same as the `$headers`
398
	 *    parameter to {@see Requests::request}
399
	 *    (array, default: `array()`)
400
	 * - `data`: Associative array of data fields or a string. Same as the
401
	 *    `$data` parameter to {@see Requests::request}
402
	 *    (array|string, default: `array()`)
403
	 * - `type`: HTTP request type (use Requests constants). Same as the `$type`
404
	 *    parameter to {@see Requests::request}
405
	 *    (string, default: `Requests::GET`)
406
	 * - `cookies`: Associative array of cookie name to value, or cookie jar.
407
	 *    (array|Requests_Cookie_Jar)
408
	 *
409
	 * If the `$options` parameter is specified, individual requests will
410
	 * inherit options from it. This can be used to use a single hooking system,
411
	 * or set all the types to `Requests::POST`, for example.
412
	 *
413
	 * In addition, the `$options` parameter takes the following global options:
414
	 *
415
	 * - `complete`: A callback for when a request is complete. Takes two
416
	 *    parameters, a Requests_Response/Requests_Exception reference, and the
417
	 *    ID from the request array (Note: this can also be overridden on a
418
	 *    per-request basis, although that's a little silly)
419
	 *    (callback)
420
	 *
421
	 * @param array $requests Requests data (see description for more information)
422
	 * @param array $options Global and default options (see {@see Requests::request})
423
	 * @return array Responses (either Requests_Response or a Requests_Exception object)
424
	 */
425
	public static function request_multiple($requests, $options = array()) {
426
		$options = array_merge(self::get_default_options(true), $options);
427
428
		if (!empty($options['hooks'])) {
429
			$options['hooks']->register('transport.internal.parse_response', array('Requests', 'parse_multiple'));
430
			if (!empty($options['complete'])) {
431
				$options['hooks']->register('multiple.request.complete', $options['complete']);
432
			}
433
		}
434
435
		foreach ($requests as $id => &$request) {
436
			if (!isset($request['headers'])) {
437
				$request['headers'] = array();
438
			}
439
			if (!isset($request['data'])) {
440
				$request['data'] = array();
441
			}
442
			if (!isset($request['type'])) {
443
				$request['type'] = self::GET;
444
			}
445
			if (!isset($request['options'])) {
446
				$request['options'] = $options;
447
				$request['options']['type'] = $request['type'];
448
			}
449
			else {
450
				if (empty($request['options']['type'])) {
451
					$request['options']['type'] = $request['type'];
452
				}
453
				$request['options'] = array_merge($options, $request['options']);
454
			}
455
456
			self::set_defaults($request['url'], $request['headers'], $request['data'], $request['type'], $request['options']);
457
458
			// Ensure we only hook in once
459
			if ($request['options']['hooks'] !== $options['hooks']) {
460
				$request['options']['hooks']->register('transport.internal.parse_response', array('Requests', 'parse_multiple'));
461
				if (!empty($request['options']['complete'])) {
462
					$request['options']['hooks']->register('multiple.request.complete', $request['options']['complete']);
463
				}
464
			}
465
		}
466
		unset($request);
467
468
		if (!empty($options['transport'])) {
469
			$transport = $options['transport'];
470
471
			if (is_string($options['transport'])) {
472
				$transport = new $transport();
473
			}
474
		}
475
		else {
476
			$transport = self::get_transport();
477
		}
478
		$responses = $transport->request_multiple($requests, $options);
479
480
		foreach ($responses as $id => &$response) {
481
			// If our hook got messed with somehow, ensure we end up with the
482
			// correct response
483
			if (is_string($response)) {
484
				$request = $requests[$id];
485
				self::parse_multiple($response, $request);
486
				$request['options']['hooks']->dispatch('multiple.request.complete', array(&$response, $id));
487
			}
488
		}
489
490
		return $responses;
491
	}
492
493
	/**
494
	 * Get the default options
495
	 *
496
	 * @see Requests::request() for values returned by this method
497
	 * @param boolean $multirequest Is this a multirequest?
498
	 * @return array Default option values
499
	 */
500
	protected static function get_default_options($multirequest = false) {
501
		$defaults = array(
502
			'timeout' => 10,
503
			'connect_timeout' => 10,
504
			'useragent' => 'php-requests/' . self::VERSION,
505
			'protocol_version' => 1.1,
506
			'redirected' => 0,
507
			'redirects' => 10,
508
			'follow_redirects' => true,
509
			'blocking' => true,
510
			'type' => self::GET,
511
			'filename' => false,
512
			'auth' => false,
513
			'proxy' => false,
514
			'cookies' => false,
515
			'max_bytes' => false,
516
			'idn' => true,
517
			'hooks' => null,
518
			'transport' => null,
519
			'verify' => Requests::get_certificate_path(),
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
520
			'verifyname' => true,
521
		);
522
		if ($multirequest !== false) {
523
			$defaults['complete'] = null;
524
		}
525
		return $defaults;
526
	}
527
528
	/**
529
	 * Get default certificate path.
530
	 *
531
	 * @return string Default certificate path.
532
	 */
533
	public static function get_certificate_path() {
534
		if ( ! empty( Requests::$certificate_path ) ) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
535
			return Requests::$certificate_path;
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
536
		}
537
538
		return dirname(__FILE__) . '/Requests/Transport/cacert.pem';
539
	}
540
541
	/**
542
	 * Set default certificate path.
543
	 *
544
	 * @param string $path Certificate path, pointing to a PEM file.
545
	 */
546
	public static function set_certificate_path( $path ) {
547
		Requests::$certificate_path = $path;
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
548
	}
549
550
	/**
551
	 * Set the default values
552
	 *
553
	 * @param string $url URL to request
554
	 * @param array $headers Extra headers to send with the request
555
	 * @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 121 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
556
	 * @param string $type HTTP request type
557
	 * @param array $options Options for the request
558
	 * @return array $options
559
	 */
560
	protected static function set_defaults(&$url, &$headers, &$data, &$type, &$options) {
561
		if (!preg_match('/^http(s)?:\/\//i', $url, $matches)) {
562
			throw new Requests_Exception('Only HTTP(S) requests are handled.', 'nonhttp', $url);
563
		}
564
565
		if (empty($options['hooks'])) {
566
			$options['hooks'] = new Requests_Hooks();
567
		}
568
569
		if (is_array($options['auth'])) {
570
			$options['auth'] = new Requests_Auth_Basic($options['auth']);
571
		}
572
		if ($options['auth'] !== false) {
573
			$options['auth']->register($options['hooks']);
574
		}
575
576
		if (!empty($options['proxy'])) {
577
			$options['proxy'] = new Requests_Proxy_HTTP($options['proxy']);
578
		}
579
		if ($options['proxy'] !== false) {
580
			$options['proxy']->register($options['hooks']);
581
		}
582
583
		if (is_array($options['cookies'])) {
584
			$options['cookies'] = new Requests_Cookie_Jar($options['cookies']);
585
		}
586
		elseif (empty($options['cookies'])) {
587
			$options['cookies'] = new Requests_Cookie_Jar();
588
		}
589
		if ($options['cookies'] !== false) {
590
			$options['cookies']->register($options['hooks']);
591
		}
592
593
		if ($options['idn'] !== false) {
594
			$iri = new Requests_IRI($url);
595
			$iri->host = Requests_IDNAEncoder::encode($iri->ihost);
596
			$url = $iri->uri;
597
		}
598
599
		// Massage the type to ensure we support it.
600
		$type = strtoupper($type);
601
602
		if (!isset($options['data_format'])) {
603
			if (in_array($type, array(self::HEAD, self::GET, self::DELETE))) {
604
				$options['data_format'] = 'query';
605
			}
606
			else {
607
				$options['data_format'] = 'body';
608
			}
609
		}
610
	}
611
612
	/**
613
	 * HTTP response parser
614
	 *
615
	 * @throws Requests_Exception On missing head/body separator (`requests.no_crlf_separator`)
616
	 * @throws Requests_Exception On missing head/body separator (`noversion`)
617
	 * @throws Requests_Exception On missing head/body separator (`toomanyredirects`)
618
	 *
619
	 * @param string $headers Full response text including headers and body
620
	 * @param string $url Original request URL
621
	 * @param array $req_headers Original $headers array passed to {@link request()}, in case we need to follow redirects
622
	 * @param array $req_data Original $data array passed to {@link request()}, in case we need to follow redirects
623
	 * @param array $options Original $options array passed to {@link request()}, in case we need to follow redirects
624
	 * @return Requests_Response
625
	 */
626
	protected static function parse_response($headers, $url, $req_headers, $req_data, $options) {
627
		$return = new Requests_Response();
628
		if (!$options['blocking']) {
629
			return $return;
630
		}
631
632
		$return->raw = $headers;
633
		$return->url = $url;
634
635
		if (!$options['filename']) {
636
			if (($pos = strpos($headers, "\r\n\r\n")) === false) {
637
				// Crap!
638
				throw new Requests_Exception('Missing header/body separator', 'requests.no_crlf_separator');
639
			}
640
641
			$headers = substr($return->raw, 0, $pos);
642
			$return->body = substr($return->raw, $pos + strlen("\n\r\n\r"));
643
		}
644
		else {
645
			$return->body = '';
646
		}
647
		// Pretend CRLF = LF for compatibility (RFC 2616, section 19.3)
648
		$headers = str_replace("\r\n", "\n", $headers);
649
		// Unfold headers (replace [CRLF] 1*( SP | HT ) with SP) as per RFC 2616 (section 2.2)
650
		$headers = preg_replace('/\n[ \t]/', ' ', $headers);
651
		$headers = explode("\n", $headers);
652
		preg_match('#^HTTP/(1\.\d)[ \t]+(\d+)#i', array_shift($headers), $matches);
653
		if (empty($matches)) {
654
			throw new Requests_Exception('Response could not be parsed', 'noversion', $headers);
655
		}
656
		$return->protocol_version = (float) $matches[1];
657
		$return->status_code = (int) $matches[2];
658
		if ($return->status_code >= 200 && $return->status_code < 300) {
659
			$return->success = true;
660
		}
661
662
		foreach ($headers as $header) {
663
			list($key, $value) = explode(':', $header, 2);
664
			$value = trim($value);
665
			preg_replace('#(\s+)#i', ' ', $value);
666
			$return->headers[$key] = $value;
667
		}
668
		if (isset($return->headers['transfer-encoding'])) {
669
			$return->body = self::decode_chunked($return->body);
670
			unset($return->headers['transfer-encoding']);
671
		}
672
		if (isset($return->headers['content-encoding'])) {
673
			$return->body = self::decompress($return->body);
674
		}
675
676
		//fsockopen and cURL compatibility
677
		if (isset($return->headers['connection'])) {
678
			unset($return->headers['connection']);
679
		}
680
681
		$options['hooks']->dispatch('requests.before_redirect_check', array(&$return, $req_headers, $req_data, $options));
682
683
		if ($return->is_redirect() && $options['follow_redirects'] === true) {
684
			if (isset($return->headers['location']) && $options['redirected'] < $options['redirects']) {
685
				if ($return->status_code === 303) {
686
					$options['type'] = self::GET;
687
				}
688
				$options['redirected']++;
689
				$location = $return->headers['location'];
690
				if (strpos($location, 'http://') !== 0 && strpos($location, 'https://') !== 0) {
691
					// relative redirect, for compatibility make it absolute
692
					$location = Requests_IRI::absolutize($url, $location);
693
					$location = $location->uri;
694
				}
695
696
				$hook_args = array(
697
					&$location,
698
					&$req_headers,
699
					&$req_data,
700
					&$options,
701
					$return
702
				);
703
				$options['hooks']->dispatch('requests.before_redirect', $hook_args);
704
				$redirected = self::request($location, $req_headers, $req_data, $options['type'], $options);
705
				$redirected->history[] = $return;
706
				return $redirected;
707
			}
708
			elseif ($options['redirected'] >= $options['redirects']) {
709
				throw new Requests_Exception('Too many redirects', 'toomanyredirects', $return);
710
			}
711
		}
712
713
		$return->redirects = $options['redirected'];
714
715
		$options['hooks']->dispatch('requests.after_request', array(&$return, $req_headers, $req_data, $options));
716
		return $return;
717
	}
718
719
	/**
720
	 * Callback for `transport.internal.parse_response`
721
	 *
722
	 * Internal use only. Converts a raw HTTP response to a Requests_Response
723
	 * while still executing a multiple request.
724
	 *
725
	 * @param string $response Full response text including headers and body (will be overwritten with Response instance)
726
	 * @param array $request Request data as passed into {@see Requests::request_multiple()}
727
	 * @return null `$response` is either set to a Requests_Response instance, or a Requests_Exception object
728
	 */
729
	public static function parse_multiple(&$response, $request) {
730
		try {
731
			$url = $request['url'];
732
			$headers = $request['headers'];
733
			$data = $request['data'];
734
			$options = $request['options'];
735
			$response = self::parse_response($response, $url, $headers, $data, $options);
736
		}
737
		catch (Requests_Exception $e) {
738
			$response = $e;
739
		}
740
	}
741
742
	/**
743
	 * Decoded a chunked body as per RFC 2616
744
	 *
745
	 * @see http://tools.ietf.org/html/rfc2616#section-3.6.1
746
	 * @param string $data Chunked body
747
	 * @return string Decoded body
748
	 */
749
	protected static function decode_chunked($data) {
750
		if (!preg_match('/^([0-9a-f]+)[^\r\n]*\r\n/i', trim($data))) {
751
			return $data;
752
		}
753
754
		$decoded = '';
755
		$encoded = $data;
756
757
		while (true) {
758
			$is_chunked = (bool) preg_match('/^([0-9a-f]+)[^\r\n]*\r\n/i', $encoded, $matches);
759
			if (!$is_chunked) {
760
				// Looks like it's not chunked after all
761
				return $data;
762
			}
763
764
			$length = hexdec(trim($matches[1]));
765
			if ($length === 0) {
766
				// Ignore trailer headers
767
				return $decoded;
768
			}
769
770
			$chunk_length = strlen($matches[0]);
771
			$decoded .= substr($encoded, $chunk_length, $length);
772
			$encoded = substr($encoded, $chunk_length + $length + 2);
773
774
			if (trim($encoded) === '0' || empty($encoded)) {
775
				return $decoded;
776
			}
777
		}
778
779
		// We'll never actually get down here
780
		// @codeCoverageIgnoreStart
781
	}
782
	// @codeCoverageIgnoreEnd
783
784
	/**
785
	 * Convert a key => value array to a 'key: value' array for headers
786
	 *
787
	 * @param array $array Dictionary of header values
788
	 * @return array List of headers
789
	 */
790
	public static function flatten($array) {
791
		$return = array();
792
		foreach ($array as $key => $value) {
793
			$return[] = sprintf('%s: %s', $key, $value);
794
		}
795
		return $return;
796
	}
797
798
	/**
799
	 * Convert a key => value array to a 'key: value' array for headers
800
	 *
801
	 * @codeCoverageIgnore
802
	 * @deprecated Misspelling of {@see Requests::flatten}
803
	 * @param array $array Dictionary of header values
804
	 * @return array List of headers
805
	 */
806
	public static function flattern($array) {
807
		return self::flatten($array);
808
	}
809
810
	/**
811
	 * Decompress an encoded body
812
	 *
813
	 * Implements gzip, compress and deflate. Guesses which it is by attempting
814
	 * to decode.
815
	 *
816
	 * @param string $data Compressed data in one of the above formats
817
	 * @return string Decompressed string
818
	 */
819
	public static function decompress($data) {
820
		if (substr($data, 0, 2) !== "\x1f\x8b" && substr($data, 0, 2) !== "\x78\x9c") {
821
			// Not actually compressed. Probably cURL ruining this for us.
822
			return $data;
823
		}
824
825
		if (function_exists('gzdecode') && ($decoded = @gzdecode($data)) !== false) {
826
			return $decoded;
827
		}
828
		elseif (function_exists('gzinflate') && ($decoded = @gzinflate($data)) !== false) {
829
			return $decoded;
830
		}
831
		elseif (($decoded = self::compatible_gzinflate($data)) !== false) {
832
			return $decoded;
833
		}
834
		elseif (function_exists('gzuncompress') && ($decoded = @gzuncompress($data)) !== false) {
835
			return $decoded;
836
		}
837
838
		return $data;
839
	}
840
841
	/**
842
	 * Decompression of deflated string while staying compatible with the majority of servers.
843
	 *
844
	 * Certain Servers will return deflated data with headers which PHP's gzinflate()
845
	 * function cannot handle out of the box. The following function has been created from
846
	 * various snippets on the gzinflate() PHP documentation.
847
	 *
848
	 * Warning: Magic numbers within. Due to the potential different formats that the compressed
849
	 * data may be returned in, some "magic offsets" are needed to ensure proper decompression
850
	 * takes place. For a simple progmatic way to determine the magic offset in use, see:
851
	 * http://core.trac.wordpress.org/ticket/18273
852
	 *
853
	 * @since 2.8.1
854
	 * @link http://core.trac.wordpress.org/ticket/18273
855
	 * @link http://au2.php.net/manual/en/function.gzinflate.php#70875
856
	 * @link http://au2.php.net/manual/en/function.gzinflate.php#77336
857
	 *
858
	 * @param string $gzData String to decompress.
859
	 * @return string|bool False on failure.
860
	 */
861
	public static function compatible_gzinflate($gzData) {
862
		// Compressed data might contain a full zlib header, if so strip it for
863
		// gzinflate()
864
		if (substr($gzData, 0, 3) == "\x1f\x8b\x08") {
865
			$i = 10;
866
			$flg = ord(substr($gzData, 3, 1));
867
			if ($flg > 0) {
868
				if ($flg & 4) {
869
					list($xlen) = unpack('v', substr($gzData, $i, 2));
870
					$i = $i + 2 + $xlen;
871
				}
872
				if ($flg & 8) {
873
					$i = strpos($gzData, "\0", $i) + 1;
874
				}
875
				if ($flg & 16) {
876
					$i = strpos($gzData, "\0", $i) + 1;
877
				}
878
				if ($flg & 2) {
879
					$i = $i + 2;
880
				}
881
			}
882
			$decompressed = self::compatible_gzinflate(substr($gzData, $i));
883
			if (false !== $decompressed) {
884
				return $decompressed;
885
			}
886
		}
887
888
		// If the data is Huffman Encoded, we must first strip the leading 2
889
		// byte Huffman marker for gzinflate()
890
		// The response is Huffman coded by many compressors such as
891
		// java.util.zip.Deflater, Ruby’s Zlib::Deflate, and .NET's
892
		// System.IO.Compression.DeflateStream.
893
		//
894
		// See http://decompres.blogspot.com/ for a quick explanation of this
895
		// data type
896
		$huffman_encoded = false;
897
898
		// low nibble of first byte should be 0x08
899
		list(, $first_nibble)    = unpack('h', $gzData);
900
901
		// First 2 bytes should be divisible by 0x1F
902
		list(, $first_two_bytes) = unpack('n', $gzData);
903
904
		if (0x08 == $first_nibble && 0 == ($first_two_bytes % 0x1F)) {
905
			$huffman_encoded = true;
906
		}
907
908 View Code Duplication
		if ($huffman_encoded) {
909
			if (false !== ($decompressed = @gzinflate(substr($gzData, 2)))) {
910
				return $decompressed;
911
			}
912
		}
913
914
		if ("\x50\x4b\x03\x04" == substr($gzData, 0, 4)) {
915
			// ZIP file format header
916
			// Offset 6: 2 bytes, General-purpose field
917
			// Offset 26: 2 bytes, filename length
918
			// Offset 28: 2 bytes, optional field length
919
			// Offset 30: Filename field, followed by optional field, followed
920
			// immediately by data
921
			list(, $general_purpose_flag) = unpack('v', substr($gzData, 6, 2));
922
923
			// If the file has been compressed on the fly, 0x08 bit is set of
924
			// the general purpose field. We can use this to differentiate
925
			// between a compressed document, and a ZIP file
926
			$zip_compressed_on_the_fly = (0x08 == (0x08 & $general_purpose_flag));
927
928
			if (!$zip_compressed_on_the_fly) {
929
				// Don't attempt to decode a compressed zip file
930
				return $gzData;
931
			}
932
933
			// Determine the first byte of data, based on the above ZIP header
934
			// offsets:
935
			$first_file_start = array_sum(unpack('v2', substr($gzData, 26, 4)));
936 View Code Duplication
			if (false !== ($decompressed = @gzinflate(substr($gzData, 30 + $first_file_start)))) {
937
				return $decompressed;
938
			}
939
			return false;
940
		}
941
942
		// Finally fall back to straight gzinflate
943
		if (false !== ($decompressed = @gzinflate($gzData))) {
944
			return $decompressed;
945
		}
946
947
		// Fallback for all above failing, not expected, but included for
948
		// debugging and preventing regressions and to track stats
949
		if (false !== ($decompressed = @gzinflate(substr($gzData, 2)))) {
950
			return $decompressed;
951
		}
952
953
		return false;
954
	}
955
956
	public static function match_domain($host, $reference) {
957
		// Check for a direct match
958
		if ($host === $reference) {
959
			return true;
960
		}
961
962
		// Calculate the valid wildcard match if the host is not an IP address
963
		// Also validates that the host has 3 parts or more, as per Firefox's
964
		// ruleset.
965
		$parts = explode('.', $host);
966 View Code Duplication
		if (ip2long($host) === false && count($parts) >= 3) {
967
			$parts[0] = '*';
968
			$wildcard = implode('.', $parts);
969
			if ($wildcard === $reference) {
970
				return true;
971
			}
972
		}
973
974
		return false;
975
	}
976
}
977