Completed
Push — feature/fix-php-cross-version-... ( 02db5a...85ad8f )
by Juliette
01:29
created

Requests   F

Complexity

Total Complexity 124

Size/Duplication

Total Lines 962
Duplicated Lines 1.56 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 0
Metric Value
dl 15
loc 962
rs 1.6379
c 0
b 0
f 0
wmc 124
lcom 1
cbo 8

27 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 1 1
A autoloader() 0 11 3
A register_autoloader() 0 3 1
A add_transport() 0 10 2
B get_transport() 0 40 8
A get() 0 3 1
A head() 0 3 1
A delete() 0 3 1
A trace() 0 3 1
A post() 0 3 1
A put() 0 3 1
A options() 0 3 1
A patch() 0 3 1
A request() 0 28 4
F request_multiple() 0 67 15
A get_default_options() 0 27 2
A get_certificate_path() 0 7 2
A set_certificate_path() 0 3 1
F set_defaults() 0 51 14
F parse_response() 0 92 19
A parse_multiple() 0 12 2
B decode_chunked() 0 35 7
A flatten() 0 7 2
A flattern() 0 3 1
B decompress() 0 21 10
F compatible_gzinflate() 8 94 17
A match_domain() 7 20 5

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Requests often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Requests, and based on these observations, apply Extract Interface, too.

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 https://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.7';
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
			$class = self::$transport[$cap_string];
191
			return new $class();
192
		}
193
		// @codeCoverageIgnoreEnd
194
195
		if (empty(self::$transports)) {
196
			self::$transports = array(
197
				'Requests_Transport_cURL',
198
				'Requests_Transport_fsockopen',
199
			);
200
		}
201
202
		// Find us a working transport
203
		foreach (self::$transports as $class) {
204
			if (!class_exists($class)) {
205
				continue;
206
			}
207
208
			$result = call_user_func(array($class, 'test'), $capabilities);
209
			if ($result) {
210
				self::$transport[$cap_string] = $class;
211
				break;
212
			}
213
		}
214
		if (self::$transport[$cap_string] === null) {
215
			throw new Requests_Exception('No working transports found', 'notransport', self::$transports);
216
		}
217
218
		$class = self::$transport[$cap_string];
219
		return new $class();
220
	}
221
222
	/**#@+
223
	 * @see request()
224
	 * @param string $url
225
	 * @param array $headers
226
	 * @param array $options
227
	 * @return Requests_Response
228
	 */
229
	/**
230
	 * Send a GET request
231
	 */
232
	public static function get($url, $headers = array(), $options = array()) {
233
		return self::request($url, $headers, null, self::GET, $options);
234
	}
235
236
	/**
237
	 * Send a HEAD request
238
	 */
239
	public static function head($url, $headers = array(), $options = array()) {
240
		return self::request($url, $headers, null, self::HEAD, $options);
241
	}
242
243
	/**
244
	 * Send a DELETE request
245
	 */
246
	public static function delete($url, $headers = array(), $options = array()) {
247
		return self::request($url, $headers, null, self::DELETE, $options);
248
	}
249
250
	/**
251
	 * Send a TRACE request
252
	 */
253
	public static function trace($url, $headers = array(), $options = array()) {
254
		return self::request($url, $headers, null, self::TRACE, $options);
255
	}
256
	/**#@-*/
257
258
	/**#@+
259
	 * @see request()
260
	 * @param string $url
261
	 * @param array $headers
262
	 * @param array $data
263
	 * @param array $options
264
	 * @return Requests_Response
265
	 */
266
	/**
267
	 * Send a POST request
268
	 */
269
	public static function post($url, $headers = array(), $data = array(), $options = array()) {
270
		return self::request($url, $headers, $data, self::POST, $options);
271
	}
272
	/**
273
	 * Send a PUT request
274
	 */
275
	public static function put($url, $headers = array(), $data = array(), $options = array()) {
276
		return self::request($url, $headers, $data, self::PUT, $options);
277
	}
278
279
	/**
280
	 * Send an OPTIONS request
281
	 */
282
	public static function options($url, $headers = array(), $data = array(), $options = array()) {
283
		return self::request($url, $headers, $data, self::OPTIONS, $options);
284
	}
285
286
	/**
287
	 * Send a PATCH request
288
	 *
289
	 * Note: Unlike {@see post} and {@see put}, `$headers` is required, as the
290
	 * specification recommends that should send an ETag
291
	 *
292
	 * @link https://tools.ietf.org/html/rfc5789
293
	 */
294
	public static function patch($url, $headers, $data = array(), $options = array()) {
295
		return self::request($url, $headers, $data, self::PATCH, $options);
296
	}
297
	/**#@-*/
298
299
	/**
300
	 * Main interface for HTTP requests
301
	 *
302
	 * This method initiates a request and sends it via a transport before
303
	 * parsing.
304
	 *
305
	 * The `$options` parameter takes an associative array with the following
306
	 * options:
307
	 *
308
	 * - `timeout`: How long should we wait for a response?
309
	 *    Note: for cURL, a minimum of 1 second applies, as DNS resolution
310
	 *    operates at second-resolution only.
311
	 *    (float, seconds with a millisecond precision, default: 10, example: 0.01)
312
	 * - `connect_timeout`: How long should we wait while trying to connect?
313
	 *    (float, seconds with a millisecond precision, default: 10, example: 0.01)
314
	 * - `useragent`: Useragent to send to the server
315
	 *    (string, default: php-requests/$version)
316
	 * - `follow_redirects`: Should we follow 3xx redirects?
317
	 *    (boolean, default: true)
318
	 * - `redirects`: How many times should we redirect before erroring?
319
	 *    (integer, default: 10)
320
	 * - `blocking`: Should we block processing on this request?
321
	 *    (boolean, default: true)
322
	 * - `filename`: File to stream the body to instead.
323
	 *    (string|boolean, default: false)
324
	 * - `auth`: Authentication handler or array of user/password details to use
325
	 *    for Basic authentication
326
	 *    (Requests_Auth|array|boolean, default: false)
327
	 * - `proxy`: Proxy details to use for proxy by-passing and authentication
328
	 *    (Requests_Proxy|array|string|boolean, default: false)
329
	 * - `max_bytes`: Limit for the response body size.
330
	 *    (integer|boolean, default: false)
331
	 * - `idn`: Enable IDN parsing
332
	 *    (boolean, default: true)
333
	 * - `transport`: Custom transport. Either a class name, or a
334
	 *    transport object. Defaults to the first working transport from
335
	 *    {@see getTransport()}
336
	 *    (string|Requests_Transport, default: {@see getTransport()})
337
	 * - `hooks`: Hooks handler.
338
	 *    (Requests_Hooker, default: new Requests_Hooks())
339
	 * - `verify`: Should we verify SSL certificates? Allows passing in a custom
340
	 *    certificate file as a string. (Using true uses the system-wide root
341
	 *    certificate store instead, but this may have different behaviour
342
	 *    across transports.)
343
	 *    (string|boolean, default: library/Requests/Transport/cacert.pem)
344
	 * - `verifyname`: Should we verify the common name in the SSL certificate?
345
	 *    (boolean, default: true)
346
	 * - `data_format`: How should we send the `$data` parameter?
347
	 *    (string, one of 'query' or 'body', default: 'query' for
348
	 *    HEAD/GET/DELETE, 'body' for POST/PUT/OPTIONS/PATCH)
349
	 *
350
	 * @throws Requests_Exception On invalid URLs (`nonhttp`)
351
	 *
352
	 * @param string $url URL to request
353
	 * @param array $headers Extra headers to send with the request
354
	 * @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests
355
	 * @param string $type HTTP request type (use Requests constants)
356
	 * @param array $options Options for the request (see description for more information)
357
	 * @return Requests_Response
358
	 */
359
	public static function request($url, $headers = array(), $data = array(), $type = self::GET, $options = array()) {
360
		if (empty($options['type'])) {
361
			$options['type'] = $type;
362
		}
363
		$options = array_merge(self::get_default_options(), $options);
364
365
		self::set_defaults($url, $headers, $data, $type, $options);
366
367
		$options['hooks']->dispatch('requests.before_request', array(&$url, &$headers, &$data, &$type, &$options));
368
369
		if (!empty($options['transport'])) {
370
			$transport = $options['transport'];
371
372
			if (is_string($options['transport'])) {
373
				$transport = new $transport();
374
			}
375
		}
376
		else {
377
			$need_ssl = (0 === stripos($url, 'https://'));
378
			$capabilities = array('ssl' => $need_ssl);
379
			$transport = self::get_transport($capabilities);
380
		}
381
		$response = $transport->request($url, $headers, $data, $options);
382
383
		$options['hooks']->dispatch('requests.before_parse', array(&$response, $url, $headers, $data, $type, $options));
384
385
		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 359 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...
386
	}
387
388
	/**
389
	 * Send multiple HTTP requests simultaneously
390
	 *
391
	 * The `$requests` parameter takes an associative or indexed array of
392
	 * request fields. The key of each request can be used to match up the
393
	 * request with the returned data, or with the request passed into your
394
	 * `multiple.request.complete` callback.
395
	 *
396
	 * The request fields value is an associative array with the following keys:
397
	 *
398
	 * - `url`: Request URL Same as the `$url` parameter to
399
	 *    {@see Requests::request}
400
	 *    (string, required)
401
	 * - `headers`: Associative array of header fields. Same as the `$headers`
402
	 *    parameter to {@see Requests::request}
403
	 *    (array, default: `array()`)
404
	 * - `data`: Associative array of data fields or a string. Same as the
405
	 *    `$data` parameter to {@see Requests::request}
406
	 *    (array|string, default: `array()`)
407
	 * - `type`: HTTP request type (use Requests constants). Same as the `$type`
408
	 *    parameter to {@see Requests::request}
409
	 *    (string, default: `Requests::GET`)
410
	 * - `cookies`: Associative array of cookie name to value, or cookie jar.
411
	 *    (array|Requests_Cookie_Jar)
412
	 *
413
	 * If the `$options` parameter is specified, individual requests will
414
	 * inherit options from it. This can be used to use a single hooking system,
415
	 * or set all the types to `Requests::POST`, for example.
416
	 *
417
	 * In addition, the `$options` parameter takes the following global options:
418
	 *
419
	 * - `complete`: A callback for when a request is complete. Takes two
420
	 *    parameters, a Requests_Response/Requests_Exception reference, and the
421
	 *    ID from the request array (Note: this can also be overridden on a
422
	 *    per-request basis, although that's a little silly)
423
	 *    (callback)
424
	 *
425
	 * @param array $requests Requests data (see description for more information)
426
	 * @param array $options Global and default options (see {@see Requests::request})
427
	 * @return array Responses (either Requests_Response or a Requests_Exception object)
428
	 */
429
	public static function request_multiple($requests, $options = array()) {
430
		$options = array_merge(self::get_default_options(true), $options);
431
432
		if (!empty($options['hooks'])) {
433
			$options['hooks']->register('transport.internal.parse_response', array('Requests', 'parse_multiple'));
434
			if (!empty($options['complete'])) {
435
				$options['hooks']->register('multiple.request.complete', $options['complete']);
436
			}
437
		}
438
439
		foreach ($requests as $id => &$request) {
440
			if (!isset($request['headers'])) {
441
				$request['headers'] = array();
442
			}
443
			if (!isset($request['data'])) {
444
				$request['data'] = array();
445
			}
446
			if (!isset($request['type'])) {
447
				$request['type'] = self::GET;
448
			}
449
			if (!isset($request['options'])) {
450
				$request['options'] = $options;
451
				$request['options']['type'] = $request['type'];
452
			}
453
			else {
454
				if (empty($request['options']['type'])) {
455
					$request['options']['type'] = $request['type'];
456
				}
457
				$request['options'] = array_merge($options, $request['options']);
458
			}
459
460
			self::set_defaults($request['url'], $request['headers'], $request['data'], $request['type'], $request['options']);
461
462
			// Ensure we only hook in once
463
			if ($request['options']['hooks'] !== $options['hooks']) {
464
				$request['options']['hooks']->register('transport.internal.parse_response', array('Requests', 'parse_multiple'));
465
				if (!empty($request['options']['complete'])) {
466
					$request['options']['hooks']->register('multiple.request.complete', $request['options']['complete']);
467
				}
468
			}
469
		}
470
		unset($request);
471
472
		if (!empty($options['transport'])) {
473
			$transport = $options['transport'];
474
475
			if (is_string($options['transport'])) {
476
				$transport = new $transport();
477
			}
478
		}
479
		else {
480
			$transport = self::get_transport();
481
		}
482
		$responses = $transport->request_multiple($requests, $options);
483
484
		foreach ($responses as $id => &$response) {
485
			// If our hook got messed with somehow, ensure we end up with the
486
			// correct response
487
			if (is_string($response)) {
488
				$request = $requests[$id];
489
				self::parse_multiple($response, $request);
490
				$request['options']['hooks']->dispatch('multiple.request.complete', array(&$response, $id));
491
			}
492
		}
493
494
		return $responses;
495
	}
496
497
	/**
498
	 * Get the default options
499
	 *
500
	 * @see Requests::request() for values returned by this method
501
	 * @param boolean $multirequest Is this a multirequest?
502
	 * @return array Default option values
503
	 */
504
	protected static function get_default_options($multirequest = false) {
505
		$defaults = array(
506
			'timeout' => 10,
507
			'connect_timeout' => 10,
508
			'useragent' => 'php-requests/' . self::VERSION,
509
			'protocol_version' => 1.1,
510
			'redirected' => 0,
511
			'redirects' => 10,
512
			'follow_redirects' => true,
513
			'blocking' => true,
514
			'type' => self::GET,
515
			'filename' => false,
516
			'auth' => false,
517
			'proxy' => false,
518
			'cookies' => false,
519
			'max_bytes' => false,
520
			'idn' => true,
521
			'hooks' => null,
522
			'transport' => null,
523
			'verify' => Requests::get_certificate_path(),
524
			'verifyname' => true,
525
		);
526
		if ($multirequest !== false) {
527
			$defaults['complete'] = null;
528
		}
529
		return $defaults;
530
	}
531
532
	/**
533
	 * Get default certificate path.
534
	 *
535
	 * @return string Default certificate path.
536
	 */
537
	public static function get_certificate_path() {
538
		if ( ! empty( Requests::$certificate_path ) ) {
539
			return Requests::$certificate_path;
540
		}
541
542
		return dirname(__FILE__) . '/Requests/Transport/cacert.pem';
543
	}
544
545
	/**
546
	 * Set default certificate path.
547
	 *
548
	 * @param string $path Certificate path, pointing to a PEM file.
549
	 */
550
	public static function set_certificate_path( $path ) {
551
		Requests::$certificate_path = $path;
552
	}
553
554
	/**
555
	 * Set the default values
556
	 *
557
	 * @param string $url URL to request
558
	 * @param array $headers Extra headers to send with the request
559
	 * @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests
560
	 * @param string $type HTTP request type
561
	 * @param array $options Options for the request
562
	 * @return array $options
563
	 */
564
	protected static function set_defaults(&$url, &$headers, &$data, &$type, &$options) {
565
		if (!preg_match('/^http(s)?:\/\//i', $url, $matches)) {
566
			throw new Requests_Exception('Only HTTP(S) requests are handled.', 'nonhttp', $url);
567
		}
568
569
		if (empty($options['hooks'])) {
570
			$options['hooks'] = new Requests_Hooks();
571
		}
572
573
		if (is_array($options['auth'])) {
574
			$options['auth'] = new Requests_Auth_Basic($options['auth']);
575
		}
576
		if ($options['auth'] !== false) {
577
			$options['auth']->register($options['hooks']);
578
		}
579
580
		if (is_string($options['proxy']) || is_array($options['proxy'])) {
581
			$options['proxy'] = new Requests_Proxy_HTTP($options['proxy']);
0 ignored issues
show
Bug introduced by
It seems like $options['proxy'] can also be of type string; however, Requests_Proxy_HTTP::__construct() does only seem to accept array|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

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