Completed
Pull Request — master (#251)
by
unknown
64:57 queued 59:24
created

Requests   D

Complexity

Total Complexity 126

Size/Duplication

Total Lines 975
Duplicated Lines 1.54 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 0
Metric Value
dl 15
loc 975
rs 4.4444
c 0
b 0
f 0
wmc 126
lcom 1
cbo 8

28 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
C get_transport() 0 38 8
A has_capability() 0 9 2
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
B request() 0 28 4
F request_multiple() 0 67 15
B 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
C decode_chunked() 0 35 7
A flatten() 0 7 2
A flattern() 0 3 1
C decompress() 0 21 10
F compatible_gzinflate() 8 94 17
B 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
			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
	 * Checks to see if we have a transport for the capabilities requested.
222
	 *
223
	 * @return bool
224
	 */
225
	public static function has_capability($capabilities = array()) {
226
		try {
227
			$transport = self::get_transport($capabilities);
0 ignored issues
show
Unused Code introduced by
$transport is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
228
		} catch(Requests_Exception $e) {
229
			return false;
230
		}
231
232
		return true;
233
	}
234
235
	/**#@+
236
	 * @see request()
237
	 * @param string $url
238
	 * @param array $headers
239
	 * @param array $options
240
	 * @return Requests_Response
241
	 */
242
	/**
243
	 * Send a GET request
244
	 */
245
	public static function get($url, $headers = array(), $options = array()) {
246
		return self::request($url, $headers, null, self::GET, $options);
247
	}
248
249
	/**
250
	 * Send a HEAD request
251
	 */
252
	public static function head($url, $headers = array(), $options = array()) {
253
		return self::request($url, $headers, null, self::HEAD, $options);
254
	}
255
256
	/**
257
	 * Send a DELETE request
258
	 */
259
	public static function delete($url, $headers = array(), $options = array()) {
260
		return self::request($url, $headers, null, self::DELETE, $options);
261
	}
262
263
	/**
264
	 * Send a TRACE request
265
	 */
266
	public static function trace($url, $headers = array(), $options = array()) {
267
		return self::request($url, $headers, null, self::TRACE, $options);
268
	}
269
	/**#@-*/
270
271
	/**#@+
272
	 * @see request()
273
	 * @param string $url
274
	 * @param array $headers
275
	 * @param array $data
276
	 * @param array $options
277
	 * @return Requests_Response
278
	 */
279
	/**
280
	 * Send a POST request
281
	 */
282
	public static function post($url, $headers = array(), $data = array(), $options = array()) {
283
		return self::request($url, $headers, $data, self::POST, $options);
284
	}
285
	/**
286
	 * Send a PUT request
287
	 */
288
	public static function put($url, $headers = array(), $data = array(), $options = array()) {
289
		return self::request($url, $headers, $data, self::PUT, $options);
290
	}
291
292
	/**
293
	 * Send an OPTIONS request
294
	 */
295
	public static function options($url, $headers = array(), $data = array(), $options = array()) {
296
		return self::request($url, $headers, $data, self::OPTIONS, $options);
297
	}
298
299
	/**
300
	 * Send a PATCH request
301
	 *
302
	 * Note: Unlike {@see post} and {@see put}, `$headers` is required, as the
303
	 * specification recommends that should send an ETag
304
	 *
305
	 * @link https://tools.ietf.org/html/rfc5789
306
	 */
307
	public static function patch($url, $headers, $data = array(), $options = array()) {
308
		return self::request($url, $headers, $data, self::PATCH, $options);
309
	}
310
	/**#@-*/
311
312
	/**
313
	 * Main interface for HTTP requests
314
	 *
315
	 * This method initiates a request and sends it via a transport before
316
	 * parsing.
317
	 *
318
	 * The `$options` parameter takes an associative array with the following
319
	 * options:
320
	 *
321
	 * - `timeout`: How long should we wait for a response?
322
	 *    Note: for cURL, a minimum of 1 second applies, as DNS resolution
323
	 *    operates at second-resolution only.
324
	 *    (float, seconds with a millisecond precision, default: 10, example: 0.01)
325
	 * - `connect_timeout`: How long should we wait while trying to connect?
326
	 *    (float, seconds with a millisecond precision, default: 10, example: 0.01)
327
	 * - `useragent`: Useragent to send to the server
328
	 *    (string, default: php-requests/$version)
329
	 * - `follow_redirects`: Should we follow 3xx redirects?
330
	 *    (boolean, default: true)
331
	 * - `redirects`: How many times should we redirect before erroring?
332
	 *    (integer, default: 10)
333
	 * - `blocking`: Should we block processing on this request?
334
	 *    (boolean, default: true)
335
	 * - `filename`: File to stream the body to instead.
336
	 *    (string|boolean, default: false)
337
	 * - `auth`: Authentication handler or array of user/password details to use
338
	 *    for Basic authentication
339
	 *    (Requests_Auth|array|boolean, default: false)
340
	 * - `proxy`: Proxy details to use for proxy by-passing and authentication
341
	 *    (Requests_Proxy|array|string|boolean, default: false)
342
	 * - `max_bytes`: Limit for the response body size.
343
	 *    (integer|boolean, default: false)
344
	 * - `idn`: Enable IDN parsing
345
	 *    (boolean, default: true)
346
	 * - `transport`: Custom transport. Either a class name, or a
347
	 *    transport object. Defaults to the first working transport from
348
	 *    {@see getTransport()}
349
	 *    (string|Requests_Transport, default: {@see getTransport()})
350
	 * - `hooks`: Hooks handler.
351
	 *    (Requests_Hooker, default: new Requests_Hooks())
352
	 * - `verify`: Should we verify SSL certificates? Allows passing in a custom
353
	 *    certificate file as a string. (Using true uses the system-wide root
354
	 *    certificate store instead, but this may have different behaviour
355
	 *    across transports.)
356
	 *    (string|boolean, default: library/Requests/Transport/cacert.pem)
357
	 * - `verifyname`: Should we verify the common name in the SSL certificate?
358
	 *    (boolean: default, true)
359
	 * - `data_format`: How should we send the `$data` parameter?
360
	 *    (string, one of 'query' or 'body', default: 'query' for
361
	 *    HEAD/GET/DELETE, 'body' for POST/PUT/OPTIONS/PATCH)
362
	 *
363
	 * @throws Requests_Exception On invalid URLs (`nonhttp`)
364
	 *
365
	 * @param string $url URL to request
366
	 * @param array $headers Extra headers to send with the request
367
	 * @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...
368
	 * @param string $type HTTP request type (use Requests constants)
369
	 * @param array $options Options for the request (see description for more information)
370
	 * @return Requests_Response
371
	 */
372
	public static function request($url, $headers = array(), $data = array(), $type = self::GET, $options = array()) {
373
		if (empty($options['type'])) {
374
			$options['type'] = $type;
375
		}
376
		$options = array_merge(self::get_default_options(), $options);
377
378
		self::set_defaults($url, $headers, $data, $type, $options);
379
380
		$options['hooks']->dispatch('requests.before_request', array(&$url, &$headers, &$data, &$type, &$options));
381
382
		if (!empty($options['transport'])) {
383
			$transport = $options['transport'];
384
385
			if (is_string($options['transport'])) {
386
				$transport = new $transport();
387
			}
388
		}
389
		else {
390
			$need_ssl = (0 === stripos($url, 'https://'));
391
			$capabilities = array('ssl' => $need_ssl);
392
			$transport = self::get_transport($capabilities);
393
		}
394
		$response = $transport->request($url, $headers, $data, $options);
395
396
		$options['hooks']->dispatch('requests.before_parse', array(&$response, $url, $headers, $data, $type, $options));
397
398
		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 372 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...
399
	}
400
401
	/**
402
	 * Send multiple HTTP requests simultaneously
403
	 *
404
	 * The `$requests` parameter takes an associative or indexed array of
405
	 * request fields. The key of each request can be used to match up the
406
	 * request with the returned data, or with the request passed into your
407
	 * `multiple.request.complete` callback.
408
	 *
409
	 * The request fields value is an associative array with the following keys:
410
	 *
411
	 * - `url`: Request URL Same as the `$url` parameter to
412
	 *    {@see Requests::request}
413
	 *    (string, required)
414
	 * - `headers`: Associative array of header fields. Same as the `$headers`
415
	 *    parameter to {@see Requests::request}
416
	 *    (array, default: `array()`)
417
	 * - `data`: Associative array of data fields or a string. Same as the
418
	 *    `$data` parameter to {@see Requests::request}
419
	 *    (array|string, default: `array()`)
420
	 * - `type`: HTTP request type (use Requests constants). Same as the `$type`
421
	 *    parameter to {@see Requests::request}
422
	 *    (string, default: `Requests::GET`)
423
	 * - `cookies`: Associative array of cookie name to value, or cookie jar.
424
	 *    (array|Requests_Cookie_Jar)
425
	 *
426
	 * If the `$options` parameter is specified, individual requests will
427
	 * inherit options from it. This can be used to use a single hooking system,
428
	 * or set all the types to `Requests::POST`, for example.
429
	 *
430
	 * In addition, the `$options` parameter takes the following global options:
431
	 *
432
	 * - `complete`: A callback for when a request is complete. Takes two
433
	 *    parameters, a Requests_Response/Requests_Exception reference, and the
434
	 *    ID from the request array (Note: this can also be overridden on a
435
	 *    per-request basis, although that's a little silly)
436
	 *    (callback)
437
	 *
438
	 * @param array $requests Requests data (see description for more information)
439
	 * @param array $options Global and default options (see {@see Requests::request})
440
	 * @return array Responses (either Requests_Response or a Requests_Exception object)
441
	 */
442
	public static function request_multiple($requests, $options = array()) {
443
		$options = array_merge(self::get_default_options(true), $options);
444
445
		if (!empty($options['hooks'])) {
446
			$options['hooks']->register('transport.internal.parse_response', array('Requests', 'parse_multiple'));
447
			if (!empty($options['complete'])) {
448
				$options['hooks']->register('multiple.request.complete', $options['complete']);
449
			}
450
		}
451
452
		foreach ($requests as $id => &$request) {
453
			if (!isset($request['headers'])) {
454
				$request['headers'] = array();
455
			}
456
			if (!isset($request['data'])) {
457
				$request['data'] = array();
458
			}
459
			if (!isset($request['type'])) {
460
				$request['type'] = self::GET;
461
			}
462
			if (!isset($request['options'])) {
463
				$request['options'] = $options;
464
				$request['options']['type'] = $request['type'];
465
			}
466
			else {
467
				if (empty($request['options']['type'])) {
468
					$request['options']['type'] = $request['type'];
469
				}
470
				$request['options'] = array_merge($options, $request['options']);
471
			}
472
473
			self::set_defaults($request['url'], $request['headers'], $request['data'], $request['type'], $request['options']);
474
475
			// Ensure we only hook in once
476
			if ($request['options']['hooks'] !== $options['hooks']) {
477
				$request['options']['hooks']->register('transport.internal.parse_response', array('Requests', 'parse_multiple'));
478
				if (!empty($request['options']['complete'])) {
479
					$request['options']['hooks']->register('multiple.request.complete', $request['options']['complete']);
480
				}
481
			}
482
		}
483
		unset($request);
484
485
		if (!empty($options['transport'])) {
486
			$transport = $options['transport'];
487
488
			if (is_string($options['transport'])) {
489
				$transport = new $transport();
490
			}
491
		}
492
		else {
493
			$transport = self::get_transport();
494
		}
495
		$responses = $transport->request_multiple($requests, $options);
496
497
		foreach ($responses as $id => &$response) {
498
			// If our hook got messed with somehow, ensure we end up with the
499
			// correct response
500
			if (is_string($response)) {
501
				$request = $requests[$id];
502
				self::parse_multiple($response, $request);
503
				$request['options']['hooks']->dispatch('multiple.request.complete', array(&$response, $id));
504
			}
505
		}
506
507
		return $responses;
508
	}
509
510
	/**
511
	 * Get the default options
512
	 *
513
	 * @see Requests::request() for values returned by this method
514
	 * @param boolean $multirequest Is this a multirequest?
515
	 * @return array Default option values
516
	 */
517
	protected static function get_default_options($multirequest = false) {
518
		$defaults = array(
519
			'timeout' => 10,
520
			'connect_timeout' => 10,
521
			'useragent' => 'php-requests/' . self::VERSION,
522
			'protocol_version' => 1.1,
523
			'redirected' => 0,
524
			'redirects' => 10,
525
			'follow_redirects' => true,
526
			'blocking' => true,
527
			'type' => self::GET,
528
			'filename' => false,
529
			'auth' => false,
530
			'proxy' => false,
531
			'cookies' => false,
532
			'max_bytes' => false,
533
			'idn' => true,
534
			'hooks' => null,
535
			'transport' => null,
536
			'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...
537
			'verifyname' => true,
538
		);
539
		if ($multirequest !== false) {
540
			$defaults['complete'] = null;
541
		}
542
		return $defaults;
543
	}
544
545
	/**
546
	 * Get default certificate path.
547
	 *
548
	 * @return string Default certificate path.
549
	 */
550
	public static function get_certificate_path() {
551
		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...
552
			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...
553
		}
554
555
		return dirname(__FILE__) . '/Requests/Transport/cacert.pem';
556
	}
557
558
	/**
559
	 * Set default certificate path.
560
	 *
561
	 * @param string $path Certificate path, pointing to a PEM file.
562
	 */
563
	public static function set_certificate_path( $path ) {
564
		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...
565
	}
566
567
	/**
568
	 * Set the default values
569
	 *
570
	 * @param string $url URL to request
571
	 * @param array $headers Extra headers to send with the request
572
	 * @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...
573
	 * @param string $type HTTP request type
574
	 * @param array $options Options for the request
575
	 * @return array $options
576
	 */
577
	protected static function set_defaults(&$url, &$headers, &$data, &$type, &$options) {
578
		if (!preg_match('/^http(s)?:\/\//i', $url, $matches)) {
579
			throw new Requests_Exception('Only HTTP(S) requests are handled.', 'nonhttp', $url);
580
		}
581
582
		if (empty($options['hooks'])) {
583
			$options['hooks'] = new Requests_Hooks();
584
		}
585
586
		if (is_array($options['auth'])) {
587
			$options['auth'] = new Requests_Auth_Basic($options['auth']);
588
		}
589
		if ($options['auth'] !== false) {
590
			$options['auth']->register($options['hooks']);
591
		}
592
593
		if (is_string($options['proxy']) || is_array($options['proxy'])) {
594
			$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...
595
		}
596
		if ($options['proxy'] !== false) {
597
			$options['proxy']->register($options['hooks']);
598
		}
599
600
		if (is_array($options['cookies'])) {
601
			$options['cookies'] = new Requests_Cookie_Jar($options['cookies']);
602
		}
603
		elseif (empty($options['cookies'])) {
604
			$options['cookies'] = new Requests_Cookie_Jar();
605
		}
606
		if ($options['cookies'] !== false) {
607
			$options['cookies']->register($options['hooks']);
608
		}
609
610
		if ($options['idn'] !== false) {
611
			$iri = new Requests_IRI($url);
612
			$iri->host = Requests_IDNAEncoder::encode($iri->ihost);
613
			$url = $iri->uri;
614
		}
615
616
		// Massage the type to ensure we support it.
617
		$type = strtoupper($type);
618
619
		if (!isset($options['data_format'])) {
620
			if (in_array($type, array(self::HEAD, self::GET, self::DELETE))) {
621
				$options['data_format'] = 'query';
622
			}
623
			else {
624
				$options['data_format'] = 'body';
625
			}
626
		}
627
	}
628
629
	/**
630
	 * HTTP response parser
631
	 *
632
	 * @throws Requests_Exception On missing head/body separator (`requests.no_crlf_separator`)
633
	 * @throws Requests_Exception On missing head/body separator (`noversion`)
634
	 * @throws Requests_Exception On missing head/body separator (`toomanyredirects`)
635
	 *
636
	 * @param string $headers Full response text including headers and body
637
	 * @param string $url Original request URL
638
	 * @param array $req_headers Original $headers array passed to {@link request()}, in case we need to follow redirects
639
	 * @param array $req_data Original $data array passed to {@link request()}, in case we need to follow redirects
640
	 * @param array $options Original $options array passed to {@link request()}, in case we need to follow redirects
641
	 * @return Requests_Response
642
	 */
643
	protected static function parse_response($headers, $url, $req_headers, $req_data, $options) {
644
		$return = new Requests_Response();
645
		if (!$options['blocking']) {
646
			return $return;
647
		}
648
649
		$return->raw = $headers;
650
		$return->url = $url;
651
652
		if (!$options['filename']) {
653
			if (($pos = strpos($headers, "\r\n\r\n")) === false) {
654
				// Crap!
655
				throw new Requests_Exception('Missing header/body separator', 'requests.no_crlf_separator');
656
			}
657
658
			$headers = substr($return->raw, 0, $pos);
659
			$return->body = substr($return->raw, $pos + strlen("\n\r\n\r"));
660
		}
661
		else {
662
			$return->body = '';
663
		}
664
		// Pretend CRLF = LF for compatibility (RFC 2616, section 19.3)
665
		$headers = str_replace("\r\n", "\n", $headers);
666
		// Unfold headers (replace [CRLF] 1*( SP | HT ) with SP) as per RFC 2616 (section 2.2)
667
		$headers = preg_replace('/\n[ \t]/', ' ', $headers);
668
		$headers = explode("\n", $headers);
669
		preg_match('#^HTTP/(1\.\d)[ \t]+(\d+)#i', array_shift($headers), $matches);
670
		if (empty($matches)) {
671
			throw new Requests_Exception('Response could not be parsed', 'noversion', $headers);
672
		}
673
		$return->protocol_version = (float) $matches[1];
674
		$return->status_code = (int) $matches[2];
675
		if ($return->status_code >= 200 && $return->status_code < 300) {
676
			$return->success = true;
677
		}
678
679
		foreach ($headers as $header) {
680
			list($key, $value) = explode(':', $header, 2);
681
			$value = trim($value);
682
			preg_replace('#(\s+)#i', ' ', $value);
683
			$return->headers[$key] = $value;
684
		}
685
		if (isset($return->headers['transfer-encoding'])) {
686
			$return->body = self::decode_chunked($return->body);
687
			unset($return->headers['transfer-encoding']);
688
		}
689
		if (isset($return->headers['content-encoding'])) {
690
			$return->body = self::decompress($return->body);
691
		}
692
693
		//fsockopen and cURL compatibility
694
		if (isset($return->headers['connection'])) {
695
			unset($return->headers['connection']);
696
		}
697
698
		$options['hooks']->dispatch('requests.before_redirect_check', array(&$return, $req_headers, $req_data, $options));
699
700
		if ($return->is_redirect() && $options['follow_redirects'] === true) {
701
			if (isset($return->headers['location']) && $options['redirected'] < $options['redirects']) {
702
				if ($return->status_code === 303) {
703
					$options['type'] = self::GET;
704
				}
705
				$options['redirected']++;
706
				$location = $return->headers['location'];
707
				if (strpos($location, 'http://') !== 0 && strpos($location, 'https://') !== 0) {
708
					// relative redirect, for compatibility make it absolute
709
					$location = Requests_IRI::absolutize($url, $location);
710
					$location = $location->uri;
711
				}
712
713
				$hook_args = array(
714
					&$location,
715
					&$req_headers,
716
					&$req_data,
717
					&$options,
718
					$return
719
				);
720
				$options['hooks']->dispatch('requests.before_redirect', $hook_args);
721
				$redirected = self::request($location, $req_headers, $req_data, $options['type'], $options);
722
				$redirected->history[] = $return;
723
				return $redirected;
724
			}
725
			elseif ($options['redirected'] >= $options['redirects']) {
726
				throw new Requests_Exception('Too many redirects', 'toomanyredirects', $return);
727
			}
728
		}
729
730
		$return->redirects = $options['redirected'];
731
732
		$options['hooks']->dispatch('requests.after_request', array(&$return, $req_headers, $req_data, $options));
733
		return $return;
734
	}
735
736
	/**
737
	 * Callback for `transport.internal.parse_response`
738
	 *
739
	 * Internal use only. Converts a raw HTTP response to a Requests_Response
740
	 * while still executing a multiple request.
741
	 *
742
	 * @param string $response Full response text including headers and body (will be overwritten with Response instance)
743
	 * @param array $request Request data as passed into {@see Requests::request_multiple()}
744
	 * @return null `$response` is either set to a Requests_Response instance, or a Requests_Exception object
745
	 */
746
	public static function parse_multiple(&$response, $request) {
747
		try {
748
			$url = $request['url'];
749
			$headers = $request['headers'];
750
			$data = $request['data'];
751
			$options = $request['options'];
752
			$response = self::parse_response($response, $url, $headers, $data, $options);
753
		}
754
		catch (Requests_Exception $e) {
755
			$response = $e;
756
		}
757
	}
758
759
	/**
760
	 * Decoded a chunked body as per RFC 2616
761
	 *
762
	 * @see https://tools.ietf.org/html/rfc2616#section-3.6.1
763
	 * @param string $data Chunked body
764
	 * @return string Decoded body
765
	 */
766
	protected static function decode_chunked($data) {
767
		if (!preg_match('/^([0-9a-f]+)(?:;(?:[\w-]*)(?:=(?:(?:[\w-]*)*|"(?:[^\r\n])*"))?)*\r\n/i', trim($data))) {
768
			return $data;
769
		}
770
771
772
773
		$decoded = '';
774
		$encoded = $data;
775
776
		while (true) {
777
			$is_chunked = (bool) preg_match('/^([0-9a-f]+)(?:;(?:[\w-]*)(?:=(?:(?:[\w-]*)*|"(?:[^\r\n])*"))?)*\r\n/i', $encoded, $matches);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 130 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...
778
			if (!$is_chunked) {
779
				// Looks like it's not chunked after all
780
				return $data;
781
			}
782
783
			$length = hexdec(trim($matches[1]));
784
			if ($length === 0) {
785
				// Ignore trailer headers
786
				return $decoded;
787
			}
788
789
			$chunk_length = strlen($matches[0]);
790
			$decoded .= substr($encoded, $chunk_length, $length);
791
			$encoded = substr($encoded, $chunk_length + $length + 2);
792
793
			if (trim($encoded) === '0' || empty($encoded)) {
794
				return $decoded;
795
			}
796
		}
797
798
		// We'll never actually get down here
799
		// @codeCoverageIgnoreStart
800
	}
801
	// @codeCoverageIgnoreEnd
802
803
	/**
804
	 * Convert a key => value array to a 'key: value' array for headers
805
	 *
806
	 * @param array $array Dictionary of header values
807
	 * @return array List of headers
808
	 */
809
	public static function flatten($array) {
810
		$return = array();
811
		foreach ($array as $key => $value) {
812
			$return[] = sprintf('%s: %s', $key, $value);
813
		}
814
		return $return;
815
	}
816
817
	/**
818
	 * Convert a key => value array to a 'key: value' array for headers
819
	 *
820
	 * @codeCoverageIgnore
821
	 * @deprecated Misspelling of {@see Requests::flatten}
822
	 * @param array $array Dictionary of header values
823
	 * @return array List of headers
824
	 */
825
	public static function flattern($array) {
826
		return self::flatten($array);
827
	}
828
829
	/**
830
	 * Decompress an encoded body
831
	 *
832
	 * Implements gzip, compress and deflate. Guesses which it is by attempting
833
	 * to decode.
834
	 *
835
	 * @param string $data Compressed data in one of the above formats
836
	 * @return string Decompressed string
837
	 */
838
	public static function decompress($data) {
839
		if (substr($data, 0, 2) !== "\x1f\x8b" && substr($data, 0, 2) !== "\x78\x9c") {
840
			// Not actually compressed. Probably cURL ruining this for us.
841
			return $data;
842
		}
843
844
		if (function_exists('gzdecode') && ($decoded = @gzdecode($data)) !== false) {
845
			return $decoded;
846
		}
847
		elseif (function_exists('gzinflate') && ($decoded = @gzinflate($data)) !== false) {
848
			return $decoded;
849
		}
850
		elseif (($decoded = self::compatible_gzinflate($data)) !== false) {
851
			return $decoded;
852
		}
853
		elseif (function_exists('gzuncompress') && ($decoded = @gzuncompress($data)) !== false) {
854
			return $decoded;
855
		}
856
857
		return $data;
858
	}
859
860
	/**
861
	 * Decompression of deflated string while staying compatible with the majority of servers.
862
	 *
863
	 * Certain Servers will return deflated data with headers which PHP's gzinflate()
864
	 * function cannot handle out of the box. The following function has been created from
865
	 * various snippets on the gzinflate() PHP documentation.
866
	 *
867
	 * Warning: Magic numbers within. Due to the potential different formats that the compressed
868
	 * data may be returned in, some "magic offsets" are needed to ensure proper decompression
869
	 * takes place. For a simple progmatic way to determine the magic offset in use, see:
870
	 * https://core.trac.wordpress.org/ticket/18273
871
	 *
872
	 * @since 2.8.1
873
	 * @link https://core.trac.wordpress.org/ticket/18273
874
	 * @link https://secure.php.net/manual/en/function.gzinflate.php#70875
875
	 * @link https://secure.php.net/manual/en/function.gzinflate.php#77336
876
	 *
877
	 * @param string $gzData String to decompress.
878
	 * @return string|bool False on failure.
879
	 */
880
	public static function compatible_gzinflate($gzData) {
881
		// Compressed data might contain a full zlib header, if so strip it for
882
		// gzinflate()
883
		if (substr($gzData, 0, 3) == "\x1f\x8b\x08") {
884
			$i = 10;
885
			$flg = ord(substr($gzData, 3, 1));
886
			if ($flg > 0) {
887
				if ($flg & 4) {
888
					list($xlen) = unpack('v', substr($gzData, $i, 2));
889
					$i = $i + 2 + $xlen;
890
				}
891
				if ($flg & 8) {
892
					$i = strpos($gzData, "\0", $i) + 1;
893
				}
894
				if ($flg & 16) {
895
					$i = strpos($gzData, "\0", $i) + 1;
896
				}
897
				if ($flg & 2) {
898
					$i = $i + 2;
899
				}
900
			}
901
			$decompressed = self::compatible_gzinflate(substr($gzData, $i));
902
			if (false !== $decompressed) {
903
				return $decompressed;
904
			}
905
		}
906
907
		// If the data is Huffman Encoded, we must first strip the leading 2
908
		// byte Huffman marker for gzinflate()
909
		// The response is Huffman coded by many compressors such as
910
		// java.util.zip.Deflater, Ruby’s Zlib::Deflate, and .NET's
911
		// System.IO.Compression.DeflateStream.
912
		//
913
		// See https://decompres.blogspot.com/ for a quick explanation of this
914
		// data type
915
		$huffman_encoded = false;
916
917
		// low nibble of first byte should be 0x08
918
		list(, $first_nibble)    = unpack('h', $gzData);
919
920
		// First 2 bytes should be divisible by 0x1F
921
		list(, $first_two_bytes) = unpack('n', $gzData);
922
923
		if (0x08 == $first_nibble && 0 == ($first_two_bytes % 0x1F)) {
924
			$huffman_encoded = true;
925
		}
926
927 View Code Duplication
		if ($huffman_encoded) {
928
			if (false !== ($decompressed = @gzinflate(substr($gzData, 2)))) {
929
				return $decompressed;
930
			}
931
		}
932
933
		if ("\x50\x4b\x03\x04" == substr($gzData, 0, 4)) {
934
			// ZIP file format header
935
			// Offset 6: 2 bytes, General-purpose field
936
			// Offset 26: 2 bytes, filename length
937
			// Offset 28: 2 bytes, optional field length
938
			// Offset 30: Filename field, followed by optional field, followed
939
			// immediately by data
940
			list(, $general_purpose_flag) = unpack('v', substr($gzData, 6, 2));
941
942
			// If the file has been compressed on the fly, 0x08 bit is set of
943
			// the general purpose field. We can use this to differentiate
944
			// between a compressed document, and a ZIP file
945
			$zip_compressed_on_the_fly = (0x08 == (0x08 & $general_purpose_flag));
946
947
			if (!$zip_compressed_on_the_fly) {
948
				// Don't attempt to decode a compressed zip file
949
				return $gzData;
950
			}
951
952
			// Determine the first byte of data, based on the above ZIP header
953
			// offsets:
954
			$first_file_start = array_sum(unpack('v2', substr($gzData, 26, 4)));
955 View Code Duplication
			if (false !== ($decompressed = @gzinflate(substr($gzData, 30 + $first_file_start)))) {
956
				return $decompressed;
957
			}
958
			return false;
959
		}
960
961
		// Finally fall back to straight gzinflate
962
		if (false !== ($decompressed = @gzinflate($gzData))) {
963
			return $decompressed;
964
		}
965
966
		// Fallback for all above failing, not expected, but included for
967
		// debugging and preventing regressions and to track stats
968
		if (false !== ($decompressed = @gzinflate(substr($gzData, 2)))) {
969
			return $decompressed;
970
		}
971
972
		return false;
973
	}
974
975
	public static function match_domain($host, $reference) {
976
		// Check for a direct match
977
		if ($host === $reference) {
978
			return true;
979
		}
980
981
		// Calculate the valid wildcard match if the host is not an IP address
982
		// Also validates that the host has 3 parts or more, as per Firefox's
983
		// ruleset.
984
		$parts = explode('.', $host);
985 View Code Duplication
		if (ip2long($host) === false && count($parts) >= 3) {
986
			$parts[0] = '*';
987
			$wildcard = implode('.', $parts);
988
			if ($wildcard === $reference) {
989
				return true;
990
			}
991
		}
992
993
		return false;
994
	}
995
}
996