Completed
Pull Request — master (#327)
by
unknown
02:47
created

Requests::get_certificate_path()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $k instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
1012
            }else{
1013
                $cur_key = $key ? "{$key}[{$k}]" : "{$k}";
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $key instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $k instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
1014
                $data[$cur_key] = $v;
1015
            }
1016
            unset($data[$k]);
1017
        }
1018
    }
1019
}
1020