Completed
Push — 1.11.x ( 4463da...cd5efc )
by José
55:32 queued 28:22
created
plugin/buycourses/src/Requests/Transport/cURL.php 1 patch
Indentation   +526 added lines, -526 removed lines patch added patch discarded remove patch
@@ -13,530 +13,530 @@
 block discarded – undo
13 13
  * @subpackage Transport
14 14
  */
15 15
 class Requests_Transport_cURL implements Requests_Transport {
16
-	const CURL_7_10_5 = 0x070A05;
17
-	const CURL_7_16_2 = 0x071002;
18
-
19
-	/**
20
-	 * Raw HTTP data
21
-	 *
22
-	 * @var string
23
-	 */
24
-	public $headers = '';
25
-
26
-	/**
27
-	 * Raw body data
28
-	 *
29
-	 * @var string
30
-	 */
31
-	public $response_data = '';
32
-
33
-	/**
34
-	 * Information on the current request
35
-	 *
36
-	 * @var array cURL information array, see {@see https://secure.php.net/curl_getinfo}
37
-	 */
38
-	public $info;
39
-
40
-	/**
41
-	 * Version string
42
-	 *
43
-	 * @var long
44
-	 */
45
-	public $version;
46
-
47
-	/**
48
-	 * cURL handle
49
-	 *
50
-	 * @var resource
51
-	 */
52
-	protected $handle;
53
-
54
-	/**
55
-	 * Hook dispatcher instance
56
-	 *
57
-	 * @var Requests_Hooks
58
-	 */
59
-	protected $hooks;
60
-
61
-	/**
62
-	 * Have we finished the headers yet?
63
-	 *
64
-	 * @var boolean
65
-	 */
66
-	protected $done_headers = false;
67
-
68
-	/**
69
-	 * If streaming to a file, keep the file pointer
70
-	 *
71
-	 * @var resource
72
-	 */
73
-	protected $stream_handle;
74
-
75
-	/**
76
-	 * How many bytes are in the response body?
77
-	 *
78
-	 * @var int
79
-	 */
80
-	protected $response_bytes;
81
-
82
-	/**
83
-	 * What's the maximum number of bytes we should keep?
84
-	 *
85
-	 * @var int|bool Byte count, or false if no limit.
86
-	 */
87
-	protected $response_byte_limit;
88
-
89
-	/**
90
-	 * Constructor
91
-	 */
92
-	public function __construct() {
93
-		$curl = curl_version();
94
-		$this->version = $curl['version_number'];
95
-		$this->handle = curl_init();
96
-
97
-		curl_setopt($this->handle, CURLOPT_HEADER, false);
98
-		curl_setopt($this->handle, CURLOPT_RETURNTRANSFER, 1);
99
-		if ($this->version >= self::CURL_7_10_5) {
100
-			curl_setopt($this->handle, CURLOPT_ENCODING, '');
101
-		}
102
-		if (defined('CURLOPT_PROTOCOLS')) {
103
-			curl_setopt($this->handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
104
-		}
105
-		if (defined('CURLOPT_REDIR_PROTOCOLS')) {
106
-			curl_setopt($this->handle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
107
-		}
108
-	}
109
-
110
-	/**
111
-	 * Destructor
112
-	 */
113
-	public function __destruct() {
114
-		if (is_resource($this->handle)) {
115
-			curl_close($this->handle);
116
-		}
117
-	}
118
-
119
-	/**
120
-	 * Perform a request
121
-	 *
122
-	 * @throws Requests_Exception On a cURL error (`curlerror`)
123
-	 *
124
-	 * @param string $url URL to request
125
-	 * @param array $headers Associative array of request headers
126
-	 * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
127
-	 * @param array $options Request options, see {@see Requests::response()} for documentation
128
-	 * @return string Raw HTTP result
129
-	 */
130
-	public function request($url, $headers = array(), $data = array(), $options = array()) {
131
-		$this->hooks = $options['hooks'];
132
-
133
-		$this->setup_handle($url, $headers, $data, $options);
134
-
135
-		$options['hooks']->dispatch('curl.before_send', array(&$this->handle));
136
-
137
-		if ($options['filename'] !== false) {
138
-			$this->stream_handle = fopen($options['filename'], 'wb');
139
-		}
140
-
141
-		$this->response_data = '';
142
-		$this->response_bytes = 0;
143
-		$this->response_byte_limit = false;
144
-		if ($options['max_bytes'] !== false) {
145
-			$this->response_byte_limit = $options['max_bytes'];
146
-		}
147
-
148
-		if (isset($options['verify'])) {
149
-			if ($options['verify'] === false) {
150
-				curl_setopt($this->handle, CURLOPT_SSL_VERIFYHOST, 0);
151
-				curl_setopt($this->handle, CURLOPT_SSL_VERIFYPEER, 0);
152
-			}
153
-			elseif (is_string($options['verify'])) {
154
-				curl_setopt($this->handle, CURLOPT_CAINFO, $options['verify']);
155
-			}
156
-		}
157
-
158
-		if (isset($options['verifyname']) && $options['verifyname'] === false) {
159
-			curl_setopt($this->handle, CURLOPT_SSL_VERIFYHOST, 0);
160
-		}
161
-
162
-		curl_exec($this->handle);
163
-		$response = $this->response_data;
164
-
165
-		$options['hooks']->dispatch('curl.after_send', array());
166
-
167
-		if (curl_errno($this->handle) === 23 || curl_errno($this->handle) === 61) {
168
-			// Reset encoding and try again
169
-			curl_setopt($this->handle, CURLOPT_ENCODING, 'none');
170
-
171
-			$this->response_data = '';
172
-			$this->response_bytes = 0;
173
-			curl_exec($this->handle);
174
-			$response = $this->response_data;
175
-		}
176
-
177
-		$this->process_response($response, $options);
178
-
179
-		// Need to remove the $this reference from the curl handle.
180
-		// Otherwise Requests_Transport_cURL wont be garbage collected and the curl_close() will never be called.
181
-		curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, null);
182
-		curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, null);
183
-
184
-		return $this->headers;
185
-	}
186
-
187
-	/**
188
-	 * Send multiple requests simultaneously
189
-	 *
190
-	 * @param array $requests Request data
191
-	 * @param array $options Global options
192
-	 * @return array Array of Requests_Response objects (may contain Requests_Exception or string responses as well)
193
-	 */
194
-	public function request_multiple($requests, $options) {
195
-		// If you're not requesting, we can't get any responses ¯\_(ツ)_/¯
196
-		if (empty($requests)) {
197
-			return array();
198
-		}
199
-
200
-		$multihandle = curl_multi_init();
201
-		$subrequests = array();
202
-		$subhandles = array();
203
-
204
-		$class = get_class($this);
205
-		foreach ($requests as $id => $request) {
206
-			$subrequests[$id] = new $class();
207
-			$subhandles[$id] = $subrequests[$id]->get_subrequest_handle($request['url'], $request['headers'], $request['data'], $request['options']);
208
-			$request['options']['hooks']->dispatch('curl.before_multi_add', array(&$subhandles[$id]));
209
-			curl_multi_add_handle($multihandle, $subhandles[$id]);
210
-		}
211
-
212
-		$completed = 0;
213
-		$responses = array();
214
-
215
-		$request['options']['hooks']->dispatch('curl.before_multi_exec', array(&$multihandle));
216
-
217
-		do {
218
-			$active = false;
219
-
220
-			do {
221
-				$status = curl_multi_exec($multihandle, $active);
222
-			}
223
-			while ($status === CURLM_CALL_MULTI_PERFORM);
224
-
225
-			$to_process = array();
226
-
227
-			// Read the information as needed
228
-			while ($done = curl_multi_info_read($multihandle)) {
229
-				$key = array_search($done['handle'], $subhandles, true);
230
-				if (!isset($to_process[$key])) {
231
-					$to_process[$key] = $done;
232
-				}
233
-			}
234
-
235
-			// Parse the finished requests before we start getting the new ones
236
-			foreach ($to_process as $key => $done) {
237
-				$options = $requests[$key]['options'];
238
-				if (CURLE_OK !== $done['result']) {
239
-					//get error string for handle.
240
-					$reason = curl_error($done['handle']);
241
-					$exception = new Requests_Exception_Transport_cURL(
242
-									$reason,
243
-									Requests_Exception_Transport_cURL::EASY,
244
-									$done['handle'],
245
-									$done['result']
246
-								);
247
-					$responses[$key] = $exception;
248
-					$options['hooks']->dispatch('transport.internal.parse_error', array(&$responses[$key], $requests[$key]));
249
-				}
250
-				else {
251
-					$responses[$key] = $subrequests[$key]->process_response($subrequests[$key]->response_data, $options);
252
-
253
-					$options['hooks']->dispatch('transport.internal.parse_response', array(&$responses[$key], $requests[$key]));
254
-				}
255
-
256
-				curl_multi_remove_handle($multihandle, $done['handle']);
257
-				curl_close($done['handle']);
258
-
259
-				if (!is_string($responses[$key])) {
260
-					$options['hooks']->dispatch('multiple.request.complete', array(&$responses[$key], $key));
261
-				}
262
-				$completed++;
263
-			}
264
-		}
265
-		while ($active || $completed < count($subrequests));
266
-
267
-		$request['options']['hooks']->dispatch('curl.after_multi_exec', array(&$multihandle));
268
-
269
-		curl_multi_close($multihandle);
270
-
271
-		return $responses;
272
-	}
273
-
274
-	/**
275
-	 * Get the cURL handle for use in a multi-request
276
-	 *
277
-	 * @param string $url URL to request
278
-	 * @param array $headers Associative array of request headers
279
-	 * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
280
-	 * @param array $options Request options, see {@see Requests::response()} for documentation
281
-	 * @return resource Subrequest's cURL handle
282
-	 */
283
-	public function &get_subrequest_handle($url, $headers, $data, $options) {
284
-		$this->setup_handle($url, $headers, $data, $options);
285
-
286
-		if ($options['filename'] !== false) {
287
-			$this->stream_handle = fopen($options['filename'], 'wb');
288
-		}
289
-
290
-		$this->response_data = '';
291
-		$this->response_bytes = 0;
292
-		$this->response_byte_limit = false;
293
-		if ($options['max_bytes'] !== false) {
294
-			$this->response_byte_limit = $options['max_bytes'];
295
-		}
296
-		$this->hooks = $options['hooks'];
297
-
298
-		return $this->handle;
299
-	}
300
-
301
-	/**
302
-	 * Setup the cURL handle for the given data
303
-	 *
304
-	 * @param string $url URL to request
305
-	 * @param array $headers Associative array of request headers
306
-	 * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
307
-	 * @param array $options Request options, see {@see Requests::response()} for documentation
308
-	 */
309
-	protected function setup_handle($url, $headers, $data, $options) {
310
-		$options['hooks']->dispatch('curl.before_request', array(&$this->handle));
311
-
312
-		// Force closing the connection for old versions of cURL (<7.22).
313
-		if ( ! isset( $headers['Connection'] ) ) {
314
-			$headers['Connection'] = 'close';
315
-		}
316
-
317
-		$headers = Requests::flatten($headers);
318
-
319
-		if (!empty($data)) {
320
-			$data_format = $options['data_format'];
321
-
322
-			if ($data_format === 'query') {
323
-				$url = self::format_get($url, $data);
324
-				$data = '';
325
-			}
326
-			elseif (!is_string($data)) {
327
-				$data = http_build_query($data, null, '&');
328
-			}
329
-		}
330
-
331
-		switch ($options['type']) {
332
-			case Requests::POST:
333
-				curl_setopt($this->handle, CURLOPT_POST, true);
334
-				curl_setopt($this->handle, CURLOPT_POSTFIELDS, $data);
335
-				break;
336
-			case Requests::HEAD:
337
-				curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']);
338
-				curl_setopt($this->handle, CURLOPT_NOBODY, true);
339
-				break;
340
-			case Requests::TRACE:
341
-				curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']);
342
-				break;
343
-			case Requests::PATCH:
344
-			case Requests::PUT:
345
-			case Requests::DELETE:
346
-			case Requests::OPTIONS:
347
-			default:
348
-				curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']);
349
-				if (!empty($data)) {
350
-					curl_setopt($this->handle, CURLOPT_POSTFIELDS, $data);
351
-				}
352
-		}
353
-
354
-		// cURL requires a minimum timeout of 1 second when using the system
355
-		// DNS resolver, as it uses `alarm()`, which is second resolution only.
356
-		// There's no way to detect which DNS resolver is being used from our
357
-		// end, so we need to round up regardless of the supplied timeout.
358
-		//
359
-		// https://github.com/curl/curl/blob/4f45240bc84a9aa648c8f7243be7b79e9f9323a5/lib/hostip.c#L606-L609
360
-		$timeout = max($options['timeout'], 1);
361
-
362
-		if (is_int($timeout) || $this->version < self::CURL_7_16_2) {
363
-			curl_setopt($this->handle, CURLOPT_TIMEOUT, ceil($timeout));
364
-		}
365
-		else {
366
-			curl_setopt($this->handle, CURLOPT_TIMEOUT_MS, round($timeout * 1000));
367
-		}
368
-
369
-		if (is_int($options['connect_timeout']) || $this->version < self::CURL_7_16_2) {
370
-			curl_setopt($this->handle, CURLOPT_CONNECTTIMEOUT, ceil($options['connect_timeout']));
371
-		}
372
-		else {
373
-			curl_setopt($this->handle, CURLOPT_CONNECTTIMEOUT_MS, round($options['connect_timeout'] * 1000));
374
-		}
375
-		curl_setopt($this->handle, CURLOPT_URL, $url);
376
-		curl_setopt($this->handle, CURLOPT_REFERER, $url);
377
-		curl_setopt($this->handle, CURLOPT_USERAGENT, $options['useragent']);
378
-		if (!empty($headers)) {
379
-			curl_setopt($this->handle, CURLOPT_HTTPHEADER, $headers);
380
-		}
381
-		if ($options['protocol_version'] === 1.1) {
382
-			curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
383
-		}
384
-		else {
385
-			curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
386
-		}
387
-
388
-		if (true === $options['blocking']) {
389
-			curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, array(&$this, 'stream_headers'));
390
-			curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, array(&$this, 'stream_body'));
391
-			curl_setopt($this->handle, CURLOPT_BUFFERSIZE, Requests::BUFFER_SIZE);
392
-		}
393
-	}
394
-
395
-	/**
396
-	 * Process a response
397
-	 *
398
-	 * @param string $response Response data from the body
399
-	 * @param array $options Request options
400
-	 * @return string HTTP response data including headers
401
-	 */
402
-	public function process_response($response, $options) {
403
-		if ($options['blocking'] === false) {
404
-			$fake_headers = '';
405
-			$options['hooks']->dispatch('curl.after_request', array(&$fake_headers));
406
-			return false;
407
-		}
408
-		if ($options['filename'] !== false) {
409
-			fclose($this->stream_handle);
410
-			$this->headers = trim($this->headers);
411
-		}
412
-		else {
413
-			$this->headers .= $response;
414
-		}
415
-
416
-		if (curl_errno($this->handle)) {
417
-			$error = sprintf(
418
-				'cURL error %s: %s',
419
-				curl_errno($this->handle),
420
-				curl_error($this->handle)
421
-			);
422
-			throw new Requests_Exception($error, 'curlerror', $this->handle);
423
-		}
424
-		$this->info = curl_getinfo($this->handle);
425
-
426
-		$options['hooks']->dispatch('curl.after_request', array(&$this->headers, &$this->info));
427
-		return $this->headers;
428
-	}
429
-
430
-	/**
431
-	 * Collect the headers as they are received
432
-	 *
433
-	 * @param resource $handle cURL resource
434
-	 * @param string $headers Header string
435
-	 * @return integer Length of provided header
436
-	 */
437
-	public function stream_headers($handle, $headers) {
438
-		// Why do we do this? cURL will send both the final response and any
439
-		// interim responses, such as a 100 Continue. We don't need that.
440
-		// (We may want to keep this somewhere just in case)
441
-		if ($this->done_headers) {
442
-			$this->headers = '';
443
-			$this->done_headers = false;
444
-		}
445
-		$this->headers .= $headers;
446
-
447
-		if ($headers === "\r\n") {
448
-			$this->done_headers = true;
449
-		}
450
-		return strlen($headers);
451
-	}
452
-
453
-	/**
454
-	 * Collect data as it's received
455
-	 *
456
-	 * @since 1.6.1
457
-	 *
458
-	 * @param resource $handle cURL resource
459
-	 * @param string $data Body data
460
-	 * @return integer Length of provided data
461
-	 */
462
-	public function stream_body($handle, $data) {
463
-		$this->hooks->dispatch('request.progress', array($data, $this->response_bytes, $this->response_byte_limit));
464
-		$data_length = strlen($data);
465
-
466
-		// Are we limiting the response size?
467
-		if ($this->response_byte_limit) {
468
-			if ($this->response_bytes === $this->response_byte_limit) {
469
-				// Already at maximum, move on
470
-				return $data_length;
471
-			}
472
-
473
-			if (($this->response_bytes + $data_length) > $this->response_byte_limit) {
474
-				// Limit the length
475
-				$limited_length = ($this->response_byte_limit - $this->response_bytes);
476
-				$data = substr($data, 0, $limited_length);
477
-			}
478
-		}
479
-
480
-		if ($this->stream_handle) {
481
-			fwrite($this->stream_handle, $data);
482
-		}
483
-		else {
484
-			$this->response_data .= $data;
485
-		}
486
-
487
-		$this->response_bytes += strlen($data);
488
-		return $data_length;
489
-	}
490
-
491
-	/**
492
-	 * Format a URL given GET data
493
-	 *
494
-	 * @param string $url
495
-	 * @param array|object $data Data to build query using, see {@see https://secure.php.net/http_build_query}
496
-	 * @return string URL with data
497
-	 */
498
-	protected static function format_get($url, $data) {
499
-		if (!empty($data)) {
500
-			$url_parts = parse_url($url);
501
-			if (empty($url_parts['query'])) {
502
-				$query = $url_parts['query'] = '';
503
-			}
504
-			else {
505
-				$query = $url_parts['query'];
506
-			}
507
-
508
-			$query .= '&' . http_build_query($data, null, '&');
509
-			$query = trim($query, '&');
510
-
511
-			if (empty($url_parts['query'])) {
512
-				$url .= '?' . $query;
513
-			}
514
-			else {
515
-				$url = str_replace($url_parts['query'], $query, $url);
516
-			}
517
-		}
518
-		return $url;
519
-	}
520
-
521
-	/**
522
-	 * Whether this transport is valid
523
-	 *
524
-	 * @codeCoverageIgnore
525
-	 * @return boolean True if the transport is valid, false otherwise.
526
-	 */
527
-	public static function test($capabilities = array()) {
528
-		if (!function_exists('curl_init') || !function_exists('curl_exec')) {
529
-			return false;
530
-		}
531
-
532
-		// If needed, check that our installed curl version supports SSL
533
-		if (isset($capabilities['ssl']) && $capabilities['ssl']) {
534
-			$curl_version = curl_version();
535
-			if (!(CURL_VERSION_SSL & $curl_version['features'])) {
536
-				return false;
537
-			}
538
-		}
539
-
540
-		return true;
541
-	}
16
+    const CURL_7_10_5 = 0x070A05;
17
+    const CURL_7_16_2 = 0x071002;
18
+
19
+    /**
20
+     * Raw HTTP data
21
+     *
22
+     * @var string
23
+     */
24
+    public $headers = '';
25
+
26
+    /**
27
+     * Raw body data
28
+     *
29
+     * @var string
30
+     */
31
+    public $response_data = '';
32
+
33
+    /**
34
+     * Information on the current request
35
+     *
36
+     * @var array cURL information array, see {@see https://secure.php.net/curl_getinfo}
37
+     */
38
+    public $info;
39
+
40
+    /**
41
+     * Version string
42
+     *
43
+     * @var long
44
+     */
45
+    public $version;
46
+
47
+    /**
48
+     * cURL handle
49
+     *
50
+     * @var resource
51
+     */
52
+    protected $handle;
53
+
54
+    /**
55
+     * Hook dispatcher instance
56
+     *
57
+     * @var Requests_Hooks
58
+     */
59
+    protected $hooks;
60
+
61
+    /**
62
+     * Have we finished the headers yet?
63
+     *
64
+     * @var boolean
65
+     */
66
+    protected $done_headers = false;
67
+
68
+    /**
69
+     * If streaming to a file, keep the file pointer
70
+     *
71
+     * @var resource
72
+     */
73
+    protected $stream_handle;
74
+
75
+    /**
76
+     * How many bytes are in the response body?
77
+     *
78
+     * @var int
79
+     */
80
+    protected $response_bytes;
81
+
82
+    /**
83
+     * What's the maximum number of bytes we should keep?
84
+     *
85
+     * @var int|bool Byte count, or false if no limit.
86
+     */
87
+    protected $response_byte_limit;
88
+
89
+    /**
90
+     * Constructor
91
+     */
92
+    public function __construct() {
93
+        $curl = curl_version();
94
+        $this->version = $curl['version_number'];
95
+        $this->handle = curl_init();
96
+
97
+        curl_setopt($this->handle, CURLOPT_HEADER, false);
98
+        curl_setopt($this->handle, CURLOPT_RETURNTRANSFER, 1);
99
+        if ($this->version >= self::CURL_7_10_5) {
100
+            curl_setopt($this->handle, CURLOPT_ENCODING, '');
101
+        }
102
+        if (defined('CURLOPT_PROTOCOLS')) {
103
+            curl_setopt($this->handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
104
+        }
105
+        if (defined('CURLOPT_REDIR_PROTOCOLS')) {
106
+            curl_setopt($this->handle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
107
+        }
108
+    }
109
+
110
+    /**
111
+     * Destructor
112
+     */
113
+    public function __destruct() {
114
+        if (is_resource($this->handle)) {
115
+            curl_close($this->handle);
116
+        }
117
+    }
118
+
119
+    /**
120
+     * Perform a request
121
+     *
122
+     * @throws Requests_Exception On a cURL error (`curlerror`)
123
+     *
124
+     * @param string $url URL to request
125
+     * @param array $headers Associative array of request headers
126
+     * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
127
+     * @param array $options Request options, see {@see Requests::response()} for documentation
128
+     * @return string Raw HTTP result
129
+     */
130
+    public function request($url, $headers = array(), $data = array(), $options = array()) {
131
+        $this->hooks = $options['hooks'];
132
+
133
+        $this->setup_handle($url, $headers, $data, $options);
134
+
135
+        $options['hooks']->dispatch('curl.before_send', array(&$this->handle));
136
+
137
+        if ($options['filename'] !== false) {
138
+            $this->stream_handle = fopen($options['filename'], 'wb');
139
+        }
140
+
141
+        $this->response_data = '';
142
+        $this->response_bytes = 0;
143
+        $this->response_byte_limit = false;
144
+        if ($options['max_bytes'] !== false) {
145
+            $this->response_byte_limit = $options['max_bytes'];
146
+        }
147
+
148
+        if (isset($options['verify'])) {
149
+            if ($options['verify'] === false) {
150
+                curl_setopt($this->handle, CURLOPT_SSL_VERIFYHOST, 0);
151
+                curl_setopt($this->handle, CURLOPT_SSL_VERIFYPEER, 0);
152
+            }
153
+            elseif (is_string($options['verify'])) {
154
+                curl_setopt($this->handle, CURLOPT_CAINFO, $options['verify']);
155
+            }
156
+        }
157
+
158
+        if (isset($options['verifyname']) && $options['verifyname'] === false) {
159
+            curl_setopt($this->handle, CURLOPT_SSL_VERIFYHOST, 0);
160
+        }
161
+
162
+        curl_exec($this->handle);
163
+        $response = $this->response_data;
164
+
165
+        $options['hooks']->dispatch('curl.after_send', array());
166
+
167
+        if (curl_errno($this->handle) === 23 || curl_errno($this->handle) === 61) {
168
+            // Reset encoding and try again
169
+            curl_setopt($this->handle, CURLOPT_ENCODING, 'none');
170
+
171
+            $this->response_data = '';
172
+            $this->response_bytes = 0;
173
+            curl_exec($this->handle);
174
+            $response = $this->response_data;
175
+        }
176
+
177
+        $this->process_response($response, $options);
178
+
179
+        // Need to remove the $this reference from the curl handle.
180
+        // Otherwise Requests_Transport_cURL wont be garbage collected and the curl_close() will never be called.
181
+        curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, null);
182
+        curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, null);
183
+
184
+        return $this->headers;
185
+    }
186
+
187
+    /**
188
+     * Send multiple requests simultaneously
189
+     *
190
+     * @param array $requests Request data
191
+     * @param array $options Global options
192
+     * @return array Array of Requests_Response objects (may contain Requests_Exception or string responses as well)
193
+     */
194
+    public function request_multiple($requests, $options) {
195
+        // If you're not requesting, we can't get any responses ¯\_(ツ)_/¯
196
+        if (empty($requests)) {
197
+            return array();
198
+        }
199
+
200
+        $multihandle = curl_multi_init();
201
+        $subrequests = array();
202
+        $subhandles = array();
203
+
204
+        $class = get_class($this);
205
+        foreach ($requests as $id => $request) {
206
+            $subrequests[$id] = new $class();
207
+            $subhandles[$id] = $subrequests[$id]->get_subrequest_handle($request['url'], $request['headers'], $request['data'], $request['options']);
208
+            $request['options']['hooks']->dispatch('curl.before_multi_add', array(&$subhandles[$id]));
209
+            curl_multi_add_handle($multihandle, $subhandles[$id]);
210
+        }
211
+
212
+        $completed = 0;
213
+        $responses = array();
214
+
215
+        $request['options']['hooks']->dispatch('curl.before_multi_exec', array(&$multihandle));
216
+
217
+        do {
218
+            $active = false;
219
+
220
+            do {
221
+                $status = curl_multi_exec($multihandle, $active);
222
+            }
223
+            while ($status === CURLM_CALL_MULTI_PERFORM);
224
+
225
+            $to_process = array();
226
+
227
+            // Read the information as needed
228
+            while ($done = curl_multi_info_read($multihandle)) {
229
+                $key = array_search($done['handle'], $subhandles, true);
230
+                if (!isset($to_process[$key])) {
231
+                    $to_process[$key] = $done;
232
+                }
233
+            }
234
+
235
+            // Parse the finished requests before we start getting the new ones
236
+            foreach ($to_process as $key => $done) {
237
+                $options = $requests[$key]['options'];
238
+                if (CURLE_OK !== $done['result']) {
239
+                    //get error string for handle.
240
+                    $reason = curl_error($done['handle']);
241
+                    $exception = new Requests_Exception_Transport_cURL(
242
+                                    $reason,
243
+                                    Requests_Exception_Transport_cURL::EASY,
244
+                                    $done['handle'],
245
+                                    $done['result']
246
+                                );
247
+                    $responses[$key] = $exception;
248
+                    $options['hooks']->dispatch('transport.internal.parse_error', array(&$responses[$key], $requests[$key]));
249
+                }
250
+                else {
251
+                    $responses[$key] = $subrequests[$key]->process_response($subrequests[$key]->response_data, $options);
252
+
253
+                    $options['hooks']->dispatch('transport.internal.parse_response', array(&$responses[$key], $requests[$key]));
254
+                }
255
+
256
+                curl_multi_remove_handle($multihandle, $done['handle']);
257
+                curl_close($done['handle']);
258
+
259
+                if (!is_string($responses[$key])) {
260
+                    $options['hooks']->dispatch('multiple.request.complete', array(&$responses[$key], $key));
261
+                }
262
+                $completed++;
263
+            }
264
+        }
265
+        while ($active || $completed < count($subrequests));
266
+
267
+        $request['options']['hooks']->dispatch('curl.after_multi_exec', array(&$multihandle));
268
+
269
+        curl_multi_close($multihandle);
270
+
271
+        return $responses;
272
+    }
273
+
274
+    /**
275
+     * Get the cURL handle for use in a multi-request
276
+     *
277
+     * @param string $url URL to request
278
+     * @param array $headers Associative array of request headers
279
+     * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
280
+     * @param array $options Request options, see {@see Requests::response()} for documentation
281
+     * @return resource Subrequest's cURL handle
282
+     */
283
+    public function &get_subrequest_handle($url, $headers, $data, $options) {
284
+        $this->setup_handle($url, $headers, $data, $options);
285
+
286
+        if ($options['filename'] !== false) {
287
+            $this->stream_handle = fopen($options['filename'], 'wb');
288
+        }
289
+
290
+        $this->response_data = '';
291
+        $this->response_bytes = 0;
292
+        $this->response_byte_limit = false;
293
+        if ($options['max_bytes'] !== false) {
294
+            $this->response_byte_limit = $options['max_bytes'];
295
+        }
296
+        $this->hooks = $options['hooks'];
297
+
298
+        return $this->handle;
299
+    }
300
+
301
+    /**
302
+     * Setup the cURL handle for the given data
303
+     *
304
+     * @param string $url URL to request
305
+     * @param array $headers Associative array of request headers
306
+     * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
307
+     * @param array $options Request options, see {@see Requests::response()} for documentation
308
+     */
309
+    protected function setup_handle($url, $headers, $data, $options) {
310
+        $options['hooks']->dispatch('curl.before_request', array(&$this->handle));
311
+
312
+        // Force closing the connection for old versions of cURL (<7.22).
313
+        if ( ! isset( $headers['Connection'] ) ) {
314
+            $headers['Connection'] = 'close';
315
+        }
316
+
317
+        $headers = Requests::flatten($headers);
318
+
319
+        if (!empty($data)) {
320
+            $data_format = $options['data_format'];
321
+
322
+            if ($data_format === 'query') {
323
+                $url = self::format_get($url, $data);
324
+                $data = '';
325
+            }
326
+            elseif (!is_string($data)) {
327
+                $data = http_build_query($data, null, '&');
328
+            }
329
+        }
330
+
331
+        switch ($options['type']) {
332
+            case Requests::POST:
333
+                curl_setopt($this->handle, CURLOPT_POST, true);
334
+                curl_setopt($this->handle, CURLOPT_POSTFIELDS, $data);
335
+                break;
336
+            case Requests::HEAD:
337
+                curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']);
338
+                curl_setopt($this->handle, CURLOPT_NOBODY, true);
339
+                break;
340
+            case Requests::TRACE:
341
+                curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']);
342
+                break;
343
+            case Requests::PATCH:
344
+            case Requests::PUT:
345
+            case Requests::DELETE:
346
+            case Requests::OPTIONS:
347
+            default:
348
+                curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']);
349
+                if (!empty($data)) {
350
+                    curl_setopt($this->handle, CURLOPT_POSTFIELDS, $data);
351
+                }
352
+        }
353
+
354
+        // cURL requires a minimum timeout of 1 second when using the system
355
+        // DNS resolver, as it uses `alarm()`, which is second resolution only.
356
+        // There's no way to detect which DNS resolver is being used from our
357
+        // end, so we need to round up regardless of the supplied timeout.
358
+        //
359
+        // https://github.com/curl/curl/blob/4f45240bc84a9aa648c8f7243be7b79e9f9323a5/lib/hostip.c#L606-L609
360
+        $timeout = max($options['timeout'], 1);
361
+
362
+        if (is_int($timeout) || $this->version < self::CURL_7_16_2) {
363
+            curl_setopt($this->handle, CURLOPT_TIMEOUT, ceil($timeout));
364
+        }
365
+        else {
366
+            curl_setopt($this->handle, CURLOPT_TIMEOUT_MS, round($timeout * 1000));
367
+        }
368
+
369
+        if (is_int($options['connect_timeout']) || $this->version < self::CURL_7_16_2) {
370
+            curl_setopt($this->handle, CURLOPT_CONNECTTIMEOUT, ceil($options['connect_timeout']));
371
+        }
372
+        else {
373
+            curl_setopt($this->handle, CURLOPT_CONNECTTIMEOUT_MS, round($options['connect_timeout'] * 1000));
374
+        }
375
+        curl_setopt($this->handle, CURLOPT_URL, $url);
376
+        curl_setopt($this->handle, CURLOPT_REFERER, $url);
377
+        curl_setopt($this->handle, CURLOPT_USERAGENT, $options['useragent']);
378
+        if (!empty($headers)) {
379
+            curl_setopt($this->handle, CURLOPT_HTTPHEADER, $headers);
380
+        }
381
+        if ($options['protocol_version'] === 1.1) {
382
+            curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
383
+        }
384
+        else {
385
+            curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
386
+        }
387
+
388
+        if (true === $options['blocking']) {
389
+            curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, array(&$this, 'stream_headers'));
390
+            curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, array(&$this, 'stream_body'));
391
+            curl_setopt($this->handle, CURLOPT_BUFFERSIZE, Requests::BUFFER_SIZE);
392
+        }
393
+    }
394
+
395
+    /**
396
+     * Process a response
397
+     *
398
+     * @param string $response Response data from the body
399
+     * @param array $options Request options
400
+     * @return string HTTP response data including headers
401
+     */
402
+    public function process_response($response, $options) {
403
+        if ($options['blocking'] === false) {
404
+            $fake_headers = '';
405
+            $options['hooks']->dispatch('curl.after_request', array(&$fake_headers));
406
+            return false;
407
+        }
408
+        if ($options['filename'] !== false) {
409
+            fclose($this->stream_handle);
410
+            $this->headers = trim($this->headers);
411
+        }
412
+        else {
413
+            $this->headers .= $response;
414
+        }
415
+
416
+        if (curl_errno($this->handle)) {
417
+            $error = sprintf(
418
+                'cURL error %s: %s',
419
+                curl_errno($this->handle),
420
+                curl_error($this->handle)
421
+            );
422
+            throw new Requests_Exception($error, 'curlerror', $this->handle);
423
+        }
424
+        $this->info = curl_getinfo($this->handle);
425
+
426
+        $options['hooks']->dispatch('curl.after_request', array(&$this->headers, &$this->info));
427
+        return $this->headers;
428
+    }
429
+
430
+    /**
431
+     * Collect the headers as they are received
432
+     *
433
+     * @param resource $handle cURL resource
434
+     * @param string $headers Header string
435
+     * @return integer Length of provided header
436
+     */
437
+    public function stream_headers($handle, $headers) {
438
+        // Why do we do this? cURL will send both the final response and any
439
+        // interim responses, such as a 100 Continue. We don't need that.
440
+        // (We may want to keep this somewhere just in case)
441
+        if ($this->done_headers) {
442
+            $this->headers = '';
443
+            $this->done_headers = false;
444
+        }
445
+        $this->headers .= $headers;
446
+
447
+        if ($headers === "\r\n") {
448
+            $this->done_headers = true;
449
+        }
450
+        return strlen($headers);
451
+    }
452
+
453
+    /**
454
+     * Collect data as it's received
455
+     *
456
+     * @since 1.6.1
457
+     *
458
+     * @param resource $handle cURL resource
459
+     * @param string $data Body data
460
+     * @return integer Length of provided data
461
+     */
462
+    public function stream_body($handle, $data) {
463
+        $this->hooks->dispatch('request.progress', array($data, $this->response_bytes, $this->response_byte_limit));
464
+        $data_length = strlen($data);
465
+
466
+        // Are we limiting the response size?
467
+        if ($this->response_byte_limit) {
468
+            if ($this->response_bytes === $this->response_byte_limit) {
469
+                // Already at maximum, move on
470
+                return $data_length;
471
+            }
472
+
473
+            if (($this->response_bytes + $data_length) > $this->response_byte_limit) {
474
+                // Limit the length
475
+                $limited_length = ($this->response_byte_limit - $this->response_bytes);
476
+                $data = substr($data, 0, $limited_length);
477
+            }
478
+        }
479
+
480
+        if ($this->stream_handle) {
481
+            fwrite($this->stream_handle, $data);
482
+        }
483
+        else {
484
+            $this->response_data .= $data;
485
+        }
486
+
487
+        $this->response_bytes += strlen($data);
488
+        return $data_length;
489
+    }
490
+
491
+    /**
492
+     * Format a URL given GET data
493
+     *
494
+     * @param string $url
495
+     * @param array|object $data Data to build query using, see {@see https://secure.php.net/http_build_query}
496
+     * @return string URL with data
497
+     */
498
+    protected static function format_get($url, $data) {
499
+        if (!empty($data)) {
500
+            $url_parts = parse_url($url);
501
+            if (empty($url_parts['query'])) {
502
+                $query = $url_parts['query'] = '';
503
+            }
504
+            else {
505
+                $query = $url_parts['query'];
506
+            }
507
+
508
+            $query .= '&' . http_build_query($data, null, '&');
509
+            $query = trim($query, '&');
510
+
511
+            if (empty($url_parts['query'])) {
512
+                $url .= '?' . $query;
513
+            }
514
+            else {
515
+                $url = str_replace($url_parts['query'], $query, $url);
516
+            }
517
+        }
518
+        return $url;
519
+    }
520
+
521
+    /**
522
+     * Whether this transport is valid
523
+     *
524
+     * @codeCoverageIgnore
525
+     * @return boolean True if the transport is valid, false otherwise.
526
+     */
527
+    public static function test($capabilities = array()) {
528
+        if (!function_exists('curl_init') || !function_exists('curl_exec')) {
529
+            return false;
530
+        }
531
+
532
+        // If needed, check that our installed curl version supports SSL
533
+        if (isset($capabilities['ssl']) && $capabilities['ssl']) {
534
+            $curl_version = curl_version();
535
+            if (!(CURL_VERSION_SSL & $curl_version['features'])) {
536
+                return false;
537
+            }
538
+        }
539
+
540
+        return true;
541
+    }
542 542
 }
Please login to merge, or discard this patch.
plugin/buycourses/src/Requests/Transport/fsockopen.php 1 patch
Indentation   +428 added lines, -428 removed lines patch added patch discarded remove patch
@@ -13,432 +13,432 @@
 block discarded – undo
13 13
  * @subpackage Transport
14 14
  */
15 15
 class Requests_Transport_fsockopen implements Requests_Transport {
16
-	/**
17
-	 * Second to microsecond conversion
18
-	 *
19
-	 * @var integer
20
-	 */
21
-	const SECOND_IN_MICROSECONDS = 1000000;
22
-
23
-	/**
24
-	 * Raw HTTP data
25
-	 *
26
-	 * @var string
27
-	 */
28
-	public $headers = '';
29
-
30
-	/**
31
-	 * Stream metadata
32
-	 *
33
-	 * @var array Associative array of properties, see {@see https://secure.php.net/stream_get_meta_data}
34
-	 */
35
-	public $info;
36
-
37
-	/**
38
-	 * What's the maximum number of bytes we should keep?
39
-	 *
40
-	 * @var int|bool Byte count, or false if no limit.
41
-	 */
42
-	protected $max_bytes = false;
43
-
44
-	protected $connect_error = '';
45
-
46
-	/**
47
-	 * Perform a request
48
-	 *
49
-	 * @throws Requests_Exception On failure to connect to socket (`fsockopenerror`)
50
-	 * @throws Requests_Exception On socket timeout (`timeout`)
51
-	 *
52
-	 * @param string $url URL to request
53
-	 * @param array $headers Associative array of request headers
54
-	 * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
55
-	 * @param array $options Request options, see {@see Requests::response()} for documentation
56
-	 * @return string Raw HTTP result
57
-	 */
58
-	public function request($url, $headers = array(), $data = array(), $options = array()) {
59
-		$options['hooks']->dispatch('fsockopen.before_request');
60
-
61
-		$url_parts = parse_url($url);
62
-		if (empty($url_parts)) {
63
-			throw new Requests_Exception('Invalid URL.', 'invalidurl', $url);
64
-		}
65
-		$host = $url_parts['host'];
66
-		$context = stream_context_create();
67
-		$verifyname = false;
68
-		$case_insensitive_headers = new Requests_Utility_CaseInsensitiveDictionary($headers);
69
-
70
-		// HTTPS support
71
-		if (isset($url_parts['scheme']) && strtolower($url_parts['scheme']) === 'https') {
72
-			$remote_socket = 'ssl://' . $host;
73
-			if (!isset($url_parts['port'])) {
74
-				$url_parts['port'] = 443;
75
-			}
76
-
77
-			$context_options = array(
78
-				'verify_peer' => true,
79
-				// 'CN_match' => $host,
80
-				'capture_peer_cert' => true
81
-			);
82
-			$verifyname = true;
83
-
84
-			// SNI, if enabled (OpenSSL >=0.9.8j)
85
-			if (defined('OPENSSL_TLSEXT_SERVER_NAME') && OPENSSL_TLSEXT_SERVER_NAME) {
86
-				$context_options['SNI_enabled'] = true;
87
-				if (isset($options['verifyname']) && $options['verifyname'] === false) {
88
-					$context_options['SNI_enabled'] = false;
89
-				}
90
-			}
91
-
92
-			if (isset($options['verify'])) {
93
-				if ($options['verify'] === false) {
94
-					$context_options['verify_peer'] = false;
95
-				}
96
-				elseif (is_string($options['verify'])) {
97
-					$context_options['cafile'] = $options['verify'];
98
-				}
99
-			}
100
-
101
-			if (isset($options['verifyname']) && $options['verifyname'] === false) {
102
-				$context_options['verify_peer_name'] = false;
103
-				$verifyname = false;
104
-			}
105
-
106
-			stream_context_set_option($context, array('ssl' => $context_options));
107
-		}
108
-		else {
109
-			$remote_socket = 'tcp://' . $host;
110
-		}
111
-
112
-		$this->max_bytes = $options['max_bytes'];
113
-
114
-		if (!isset($url_parts['port'])) {
115
-			$url_parts['port'] = 80;
116
-		}
117
-		$remote_socket .= ':' . $url_parts['port'];
118
-
119
-		set_error_handler(array($this, 'connect_error_handler'), E_WARNING | E_NOTICE);
120
-
121
-		$options['hooks']->dispatch('fsockopen.remote_socket', array(&$remote_socket));
122
-
123
-		$socket = stream_socket_client($remote_socket, $errno, $errstr, ceil($options['connect_timeout']), STREAM_CLIENT_CONNECT, $context);
124
-
125
-		restore_error_handler();
126
-
127
-		if ($verifyname && !$this->verify_certificate_from_context($host, $context)) {
128
-			throw new Requests_Exception('SSL certificate did not match the requested domain name', 'ssl.no_match');
129
-		}
130
-
131
-		if (!$socket) {
132
-			if ($errno === 0) {
133
-				// Connection issue
134
-				throw new Requests_Exception(rtrim($this->connect_error), 'fsockopen.connect_error');
135
-			}
136
-
137
-			throw new Requests_Exception($errstr, 'fsockopenerror', null, $errno);
138
-		}
139
-
140
-		$data_format = $options['data_format'];
141
-
142
-		if ($data_format === 'query') {
143
-			$path = self::format_get($url_parts, $data);
144
-			$data = '';
145
-		}
146
-		else {
147
-			$path = self::format_get($url_parts, array());
148
-		}
149
-
150
-		$options['hooks']->dispatch('fsockopen.remote_host_path', array(&$path, $url));
151
-
152
-		$request_body = '';
153
-		$out = sprintf("%s %s HTTP/%.1f\r\n", $options['type'], $path, $options['protocol_version']);
154
-
155
-		if ($options['type'] !== Requests::TRACE) {
156
-			if (is_array($data)) {
157
-				$request_body = http_build_query($data, null, '&');
158
-			}
159
-			else {
160
-				$request_body = $data;
161
-			}
162
-
163
-			if (!empty($data)) {
164
-				if (!isset($case_insensitive_headers['Content-Length'])) {
165
-					$headers['Content-Length'] = strlen($request_body);
166
-				}
167
-
168
-				if (!isset($case_insensitive_headers['Content-Type'])) {
169
-					$headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
170
-				}
171
-			}
172
-		}
173
-
174
-		if (!isset($case_insensitive_headers['Host'])) {
175
-			$out .= sprintf('Host: %s', $url_parts['host']);
176
-
177
-			if (( 'http' === strtolower($url_parts['scheme']) && $url_parts['port'] !== 80 ) || ( 'https' === strtolower($url_parts['scheme']) && $url_parts['port'] !== 443 )) {
178
-				$out .= ':' . $url_parts['port'];
179
-			}
180
-			$out .= "\r\n";
181
-		}
182
-
183
-		if (!isset($case_insensitive_headers['User-Agent'])) {
184
-			$out .= sprintf("User-Agent: %s\r\n", $options['useragent']);
185
-		}
186
-
187
-		$accept_encoding = $this->accept_encoding();
188
-		if (!isset($case_insensitive_headers['Accept-Encoding']) && !empty($accept_encoding)) {
189
-			$out .= sprintf("Accept-Encoding: %s\r\n", $accept_encoding);
190
-		}
191
-
192
-		$headers = Requests::flatten($headers);
193
-
194
-		if (!empty($headers)) {
195
-			$out .= implode($headers, "\r\n") . "\r\n";
196
-		}
197
-
198
-		$options['hooks']->dispatch('fsockopen.after_headers', array(&$out));
199
-
200
-		if (substr($out, -2) !== "\r\n") {
201
-			$out .= "\r\n";
202
-		}
203
-
204
-		if (!isset($case_insensitive_headers['Connection'])) {
205
-			$out .= "Connection: Close\r\n";
206
-		}
207
-
208
-		$out .= "\r\n" . $request_body;
209
-
210
-		$options['hooks']->dispatch('fsockopen.before_send', array(&$out));
211
-
212
-		fwrite($socket, $out);
213
-		$options['hooks']->dispatch('fsockopen.after_send', array($out));
214
-
215
-		if (!$options['blocking']) {
216
-			fclose($socket);
217
-			$fake_headers = '';
218
-			$options['hooks']->dispatch('fsockopen.after_request', array(&$fake_headers));
219
-			return '';
220
-		}
221
-
222
-		$timeout_sec = (int) floor($options['timeout']);
223
-		if ($timeout_sec == $options['timeout']) {
224
-			$timeout_msec = 0;
225
-		}
226
-		else {
227
-			$timeout_msec = self::SECOND_IN_MICROSECONDS * $options['timeout'] % self::SECOND_IN_MICROSECONDS;
228
-		}
229
-		stream_set_timeout($socket, $timeout_sec, $timeout_msec);
230
-
231
-		$response = $body = $headers = '';
232
-		$this->info = stream_get_meta_data($socket);
233
-		$size = 0;
234
-		$doingbody = false;
235
-		$download = false;
236
-		if ($options['filename']) {
237
-			$download = fopen($options['filename'], 'wb');
238
-		}
239
-
240
-		while (!feof($socket)) {
241
-			$this->info = stream_get_meta_data($socket);
242
-			if ($this->info['timed_out']) {
243
-				throw new Requests_Exception('fsocket timed out', 'timeout');
244
-			}
245
-
246
-			$block = fread($socket, Requests::BUFFER_SIZE);
247
-			if (!$doingbody) {
248
-				$response .= $block;
249
-				if (strpos($response, "\r\n\r\n")) {
250
-					list($headers, $block) = explode("\r\n\r\n", $response, 2);
251
-					$doingbody = true;
252
-				}
253
-			}
254
-
255
-			// Are we in body mode now?
256
-			if ($doingbody) {
257
-				$options['hooks']->dispatch('request.progress', array($block, $size, $this->max_bytes));
258
-				$data_length = strlen($block);
259
-				if ($this->max_bytes) {
260
-					// Have we already hit a limit?
261
-					if ($size === $this->max_bytes) {
262
-						continue;
263
-					}
264
-					if (($size + $data_length) > $this->max_bytes) {
265
-						// Limit the length
266
-						$limited_length = ($this->max_bytes - $size);
267
-						$block = substr($block, 0, $limited_length);
268
-					}
269
-				}
270
-
271
-				$size += strlen($block);
272
-				if ($download) {
273
-					fwrite($download, $block);
274
-				}
275
-				else {
276
-					$body .= $block;
277
-				}
278
-			}
279
-		}
280
-		$this->headers = $headers;
281
-
282
-		if ($download) {
283
-			fclose($download);
284
-		}
285
-		else {
286
-			$this->headers .= "\r\n\r\n" . $body;
287
-		}
288
-		fclose($socket);
289
-
290
-		$options['hooks']->dispatch('fsockopen.after_request', array(&$this->headers, &$this->info));
291
-		return $this->headers;
292
-	}
293
-
294
-	/**
295
-	 * Send multiple requests simultaneously
296
-	 *
297
-	 * @param array $requests Request data (array of 'url', 'headers', 'data', 'options') as per {@see Requests_Transport::request}
298
-	 * @param array $options Global options, see {@see Requests::response()} for documentation
299
-	 * @return array Array of Requests_Response objects (may contain Requests_Exception or string responses as well)
300
-	 */
301
-	public function request_multiple($requests, $options) {
302
-		$responses = array();
303
-		$class = get_class($this);
304
-		foreach ($requests as $id => $request) {
305
-			try {
306
-				$handler = new $class();
307
-				$responses[$id] = $handler->request($request['url'], $request['headers'], $request['data'], $request['options']);
308
-
309
-				$request['options']['hooks']->dispatch('transport.internal.parse_response', array(&$responses[$id], $request));
310
-			}
311
-			catch (Requests_Exception $e) {
312
-				$responses[$id] = $e;
313
-			}
314
-
315
-			if (!is_string($responses[$id])) {
316
-				$request['options']['hooks']->dispatch('multiple.request.complete', array(&$responses[$id], $id));
317
-			}
318
-		}
319
-
320
-		return $responses;
321
-	}
322
-
323
-	/**
324
-	 * Retrieve the encodings we can accept
325
-	 *
326
-	 * @return string Accept-Encoding header value
327
-	 */
328
-	protected static function accept_encoding() {
329
-		$type = array();
330
-		if (function_exists('gzinflate')) {
331
-			$type[] = 'deflate;q=1.0';
332
-		}
333
-
334
-		if (function_exists('gzuncompress')) {
335
-			$type[] = 'compress;q=0.5';
336
-		}
337
-
338
-		$type[] = 'gzip;q=0.5';
339
-
340
-		return implode(', ', $type);
341
-	}
342
-
343
-	/**
344
-	 * Format a URL given GET data
345
-	 *
346
-	 * @param array $url_parts
347
-	 * @param array|object $data Data to build query using, see {@see https://secure.php.net/http_build_query}
348
-	 * @return string URL with data
349
-	 */
350
-	protected static function format_get($url_parts, $data) {
351
-		if (!empty($data)) {
352
-			if (empty($url_parts['query'])) {
353
-				$url_parts['query'] = '';
354
-			}
355
-
356
-			$url_parts['query'] .= '&' . http_build_query($data, null, '&');
357
-			$url_parts['query'] = trim($url_parts['query'], '&');
358
-		}
359
-		if (isset($url_parts['path'])) {
360
-			if (isset($url_parts['query'])) {
361
-				$get = $url_parts['path'] . '?' . $url_parts['query'];
362
-			}
363
-			else {
364
-				$get = $url_parts['path'];
365
-			}
366
-		}
367
-		else {
368
-			$get = '/';
369
-		}
370
-		return $get;
371
-	}
372
-
373
-	/**
374
-	 * Error handler for stream_socket_client()
375
-	 *
376
-	 * @param int $errno Error number (e.g. E_WARNING)
377
-	 * @param string $errstr Error message
378
-	 */
379
-	public function connect_error_handler($errno, $errstr) {
380
-		// Double-check we can handle it
381
-		if (($errno & E_WARNING) === 0 && ($errno & E_NOTICE) === 0) {
382
-			// Return false to indicate the default error handler should engage
383
-			return false;
384
-		}
385
-
386
-		$this->connect_error .= $errstr . "\n";
387
-		return true;
388
-	}
389
-
390
-	/**
391
-	 * Verify the certificate against common name and subject alternative names
392
-	 *
393
-	 * Unfortunately, PHP doesn't check the certificate against the alternative
394
-	 * names, leading things like 'https://www.github.com/' to be invalid.
395
-	 * Instead
396
-	 *
397
-	 * @see https://tools.ietf.org/html/rfc2818#section-3.1 RFC2818, Section 3.1
398
-	 *
399
-	 * @throws Requests_Exception On failure to connect via TLS (`fsockopen.ssl.connect_error`)
400
-	 * @throws Requests_Exception On not obtaining a match for the host (`fsockopen.ssl.no_match`)
401
-	 * @param string $host Host name to verify against
402
-	 * @param resource $context Stream context
403
-	 * @return bool
404
-	 */
405
-	public function verify_certificate_from_context($host, $context) {
406
-		$meta = stream_context_get_options($context);
407
-
408
-		// If we don't have SSL options, then we couldn't make the connection at
409
-		// all
410
-		if (empty($meta) || empty($meta['ssl']) || empty($meta['ssl']['peer_certificate'])) {
411
-			throw new Requests_Exception(rtrim($this->connect_error), 'ssl.connect_error');
412
-		}
413
-
414
-		$cert = openssl_x509_parse($meta['ssl']['peer_certificate']);
415
-
416
-		return Requests_SSL::verify_certificate($host, $cert);
417
-	}
418
-
419
-	/**
420
-	 * Whether this transport is valid
421
-	 *
422
-	 * @codeCoverageIgnore
423
-	 * @return boolean True if the transport is valid, false otherwise.
424
-	 */
425
-	public static function test($capabilities = array()) {
426
-		if (!function_exists('fsockopen')) {
427
-			return false;
428
-		}
429
-
430
-		// If needed, check that streams support SSL
431
-		if (isset($capabilities['ssl']) && $capabilities['ssl']) {
432
-			if (!extension_loaded('openssl') || !function_exists('openssl_x509_parse')) {
433
-				return false;
434
-			}
435
-
436
-			// Currently broken, thanks to https://github.com/facebook/hhvm/issues/2156
437
-			if (defined('HHVM_VERSION')) {
438
-				return false;
439
-			}
440
-		}
441
-
442
-		return true;
443
-	}
16
+    /**
17
+     * Second to microsecond conversion
18
+     *
19
+     * @var integer
20
+     */
21
+    const SECOND_IN_MICROSECONDS = 1000000;
22
+
23
+    /**
24
+     * Raw HTTP data
25
+     *
26
+     * @var string
27
+     */
28
+    public $headers = '';
29
+
30
+    /**
31
+     * Stream metadata
32
+     *
33
+     * @var array Associative array of properties, see {@see https://secure.php.net/stream_get_meta_data}
34
+     */
35
+    public $info;
36
+
37
+    /**
38
+     * What's the maximum number of bytes we should keep?
39
+     *
40
+     * @var int|bool Byte count, or false if no limit.
41
+     */
42
+    protected $max_bytes = false;
43
+
44
+    protected $connect_error = '';
45
+
46
+    /**
47
+     * Perform a request
48
+     *
49
+     * @throws Requests_Exception On failure to connect to socket (`fsockopenerror`)
50
+     * @throws Requests_Exception On socket timeout (`timeout`)
51
+     *
52
+     * @param string $url URL to request
53
+     * @param array $headers Associative array of request headers
54
+     * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
55
+     * @param array $options Request options, see {@see Requests::response()} for documentation
56
+     * @return string Raw HTTP result
57
+     */
58
+    public function request($url, $headers = array(), $data = array(), $options = array()) {
59
+        $options['hooks']->dispatch('fsockopen.before_request');
60
+
61
+        $url_parts = parse_url($url);
62
+        if (empty($url_parts)) {
63
+            throw new Requests_Exception('Invalid URL.', 'invalidurl', $url);
64
+        }
65
+        $host = $url_parts['host'];
66
+        $context = stream_context_create();
67
+        $verifyname = false;
68
+        $case_insensitive_headers = new Requests_Utility_CaseInsensitiveDictionary($headers);
69
+
70
+        // HTTPS support
71
+        if (isset($url_parts['scheme']) && strtolower($url_parts['scheme']) === 'https') {
72
+            $remote_socket = 'ssl://' . $host;
73
+            if (!isset($url_parts['port'])) {
74
+                $url_parts['port'] = 443;
75
+            }
76
+
77
+            $context_options = array(
78
+                'verify_peer' => true,
79
+                // 'CN_match' => $host,
80
+                'capture_peer_cert' => true
81
+            );
82
+            $verifyname = true;
83
+
84
+            // SNI, if enabled (OpenSSL >=0.9.8j)
85
+            if (defined('OPENSSL_TLSEXT_SERVER_NAME') && OPENSSL_TLSEXT_SERVER_NAME) {
86
+                $context_options['SNI_enabled'] = true;
87
+                if (isset($options['verifyname']) && $options['verifyname'] === false) {
88
+                    $context_options['SNI_enabled'] = false;
89
+                }
90
+            }
91
+
92
+            if (isset($options['verify'])) {
93
+                if ($options['verify'] === false) {
94
+                    $context_options['verify_peer'] = false;
95
+                }
96
+                elseif (is_string($options['verify'])) {
97
+                    $context_options['cafile'] = $options['verify'];
98
+                }
99
+            }
100
+
101
+            if (isset($options['verifyname']) && $options['verifyname'] === false) {
102
+                $context_options['verify_peer_name'] = false;
103
+                $verifyname = false;
104
+            }
105
+
106
+            stream_context_set_option($context, array('ssl' => $context_options));
107
+        }
108
+        else {
109
+            $remote_socket = 'tcp://' . $host;
110
+        }
111
+
112
+        $this->max_bytes = $options['max_bytes'];
113
+
114
+        if (!isset($url_parts['port'])) {
115
+            $url_parts['port'] = 80;
116
+        }
117
+        $remote_socket .= ':' . $url_parts['port'];
118
+
119
+        set_error_handler(array($this, 'connect_error_handler'), E_WARNING | E_NOTICE);
120
+
121
+        $options['hooks']->dispatch('fsockopen.remote_socket', array(&$remote_socket));
122
+
123
+        $socket = stream_socket_client($remote_socket, $errno, $errstr, ceil($options['connect_timeout']), STREAM_CLIENT_CONNECT, $context);
124
+
125
+        restore_error_handler();
126
+
127
+        if ($verifyname && !$this->verify_certificate_from_context($host, $context)) {
128
+            throw new Requests_Exception('SSL certificate did not match the requested domain name', 'ssl.no_match');
129
+        }
130
+
131
+        if (!$socket) {
132
+            if ($errno === 0) {
133
+                // Connection issue
134
+                throw new Requests_Exception(rtrim($this->connect_error), 'fsockopen.connect_error');
135
+            }
136
+
137
+            throw new Requests_Exception($errstr, 'fsockopenerror', null, $errno);
138
+        }
139
+
140
+        $data_format = $options['data_format'];
141
+
142
+        if ($data_format === 'query') {
143
+            $path = self::format_get($url_parts, $data);
144
+            $data = '';
145
+        }
146
+        else {
147
+            $path = self::format_get($url_parts, array());
148
+        }
149
+
150
+        $options['hooks']->dispatch('fsockopen.remote_host_path', array(&$path, $url));
151
+
152
+        $request_body = '';
153
+        $out = sprintf("%s %s HTTP/%.1f\r\n", $options['type'], $path, $options['protocol_version']);
154
+
155
+        if ($options['type'] !== Requests::TRACE) {
156
+            if (is_array($data)) {
157
+                $request_body = http_build_query($data, null, '&');
158
+            }
159
+            else {
160
+                $request_body = $data;
161
+            }
162
+
163
+            if (!empty($data)) {
164
+                if (!isset($case_insensitive_headers['Content-Length'])) {
165
+                    $headers['Content-Length'] = strlen($request_body);
166
+                }
167
+
168
+                if (!isset($case_insensitive_headers['Content-Type'])) {
169
+                    $headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
170
+                }
171
+            }
172
+        }
173
+
174
+        if (!isset($case_insensitive_headers['Host'])) {
175
+            $out .= sprintf('Host: %s', $url_parts['host']);
176
+
177
+            if (( 'http' === strtolower($url_parts['scheme']) && $url_parts['port'] !== 80 ) || ( 'https' === strtolower($url_parts['scheme']) && $url_parts['port'] !== 443 )) {
178
+                $out .= ':' . $url_parts['port'];
179
+            }
180
+            $out .= "\r\n";
181
+        }
182
+
183
+        if (!isset($case_insensitive_headers['User-Agent'])) {
184
+            $out .= sprintf("User-Agent: %s\r\n", $options['useragent']);
185
+        }
186
+
187
+        $accept_encoding = $this->accept_encoding();
188
+        if (!isset($case_insensitive_headers['Accept-Encoding']) && !empty($accept_encoding)) {
189
+            $out .= sprintf("Accept-Encoding: %s\r\n", $accept_encoding);
190
+        }
191
+
192
+        $headers = Requests::flatten($headers);
193
+
194
+        if (!empty($headers)) {
195
+            $out .= implode($headers, "\r\n") . "\r\n";
196
+        }
197
+
198
+        $options['hooks']->dispatch('fsockopen.after_headers', array(&$out));
199
+
200
+        if (substr($out, -2) !== "\r\n") {
201
+            $out .= "\r\n";
202
+        }
203
+
204
+        if (!isset($case_insensitive_headers['Connection'])) {
205
+            $out .= "Connection: Close\r\n";
206
+        }
207
+
208
+        $out .= "\r\n" . $request_body;
209
+
210
+        $options['hooks']->dispatch('fsockopen.before_send', array(&$out));
211
+
212
+        fwrite($socket, $out);
213
+        $options['hooks']->dispatch('fsockopen.after_send', array($out));
214
+
215
+        if (!$options['blocking']) {
216
+            fclose($socket);
217
+            $fake_headers = '';
218
+            $options['hooks']->dispatch('fsockopen.after_request', array(&$fake_headers));
219
+            return '';
220
+        }
221
+
222
+        $timeout_sec = (int) floor($options['timeout']);
223
+        if ($timeout_sec == $options['timeout']) {
224
+            $timeout_msec = 0;
225
+        }
226
+        else {
227
+            $timeout_msec = self::SECOND_IN_MICROSECONDS * $options['timeout'] % self::SECOND_IN_MICROSECONDS;
228
+        }
229
+        stream_set_timeout($socket, $timeout_sec, $timeout_msec);
230
+
231
+        $response = $body = $headers = '';
232
+        $this->info = stream_get_meta_data($socket);
233
+        $size = 0;
234
+        $doingbody = false;
235
+        $download = false;
236
+        if ($options['filename']) {
237
+            $download = fopen($options['filename'], 'wb');
238
+        }
239
+
240
+        while (!feof($socket)) {
241
+            $this->info = stream_get_meta_data($socket);
242
+            if ($this->info['timed_out']) {
243
+                throw new Requests_Exception('fsocket timed out', 'timeout');
244
+            }
245
+
246
+            $block = fread($socket, Requests::BUFFER_SIZE);
247
+            if (!$doingbody) {
248
+                $response .= $block;
249
+                if (strpos($response, "\r\n\r\n")) {
250
+                    list($headers, $block) = explode("\r\n\r\n", $response, 2);
251
+                    $doingbody = true;
252
+                }
253
+            }
254
+
255
+            // Are we in body mode now?
256
+            if ($doingbody) {
257
+                $options['hooks']->dispatch('request.progress', array($block, $size, $this->max_bytes));
258
+                $data_length = strlen($block);
259
+                if ($this->max_bytes) {
260
+                    // Have we already hit a limit?
261
+                    if ($size === $this->max_bytes) {
262
+                        continue;
263
+                    }
264
+                    if (($size + $data_length) > $this->max_bytes) {
265
+                        // Limit the length
266
+                        $limited_length = ($this->max_bytes - $size);
267
+                        $block = substr($block, 0, $limited_length);
268
+                    }
269
+                }
270
+
271
+                $size += strlen($block);
272
+                if ($download) {
273
+                    fwrite($download, $block);
274
+                }
275
+                else {
276
+                    $body .= $block;
277
+                }
278
+            }
279
+        }
280
+        $this->headers = $headers;
281
+
282
+        if ($download) {
283
+            fclose($download);
284
+        }
285
+        else {
286
+            $this->headers .= "\r\n\r\n" . $body;
287
+        }
288
+        fclose($socket);
289
+
290
+        $options['hooks']->dispatch('fsockopen.after_request', array(&$this->headers, &$this->info));
291
+        return $this->headers;
292
+    }
293
+
294
+    /**
295
+     * Send multiple requests simultaneously
296
+     *
297
+     * @param array $requests Request data (array of 'url', 'headers', 'data', 'options') as per {@see Requests_Transport::request}
298
+     * @param array $options Global options, see {@see Requests::response()} for documentation
299
+     * @return array Array of Requests_Response objects (may contain Requests_Exception or string responses as well)
300
+     */
301
+    public function request_multiple($requests, $options) {
302
+        $responses = array();
303
+        $class = get_class($this);
304
+        foreach ($requests as $id => $request) {
305
+            try {
306
+                $handler = new $class();
307
+                $responses[$id] = $handler->request($request['url'], $request['headers'], $request['data'], $request['options']);
308
+
309
+                $request['options']['hooks']->dispatch('transport.internal.parse_response', array(&$responses[$id], $request));
310
+            }
311
+            catch (Requests_Exception $e) {
312
+                $responses[$id] = $e;
313
+            }
314
+
315
+            if (!is_string($responses[$id])) {
316
+                $request['options']['hooks']->dispatch('multiple.request.complete', array(&$responses[$id], $id));
317
+            }
318
+        }
319
+
320
+        return $responses;
321
+    }
322
+
323
+    /**
324
+     * Retrieve the encodings we can accept
325
+     *
326
+     * @return string Accept-Encoding header value
327
+     */
328
+    protected static function accept_encoding() {
329
+        $type = array();
330
+        if (function_exists('gzinflate')) {
331
+            $type[] = 'deflate;q=1.0';
332
+        }
333
+
334
+        if (function_exists('gzuncompress')) {
335
+            $type[] = 'compress;q=0.5';
336
+        }
337
+
338
+        $type[] = 'gzip;q=0.5';
339
+
340
+        return implode(', ', $type);
341
+    }
342
+
343
+    /**
344
+     * Format a URL given GET data
345
+     *
346
+     * @param array $url_parts
347
+     * @param array|object $data Data to build query using, see {@see https://secure.php.net/http_build_query}
348
+     * @return string URL with data
349
+     */
350
+    protected static function format_get($url_parts, $data) {
351
+        if (!empty($data)) {
352
+            if (empty($url_parts['query'])) {
353
+                $url_parts['query'] = '';
354
+            }
355
+
356
+            $url_parts['query'] .= '&' . http_build_query($data, null, '&');
357
+            $url_parts['query'] = trim($url_parts['query'], '&');
358
+        }
359
+        if (isset($url_parts['path'])) {
360
+            if (isset($url_parts['query'])) {
361
+                $get = $url_parts['path'] . '?' . $url_parts['query'];
362
+            }
363
+            else {
364
+                $get = $url_parts['path'];
365
+            }
366
+        }
367
+        else {
368
+            $get = '/';
369
+        }
370
+        return $get;
371
+    }
372
+
373
+    /**
374
+     * Error handler for stream_socket_client()
375
+     *
376
+     * @param int $errno Error number (e.g. E_WARNING)
377
+     * @param string $errstr Error message
378
+     */
379
+    public function connect_error_handler($errno, $errstr) {
380
+        // Double-check we can handle it
381
+        if (($errno & E_WARNING) === 0 && ($errno & E_NOTICE) === 0) {
382
+            // Return false to indicate the default error handler should engage
383
+            return false;
384
+        }
385
+
386
+        $this->connect_error .= $errstr . "\n";
387
+        return true;
388
+    }
389
+
390
+    /**
391
+     * Verify the certificate against common name and subject alternative names
392
+     *
393
+     * Unfortunately, PHP doesn't check the certificate against the alternative
394
+     * names, leading things like 'https://www.github.com/' to be invalid.
395
+     * Instead
396
+     *
397
+     * @see https://tools.ietf.org/html/rfc2818#section-3.1 RFC2818, Section 3.1
398
+     *
399
+     * @throws Requests_Exception On failure to connect via TLS (`fsockopen.ssl.connect_error`)
400
+     * @throws Requests_Exception On not obtaining a match for the host (`fsockopen.ssl.no_match`)
401
+     * @param string $host Host name to verify against
402
+     * @param resource $context Stream context
403
+     * @return bool
404
+     */
405
+    public function verify_certificate_from_context($host, $context) {
406
+        $meta = stream_context_get_options($context);
407
+
408
+        // If we don't have SSL options, then we couldn't make the connection at
409
+        // all
410
+        if (empty($meta) || empty($meta['ssl']) || empty($meta['ssl']['peer_certificate'])) {
411
+            throw new Requests_Exception(rtrim($this->connect_error), 'ssl.connect_error');
412
+        }
413
+
414
+        $cert = openssl_x509_parse($meta['ssl']['peer_certificate']);
415
+
416
+        return Requests_SSL::verify_certificate($host, $cert);
417
+    }
418
+
419
+    /**
420
+     * Whether this transport is valid
421
+     *
422
+     * @codeCoverageIgnore
423
+     * @return boolean True if the transport is valid, false otherwise.
424
+     */
425
+    public static function test($capabilities = array()) {
426
+        if (!function_exists('fsockopen')) {
427
+            return false;
428
+        }
429
+
430
+        // If needed, check that streams support SSL
431
+        if (isset($capabilities['ssl']) && $capabilities['ssl']) {
432
+            if (!extension_loaded('openssl') || !function_exists('openssl_x509_parse')) {
433
+                return false;
434
+            }
435
+
436
+            // Currently broken, thanks to https://github.com/facebook/hhvm/issues/2156
437
+            if (defined('HHVM_VERSION')) {
438
+                return false;
439
+            }
440
+        }
441
+
442
+        return true;
443
+    }
444 444
 }
Please login to merge, or discard this patch.
plugin/buycourses/src/Requests/SSL.php 1 patch
Indentation   +134 added lines, -134 removed lines patch added patch discarded remove patch
@@ -15,138 +15,138 @@
 block discarded – undo
15 15
  * @subpackage Utilities
16 16
  */
17 17
 class Requests_SSL {
18
-	/**
19
-	 * Verify the certificate against common name and subject alternative names
20
-	 *
21
-	 * Unfortunately, PHP doesn't check the certificate against the alternative
22
-	 * names, leading things like 'https://www.github.com/' to be invalid.
23
-	 * Instead
24
-	 *
25
-	 * @see https://tools.ietf.org/html/rfc2818#section-3.1 RFC2818, Section 3.1
26
-	 *
27
-	 * @throws Requests_Exception On not obtaining a match for the host (`fsockopen.ssl.no_match`)
28
-	 * @param string $host Host name to verify against
29
-	 * @param array $cert Certificate data from openssl_x509_parse()
30
-	 * @return bool
31
-	 */
32
-	public static function verify_certificate($host, $cert) {
33
-		// Calculate the valid wildcard match if the host is not an IP address
34
-		$parts = explode('.', $host);
35
-		if (ip2long($host) === false) {
36
-			$parts[0] = '*';
37
-		}
38
-		$wildcard = implode('.', $parts);
39
-
40
-		$has_dns_alt = false;
41
-
42
-		// Check the subjectAltName
43
-		if (!empty($cert['extensions']) && !empty($cert['extensions']['subjectAltName'])) {
44
-			$altnames = explode(',', $cert['extensions']['subjectAltName']);
45
-			foreach ($altnames as $altname) {
46
-				$altname = trim($altname);
47
-				if (strpos($altname, 'DNS:') !== 0) {
48
-					continue;
49
-				}
50
-
51
-				$has_dns_alt = true;
52
-
53
-				// Strip the 'DNS:' prefix and trim whitespace
54
-				$altname = trim(substr($altname, 4));
55
-
56
-				// Check for a match
57
-				if (self::match_domain($host, $altname) === true) {
58
-					return true;
59
-				}
60
-			}
61
-		}
62
-
63
-		// Fall back to checking the common name if we didn't get any dNSName
64
-		// alt names, as per RFC2818
65
-		if (!$has_dns_alt && !empty($cert['subject']['CN'])) {
66
-			// Check for a match
67
-			if (self::match_domain($host, $cert['subject']['CN']) === true) {
68
-				return true;
69
-			}
70
-		}
71
-
72
-		return false;
73
-	}
74
-
75
-	/**
76
-	 * Verify that a reference name is valid
77
-	 *
78
-	 * Verifies a dNSName for HTTPS usage, (almost) as per Firefox's rules:
79
-	 * - Wildcards can only occur in a name with more than 3 components
80
-	 * - Wildcards can only occur as the last character in the first
81
-	 *   component
82
-	 * - Wildcards may be preceded by additional characters
83
-	 *
84
-	 * We modify these rules to be a bit stricter and only allow the wildcard
85
-	 * character to be the full first component; that is, with the exclusion of
86
-	 * the third rule.
87
-	 *
88
-	 * @param string $reference Reference dNSName
89
-	 * @return boolean Is the name valid?
90
-	 */
91
-	public static function verify_reference_name($reference) {
92
-		$parts = explode('.', $reference);
93
-
94
-		// Check the first part of the name
95
-		$first = array_shift($parts);
96
-
97
-		if (strpos($first, '*') !== false) {
98
-			// Check that the wildcard is the full part
99
-			if ($first !== '*') {
100
-				return false;
101
-			}
102
-
103
-			// Check that we have at least 3 components (including first)
104
-			if (count($parts) < 2) {
105
-				return false;
106
-			}
107
-		}
108
-
109
-		// Check the remaining parts
110
-		foreach ($parts as $part) {
111
-			if (strpos($part, '*') !== false) {
112
-				return false;
113
-			}
114
-		}
115
-
116
-		// Nothing found, verified!
117
-		return true;
118
-	}
119
-
120
-	/**
121
-	 * Match a hostname against a dNSName reference
122
-	 *
123
-	 * @param string $host Requested host
124
-	 * @param string $reference dNSName to match against
125
-	 * @return boolean Does the domain match?
126
-	 */
127
-	public static function match_domain($host, $reference) {
128
-		// Check if the reference is blacklisted first
129
-		if (self::verify_reference_name($reference) !== true) {
130
-			return false;
131
-		}
132
-
133
-		// Check for a direct match
134
-		if ($host === $reference) {
135
-			return true;
136
-		}
137
-
138
-		// Calculate the valid wildcard match if the host is not an IP address
139
-		// Also validates that the host has 3 parts or more, as per Firefox's
140
-		// ruleset.
141
-		if (ip2long($host) === false) {
142
-			$parts = explode('.', $host);
143
-			$parts[0] = '*';
144
-			$wildcard = implode('.', $parts);
145
-			if ($wildcard === $reference) {
146
-				return true;
147
-			}
148
-		}
149
-
150
-		return false;
151
-	}
18
+    /**
19
+     * Verify the certificate against common name and subject alternative names
20
+     *
21
+     * Unfortunately, PHP doesn't check the certificate against the alternative
22
+     * names, leading things like 'https://www.github.com/' to be invalid.
23
+     * Instead
24
+     *
25
+     * @see https://tools.ietf.org/html/rfc2818#section-3.1 RFC2818, Section 3.1
26
+     *
27
+     * @throws Requests_Exception On not obtaining a match for the host (`fsockopen.ssl.no_match`)
28
+     * @param string $host Host name to verify against
29
+     * @param array $cert Certificate data from openssl_x509_parse()
30
+     * @return bool
31
+     */
32
+    public static function verify_certificate($host, $cert) {
33
+        // Calculate the valid wildcard match if the host is not an IP address
34
+        $parts = explode('.', $host);
35
+        if (ip2long($host) === false) {
36
+            $parts[0] = '*';
37
+        }
38
+        $wildcard = implode('.', $parts);
39
+
40
+        $has_dns_alt = false;
41
+
42
+        // Check the subjectAltName
43
+        if (!empty($cert['extensions']) && !empty($cert['extensions']['subjectAltName'])) {
44
+            $altnames = explode(',', $cert['extensions']['subjectAltName']);
45
+            foreach ($altnames as $altname) {
46
+                $altname = trim($altname);
47
+                if (strpos($altname, 'DNS:') !== 0) {
48
+                    continue;
49
+                }
50
+
51
+                $has_dns_alt = true;
52
+
53
+                // Strip the 'DNS:' prefix and trim whitespace
54
+                $altname = trim(substr($altname, 4));
55
+
56
+                // Check for a match
57
+                if (self::match_domain($host, $altname) === true) {
58
+                    return true;
59
+                }
60
+            }
61
+        }
62
+
63
+        // Fall back to checking the common name if we didn't get any dNSName
64
+        // alt names, as per RFC2818
65
+        if (!$has_dns_alt && !empty($cert['subject']['CN'])) {
66
+            // Check for a match
67
+            if (self::match_domain($host, $cert['subject']['CN']) === true) {
68
+                return true;
69
+            }
70
+        }
71
+
72
+        return false;
73
+    }
74
+
75
+    /**
76
+     * Verify that a reference name is valid
77
+     *
78
+     * Verifies a dNSName for HTTPS usage, (almost) as per Firefox's rules:
79
+     * - Wildcards can only occur in a name with more than 3 components
80
+     * - Wildcards can only occur as the last character in the first
81
+     *   component
82
+     * - Wildcards may be preceded by additional characters
83
+     *
84
+     * We modify these rules to be a bit stricter and only allow the wildcard
85
+     * character to be the full first component; that is, with the exclusion of
86
+     * the third rule.
87
+     *
88
+     * @param string $reference Reference dNSName
89
+     * @return boolean Is the name valid?
90
+     */
91
+    public static function verify_reference_name($reference) {
92
+        $parts = explode('.', $reference);
93
+
94
+        // Check the first part of the name
95
+        $first = array_shift($parts);
96
+
97
+        if (strpos($first, '*') !== false) {
98
+            // Check that the wildcard is the full part
99
+            if ($first !== '*') {
100
+                return false;
101
+            }
102
+
103
+            // Check that we have at least 3 components (including first)
104
+            if (count($parts) < 2) {
105
+                return false;
106
+            }
107
+        }
108
+
109
+        // Check the remaining parts
110
+        foreach ($parts as $part) {
111
+            if (strpos($part, '*') !== false) {
112
+                return false;
113
+            }
114
+        }
115
+
116
+        // Nothing found, verified!
117
+        return true;
118
+    }
119
+
120
+    /**
121
+     * Match a hostname against a dNSName reference
122
+     *
123
+     * @param string $host Requested host
124
+     * @param string $reference dNSName to match against
125
+     * @return boolean Does the domain match?
126
+     */
127
+    public static function match_domain($host, $reference) {
128
+        // Check if the reference is blacklisted first
129
+        if (self::verify_reference_name($reference) !== true) {
130
+            return false;
131
+        }
132
+
133
+        // Check for a direct match
134
+        if ($host === $reference) {
135
+            return true;
136
+        }
137
+
138
+        // Calculate the valid wildcard match if the host is not an IP address
139
+        // Also validates that the host has 3 parts or more, as per Firefox's
140
+        // ruleset.
141
+        if (ip2long($host) === false) {
142
+            $parts = explode('.', $host);
143
+            $parts[0] = '*';
144
+            $wildcard = implode('.', $parts);
145
+            if ($wildcard === $reference) {
146
+                return true;
147
+            }
148
+        }
149
+
150
+        return false;
151
+    }
152 152
 }
153 153
\ No newline at end of file
Please login to merge, or discard this patch.
plugin/buycourses/src/Requests/Hooks.php 1 patch
Indentation   +46 added lines, -46 removed lines patch added patch discarded remove patch
@@ -13,56 +13,56 @@
 block discarded – undo
13 13
  * @subpackage Utilities
14 14
  */
15 15
 class Requests_Hooks implements Requests_Hooker {
16
-	/**
17
-	 * Registered callbacks for each hook
18
-	 *
19
-	 * @var array
20
-	 */
21
-	protected $hooks = array();
16
+    /**
17
+     * Registered callbacks for each hook
18
+     *
19
+     * @var array
20
+     */
21
+    protected $hooks = array();
22 22
 
23
-	/**
24
-	 * Constructor
25
-	 */
26
-	public function __construct() {
27
-		// pass
28
-	}
23
+    /**
24
+     * Constructor
25
+     */
26
+    public function __construct() {
27
+        // pass
28
+    }
29 29
 
30
-	/**
31
-	 * Register a callback for a hook
32
-	 *
33
-	 * @param string $hook Hook name
34
-	 * @param callback $callback Function/method to call on event
35
-	 * @param int $priority Priority number. <0 is executed earlier, >0 is executed later
36
-	 */
37
-	public function register($hook, $callback, $priority = 0) {
38
-		if (!isset($this->hooks[$hook])) {
39
-			$this->hooks[$hook] = array();
40
-		}
41
-		if (!isset($this->hooks[$hook][$priority])) {
42
-			$this->hooks[$hook][$priority] = array();
43
-		}
30
+    /**
31
+     * Register a callback for a hook
32
+     *
33
+     * @param string $hook Hook name
34
+     * @param callback $callback Function/method to call on event
35
+     * @param int $priority Priority number. <0 is executed earlier, >0 is executed later
36
+     */
37
+    public function register($hook, $callback, $priority = 0) {
38
+        if (!isset($this->hooks[$hook])) {
39
+            $this->hooks[$hook] = array();
40
+        }
41
+        if (!isset($this->hooks[$hook][$priority])) {
42
+            $this->hooks[$hook][$priority] = array();
43
+        }
44 44
 
45
-		$this->hooks[$hook][$priority][] = $callback;
46
-	}
45
+        $this->hooks[$hook][$priority][] = $callback;
46
+    }
47 47
 
48
-	/**
49
-	 * Dispatch a message
50
-	 *
51
-	 * @param string $hook Hook name
52
-	 * @param array $parameters Parameters to pass to callbacks
53
-	 * @return boolean Successfulness
54
-	 */
55
-	public function dispatch($hook, $parameters = array()) {
56
-		if (empty($this->hooks[$hook])) {
57
-			return false;
58
-		}
48
+    /**
49
+     * Dispatch a message
50
+     *
51
+     * @param string $hook Hook name
52
+     * @param array $parameters Parameters to pass to callbacks
53
+     * @return boolean Successfulness
54
+     */
55
+    public function dispatch($hook, $parameters = array()) {
56
+        if (empty($this->hooks[$hook])) {
57
+            return false;
58
+        }
59 59
 
60
-		foreach ($this->hooks[$hook] as $priority => $hooked) {
61
-			foreach ($hooked as $callback) {
62
-				call_user_func_array($callback, $parameters);
63
-			}
64
-		}
60
+        foreach ($this->hooks[$hook] as $priority => $hooked) {
61
+            foreach ($hooked as $callback) {
62
+                call_user_func_array($callback, $parameters);
63
+            }
64
+        }
65 65
 
66
-		return true;
67
-	}
66
+        return true;
67
+    }
68 68
 }
69 69
\ No newline at end of file
Please login to merge, or discard this patch.
plugin/buycourses/src/Requests/Transport.php 1 patch
Indentation   +23 added lines, -23 removed lines patch added patch discarded remove patch
@@ -13,29 +13,29 @@
 block discarded – undo
13 13
  * @subpackage Transport
14 14
  */
15 15
 interface Requests_Transport {
16
-	/**
17
-	 * Perform a request
18
-	 *
19
-	 * @param string $url URL to request
20
-	 * @param array $headers Associative array of request headers
21
-	 * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
22
-	 * @param array $options Request options, see {@see Requests::response()} for documentation
23
-	 * @return string Raw HTTP result
24
-	 */
25
-	public function request($url, $headers = array(), $data = array(), $options = array());
16
+    /**
17
+     * Perform a request
18
+     *
19
+     * @param string $url URL to request
20
+     * @param array $headers Associative array of request headers
21
+     * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
22
+     * @param array $options Request options, see {@see Requests::response()} for documentation
23
+     * @return string Raw HTTP result
24
+     */
25
+    public function request($url, $headers = array(), $data = array(), $options = array());
26 26
 
27
-	/**
28
-	 * Send multiple requests simultaneously
29
-	 *
30
-	 * @param array $requests Request data (array of 'url', 'headers', 'data', 'options') as per {@see Requests_Transport::request}
31
-	 * @param array $options Global options, see {@see Requests::response()} for documentation
32
-	 * @return array Array of Requests_Response objects (may contain Requests_Exception or string responses as well)
33
-	 */
34
-	public function request_multiple($requests, $options);
27
+    /**
28
+     * Send multiple requests simultaneously
29
+     *
30
+     * @param array $requests Request data (array of 'url', 'headers', 'data', 'options') as per {@see Requests_Transport::request}
31
+     * @param array $options Global options, see {@see Requests::response()} for documentation
32
+     * @return array Array of Requests_Response objects (may contain Requests_Exception or string responses as well)
33
+     */
34
+    public function request_multiple($requests, $options);
35 35
 
36
-	/**
37
-	 * Self-test whether the transport can be used
38
-	 * @return bool
39
-	 */
40
-	public static function test();
36
+    /**
37
+     * Self-test whether the transport can be used
38
+     * @return bool
39
+     */
40
+    public static function test();
41 41
 }
42 42
\ No newline at end of file
Please login to merge, or discard this patch.
plugin/buycourses/src/Requests/IRI.php 1 patch
Indentation   +1017 added lines, -1017 removed lines patch added patch discarded remove patch
@@ -64,1021 +64,1021 @@
 block discarded – undo
64 64
  * @property string $ifragment Fragment part of the IRI (after '#')
65 65
  */
66 66
 class Requests_IRI {
67
-	/**
68
-	 * Scheme
69
-	 *
70
-	 * @var string
71
-	 */
72
-	protected $scheme = null;
73
-
74
-	/**
75
-	 * User Information
76
-	 *
77
-	 * @var string
78
-	 */
79
-	protected $iuserinfo = null;
80
-
81
-	/**
82
-	 * ihost
83
-	 *
84
-	 * @var string
85
-	 */
86
-	protected $ihost = null;
87
-
88
-	/**
89
-	 * Port
90
-	 *
91
-	 * @var string
92
-	 */
93
-	protected $port = null;
94
-
95
-	/**
96
-	 * ipath
97
-	 *
98
-	 * @var string
99
-	 */
100
-	protected $ipath = '';
101
-
102
-	/**
103
-	 * iquery
104
-	 *
105
-	 * @var string
106
-	 */
107
-	protected $iquery = null;
108
-
109
-	/**
110
-	 * ifragment
111
-	 *
112
-	 * @var string
113
-	 */
114
-	protected $ifragment = null;
115
-
116
-	/**
117
-	 * Normalization database
118
-	 *
119
-	 * Each key is the scheme, each value is an array with each key as the IRI
120
-	 * part and value as the default value for that part.
121
-	 */
122
-	protected $normalization = array(
123
-		'acap' => array(
124
-			'port' => 674
125
-		),
126
-		'dict' => array(
127
-			'port' => 2628
128
-		),
129
-		'file' => array(
130
-			'ihost' => 'localhost'
131
-		),
132
-		'http' => array(
133
-			'port' => 80,
134
-		),
135
-		'https' => array(
136
-			'port' => 443,
137
-		),
138
-	);
139
-
140
-	/**
141
-	 * Return the entire IRI when you try and read the object as a string
142
-	 *
143
-	 * @return string
144
-	 */
145
-	public function __toString() {
146
-		return $this->get_iri();
147
-	}
148
-
149
-	/**
150
-	 * Overload __set() to provide access via properties
151
-	 *
152
-	 * @param string $name Property name
153
-	 * @param mixed $value Property value
154
-	 */
155
-	public function __set($name, $value) {
156
-		if (method_exists($this, 'set_' . $name)) {
157
-			call_user_func(array($this, 'set_' . $name), $value);
158
-		}
159
-		elseif (
160
-			   $name === 'iauthority'
161
-			|| $name === 'iuserinfo'
162
-			|| $name === 'ihost'
163
-			|| $name === 'ipath'
164
-			|| $name === 'iquery'
165
-			|| $name === 'ifragment'
166
-		) {
167
-			call_user_func(array($this, 'set_' . substr($name, 1)), $value);
168
-		}
169
-	}
170
-
171
-	/**
172
-	 * Overload __get() to provide access via properties
173
-	 *
174
-	 * @param string $name Property name
175
-	 * @return mixed
176
-	 */
177
-	public function __get($name) {
178
-		// isset() returns false for null, we don't want to do that
179
-		// Also why we use array_key_exists below instead of isset()
180
-		$props = get_object_vars($this);
181
-
182
-		if (
183
-			$name === 'iri' ||
184
-			$name === 'uri' ||
185
-			$name === 'iauthority' ||
186
-			$name === 'authority'
187
-		) {
188
-			$method = 'get_' . $name;
189
-			$return = $this->$method();
190
-		}
191
-		elseif (array_key_exists($name, $props)) {
192
-			$return = $this->$name;
193
-		}
194
-		// host -> ihost
195
-		elseif (($prop = 'i' . $name) && array_key_exists($prop, $props)) {
196
-			$name = $prop;
197
-			$return = $this->$prop;
198
-		}
199
-		// ischeme -> scheme
200
-		elseif (($prop = substr($name, 1)) && array_key_exists($prop, $props)) {
201
-			$name = $prop;
202
-			$return = $this->$prop;
203
-		}
204
-		else {
205
-			trigger_error('Undefined property: ' . get_class($this) . '::' . $name, E_USER_NOTICE);
206
-			$return = null;
207
-		}
208
-
209
-		if ($return === null && isset($this->normalization[$this->scheme][$name])) {
210
-			return $this->normalization[$this->scheme][$name];
211
-		}
212
-		else {
213
-			return $return;
214
-		}
215
-	}
216
-
217
-	/**
218
-	 * Overload __isset() to provide access via properties
219
-	 *
220
-	 * @param string $name Property name
221
-	 * @return bool
222
-	 */
223
-	public function __isset($name) {
224
-		return (method_exists($this, 'get_' . $name) || isset($this->$name));
225
-	}
226
-
227
-	/**
228
-	 * Overload __unset() to provide access via properties
229
-	 *
230
-	 * @param string $name Property name
231
-	 */
232
-	public function __unset($name) {
233
-		if (method_exists($this, 'set_' . $name)) {
234
-			call_user_func(array($this, 'set_' . $name), '');
235
-		}
236
-	}
237
-
238
-	/**
239
-	 * Create a new IRI object, from a specified string
240
-	 *
241
-	 * @param string|null $iri
242
-	 */
243
-	public function __construct($iri = null) {
244
-		$this->set_iri($iri);
245
-	}
246
-
247
-	/**
248
-	 * Create a new IRI object by resolving a relative IRI
249
-	 *
250
-	 * Returns false if $base is not absolute, otherwise an IRI.
251
-	 *
252
-	 * @param IRI|string $base (Absolute) Base IRI
253
-	 * @param IRI|string $relative Relative IRI
254
-	 * @return IRI|false
255
-	 */
256
-	public static function absolutize($base, $relative) {
257
-		if (!($relative instanceof Requests_IRI)) {
258
-			$relative = new Requests_IRI($relative);
259
-		}
260
-		if (!$relative->is_valid()) {
261
-			return false;
262
-		}
263
-		elseif ($relative->scheme !== null) {
264
-			return clone $relative;
265
-		}
266
-
267
-		if (!($base instanceof Requests_IRI)) {
268
-			$base = new Requests_IRI($base);
269
-		}
270
-		if ($base->scheme === null || !$base->is_valid()) {
271
-			return false;
272
-		}
273
-
274
-		if ($relative->get_iri() !== '') {
275
-			if ($relative->iuserinfo !== null || $relative->ihost !== null || $relative->port !== null) {
276
-				$target = clone $relative;
277
-				$target->scheme = $base->scheme;
278
-			}
279
-			else {
280
-				$target = new Requests_IRI;
281
-				$target->scheme = $base->scheme;
282
-				$target->iuserinfo = $base->iuserinfo;
283
-				$target->ihost = $base->ihost;
284
-				$target->port = $base->port;
285
-				if ($relative->ipath !== '') {
286
-					if ($relative->ipath[0] === '/') {
287
-						$target->ipath = $relative->ipath;
288
-					}
289
-					elseif (($base->iuserinfo !== null || $base->ihost !== null || $base->port !== null) && $base->ipath === '') {
290
-						$target->ipath = '/' . $relative->ipath;
291
-					}
292
-					elseif (($last_segment = strrpos($base->ipath, '/')) !== false) {
293
-						$target->ipath = substr($base->ipath, 0, $last_segment + 1) . $relative->ipath;
294
-					}
295
-					else {
296
-						$target->ipath = $relative->ipath;
297
-					}
298
-					$target->ipath = $target->remove_dot_segments($target->ipath);
299
-					$target->iquery = $relative->iquery;
300
-				}
301
-				else {
302
-					$target->ipath = $base->ipath;
303
-					if ($relative->iquery !== null) {
304
-						$target->iquery = $relative->iquery;
305
-					}
306
-					elseif ($base->iquery !== null) {
307
-						$target->iquery = $base->iquery;
308
-					}
309
-				}
310
-				$target->ifragment = $relative->ifragment;
311
-			}
312
-		}
313
-		else {
314
-			$target = clone $base;
315
-			$target->ifragment = null;
316
-		}
317
-		$target->scheme_normalization();
318
-		return $target;
319
-	}
320
-
321
-	/**
322
-	 * Parse an IRI into scheme/authority/path/query/fragment segments
323
-	 *
324
-	 * @param string $iri
325
-	 * @return array
326
-	 */
327
-	protected function parse_iri($iri) {
328
-		$iri = trim($iri, "\x20\x09\x0A\x0C\x0D");
329
-		$has_match = preg_match('/^((?P<scheme>[^:\/?#]+):)?(\/\/(?P<authority>[^\/?#]*))?(?P<path>[^?#]*)(\?(?P<query>[^#]*))?(#(?P<fragment>.*))?$/', $iri, $match);
330
-		if (!$has_match) {
331
-			throw new Requests_Exception('Cannot parse supplied IRI', 'iri.cannot_parse', $iri);
332
-		}
333
-
334
-		if ($match[1] === '') {
335
-			$match['scheme'] = null;
336
-		}
337
-		if (!isset($match[3]) || $match[3] === '') {
338
-			$match['authority'] = null;
339
-		}
340
-		if (!isset($match[5])) {
341
-			$match['path'] = '';
342
-		}
343
-		if (!isset($match[6]) || $match[6] === '') {
344
-			$match['query'] = null;
345
-		}
346
-		if (!isset($match[8]) || $match[8] === '') {
347
-			$match['fragment'] = null;
348
-		}
349
-		return $match;
350
-	}
351
-
352
-	/**
353
-	 * Remove dot segments from a path
354
-	 *
355
-	 * @param string $input
356
-	 * @return string
357
-	 */
358
-	protected function remove_dot_segments($input) {
359
-		$output = '';
360
-		while (strpos($input, './') !== false || strpos($input, '/.') !== false || $input === '.' || $input === '..') {
361
-			// A: If the input buffer begins with a prefix of "../" or "./",
362
-			// then remove that prefix from the input buffer; otherwise,
363
-			if (strpos($input, '../') === 0) {
364
-				$input = substr($input, 3);
365
-			}
366
-			elseif (strpos($input, './') === 0) {
367
-				$input = substr($input, 2);
368
-			}
369
-			// B: if the input buffer begins with a prefix of "/./" or "/.",
370
-			// where "." is a complete path segment, then replace that prefix
371
-			// with "/" in the input buffer; otherwise,
372
-			elseif (strpos($input, '/./') === 0) {
373
-				$input = substr($input, 2);
374
-			}
375
-			elseif ($input === '/.') {
376
-				$input = '/';
377
-			}
378
-			// C: if the input buffer begins with a prefix of "/../" or "/..",
379
-			// where ".." is a complete path segment, then replace that prefix
380
-			// with "/" in the input buffer and remove the last segment and its
381
-			// preceding "/" (if any) from the output buffer; otherwise,
382
-			elseif (strpos($input, '/../') === 0) {
383
-				$input = substr($input, 3);
384
-				$output = substr_replace($output, '', strrpos($output, '/'));
385
-			}
386
-			elseif ($input === '/..') {
387
-				$input = '/';
388
-				$output = substr_replace($output, '', strrpos($output, '/'));
389
-			}
390
-			// D: if the input buffer consists only of "." or "..", then remove
391
-			// that from the input buffer; otherwise,
392
-			elseif ($input === '.' || $input === '..') {
393
-				$input = '';
394
-			}
395
-			// E: move the first path segment in the input buffer to the end of
396
-			// the output buffer, including the initial "/" character (if any)
397
-			// and any subsequent characters up to, but not including, the next
398
-			// "/" character or the end of the input buffer
399
-			elseif (($pos = strpos($input, '/', 1)) !== false) {
400
-				$output .= substr($input, 0, $pos);
401
-				$input = substr_replace($input, '', 0, $pos);
402
-			}
403
-			else {
404
-				$output .= $input;
405
-				$input = '';
406
-			}
407
-		}
408
-		return $output . $input;
409
-	}
410
-
411
-	/**
412
-	 * Replace invalid character with percent encoding
413
-	 *
414
-	 * @param string $string Input string
415
-	 * @param string $extra_chars Valid characters not in iunreserved or
416
-	 *                            iprivate (this is ASCII-only)
417
-	 * @param bool $iprivate Allow iprivate
418
-	 * @return string
419
-	 */
420
-	protected function replace_invalid_with_pct_encoding($string, $extra_chars, $iprivate = false) {
421
-		// Normalize as many pct-encoded sections as possible
422
-		$string = preg_replace_callback('/(?:%[A-Fa-f0-9]{2})+/', array(&$this, 'remove_iunreserved_percent_encoded'), $string);
423
-
424
-		// Replace invalid percent characters
425
-		$string = preg_replace('/%(?![A-Fa-f0-9]{2})/', '%25', $string);
426
-
427
-		// Add unreserved and % to $extra_chars (the latter is safe because all
428
-		// pct-encoded sections are now valid).
429
-		$extra_chars .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~%';
430
-
431
-		// Now replace any bytes that aren't allowed with their pct-encoded versions
432
-		$position = 0;
433
-		$strlen = strlen($string);
434
-		while (($position += strspn($string, $extra_chars, $position)) < $strlen) {
435
-			$value = ord($string[$position]);
436
-
437
-			// Start position
438
-			$start = $position;
439
-
440
-			// By default we are valid
441
-			$valid = true;
442
-
443
-			// No one byte sequences are valid due to the while.
444
-			// Two byte sequence:
445
-			if (($value & 0xE0) === 0xC0) {
446
-				$character = ($value & 0x1F) << 6;
447
-				$length = 2;
448
-				$remaining = 1;
449
-			}
450
-			// Three byte sequence:
451
-			elseif (($value & 0xF0) === 0xE0) {
452
-				$character = ($value & 0x0F) << 12;
453
-				$length = 3;
454
-				$remaining = 2;
455
-			}
456
-			// Four byte sequence:
457
-			elseif (($value & 0xF8) === 0xF0) {
458
-				$character = ($value & 0x07) << 18;
459
-				$length = 4;
460
-				$remaining = 3;
461
-			}
462
-			// Invalid byte:
463
-			else {
464
-				$valid = false;
465
-				$length = 1;
466
-				$remaining = 0;
467
-			}
468
-
469
-			if ($remaining) {
470
-				if ($position + $length <= $strlen) {
471
-					for ($position++; $remaining; $position++) {
472
-						$value = ord($string[$position]);
473
-
474
-						// Check that the byte is valid, then add it to the character:
475
-						if (($value & 0xC0) === 0x80) {
476
-							$character |= ($value & 0x3F) << (--$remaining * 6);
477
-						}
478
-						// If it is invalid, count the sequence as invalid and reprocess the current byte:
479
-						else {
480
-							$valid = false;
481
-							$position--;
482
-							break;
483
-						}
484
-					}
485
-				}
486
-				else {
487
-					$position = $strlen - 1;
488
-					$valid = false;
489
-				}
490
-			}
491
-
492
-			// Percent encode anything invalid or not in ucschar
493
-			if (
494
-				// Invalid sequences
495
-				!$valid
496
-				// Non-shortest form sequences are invalid
497
-				|| $length > 1 && $character <= 0x7F
498
-				|| $length > 2 && $character <= 0x7FF
499
-				|| $length > 3 && $character <= 0xFFFF
500
-				// Outside of range of ucschar codepoints
501
-				// Noncharacters
502
-				|| ($character & 0xFFFE) === 0xFFFE
503
-				|| $character >= 0xFDD0 && $character <= 0xFDEF
504
-				|| (
505
-					// Everything else not in ucschar
506
-					   $character > 0xD7FF && $character < 0xF900
507
-					|| $character < 0xA0
508
-					|| $character > 0xEFFFD
509
-				)
510
-				&& (
511
-					// Everything not in iprivate, if it applies
512
-					   !$iprivate
513
-					|| $character < 0xE000
514
-					|| $character > 0x10FFFD
515
-				)
516
-			) {
517
-				// If we were a character, pretend we weren't, but rather an error.
518
-				if ($valid) {
519
-					$position--;
520
-				}
521
-
522
-				for ($j = $start; $j <= $position; $j++) {
523
-					$string = substr_replace($string, sprintf('%%%02X', ord($string[$j])), $j, 1);
524
-					$j += 2;
525
-					$position += 2;
526
-					$strlen += 2;
527
-				}
528
-			}
529
-		}
530
-
531
-		return $string;
532
-	}
533
-
534
-	/**
535
-	 * Callback function for preg_replace_callback.
536
-	 *
537
-	 * Removes sequences of percent encoded bytes that represent UTF-8
538
-	 * encoded characters in iunreserved
539
-	 *
540
-	 * @param array $match PCRE match
541
-	 * @return string Replacement
542
-	 */
543
-	protected function remove_iunreserved_percent_encoded($match) {
544
-		// As we just have valid percent encoded sequences we can just explode
545
-		// and ignore the first member of the returned array (an empty string).
546
-		$bytes = explode('%', $match[0]);
547
-
548
-		// Initialize the new string (this is what will be returned) and that
549
-		// there are no bytes remaining in the current sequence (unsurprising
550
-		// at the first byte!).
551
-		$string = '';
552
-		$remaining = 0;
553
-
554
-		// Loop over each and every byte, and set $value to its value
555
-		for ($i = 1, $len = count($bytes); $i < $len; $i++) {
556
-			$value = hexdec($bytes[$i]);
557
-
558
-			// If we're the first byte of sequence:
559
-			if (!$remaining) {
560
-				// Start position
561
-				$start = $i;
562
-
563
-				// By default we are valid
564
-				$valid = true;
565
-
566
-				// One byte sequence:
567
-				if ($value <= 0x7F) {
568
-					$character = $value;
569
-					$length = 1;
570
-				}
571
-				// Two byte sequence:
572
-				elseif (($value & 0xE0) === 0xC0) {
573
-					$character = ($value & 0x1F) << 6;
574
-					$length = 2;
575
-					$remaining = 1;
576
-				}
577
-				// Three byte sequence:
578
-				elseif (($value & 0xF0) === 0xE0) {
579
-					$character = ($value & 0x0F) << 12;
580
-					$length = 3;
581
-					$remaining = 2;
582
-				}
583
-				// Four byte sequence:
584
-				elseif (($value & 0xF8) === 0xF0) {
585
-					$character = ($value & 0x07) << 18;
586
-					$length = 4;
587
-					$remaining = 3;
588
-				}
589
-				// Invalid byte:
590
-				else {
591
-					$valid = false;
592
-					$remaining = 0;
593
-				}
594
-			}
595
-			// Continuation byte:
596
-			else {
597
-				// Check that the byte is valid, then add it to the character:
598
-				if (($value & 0xC0) === 0x80) {
599
-					$remaining--;
600
-					$character |= ($value & 0x3F) << ($remaining * 6);
601
-				}
602
-				// If it is invalid, count the sequence as invalid and reprocess the current byte as the start of a sequence:
603
-				else {
604
-					$valid = false;
605
-					$remaining = 0;
606
-					$i--;
607
-				}
608
-			}
609
-
610
-			// If we've reached the end of the current byte sequence, append it to Unicode::$data
611
-			if (!$remaining) {
612
-				// Percent encode anything invalid or not in iunreserved
613
-				if (
614
-					// Invalid sequences
615
-					!$valid
616
-					// Non-shortest form sequences are invalid
617
-					|| $length > 1 && $character <= 0x7F
618
-					|| $length > 2 && $character <= 0x7FF
619
-					|| $length > 3 && $character <= 0xFFFF
620
-					// Outside of range of iunreserved codepoints
621
-					|| $character < 0x2D
622
-					|| $character > 0xEFFFD
623
-					// Noncharacters
624
-					|| ($character & 0xFFFE) === 0xFFFE
625
-					|| $character >= 0xFDD0 && $character <= 0xFDEF
626
-					// Everything else not in iunreserved (this is all BMP)
627
-					|| $character === 0x2F
628
-					|| $character > 0x39 && $character < 0x41
629
-					|| $character > 0x5A && $character < 0x61
630
-					|| $character > 0x7A && $character < 0x7E
631
-					|| $character > 0x7E && $character < 0xA0
632
-					|| $character > 0xD7FF && $character < 0xF900
633
-				) {
634
-					for ($j = $start; $j <= $i; $j++) {
635
-						$string .= '%' . strtoupper($bytes[$j]);
636
-					}
637
-				}
638
-				else {
639
-					for ($j = $start; $j <= $i; $j++) {
640
-						$string .= chr(hexdec($bytes[$j]));
641
-					}
642
-				}
643
-			}
644
-		}
645
-
646
-		// If we have any bytes left over they are invalid (i.e., we are
647
-		// mid-way through a multi-byte sequence)
648
-		if ($remaining) {
649
-			for ($j = $start; $j < $len; $j++) {
650
-				$string .= '%' . strtoupper($bytes[$j]);
651
-			}
652
-		}
653
-
654
-		return $string;
655
-	}
656
-
657
-	protected function scheme_normalization() {
658
-		if (isset($this->normalization[$this->scheme]['iuserinfo']) && $this->iuserinfo === $this->normalization[$this->scheme]['iuserinfo']) {
659
-			$this->iuserinfo = null;
660
-		}
661
-		if (isset($this->normalization[$this->scheme]['ihost']) && $this->ihost === $this->normalization[$this->scheme]['ihost']) {
662
-			$this->ihost = null;
663
-		}
664
-		if (isset($this->normalization[$this->scheme]['port']) && $this->port === $this->normalization[$this->scheme]['port']) {
665
-			$this->port = null;
666
-		}
667
-		if (isset($this->normalization[$this->scheme]['ipath']) && $this->ipath === $this->normalization[$this->scheme]['ipath']) {
668
-			$this->ipath = '';
669
-		}
670
-		if (isset($this->ihost) && empty($this->ipath)) {
671
-			$this->ipath = '/';
672
-		}
673
-		if (isset($this->normalization[$this->scheme]['iquery']) && $this->iquery === $this->normalization[$this->scheme]['iquery']) {
674
-			$this->iquery = null;
675
-		}
676
-		if (isset($this->normalization[$this->scheme]['ifragment']) && $this->ifragment === $this->normalization[$this->scheme]['ifragment']) {
677
-			$this->ifragment = null;
678
-		}
679
-	}
680
-
681
-	/**
682
-	 * Check if the object represents a valid IRI. This needs to be done on each
683
-	 * call as some things change depending on another part of the IRI.
684
-	 *
685
-	 * @return bool
686
-	 */
687
-	public function is_valid() {
688
-		$isauthority = $this->iuserinfo !== null || $this->ihost !== null || $this->port !== null;
689
-		if ($this->ipath !== '' &&
690
-			(
691
-				$isauthority && $this->ipath[0] !== '/' ||
692
-				(
693
-					$this->scheme === null &&
694
-					!$isauthority &&
695
-					strpos($this->ipath, ':') !== false &&
696
-					(strpos($this->ipath, '/') === false ? true : strpos($this->ipath, ':') < strpos($this->ipath, '/'))
697
-				)
698
-			)
699
-		) {
700
-			return false;
701
-		}
702
-
703
-		return true;
704
-	}
705
-
706
-	/**
707
-	 * Set the entire IRI. Returns true on success, false on failure (if there
708
-	 * are any invalid characters).
709
-	 *
710
-	 * @param string $iri
711
-	 * @return bool
712
-	 */
713
-	protected function set_iri($iri) {
714
-		static $cache;
715
-		if (!$cache) {
716
-			$cache = array();
717
-		}
718
-
719
-		if ($iri === null) {
720
-			return true;
721
-		}
722
-		if (isset($cache[$iri])) {
723
-			list($this->scheme,
724
-				 $this->iuserinfo,
725
-				 $this->ihost,
726
-				 $this->port,
727
-				 $this->ipath,
728
-				 $this->iquery,
729
-				 $this->ifragment,
730
-				 $return) = $cache[$iri];
731
-			return $return;
732
-		}
733
-
734
-		$parsed = $this->parse_iri((string) $iri);
735
-
736
-		$return = $this->set_scheme($parsed['scheme'])
737
-			&& $this->set_authority($parsed['authority'])
738
-			&& $this->set_path($parsed['path'])
739
-			&& $this->set_query($parsed['query'])
740
-			&& $this->set_fragment($parsed['fragment']);
741
-
742
-		$cache[$iri] = array($this->scheme,
743
-							 $this->iuserinfo,
744
-							 $this->ihost,
745
-							 $this->port,
746
-							 $this->ipath,
747
-							 $this->iquery,
748
-							 $this->ifragment,
749
-							 $return);
750
-		return $return;
751
-	}
752
-
753
-	/**
754
-	 * Set the scheme. Returns true on success, false on failure (if there are
755
-	 * any invalid characters).
756
-	 *
757
-	 * @param string $scheme
758
-	 * @return bool
759
-	 */
760
-	protected function set_scheme($scheme) {
761
-		if ($scheme === null) {
762
-			$this->scheme = null;
763
-		}
764
-		elseif (!preg_match('/^[A-Za-z][0-9A-Za-z+\-.]*$/', $scheme)) {
765
-			$this->scheme = null;
766
-			return false;
767
-		}
768
-		else {
769
-			$this->scheme = strtolower($scheme);
770
-		}
771
-		return true;
772
-	}
773
-
774
-	/**
775
-	 * Set the authority. Returns true on success, false on failure (if there are
776
-	 * any invalid characters).
777
-	 *
778
-	 * @param string $authority
779
-	 * @return bool
780
-	 */
781
-	protected function set_authority($authority) {
782
-		static $cache;
783
-		if (!$cache) {
784
-			$cache = array();
785
-		}
786
-
787
-		if ($authority === null) {
788
-			$this->iuserinfo = null;
789
-			$this->ihost = null;
790
-			$this->port = null;
791
-			return true;
792
-		}
793
-		if (isset($cache[$authority])) {
794
-			list($this->iuserinfo,
795
-				 $this->ihost,
796
-				 $this->port,
797
-				 $return) = $cache[$authority];
798
-
799
-			return $return;
800
-		}
801
-
802
-		$remaining = $authority;
803
-		if (($iuserinfo_end = strrpos($remaining, '@')) !== false) {
804
-			$iuserinfo = substr($remaining, 0, $iuserinfo_end);
805
-			$remaining = substr($remaining, $iuserinfo_end + 1);
806
-		}
807
-		else {
808
-			$iuserinfo = null;
809
-		}
810
-		if (($port_start = strpos($remaining, ':', strpos($remaining, ']'))) !== false) {
811
-			$port = substr($remaining, $port_start + 1);
812
-			if ($port === false || $port === '') {
813
-				$port = null;
814
-			}
815
-			$remaining = substr($remaining, 0, $port_start);
816
-		}
817
-		else {
818
-			$port = null;
819
-		}
820
-
821
-		$return = $this->set_userinfo($iuserinfo) &&
822
-				  $this->set_host($remaining) &&
823
-				  $this->set_port($port);
824
-
825
-		$cache[$authority] = array($this->iuserinfo,
826
-								   $this->ihost,
827
-								   $this->port,
828
-								   $return);
829
-
830
-		return $return;
831
-	}
832
-
833
-	/**
834
-	 * Set the iuserinfo.
835
-	 *
836
-	 * @param string $iuserinfo
837
-	 * @return bool
838
-	 */
839
-	protected function set_userinfo($iuserinfo) {
840
-		if ($iuserinfo === null) {
841
-			$this->iuserinfo = null;
842
-		}
843
-		else {
844
-			$this->iuserinfo = $this->replace_invalid_with_pct_encoding($iuserinfo, '!$&\'()*+,;=:');
845
-			$this->scheme_normalization();
846
-		}
847
-
848
-		return true;
849
-	}
850
-
851
-	/**
852
-	 * Set the ihost. Returns true on success, false on failure (if there are
853
-	 * any invalid characters).
854
-	 *
855
-	 * @param string $ihost
856
-	 * @return bool
857
-	 */
858
-	protected function set_host($ihost) {
859
-		if ($ihost === null) {
860
-			$this->ihost = null;
861
-			return true;
862
-		}
863
-		if (substr($ihost, 0, 1) === '[' && substr($ihost, -1) === ']') {
864
-			if (Requests_IPv6::check_ipv6(substr($ihost, 1, -1))) {
865
-				$this->ihost = '[' . Requests_IPv6::compress(substr($ihost, 1, -1)) . ']';
866
-			}
867
-			else {
868
-				$this->ihost = null;
869
-				return false;
870
-			}
871
-		}
872
-		else {
873
-			$ihost = $this->replace_invalid_with_pct_encoding($ihost, '!$&\'()*+,;=');
874
-
875
-			// Lowercase, but ignore pct-encoded sections (as they should
876
-			// remain uppercase). This must be done after the previous step
877
-			// as that can add unescaped characters.
878
-			$position = 0;
879
-			$strlen = strlen($ihost);
880
-			while (($position += strcspn($ihost, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ%', $position)) < $strlen) {
881
-				if ($ihost[$position] === '%') {
882
-					$position += 3;
883
-				}
884
-				else {
885
-					$ihost[$position] = strtolower($ihost[$position]);
886
-					$position++;
887
-				}
888
-			}
889
-
890
-			$this->ihost = $ihost;
891
-		}
892
-
893
-		$this->scheme_normalization();
894
-
895
-		return true;
896
-	}
897
-
898
-	/**
899
-	 * Set the port. Returns true on success, false on failure (if there are
900
-	 * any invalid characters).
901
-	 *
902
-	 * @param string $port
903
-	 * @return bool
904
-	 */
905
-	protected function set_port($port) {
906
-		if ($port === null) {
907
-			$this->port = null;
908
-			return true;
909
-		}
910
-
911
-		if (strspn($port, '0123456789') === strlen($port)) {
912
-			$this->port = (int) $port;
913
-			$this->scheme_normalization();
914
-			return true;
915
-		}
916
-
917
-		$this->port = null;
918
-		return false;
919
-	}
920
-
921
-	/**
922
-	 * Set the ipath.
923
-	 *
924
-	 * @param string $ipath
925
-	 * @return bool
926
-	 */
927
-	protected function set_path($ipath) {
928
-		static $cache;
929
-		if (!$cache) {
930
-			$cache = array();
931
-		}
932
-
933
-		$ipath = (string) $ipath;
934
-
935
-		if (isset($cache[$ipath])) {
936
-			$this->ipath = $cache[$ipath][(int) ($this->scheme !== null)];
937
-		}
938
-		else {
939
-			$valid = $this->replace_invalid_with_pct_encoding($ipath, '!$&\'()*+,;=@:/');
940
-			$removed = $this->remove_dot_segments($valid);
941
-
942
-			$cache[$ipath] = array($valid, $removed);
943
-			$this->ipath = ($this->scheme !== null) ? $removed : $valid;
944
-		}
945
-		$this->scheme_normalization();
946
-		return true;
947
-	}
948
-
949
-	/**
950
-	 * Set the iquery.
951
-	 *
952
-	 * @param string $iquery
953
-	 * @return bool
954
-	 */
955
-	protected function set_query($iquery) {
956
-		if ($iquery === null) {
957
-			$this->iquery = null;
958
-		}
959
-		else {
960
-			$this->iquery = $this->replace_invalid_with_pct_encoding($iquery, '!$&\'()*+,;=:@/?', true);
961
-			$this->scheme_normalization();
962
-		}
963
-		return true;
964
-	}
965
-
966
-	/**
967
-	 * Set the ifragment.
968
-	 *
969
-	 * @param string $ifragment
970
-	 * @return bool
971
-	 */
972
-	protected function set_fragment($ifragment) {
973
-		if ($ifragment === null) {
974
-			$this->ifragment = null;
975
-		}
976
-		else {
977
-			$this->ifragment = $this->replace_invalid_with_pct_encoding($ifragment, '!$&\'()*+,;=:@/?');
978
-			$this->scheme_normalization();
979
-		}
980
-		return true;
981
-	}
982
-
983
-	/**
984
-	 * Convert an IRI to a URI (or parts thereof)
985
-	 *
986
-	 * @param string|bool IRI to convert (or false from {@see get_iri})
987
-	 * @return string|false URI if IRI is valid, false otherwise.
988
-	 */
989
-	protected function to_uri($string) {
990
-		if (!is_string($string)) {
991
-			return false;
992
-		}
993
-
994
-		static $non_ascii;
995
-		if (!$non_ascii) {
996
-			$non_ascii = implode('', range("\x80", "\xFF"));
997
-		}
998
-
999
-		$position = 0;
1000
-		$strlen = strlen($string);
1001
-		while (($position += strcspn($string, $non_ascii, $position)) < $strlen) {
1002
-			$string = substr_replace($string, sprintf('%%%02X', ord($string[$position])), $position, 1);
1003
-			$position += 3;
1004
-			$strlen += 2;
1005
-		}
1006
-
1007
-		return $string;
1008
-	}
1009
-
1010
-	/**
1011
-	 * Get the complete IRI
1012
-	 *
1013
-	 * @return string
1014
-	 */
1015
-	protected function get_iri() {
1016
-		if (!$this->is_valid()) {
1017
-			return false;
1018
-		}
1019
-
1020
-		$iri = '';
1021
-		if ($this->scheme !== null) {
1022
-			$iri .= $this->scheme . ':';
1023
-		}
1024
-		if (($iauthority = $this->get_iauthority()) !== null) {
1025
-			$iri .= '//' . $iauthority;
1026
-		}
1027
-		$iri .= $this->ipath;
1028
-		if ($this->iquery !== null) {
1029
-			$iri .= '?' . $this->iquery;
1030
-		}
1031
-		if ($this->ifragment !== null) {
1032
-			$iri .= '#' . $this->ifragment;
1033
-		}
1034
-
1035
-		return $iri;
1036
-	}
1037
-
1038
-	/**
1039
-	 * Get the complete URI
1040
-	 *
1041
-	 * @return string
1042
-	 */
1043
-	protected function get_uri() {
1044
-		return $this->to_uri($this->get_iri());
1045
-	}
1046
-
1047
-	/**
1048
-	 * Get the complete iauthority
1049
-	 *
1050
-	 * @return string
1051
-	 */
1052
-	protected function get_iauthority() {
1053
-		if ($this->iuserinfo === null && $this->ihost === null && $this->port === null) {
1054
-			return null;
1055
-		}
1056
-
1057
-		$iauthority = '';
1058
-		if ($this->iuserinfo !== null) {
1059
-			$iauthority .= $this->iuserinfo . '@';
1060
-		}
1061
-		if ($this->ihost !== null) {
1062
-			$iauthority .= $this->ihost;
1063
-		}
1064
-		if ($this->port !== null) {
1065
-			$iauthority .= ':' . $this->port;
1066
-		}
1067
-		return $iauthority;
1068
-	}
1069
-
1070
-	/**
1071
-	 * Get the complete authority
1072
-	 *
1073
-	 * @return string
1074
-	 */
1075
-	protected function get_authority() {
1076
-		$iauthority = $this->get_iauthority();
1077
-		if (is_string($iauthority)) {
1078
-			return $this->to_uri($iauthority);
1079
-		}
1080
-		else {
1081
-			return $iauthority;
1082
-		}
1083
-	}
67
+    /**
68
+     * Scheme
69
+     *
70
+     * @var string
71
+     */
72
+    protected $scheme = null;
73
+
74
+    /**
75
+     * User Information
76
+     *
77
+     * @var string
78
+     */
79
+    protected $iuserinfo = null;
80
+
81
+    /**
82
+     * ihost
83
+     *
84
+     * @var string
85
+     */
86
+    protected $ihost = null;
87
+
88
+    /**
89
+     * Port
90
+     *
91
+     * @var string
92
+     */
93
+    protected $port = null;
94
+
95
+    /**
96
+     * ipath
97
+     *
98
+     * @var string
99
+     */
100
+    protected $ipath = '';
101
+
102
+    /**
103
+     * iquery
104
+     *
105
+     * @var string
106
+     */
107
+    protected $iquery = null;
108
+
109
+    /**
110
+     * ifragment
111
+     *
112
+     * @var string
113
+     */
114
+    protected $ifragment = null;
115
+
116
+    /**
117
+     * Normalization database
118
+     *
119
+     * Each key is the scheme, each value is an array with each key as the IRI
120
+     * part and value as the default value for that part.
121
+     */
122
+    protected $normalization = array(
123
+        'acap' => array(
124
+            'port' => 674
125
+        ),
126
+        'dict' => array(
127
+            'port' => 2628
128
+        ),
129
+        'file' => array(
130
+            'ihost' => 'localhost'
131
+        ),
132
+        'http' => array(
133
+            'port' => 80,
134
+        ),
135
+        'https' => array(
136
+            'port' => 443,
137
+        ),
138
+    );
139
+
140
+    /**
141
+     * Return the entire IRI when you try and read the object as a string
142
+     *
143
+     * @return string
144
+     */
145
+    public function __toString() {
146
+        return $this->get_iri();
147
+    }
148
+
149
+    /**
150
+     * Overload __set() to provide access via properties
151
+     *
152
+     * @param string $name Property name
153
+     * @param mixed $value Property value
154
+     */
155
+    public function __set($name, $value) {
156
+        if (method_exists($this, 'set_' . $name)) {
157
+            call_user_func(array($this, 'set_' . $name), $value);
158
+        }
159
+        elseif (
160
+                $name === 'iauthority'
161
+            || $name === 'iuserinfo'
162
+            || $name === 'ihost'
163
+            || $name === 'ipath'
164
+            || $name === 'iquery'
165
+            || $name === 'ifragment'
166
+        ) {
167
+            call_user_func(array($this, 'set_' . substr($name, 1)), $value);
168
+        }
169
+    }
170
+
171
+    /**
172
+     * Overload __get() to provide access via properties
173
+     *
174
+     * @param string $name Property name
175
+     * @return mixed
176
+     */
177
+    public function __get($name) {
178
+        // isset() returns false for null, we don't want to do that
179
+        // Also why we use array_key_exists below instead of isset()
180
+        $props = get_object_vars($this);
181
+
182
+        if (
183
+            $name === 'iri' ||
184
+            $name === 'uri' ||
185
+            $name === 'iauthority' ||
186
+            $name === 'authority'
187
+        ) {
188
+            $method = 'get_' . $name;
189
+            $return = $this->$method();
190
+        }
191
+        elseif (array_key_exists($name, $props)) {
192
+            $return = $this->$name;
193
+        }
194
+        // host -> ihost
195
+        elseif (($prop = 'i' . $name) && array_key_exists($prop, $props)) {
196
+            $name = $prop;
197
+            $return = $this->$prop;
198
+        }
199
+        // ischeme -> scheme
200
+        elseif (($prop = substr($name, 1)) && array_key_exists($prop, $props)) {
201
+            $name = $prop;
202
+            $return = $this->$prop;
203
+        }
204
+        else {
205
+            trigger_error('Undefined property: ' . get_class($this) . '::' . $name, E_USER_NOTICE);
206
+            $return = null;
207
+        }
208
+
209
+        if ($return === null && isset($this->normalization[$this->scheme][$name])) {
210
+            return $this->normalization[$this->scheme][$name];
211
+        }
212
+        else {
213
+            return $return;
214
+        }
215
+    }
216
+
217
+    /**
218
+     * Overload __isset() to provide access via properties
219
+     *
220
+     * @param string $name Property name
221
+     * @return bool
222
+     */
223
+    public function __isset($name) {
224
+        return (method_exists($this, 'get_' . $name) || isset($this->$name));
225
+    }
226
+
227
+    /**
228
+     * Overload __unset() to provide access via properties
229
+     *
230
+     * @param string $name Property name
231
+     */
232
+    public function __unset($name) {
233
+        if (method_exists($this, 'set_' . $name)) {
234
+            call_user_func(array($this, 'set_' . $name), '');
235
+        }
236
+    }
237
+
238
+    /**
239
+     * Create a new IRI object, from a specified string
240
+     *
241
+     * @param string|null $iri
242
+     */
243
+    public function __construct($iri = null) {
244
+        $this->set_iri($iri);
245
+    }
246
+
247
+    /**
248
+     * Create a new IRI object by resolving a relative IRI
249
+     *
250
+     * Returns false if $base is not absolute, otherwise an IRI.
251
+     *
252
+     * @param IRI|string $base (Absolute) Base IRI
253
+     * @param IRI|string $relative Relative IRI
254
+     * @return IRI|false
255
+     */
256
+    public static function absolutize($base, $relative) {
257
+        if (!($relative instanceof Requests_IRI)) {
258
+            $relative = new Requests_IRI($relative);
259
+        }
260
+        if (!$relative->is_valid()) {
261
+            return false;
262
+        }
263
+        elseif ($relative->scheme !== null) {
264
+            return clone $relative;
265
+        }
266
+
267
+        if (!($base instanceof Requests_IRI)) {
268
+            $base = new Requests_IRI($base);
269
+        }
270
+        if ($base->scheme === null || !$base->is_valid()) {
271
+            return false;
272
+        }
273
+
274
+        if ($relative->get_iri() !== '') {
275
+            if ($relative->iuserinfo !== null || $relative->ihost !== null || $relative->port !== null) {
276
+                $target = clone $relative;
277
+                $target->scheme = $base->scheme;
278
+            }
279
+            else {
280
+                $target = new Requests_IRI;
281
+                $target->scheme = $base->scheme;
282
+                $target->iuserinfo = $base->iuserinfo;
283
+                $target->ihost = $base->ihost;
284
+                $target->port = $base->port;
285
+                if ($relative->ipath !== '') {
286
+                    if ($relative->ipath[0] === '/') {
287
+                        $target->ipath = $relative->ipath;
288
+                    }
289
+                    elseif (($base->iuserinfo !== null || $base->ihost !== null || $base->port !== null) && $base->ipath === '') {
290
+                        $target->ipath = '/' . $relative->ipath;
291
+                    }
292
+                    elseif (($last_segment = strrpos($base->ipath, '/')) !== false) {
293
+                        $target->ipath = substr($base->ipath, 0, $last_segment + 1) . $relative->ipath;
294
+                    }
295
+                    else {
296
+                        $target->ipath = $relative->ipath;
297
+                    }
298
+                    $target->ipath = $target->remove_dot_segments($target->ipath);
299
+                    $target->iquery = $relative->iquery;
300
+                }
301
+                else {
302
+                    $target->ipath = $base->ipath;
303
+                    if ($relative->iquery !== null) {
304
+                        $target->iquery = $relative->iquery;
305
+                    }
306
+                    elseif ($base->iquery !== null) {
307
+                        $target->iquery = $base->iquery;
308
+                    }
309
+                }
310
+                $target->ifragment = $relative->ifragment;
311
+            }
312
+        }
313
+        else {
314
+            $target = clone $base;
315
+            $target->ifragment = null;
316
+        }
317
+        $target->scheme_normalization();
318
+        return $target;
319
+    }
320
+
321
+    /**
322
+     * Parse an IRI into scheme/authority/path/query/fragment segments
323
+     *
324
+     * @param string $iri
325
+     * @return array
326
+     */
327
+    protected function parse_iri($iri) {
328
+        $iri = trim($iri, "\x20\x09\x0A\x0C\x0D");
329
+        $has_match = preg_match('/^((?P<scheme>[^:\/?#]+):)?(\/\/(?P<authority>[^\/?#]*))?(?P<path>[^?#]*)(\?(?P<query>[^#]*))?(#(?P<fragment>.*))?$/', $iri, $match);
330
+        if (!$has_match) {
331
+            throw new Requests_Exception('Cannot parse supplied IRI', 'iri.cannot_parse', $iri);
332
+        }
333
+
334
+        if ($match[1] === '') {
335
+            $match['scheme'] = null;
336
+        }
337
+        if (!isset($match[3]) || $match[3] === '') {
338
+            $match['authority'] = null;
339
+        }
340
+        if (!isset($match[5])) {
341
+            $match['path'] = '';
342
+        }
343
+        if (!isset($match[6]) || $match[6] === '') {
344
+            $match['query'] = null;
345
+        }
346
+        if (!isset($match[8]) || $match[8] === '') {
347
+            $match['fragment'] = null;
348
+        }
349
+        return $match;
350
+    }
351
+
352
+    /**
353
+     * Remove dot segments from a path
354
+     *
355
+     * @param string $input
356
+     * @return string
357
+     */
358
+    protected function remove_dot_segments($input) {
359
+        $output = '';
360
+        while (strpos($input, './') !== false || strpos($input, '/.') !== false || $input === '.' || $input === '..') {
361
+            // A: If the input buffer begins with a prefix of "../" or "./",
362
+            // then remove that prefix from the input buffer; otherwise,
363
+            if (strpos($input, '../') === 0) {
364
+                $input = substr($input, 3);
365
+            }
366
+            elseif (strpos($input, './') === 0) {
367
+                $input = substr($input, 2);
368
+            }
369
+            // B: if the input buffer begins with a prefix of "/./" or "/.",
370
+            // where "." is a complete path segment, then replace that prefix
371
+            // with "/" in the input buffer; otherwise,
372
+            elseif (strpos($input, '/./') === 0) {
373
+                $input = substr($input, 2);
374
+            }
375
+            elseif ($input === '/.') {
376
+                $input = '/';
377
+            }
378
+            // C: if the input buffer begins with a prefix of "/../" or "/..",
379
+            // where ".." is a complete path segment, then replace that prefix
380
+            // with "/" in the input buffer and remove the last segment and its
381
+            // preceding "/" (if any) from the output buffer; otherwise,
382
+            elseif (strpos($input, '/../') === 0) {
383
+                $input = substr($input, 3);
384
+                $output = substr_replace($output, '', strrpos($output, '/'));
385
+            }
386
+            elseif ($input === '/..') {
387
+                $input = '/';
388
+                $output = substr_replace($output, '', strrpos($output, '/'));
389
+            }
390
+            // D: if the input buffer consists only of "." or "..", then remove
391
+            // that from the input buffer; otherwise,
392
+            elseif ($input === '.' || $input === '..') {
393
+                $input = '';
394
+            }
395
+            // E: move the first path segment in the input buffer to the end of
396
+            // the output buffer, including the initial "/" character (if any)
397
+            // and any subsequent characters up to, but not including, the next
398
+            // "/" character or the end of the input buffer
399
+            elseif (($pos = strpos($input, '/', 1)) !== false) {
400
+                $output .= substr($input, 0, $pos);
401
+                $input = substr_replace($input, '', 0, $pos);
402
+            }
403
+            else {
404
+                $output .= $input;
405
+                $input = '';
406
+            }
407
+        }
408
+        return $output . $input;
409
+    }
410
+
411
+    /**
412
+     * Replace invalid character with percent encoding
413
+     *
414
+     * @param string $string Input string
415
+     * @param string $extra_chars Valid characters not in iunreserved or
416
+     *                            iprivate (this is ASCII-only)
417
+     * @param bool $iprivate Allow iprivate
418
+     * @return string
419
+     */
420
+    protected function replace_invalid_with_pct_encoding($string, $extra_chars, $iprivate = false) {
421
+        // Normalize as many pct-encoded sections as possible
422
+        $string = preg_replace_callback('/(?:%[A-Fa-f0-9]{2})+/', array(&$this, 'remove_iunreserved_percent_encoded'), $string);
423
+
424
+        // Replace invalid percent characters
425
+        $string = preg_replace('/%(?![A-Fa-f0-9]{2})/', '%25', $string);
426
+
427
+        // Add unreserved and % to $extra_chars (the latter is safe because all
428
+        // pct-encoded sections are now valid).
429
+        $extra_chars .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~%';
430
+
431
+        // Now replace any bytes that aren't allowed with their pct-encoded versions
432
+        $position = 0;
433
+        $strlen = strlen($string);
434
+        while (($position += strspn($string, $extra_chars, $position)) < $strlen) {
435
+            $value = ord($string[$position]);
436
+
437
+            // Start position
438
+            $start = $position;
439
+
440
+            // By default we are valid
441
+            $valid = true;
442
+
443
+            // No one byte sequences are valid due to the while.
444
+            // Two byte sequence:
445
+            if (($value & 0xE0) === 0xC0) {
446
+                $character = ($value & 0x1F) << 6;
447
+                $length = 2;
448
+                $remaining = 1;
449
+            }
450
+            // Three byte sequence:
451
+            elseif (($value & 0xF0) === 0xE0) {
452
+                $character = ($value & 0x0F) << 12;
453
+                $length = 3;
454
+                $remaining = 2;
455
+            }
456
+            // Four byte sequence:
457
+            elseif (($value & 0xF8) === 0xF0) {
458
+                $character = ($value & 0x07) << 18;
459
+                $length = 4;
460
+                $remaining = 3;
461
+            }
462
+            // Invalid byte:
463
+            else {
464
+                $valid = false;
465
+                $length = 1;
466
+                $remaining = 0;
467
+            }
468
+
469
+            if ($remaining) {
470
+                if ($position + $length <= $strlen) {
471
+                    for ($position++; $remaining; $position++) {
472
+                        $value = ord($string[$position]);
473
+
474
+                        // Check that the byte is valid, then add it to the character:
475
+                        if (($value & 0xC0) === 0x80) {
476
+                            $character |= ($value & 0x3F) << (--$remaining * 6);
477
+                        }
478
+                        // If it is invalid, count the sequence as invalid and reprocess the current byte:
479
+                        else {
480
+                            $valid = false;
481
+                            $position--;
482
+                            break;
483
+                        }
484
+                    }
485
+                }
486
+                else {
487
+                    $position = $strlen - 1;
488
+                    $valid = false;
489
+                }
490
+            }
491
+
492
+            // Percent encode anything invalid or not in ucschar
493
+            if (
494
+                // Invalid sequences
495
+                !$valid
496
+                // Non-shortest form sequences are invalid
497
+                || $length > 1 && $character <= 0x7F
498
+                || $length > 2 && $character <= 0x7FF
499
+                || $length > 3 && $character <= 0xFFFF
500
+                // Outside of range of ucschar codepoints
501
+                // Noncharacters
502
+                || ($character & 0xFFFE) === 0xFFFE
503
+                || $character >= 0xFDD0 && $character <= 0xFDEF
504
+                || (
505
+                    // Everything else not in ucschar
506
+                        $character > 0xD7FF && $character < 0xF900
507
+                    || $character < 0xA0
508
+                    || $character > 0xEFFFD
509
+                )
510
+                && (
511
+                    // Everything not in iprivate, if it applies
512
+                        !$iprivate
513
+                    || $character < 0xE000
514
+                    || $character > 0x10FFFD
515
+                )
516
+            ) {
517
+                // If we were a character, pretend we weren't, but rather an error.
518
+                if ($valid) {
519
+                    $position--;
520
+                }
521
+
522
+                for ($j = $start; $j <= $position; $j++) {
523
+                    $string = substr_replace($string, sprintf('%%%02X', ord($string[$j])), $j, 1);
524
+                    $j += 2;
525
+                    $position += 2;
526
+                    $strlen += 2;
527
+                }
528
+            }
529
+        }
530
+
531
+        return $string;
532
+    }
533
+
534
+    /**
535
+     * Callback function for preg_replace_callback.
536
+     *
537
+     * Removes sequences of percent encoded bytes that represent UTF-8
538
+     * encoded characters in iunreserved
539
+     *
540
+     * @param array $match PCRE match
541
+     * @return string Replacement
542
+     */
543
+    protected function remove_iunreserved_percent_encoded($match) {
544
+        // As we just have valid percent encoded sequences we can just explode
545
+        // and ignore the first member of the returned array (an empty string).
546
+        $bytes = explode('%', $match[0]);
547
+
548
+        // Initialize the new string (this is what will be returned) and that
549
+        // there are no bytes remaining in the current sequence (unsurprising
550
+        // at the first byte!).
551
+        $string = '';
552
+        $remaining = 0;
553
+
554
+        // Loop over each and every byte, and set $value to its value
555
+        for ($i = 1, $len = count($bytes); $i < $len; $i++) {
556
+            $value = hexdec($bytes[$i]);
557
+
558
+            // If we're the first byte of sequence:
559
+            if (!$remaining) {
560
+                // Start position
561
+                $start = $i;
562
+
563
+                // By default we are valid
564
+                $valid = true;
565
+
566
+                // One byte sequence:
567
+                if ($value <= 0x7F) {
568
+                    $character = $value;
569
+                    $length = 1;
570
+                }
571
+                // Two byte sequence:
572
+                elseif (($value & 0xE0) === 0xC0) {
573
+                    $character = ($value & 0x1F) << 6;
574
+                    $length = 2;
575
+                    $remaining = 1;
576
+                }
577
+                // Three byte sequence:
578
+                elseif (($value & 0xF0) === 0xE0) {
579
+                    $character = ($value & 0x0F) << 12;
580
+                    $length = 3;
581
+                    $remaining = 2;
582
+                }
583
+                // Four byte sequence:
584
+                elseif (($value & 0xF8) === 0xF0) {
585
+                    $character = ($value & 0x07) << 18;
586
+                    $length = 4;
587
+                    $remaining = 3;
588
+                }
589
+                // Invalid byte:
590
+                else {
591
+                    $valid = false;
592
+                    $remaining = 0;
593
+                }
594
+            }
595
+            // Continuation byte:
596
+            else {
597
+                // Check that the byte is valid, then add it to the character:
598
+                if (($value & 0xC0) === 0x80) {
599
+                    $remaining--;
600
+                    $character |= ($value & 0x3F) << ($remaining * 6);
601
+                }
602
+                // If it is invalid, count the sequence as invalid and reprocess the current byte as the start of a sequence:
603
+                else {
604
+                    $valid = false;
605
+                    $remaining = 0;
606
+                    $i--;
607
+                }
608
+            }
609
+
610
+            // If we've reached the end of the current byte sequence, append it to Unicode::$data
611
+            if (!$remaining) {
612
+                // Percent encode anything invalid or not in iunreserved
613
+                if (
614
+                    // Invalid sequences
615
+                    !$valid
616
+                    // Non-shortest form sequences are invalid
617
+                    || $length > 1 && $character <= 0x7F
618
+                    || $length > 2 && $character <= 0x7FF
619
+                    || $length > 3 && $character <= 0xFFFF
620
+                    // Outside of range of iunreserved codepoints
621
+                    || $character < 0x2D
622
+                    || $character > 0xEFFFD
623
+                    // Noncharacters
624
+                    || ($character & 0xFFFE) === 0xFFFE
625
+                    || $character >= 0xFDD0 && $character <= 0xFDEF
626
+                    // Everything else not in iunreserved (this is all BMP)
627
+                    || $character === 0x2F
628
+                    || $character > 0x39 && $character < 0x41
629
+                    || $character > 0x5A && $character < 0x61
630
+                    || $character > 0x7A && $character < 0x7E
631
+                    || $character > 0x7E && $character < 0xA0
632
+                    || $character > 0xD7FF && $character < 0xF900
633
+                ) {
634
+                    for ($j = $start; $j <= $i; $j++) {
635
+                        $string .= '%' . strtoupper($bytes[$j]);
636
+                    }
637
+                }
638
+                else {
639
+                    for ($j = $start; $j <= $i; $j++) {
640
+                        $string .= chr(hexdec($bytes[$j]));
641
+                    }
642
+                }
643
+            }
644
+        }
645
+
646
+        // If we have any bytes left over they are invalid (i.e., we are
647
+        // mid-way through a multi-byte sequence)
648
+        if ($remaining) {
649
+            for ($j = $start; $j < $len; $j++) {
650
+                $string .= '%' . strtoupper($bytes[$j]);
651
+            }
652
+        }
653
+
654
+        return $string;
655
+    }
656
+
657
+    protected function scheme_normalization() {
658
+        if (isset($this->normalization[$this->scheme]['iuserinfo']) && $this->iuserinfo === $this->normalization[$this->scheme]['iuserinfo']) {
659
+            $this->iuserinfo = null;
660
+        }
661
+        if (isset($this->normalization[$this->scheme]['ihost']) && $this->ihost === $this->normalization[$this->scheme]['ihost']) {
662
+            $this->ihost = null;
663
+        }
664
+        if (isset($this->normalization[$this->scheme]['port']) && $this->port === $this->normalization[$this->scheme]['port']) {
665
+            $this->port = null;
666
+        }
667
+        if (isset($this->normalization[$this->scheme]['ipath']) && $this->ipath === $this->normalization[$this->scheme]['ipath']) {
668
+            $this->ipath = '';
669
+        }
670
+        if (isset($this->ihost) && empty($this->ipath)) {
671
+            $this->ipath = '/';
672
+        }
673
+        if (isset($this->normalization[$this->scheme]['iquery']) && $this->iquery === $this->normalization[$this->scheme]['iquery']) {
674
+            $this->iquery = null;
675
+        }
676
+        if (isset($this->normalization[$this->scheme]['ifragment']) && $this->ifragment === $this->normalization[$this->scheme]['ifragment']) {
677
+            $this->ifragment = null;
678
+        }
679
+    }
680
+
681
+    /**
682
+     * Check if the object represents a valid IRI. This needs to be done on each
683
+     * call as some things change depending on another part of the IRI.
684
+     *
685
+     * @return bool
686
+     */
687
+    public function is_valid() {
688
+        $isauthority = $this->iuserinfo !== null || $this->ihost !== null || $this->port !== null;
689
+        if ($this->ipath !== '' &&
690
+            (
691
+                $isauthority && $this->ipath[0] !== '/' ||
692
+                (
693
+                    $this->scheme === null &&
694
+                    !$isauthority &&
695
+                    strpos($this->ipath, ':') !== false &&
696
+                    (strpos($this->ipath, '/') === false ? true : strpos($this->ipath, ':') < strpos($this->ipath, '/'))
697
+                )
698
+            )
699
+        ) {
700
+            return false;
701
+        }
702
+
703
+        return true;
704
+    }
705
+
706
+    /**
707
+     * Set the entire IRI. Returns true on success, false on failure (if there
708
+     * are any invalid characters).
709
+     *
710
+     * @param string $iri
711
+     * @return bool
712
+     */
713
+    protected function set_iri($iri) {
714
+        static $cache;
715
+        if (!$cache) {
716
+            $cache = array();
717
+        }
718
+
719
+        if ($iri === null) {
720
+            return true;
721
+        }
722
+        if (isset($cache[$iri])) {
723
+            list($this->scheme,
724
+                    $this->iuserinfo,
725
+                    $this->ihost,
726
+                    $this->port,
727
+                    $this->ipath,
728
+                    $this->iquery,
729
+                    $this->ifragment,
730
+                    $return) = $cache[$iri];
731
+            return $return;
732
+        }
733
+
734
+        $parsed = $this->parse_iri((string) $iri);
735
+
736
+        $return = $this->set_scheme($parsed['scheme'])
737
+            && $this->set_authority($parsed['authority'])
738
+            && $this->set_path($parsed['path'])
739
+            && $this->set_query($parsed['query'])
740
+            && $this->set_fragment($parsed['fragment']);
741
+
742
+        $cache[$iri] = array($this->scheme,
743
+                                $this->iuserinfo,
744
+                                $this->ihost,
745
+                                $this->port,
746
+                                $this->ipath,
747
+                                $this->iquery,
748
+                                $this->ifragment,
749
+                                $return);
750
+        return $return;
751
+    }
752
+
753
+    /**
754
+     * Set the scheme. Returns true on success, false on failure (if there are
755
+     * any invalid characters).
756
+     *
757
+     * @param string $scheme
758
+     * @return bool
759
+     */
760
+    protected function set_scheme($scheme) {
761
+        if ($scheme === null) {
762
+            $this->scheme = null;
763
+        }
764
+        elseif (!preg_match('/^[A-Za-z][0-9A-Za-z+\-.]*$/', $scheme)) {
765
+            $this->scheme = null;
766
+            return false;
767
+        }
768
+        else {
769
+            $this->scheme = strtolower($scheme);
770
+        }
771
+        return true;
772
+    }
773
+
774
+    /**
775
+     * Set the authority. Returns true on success, false on failure (if there are
776
+     * any invalid characters).
777
+     *
778
+     * @param string $authority
779
+     * @return bool
780
+     */
781
+    protected function set_authority($authority) {
782
+        static $cache;
783
+        if (!$cache) {
784
+            $cache = array();
785
+        }
786
+
787
+        if ($authority === null) {
788
+            $this->iuserinfo = null;
789
+            $this->ihost = null;
790
+            $this->port = null;
791
+            return true;
792
+        }
793
+        if (isset($cache[$authority])) {
794
+            list($this->iuserinfo,
795
+                    $this->ihost,
796
+                    $this->port,
797
+                    $return) = $cache[$authority];
798
+
799
+            return $return;
800
+        }
801
+
802
+        $remaining = $authority;
803
+        if (($iuserinfo_end = strrpos($remaining, '@')) !== false) {
804
+            $iuserinfo = substr($remaining, 0, $iuserinfo_end);
805
+            $remaining = substr($remaining, $iuserinfo_end + 1);
806
+        }
807
+        else {
808
+            $iuserinfo = null;
809
+        }
810
+        if (($port_start = strpos($remaining, ':', strpos($remaining, ']'))) !== false) {
811
+            $port = substr($remaining, $port_start + 1);
812
+            if ($port === false || $port === '') {
813
+                $port = null;
814
+            }
815
+            $remaining = substr($remaining, 0, $port_start);
816
+        }
817
+        else {
818
+            $port = null;
819
+        }
820
+
821
+        $return = $this->set_userinfo($iuserinfo) &&
822
+                  $this->set_host($remaining) &&
823
+                  $this->set_port($port);
824
+
825
+        $cache[$authority] = array($this->iuserinfo,
826
+                                    $this->ihost,
827
+                                    $this->port,
828
+                                    $return);
829
+
830
+        return $return;
831
+    }
832
+
833
+    /**
834
+     * Set the iuserinfo.
835
+     *
836
+     * @param string $iuserinfo
837
+     * @return bool
838
+     */
839
+    protected function set_userinfo($iuserinfo) {
840
+        if ($iuserinfo === null) {
841
+            $this->iuserinfo = null;
842
+        }
843
+        else {
844
+            $this->iuserinfo = $this->replace_invalid_with_pct_encoding($iuserinfo, '!$&\'()*+,;=:');
845
+            $this->scheme_normalization();
846
+        }
847
+
848
+        return true;
849
+    }
850
+
851
+    /**
852
+     * Set the ihost. Returns true on success, false on failure (if there are
853
+     * any invalid characters).
854
+     *
855
+     * @param string $ihost
856
+     * @return bool
857
+     */
858
+    protected function set_host($ihost) {
859
+        if ($ihost === null) {
860
+            $this->ihost = null;
861
+            return true;
862
+        }
863
+        if (substr($ihost, 0, 1) === '[' && substr($ihost, -1) === ']') {
864
+            if (Requests_IPv6::check_ipv6(substr($ihost, 1, -1))) {
865
+                $this->ihost = '[' . Requests_IPv6::compress(substr($ihost, 1, -1)) . ']';
866
+            }
867
+            else {
868
+                $this->ihost = null;
869
+                return false;
870
+            }
871
+        }
872
+        else {
873
+            $ihost = $this->replace_invalid_with_pct_encoding($ihost, '!$&\'()*+,;=');
874
+
875
+            // Lowercase, but ignore pct-encoded sections (as they should
876
+            // remain uppercase). This must be done after the previous step
877
+            // as that can add unescaped characters.
878
+            $position = 0;
879
+            $strlen = strlen($ihost);
880
+            while (($position += strcspn($ihost, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ%', $position)) < $strlen) {
881
+                if ($ihost[$position] === '%') {
882
+                    $position += 3;
883
+                }
884
+                else {
885
+                    $ihost[$position] = strtolower($ihost[$position]);
886
+                    $position++;
887
+                }
888
+            }
889
+
890
+            $this->ihost = $ihost;
891
+        }
892
+
893
+        $this->scheme_normalization();
894
+
895
+        return true;
896
+    }
897
+
898
+    /**
899
+     * Set the port. Returns true on success, false on failure (if there are
900
+     * any invalid characters).
901
+     *
902
+     * @param string $port
903
+     * @return bool
904
+     */
905
+    protected function set_port($port) {
906
+        if ($port === null) {
907
+            $this->port = null;
908
+            return true;
909
+        }
910
+
911
+        if (strspn($port, '0123456789') === strlen($port)) {
912
+            $this->port = (int) $port;
913
+            $this->scheme_normalization();
914
+            return true;
915
+        }
916
+
917
+        $this->port = null;
918
+        return false;
919
+    }
920
+
921
+    /**
922
+     * Set the ipath.
923
+     *
924
+     * @param string $ipath
925
+     * @return bool
926
+     */
927
+    protected function set_path($ipath) {
928
+        static $cache;
929
+        if (!$cache) {
930
+            $cache = array();
931
+        }
932
+
933
+        $ipath = (string) $ipath;
934
+
935
+        if (isset($cache[$ipath])) {
936
+            $this->ipath = $cache[$ipath][(int) ($this->scheme !== null)];
937
+        }
938
+        else {
939
+            $valid = $this->replace_invalid_with_pct_encoding($ipath, '!$&\'()*+,;=@:/');
940
+            $removed = $this->remove_dot_segments($valid);
941
+
942
+            $cache[$ipath] = array($valid, $removed);
943
+            $this->ipath = ($this->scheme !== null) ? $removed : $valid;
944
+        }
945
+        $this->scheme_normalization();
946
+        return true;
947
+    }
948
+
949
+    /**
950
+     * Set the iquery.
951
+     *
952
+     * @param string $iquery
953
+     * @return bool
954
+     */
955
+    protected function set_query($iquery) {
956
+        if ($iquery === null) {
957
+            $this->iquery = null;
958
+        }
959
+        else {
960
+            $this->iquery = $this->replace_invalid_with_pct_encoding($iquery, '!$&\'()*+,;=:@/?', true);
961
+            $this->scheme_normalization();
962
+        }
963
+        return true;
964
+    }
965
+
966
+    /**
967
+     * Set the ifragment.
968
+     *
969
+     * @param string $ifragment
970
+     * @return bool
971
+     */
972
+    protected function set_fragment($ifragment) {
973
+        if ($ifragment === null) {
974
+            $this->ifragment = null;
975
+        }
976
+        else {
977
+            $this->ifragment = $this->replace_invalid_with_pct_encoding($ifragment, '!$&\'()*+,;=:@/?');
978
+            $this->scheme_normalization();
979
+        }
980
+        return true;
981
+    }
982
+
983
+    /**
984
+     * Convert an IRI to a URI (or parts thereof)
985
+     *
986
+     * @param string|bool IRI to convert (or false from {@see get_iri})
987
+     * @return string|false URI if IRI is valid, false otherwise.
988
+     */
989
+    protected function to_uri($string) {
990
+        if (!is_string($string)) {
991
+            return false;
992
+        }
993
+
994
+        static $non_ascii;
995
+        if (!$non_ascii) {
996
+            $non_ascii = implode('', range("\x80", "\xFF"));
997
+        }
998
+
999
+        $position = 0;
1000
+        $strlen = strlen($string);
1001
+        while (($position += strcspn($string, $non_ascii, $position)) < $strlen) {
1002
+            $string = substr_replace($string, sprintf('%%%02X', ord($string[$position])), $position, 1);
1003
+            $position += 3;
1004
+            $strlen += 2;
1005
+        }
1006
+
1007
+        return $string;
1008
+    }
1009
+
1010
+    /**
1011
+     * Get the complete IRI
1012
+     *
1013
+     * @return string
1014
+     */
1015
+    protected function get_iri() {
1016
+        if (!$this->is_valid()) {
1017
+            return false;
1018
+        }
1019
+
1020
+        $iri = '';
1021
+        if ($this->scheme !== null) {
1022
+            $iri .= $this->scheme . ':';
1023
+        }
1024
+        if (($iauthority = $this->get_iauthority()) !== null) {
1025
+            $iri .= '//' . $iauthority;
1026
+        }
1027
+        $iri .= $this->ipath;
1028
+        if ($this->iquery !== null) {
1029
+            $iri .= '?' . $this->iquery;
1030
+        }
1031
+        if ($this->ifragment !== null) {
1032
+            $iri .= '#' . $this->ifragment;
1033
+        }
1034
+
1035
+        return $iri;
1036
+    }
1037
+
1038
+    /**
1039
+     * Get the complete URI
1040
+     *
1041
+     * @return string
1042
+     */
1043
+    protected function get_uri() {
1044
+        return $this->to_uri($this->get_iri());
1045
+    }
1046
+
1047
+    /**
1048
+     * Get the complete iauthority
1049
+     *
1050
+     * @return string
1051
+     */
1052
+    protected function get_iauthority() {
1053
+        if ($this->iuserinfo === null && $this->ihost === null && $this->port === null) {
1054
+            return null;
1055
+        }
1056
+
1057
+        $iauthority = '';
1058
+        if ($this->iuserinfo !== null) {
1059
+            $iauthority .= $this->iuserinfo . '@';
1060
+        }
1061
+        if ($this->ihost !== null) {
1062
+            $iauthority .= $this->ihost;
1063
+        }
1064
+        if ($this->port !== null) {
1065
+            $iauthority .= ':' . $this->port;
1066
+        }
1067
+        return $iauthority;
1068
+    }
1069
+
1070
+    /**
1071
+     * Get the complete authority
1072
+     *
1073
+     * @return string
1074
+     */
1075
+    protected function get_authority() {
1076
+        $iauthority = $this->get_iauthority();
1077
+        if (is_string($iauthority)) {
1078
+            return $this->to_uri($iauthority);
1079
+        }
1080
+        else {
1081
+            return $iauthority;
1082
+        }
1083
+    }
1084 1084
 }
Please login to merge, or discard this patch.
plugin/buycourses/src/Requests/IPv6.php 1 patch
Indentation   +159 added lines, -159 removed lines patch added patch discarded remove patch
@@ -16,175 +16,175 @@
 block discarded – undo
16 16
  * @subpackage Utilities
17 17
  */
18 18
 class Requests_IPv6 {
19
-	/**
20
-	 * Uncompresses an IPv6 address
21
-	 *
22
-	 * RFC 4291 allows you to compress consecutive zero pieces in an address to
23
-	 * '::'. This method expects a valid IPv6 address and expands the '::' to
24
-	 * the required number of zero pieces.
25
-	 *
26
-	 * Example:  FF01::101   ->  FF01:0:0:0:0:0:0:101
27
-	 *           ::1         ->  0:0:0:0:0:0:0:1
28
-	 *
29
-	 * @author Alexander Merz <[email protected]>
30
-	 * @author elfrink at introweb dot nl
31
-	 * @author Josh Peck <jmp at joshpeck dot org>
32
-	 * @copyright 2003-2005 The PHP Group
33
-	 * @license http://www.opensource.org/licenses/bsd-license.php
34
-	 * @param string $ip An IPv6 address
35
-	 * @return string The uncompressed IPv6 address
36
-	 */
37
-	public static function uncompress($ip) {
38
-		if (substr_count($ip, '::') !== 1) {
39
-			return $ip;
40
-		}
19
+    /**
20
+     * Uncompresses an IPv6 address
21
+     *
22
+     * RFC 4291 allows you to compress consecutive zero pieces in an address to
23
+     * '::'. This method expects a valid IPv6 address and expands the '::' to
24
+     * the required number of zero pieces.
25
+     *
26
+     * Example:  FF01::101   ->  FF01:0:0:0:0:0:0:101
27
+     *           ::1         ->  0:0:0:0:0:0:0:1
28
+     *
29
+     * @author Alexander Merz <[email protected]>
30
+     * @author elfrink at introweb dot nl
31
+     * @author Josh Peck <jmp at joshpeck dot org>
32
+     * @copyright 2003-2005 The PHP Group
33
+     * @license http://www.opensource.org/licenses/bsd-license.php
34
+     * @param string $ip An IPv6 address
35
+     * @return string The uncompressed IPv6 address
36
+     */
37
+    public static function uncompress($ip) {
38
+        if (substr_count($ip, '::') !== 1) {
39
+            return $ip;
40
+        }
41 41
 
42
-		list($ip1, $ip2) = explode('::', $ip);
43
-		$c1 = ($ip1 === '') ? -1 : substr_count($ip1, ':');
44
-		$c2 = ($ip2 === '') ? -1 : substr_count($ip2, ':');
42
+        list($ip1, $ip2) = explode('::', $ip);
43
+        $c1 = ($ip1 === '') ? -1 : substr_count($ip1, ':');
44
+        $c2 = ($ip2 === '') ? -1 : substr_count($ip2, ':');
45 45
 
46
-		if (strpos($ip2, '.') !== false) {
47
-			$c2++;
48
-		}
49
-		// ::
50
-		if ($c1 === -1 && $c2 === -1) {
51
-			$ip = '0:0:0:0:0:0:0:0';
52
-		}
53
-		// ::xxx
54
-		else if ($c1 === -1) {
55
-			$fill = str_repeat('0:', 7 - $c2);
56
-			$ip = str_replace('::', $fill, $ip);
57
-		}
58
-		// xxx::
59
-		else if ($c2 === -1) {
60
-			$fill = str_repeat(':0', 7 - $c1);
61
-			$ip = str_replace('::', $fill, $ip);
62
-		}
63
-		// xxx::xxx
64
-		else {
65
-			$fill = ':' . str_repeat('0:', 6 - $c2 - $c1);
66
-			$ip = str_replace('::', $fill, $ip);
67
-		}
68
-		return $ip;
69
-	}
46
+        if (strpos($ip2, '.') !== false) {
47
+            $c2++;
48
+        }
49
+        // ::
50
+        if ($c1 === -1 && $c2 === -1) {
51
+            $ip = '0:0:0:0:0:0:0:0';
52
+        }
53
+        // ::xxx
54
+        else if ($c1 === -1) {
55
+            $fill = str_repeat('0:', 7 - $c2);
56
+            $ip = str_replace('::', $fill, $ip);
57
+        }
58
+        // xxx::
59
+        else if ($c2 === -1) {
60
+            $fill = str_repeat(':0', 7 - $c1);
61
+            $ip = str_replace('::', $fill, $ip);
62
+        }
63
+        // xxx::xxx
64
+        else {
65
+            $fill = ':' . str_repeat('0:', 6 - $c2 - $c1);
66
+            $ip = str_replace('::', $fill, $ip);
67
+        }
68
+        return $ip;
69
+    }
70 70
 
71
-	/**
72
-	 * Compresses an IPv6 address
73
-	 *
74
-	 * RFC 4291 allows you to compress consecutive zero pieces in an address to
75
-	 * '::'. This method expects a valid IPv6 address and compresses consecutive
76
-	 * zero pieces to '::'.
77
-	 *
78
-	 * Example:  FF01:0:0:0:0:0:0:101   ->  FF01::101
79
-	 *           0:0:0:0:0:0:0:1        ->  ::1
80
-	 *
81
-	 * @see uncompress()
82
-	 * @param string $ip An IPv6 address
83
-	 * @return string The compressed IPv6 address
84
-	 */
85
-	public static function compress($ip) {
86
-		// Prepare the IP to be compressed
87
-		$ip = self::uncompress($ip);
88
-		$ip_parts = self::split_v6_v4($ip);
71
+    /**
72
+     * Compresses an IPv6 address
73
+     *
74
+     * RFC 4291 allows you to compress consecutive zero pieces in an address to
75
+     * '::'. This method expects a valid IPv6 address and compresses consecutive
76
+     * zero pieces to '::'.
77
+     *
78
+     * Example:  FF01:0:0:0:0:0:0:101   ->  FF01::101
79
+     *           0:0:0:0:0:0:0:1        ->  ::1
80
+     *
81
+     * @see uncompress()
82
+     * @param string $ip An IPv6 address
83
+     * @return string The compressed IPv6 address
84
+     */
85
+    public static function compress($ip) {
86
+        // Prepare the IP to be compressed
87
+        $ip = self::uncompress($ip);
88
+        $ip_parts = self::split_v6_v4($ip);
89 89
 
90
-		// Replace all leading zeros
91
-		$ip_parts[0] = preg_replace('/(^|:)0+([0-9])/', '\1\2', $ip_parts[0]);
90
+        // Replace all leading zeros
91
+        $ip_parts[0] = preg_replace('/(^|:)0+([0-9])/', '\1\2', $ip_parts[0]);
92 92
 
93
-		// Find bunches of zeros
94
-		if (preg_match_all('/(?:^|:)(?:0(?::|$))+/', $ip_parts[0], $matches, PREG_OFFSET_CAPTURE)) {
95
-			$max = 0;
96
-			$pos = null;
97
-			foreach ($matches[0] as $match) {
98
-				if (strlen($match[0]) > $max) {
99
-					$max = strlen($match[0]);
100
-					$pos = $match[1];
101
-				}
102
-			}
93
+        // Find bunches of zeros
94
+        if (preg_match_all('/(?:^|:)(?:0(?::|$))+/', $ip_parts[0], $matches, PREG_OFFSET_CAPTURE)) {
95
+            $max = 0;
96
+            $pos = null;
97
+            foreach ($matches[0] as $match) {
98
+                if (strlen($match[0]) > $max) {
99
+                    $max = strlen($match[0]);
100
+                    $pos = $match[1];
101
+                }
102
+            }
103 103
 
104
-			$ip_parts[0] = substr_replace($ip_parts[0], '::', $pos, $max);
105
-		}
104
+            $ip_parts[0] = substr_replace($ip_parts[0], '::', $pos, $max);
105
+        }
106 106
 
107
-		if ($ip_parts[1] !== '') {
108
-			return implode(':', $ip_parts);
109
-		}
110
-		else {
111
-			return $ip_parts[0];
112
-		}
113
-	}
107
+        if ($ip_parts[1] !== '') {
108
+            return implode(':', $ip_parts);
109
+        }
110
+        else {
111
+            return $ip_parts[0];
112
+        }
113
+    }
114 114
 
115
-	/**
116
-	 * Splits an IPv6 address into the IPv6 and IPv4 representation parts
117
-	 *
118
-	 * RFC 4291 allows you to represent the last two parts of an IPv6 address
119
-	 * using the standard IPv4 representation
120
-	 *
121
-	 * Example:  0:0:0:0:0:0:13.1.68.3
122
-	 *           0:0:0:0:0:FFFF:129.144.52.38
123
-	 *
124
-	 * @param string $ip An IPv6 address
125
-	 * @return string[] [0] contains the IPv6 represented part, and [1] the IPv4 represented part
126
-	 */
127
-	protected static function split_v6_v4($ip) {
128
-		if (strpos($ip, '.') !== false) {
129
-			$pos = strrpos($ip, ':');
130
-			$ipv6_part = substr($ip, 0, $pos);
131
-			$ipv4_part = substr($ip, $pos + 1);
132
-			return array($ipv6_part, $ipv4_part);
133
-		}
134
-		else {
135
-			return array($ip, '');
136
-		}
137
-	}
115
+    /**
116
+     * Splits an IPv6 address into the IPv6 and IPv4 representation parts
117
+     *
118
+     * RFC 4291 allows you to represent the last two parts of an IPv6 address
119
+     * using the standard IPv4 representation
120
+     *
121
+     * Example:  0:0:0:0:0:0:13.1.68.3
122
+     *           0:0:0:0:0:FFFF:129.144.52.38
123
+     *
124
+     * @param string $ip An IPv6 address
125
+     * @return string[] [0] contains the IPv6 represented part, and [1] the IPv4 represented part
126
+     */
127
+    protected static function split_v6_v4($ip) {
128
+        if (strpos($ip, '.') !== false) {
129
+            $pos = strrpos($ip, ':');
130
+            $ipv6_part = substr($ip, 0, $pos);
131
+            $ipv4_part = substr($ip, $pos + 1);
132
+            return array($ipv6_part, $ipv4_part);
133
+        }
134
+        else {
135
+            return array($ip, '');
136
+        }
137
+    }
138 138
 
139
-	/**
140
-	 * Checks an IPv6 address
141
-	 *
142
-	 * Checks if the given IP is a valid IPv6 address
143
-	 *
144
-	 * @param string $ip An IPv6 address
145
-	 * @return bool true if $ip is a valid IPv6 address
146
-	 */
147
-	public static function check_ipv6($ip) {
148
-		$ip = self::uncompress($ip);
149
-		list($ipv6, $ipv4) = self::split_v6_v4($ip);
150
-		$ipv6 = explode(':', $ipv6);
151
-		$ipv4 = explode('.', $ipv4);
152
-		if (count($ipv6) === 8 && count($ipv4) === 1 || count($ipv6) === 6 && count($ipv4) === 4) {
153
-			foreach ($ipv6 as $ipv6_part) {
154
-				// The section can't be empty
155
-				if ($ipv6_part === '') {
156
-					return false;
157
-				}
139
+    /**
140
+     * Checks an IPv6 address
141
+     *
142
+     * Checks if the given IP is a valid IPv6 address
143
+     *
144
+     * @param string $ip An IPv6 address
145
+     * @return bool true if $ip is a valid IPv6 address
146
+     */
147
+    public static function check_ipv6($ip) {
148
+        $ip = self::uncompress($ip);
149
+        list($ipv6, $ipv4) = self::split_v6_v4($ip);
150
+        $ipv6 = explode(':', $ipv6);
151
+        $ipv4 = explode('.', $ipv4);
152
+        if (count($ipv6) === 8 && count($ipv4) === 1 || count($ipv6) === 6 && count($ipv4) === 4) {
153
+            foreach ($ipv6 as $ipv6_part) {
154
+                // The section can't be empty
155
+                if ($ipv6_part === '') {
156
+                    return false;
157
+                }
158 158
 
159
-				// Nor can it be over four characters
160
-				if (strlen($ipv6_part) > 4) {
161
-					return false;
162
-				}
159
+                // Nor can it be over four characters
160
+                if (strlen($ipv6_part) > 4) {
161
+                    return false;
162
+                }
163 163
 
164
-				// Remove leading zeros (this is safe because of the above)
165
-				$ipv6_part = ltrim($ipv6_part, '0');
166
-				if ($ipv6_part === '') {
167
-					$ipv6_part = '0';
168
-				}
164
+                // Remove leading zeros (this is safe because of the above)
165
+                $ipv6_part = ltrim($ipv6_part, '0');
166
+                if ($ipv6_part === '') {
167
+                    $ipv6_part = '0';
168
+                }
169 169
 
170
-				// Check the value is valid
171
-				$value = hexdec($ipv6_part);
172
-				if (dechex($value) !== strtolower($ipv6_part) || $value < 0 || $value > 0xFFFF) {
173
-					return false;
174
-				}
175
-			}
176
-			if (count($ipv4) === 4) {
177
-				foreach ($ipv4 as $ipv4_part) {
178
-					$value = (int) $ipv4_part;
179
-					if ((string) $value !== $ipv4_part || $value < 0 || $value > 0xFF) {
180
-						return false;
181
-					}
182
-				}
183
-			}
184
-			return true;
185
-		}
186
-		else {
187
-			return false;
188
-		}
189
-	}
170
+                // Check the value is valid
171
+                $value = hexdec($ipv6_part);
172
+                if (dechex($value) !== strtolower($ipv6_part) || $value < 0 || $value > 0xFFFF) {
173
+                    return false;
174
+                }
175
+            }
176
+            if (count($ipv4) === 4) {
177
+                foreach ($ipv4 as $ipv4_part) {
178
+                    $value = (int) $ipv4_part;
179
+                    if ((string) $value !== $ipv4_part || $value < 0 || $value > 0xFF) {
180
+                        return false;
181
+                    }
182
+                }
183
+            }
184
+            return true;
185
+        }
186
+        else {
187
+            return false;
188
+        }
189
+    }
190 190
 }
Please login to merge, or discard this patch.
plugin/buycourses/src/Requests/Proxy.php 1 patch
Indentation   +11 added lines, -11 removed lines patch added patch discarded remove patch
@@ -21,15 +21,15 @@
 block discarded – undo
21 21
  * @since 1.6
22 22
  */
23 23
 interface Requests_Proxy {
24
-	/**
25
-	 * Register hooks as needed
26
-	 *
27
-	 * This method is called in {@see Requests::request} when the user has set
28
-	 * an instance as the 'auth' option. Use this callback to register all the
29
-	 * hooks you'll need.
30
-	 *
31
-	 * @see Requests_Hooks::register
32
-	 * @param Requests_Hooks $hooks Hook system
33
-	 */
34
-	public function register(Requests_Hooks &$hooks);
24
+    /**
25
+     * Register hooks as needed
26
+     *
27
+     * This method is called in {@see Requests::request} when the user has set
28
+     * an instance as the 'auth' option. Use this callback to register all the
29
+     * hooks you'll need.
30
+     *
31
+     * @see Requests_Hooks::register
32
+     * @param Requests_Hooks $hooks Hook system
33
+     */
34
+    public function register(Requests_Hooks &$hooks);
35 35
 }
36 36
\ No newline at end of file
Please login to merge, or discard this patch.
plugin/buycourses/src/Requests/Exception.php 1 patch
Indentation   +43 added lines, -43 removed lines patch added patch discarded remove patch
@@ -11,52 +11,52 @@
 block discarded – undo
11 11
  * @package Requests
12 12
  */
13 13
 class Requests_Exception extends Exception {
14
-	/**
15
-	 * Type of exception
16
-	 *
17
-	 * @var string
18
-	 */
19
-	protected $type;
14
+    /**
15
+     * Type of exception
16
+     *
17
+     * @var string
18
+     */
19
+    protected $type;
20 20
 
21
-	/**
22
-	 * Data associated with the exception
23
-	 *
24
-	 * @var mixed
25
-	 */
26
-	protected $data;
21
+    /**
22
+     * Data associated with the exception
23
+     *
24
+     * @var mixed
25
+     */
26
+    protected $data;
27 27
 
28
-	/**
29
-	 * Create a new exception
30
-	 *
31
-	 * @param string $message Exception message
32
-	 * @param string $type Exception type
33
-	 * @param mixed $data Associated data
34
-	 * @param integer $code Exception numerical code, if applicable
35
-	 */
36
-	public function __construct($message, $type, $data = null, $code = 0) {
37
-		parent::__construct($message, $code);
28
+    /**
29
+     * Create a new exception
30
+     *
31
+     * @param string $message Exception message
32
+     * @param string $type Exception type
33
+     * @param mixed $data Associated data
34
+     * @param integer $code Exception numerical code, if applicable
35
+     */
36
+    public function __construct($message, $type, $data = null, $code = 0) {
37
+        parent::__construct($message, $code);
38 38
 
39
-		$this->type = $type;
40
-		$this->data = $data;
41
-	}
39
+        $this->type = $type;
40
+        $this->data = $data;
41
+    }
42 42
 
43
-	/**
44
-	 * Like {@see getCode()}, but a string code.
45
-	 *
46
-	 * @codeCoverageIgnore
47
-	 * @return string
48
-	 */
49
-	public function getType() {
50
-		return $this->type;
51
-	}
43
+    /**
44
+     * Like {@see getCode()}, but a string code.
45
+     *
46
+     * @codeCoverageIgnore
47
+     * @return string
48
+     */
49
+    public function getType() {
50
+        return $this->type;
51
+    }
52 52
 
53
-	/**
54
-	 * Gives any relevant data
55
-	 *
56
-	 * @codeCoverageIgnore
57
-	 * @return mixed
58
-	 */
59
-	public function getData() {
60
-		return $this->data;
61
-	}
53
+    /**
54
+     * Gives any relevant data
55
+     *
56
+     * @codeCoverageIgnore
57
+     * @return mixed
58
+     */
59
+    public function getData() {
60
+        return $this->data;
61
+    }
62 62
 }
63 63
\ No newline at end of file
Please login to merge, or discard this patch.