Completed
Pull Request — 1.11.x (#1599)
by José
28:19
created
plugin/buycourses/src/Requests/IDNAEncoder.php 1 patch
Indentation   +300 added lines, -300 removed lines patch added patch discarded remove patch
@@ -11,378 +11,378 @@
 block discarded – undo
11 11
  * @see https://tools.ietf.org/html/rfc3492 Punycode/Bootstrap specification
12 12
  */
13 13
 class Requests_IDNAEncoder {
14
-	/**
15
-	 * ACE prefix used for IDNA
16
-	 *
17
-	 * @see https://tools.ietf.org/html/rfc3490#section-5
18
-	 * @var string
19
-	 */
20
-	const ACE_PREFIX = 'xn--';
14
+    /**
15
+     * ACE prefix used for IDNA
16
+     *
17
+     * @see https://tools.ietf.org/html/rfc3490#section-5
18
+     * @var string
19
+     */
20
+    const ACE_PREFIX = 'xn--';
21 21
 
22
-	/**#@+
22
+    /**#@+
23 23
 	 * Bootstrap constant for Punycode
24 24
 	 *
25 25
 	 * @see https://tools.ietf.org/html/rfc3492#section-5
26 26
 	 * @var int
27 27
 	 */
28
-	const BOOTSTRAP_BASE         = 36;
29
-	const BOOTSTRAP_TMIN         = 1;
30
-	const BOOTSTRAP_TMAX         = 26;
31
-	const BOOTSTRAP_SKEW         = 38;
32
-	const BOOTSTRAP_DAMP         = 700;
33
-	const BOOTSTRAP_INITIAL_BIAS = 72;
34
-	const BOOTSTRAP_INITIAL_N    = 128;
35
-	/**#@-*/
28
+    const BOOTSTRAP_BASE         = 36;
29
+    const BOOTSTRAP_TMIN         = 1;
30
+    const BOOTSTRAP_TMAX         = 26;
31
+    const BOOTSTRAP_SKEW         = 38;
32
+    const BOOTSTRAP_DAMP         = 700;
33
+    const BOOTSTRAP_INITIAL_BIAS = 72;
34
+    const BOOTSTRAP_INITIAL_N    = 128;
35
+    /**#@-*/
36 36
 
37
-	/**
38
-	 * Encode a hostname using Punycode
39
-	 *
40
-	 * @param string $string Hostname
41
-	 * @return string Punycode-encoded hostname
42
-	 */
43
-	public static function encode($string) {
44
-		$parts = explode('.', $string);
45
-		foreach ($parts as &$part) {
46
-			$part = self::to_ascii($part);
47
-		}
48
-		return implode('.', $parts);
49
-	}
37
+    /**
38
+     * Encode a hostname using Punycode
39
+     *
40
+     * @param string $string Hostname
41
+     * @return string Punycode-encoded hostname
42
+     */
43
+    public static function encode($string) {
44
+        $parts = explode('.', $string);
45
+        foreach ($parts as &$part) {
46
+            $part = self::to_ascii($part);
47
+        }
48
+        return implode('.', $parts);
49
+    }
50 50
 
51
-	/**
52
-	 * Convert a UTF-8 string to an ASCII string using Punycode
53
-	 *
54
-	 * @throws Requests_Exception Provided string longer than 64 ASCII characters (`idna.provided_too_long`)
55
-	 * @throws Requests_Exception Prepared string longer than 64 ASCII characters (`idna.prepared_too_long`)
56
-	 * @throws Requests_Exception Provided string already begins with xn-- (`idna.provided_is_prefixed`)
57
-	 * @throws Requests_Exception Encoded string longer than 64 ASCII characters (`idna.encoded_too_long`)
58
-	 *
59
-	 * @param string $string ASCII or UTF-8 string (max length 64 characters)
60
-	 * @return string ASCII string
61
-	 */
62
-	public static function to_ascii($string) {
63
-		// Step 1: Check if the string is already ASCII
64
-		if (self::is_ascii($string)) {
65
-			// Skip to step 7
66
-			if (strlen($string) < 64) {
67
-				return $string;
68
-			}
51
+    /**
52
+     * Convert a UTF-8 string to an ASCII string using Punycode
53
+     *
54
+     * @throws Requests_Exception Provided string longer than 64 ASCII characters (`idna.provided_too_long`)
55
+     * @throws Requests_Exception Prepared string longer than 64 ASCII characters (`idna.prepared_too_long`)
56
+     * @throws Requests_Exception Provided string already begins with xn-- (`idna.provided_is_prefixed`)
57
+     * @throws Requests_Exception Encoded string longer than 64 ASCII characters (`idna.encoded_too_long`)
58
+     *
59
+     * @param string $string ASCII or UTF-8 string (max length 64 characters)
60
+     * @return string ASCII string
61
+     */
62
+    public static function to_ascii($string) {
63
+        // Step 1: Check if the string is already ASCII
64
+        if (self::is_ascii($string)) {
65
+            // Skip to step 7
66
+            if (strlen($string) < 64) {
67
+                return $string;
68
+            }
69 69
 
70
-			throw new Requests_Exception('Provided string is too long', 'idna.provided_too_long', $string);
71
-		}
70
+            throw new Requests_Exception('Provided string is too long', 'idna.provided_too_long', $string);
71
+        }
72 72
 
73
-		// Step 2: nameprep
74
-		$string = self::nameprep($string);
73
+        // Step 2: nameprep
74
+        $string = self::nameprep($string);
75 75
 
76
-		// Step 3: UseSTD3ASCIIRules is false, continue
77
-		// Step 4: Check if it's ASCII now
78
-		if (self::is_ascii($string)) {
79
-			// Skip to step 7
80
-			if (strlen($string) < 64) {
81
-				return $string;
82
-			}
76
+        // Step 3: UseSTD3ASCIIRules is false, continue
77
+        // Step 4: Check if it's ASCII now
78
+        if (self::is_ascii($string)) {
79
+            // Skip to step 7
80
+            if (strlen($string) < 64) {
81
+                return $string;
82
+            }
83 83
 
84
-			throw new Requests_Exception('Prepared string is too long', 'idna.prepared_too_long', $string);
85
-		}
84
+            throw new Requests_Exception('Prepared string is too long', 'idna.prepared_too_long', $string);
85
+        }
86 86
 
87
-		// Step 5: Check ACE prefix
88
-		if (strpos($string, self::ACE_PREFIX) === 0) {
89
-			throw new Requests_Exception('Provided string begins with ACE prefix', 'idna.provided_is_prefixed', $string);
90
-		}
87
+        // Step 5: Check ACE prefix
88
+        if (strpos($string, self::ACE_PREFIX) === 0) {
89
+            throw new Requests_Exception('Provided string begins with ACE prefix', 'idna.provided_is_prefixed', $string);
90
+        }
91 91
 
92
-		// Step 6: Encode with Punycode
93
-		$string = self::punycode_encode($string);
92
+        // Step 6: Encode with Punycode
93
+        $string = self::punycode_encode($string);
94 94
 
95
-		// Step 7: Prepend ACE prefix
96
-		$string = self::ACE_PREFIX . $string;
95
+        // Step 7: Prepend ACE prefix
96
+        $string = self::ACE_PREFIX . $string;
97 97
 
98
-		// Step 8: Check size
99
-		if (strlen($string) < 64) {
100
-			return $string;
101
-		}
98
+        // Step 8: Check size
99
+        if (strlen($string) < 64) {
100
+            return $string;
101
+        }
102 102
 
103
-		throw new Requests_Exception('Encoded string is too long', 'idna.encoded_too_long', $string);
104
-	}
103
+        throw new Requests_Exception('Encoded string is too long', 'idna.encoded_too_long', $string);
104
+    }
105 105
 
106
-	/**
107
-	 * Check whether a given string contains only ASCII characters
108
-	 *
109
-	 * @internal (Testing found regex was the fastest implementation)
110
-	 *
111
-	 * @param string $string
112
-	 * @return bool Is the string ASCII-only?
113
-	 */
114
-	protected static function is_ascii($string) {
115
-		return (preg_match('/(?:[^\x00-\x7F])/', $string) !== 1);
116
-	}
106
+    /**
107
+     * Check whether a given string contains only ASCII characters
108
+     *
109
+     * @internal (Testing found regex was the fastest implementation)
110
+     *
111
+     * @param string $string
112
+     * @return bool Is the string ASCII-only?
113
+     */
114
+    protected static function is_ascii($string) {
115
+        return (preg_match('/(?:[^\x00-\x7F])/', $string) !== 1);
116
+    }
117 117
 
118
-	/**
119
-	 * Prepare a string for use as an IDNA name
120
-	 *
121
-	 * @todo Implement this based on RFC 3491 and the newer 5891
122
-	 * @param string $string
123
-	 * @return string Prepared string
124
-	 */
125
-	protected static function nameprep($string) {
126
-		return $string;
127
-	}
118
+    /**
119
+     * Prepare a string for use as an IDNA name
120
+     *
121
+     * @todo Implement this based on RFC 3491 and the newer 5891
122
+     * @param string $string
123
+     * @return string Prepared string
124
+     */
125
+    protected static function nameprep($string) {
126
+        return $string;
127
+    }
128 128
 
129
-	/**
130
-	 * Convert a UTF-8 string to a UCS-4 codepoint array
131
-	 *
132
-	 * Based on Requests_IRI::replace_invalid_with_pct_encoding()
133
-	 *
134
-	 * @throws Requests_Exception Invalid UTF-8 codepoint (`idna.invalidcodepoint`)
135
-	 * @param string $input
136
-	 * @return array Unicode code points
137
-	 */
138
-	protected static function utf8_to_codepoints($input) {
139
-		$codepoints = array();
129
+    /**
130
+     * Convert a UTF-8 string to a UCS-4 codepoint array
131
+     *
132
+     * Based on Requests_IRI::replace_invalid_with_pct_encoding()
133
+     *
134
+     * @throws Requests_Exception Invalid UTF-8 codepoint (`idna.invalidcodepoint`)
135
+     * @param string $input
136
+     * @return array Unicode code points
137
+     */
138
+    protected static function utf8_to_codepoints($input) {
139
+        $codepoints = array();
140 140
 
141
-		// Get number of bytes
142
-		$strlen = strlen($input);
141
+        // Get number of bytes
142
+        $strlen = strlen($input);
143 143
 
144
-		for ($position = 0; $position < $strlen; $position++) {
145
-			$value = ord($input[$position]);
144
+        for ($position = 0; $position < $strlen; $position++) {
145
+            $value = ord($input[$position]);
146 146
 
147
-			// One byte sequence:
148
-			if ((~$value & 0x80) === 0x80) {
149
-				$character = $value;
150
-				$length = 1;
151
-				$remaining = 0;
152
-			}
153
-			// Two byte sequence:
154
-			elseif (($value & 0xE0) === 0xC0) {
155
-				$character = ($value & 0x1F) << 6;
156
-				$length = 2;
157
-				$remaining = 1;
158
-			}
159
-			// Three byte sequence:
160
-			elseif (($value & 0xF0) === 0xE0) {
161
-				$character = ($value & 0x0F) << 12;
162
-				$length = 3;
163
-				$remaining = 2;
164
-			}
165
-			// Four byte sequence:
166
-			elseif (($value & 0xF8) === 0xF0) {
167
-				$character = ($value & 0x07) << 18;
168
-				$length = 4;
169
-				$remaining = 3;
170
-			}
171
-			// Invalid byte:
172
-			else {
173
-				throw new Requests_Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $value);
174
-			}
147
+            // One byte sequence:
148
+            if ((~$value & 0x80) === 0x80) {
149
+                $character = $value;
150
+                $length = 1;
151
+                $remaining = 0;
152
+            }
153
+            // Two byte sequence:
154
+            elseif (($value & 0xE0) === 0xC0) {
155
+                $character = ($value & 0x1F) << 6;
156
+                $length = 2;
157
+                $remaining = 1;
158
+            }
159
+            // Three byte sequence:
160
+            elseif (($value & 0xF0) === 0xE0) {
161
+                $character = ($value & 0x0F) << 12;
162
+                $length = 3;
163
+                $remaining = 2;
164
+            }
165
+            // Four byte sequence:
166
+            elseif (($value & 0xF8) === 0xF0) {
167
+                $character = ($value & 0x07) << 18;
168
+                $length = 4;
169
+                $remaining = 3;
170
+            }
171
+            // Invalid byte:
172
+            else {
173
+                throw new Requests_Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $value);
174
+            }
175 175
 
176
-			if ($remaining > 0) {
177
-				if ($position + $length > $strlen) {
178
-					throw new Requests_Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character);
179
-				}
180
-				for ($position++; $remaining > 0; $position++) {
181
-					$value = ord($input[$position]);
176
+            if ($remaining > 0) {
177
+                if ($position + $length > $strlen) {
178
+                    throw new Requests_Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character);
179
+                }
180
+                for ($position++; $remaining > 0; $position++) {
181
+                    $value = ord($input[$position]);
182 182
 
183
-					// If it is invalid, count the sequence as invalid and reprocess the current byte:
184
-					if (($value & 0xC0) !== 0x80) {
185
-						throw new Requests_Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character);
186
-					}
183
+                    // If it is invalid, count the sequence as invalid and reprocess the current byte:
184
+                    if (($value & 0xC0) !== 0x80) {
185
+                        throw new Requests_Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character);
186
+                    }
187 187
 
188
-					$character |= ($value & 0x3F) << (--$remaining * 6);
189
-				}
190
-				$position--;
191
-			}
188
+                    $character |= ($value & 0x3F) << (--$remaining * 6);
189
+                }
190
+                $position--;
191
+            }
192 192
 
193
-			if (
194
-				// Non-shortest form sequences are invalid
195
-				   $length > 1 && $character <= 0x7F
196
-				|| $length > 2 && $character <= 0x7FF
197
-				|| $length > 3 && $character <= 0xFFFF
198
-				// Outside of range of ucschar codepoints
199
-				// Noncharacters
200
-				|| ($character & 0xFFFE) === 0xFFFE
201
-				|| $character >= 0xFDD0 && $character <= 0xFDEF
202
-				|| (
203
-					// Everything else not in ucschar
204
-					   $character > 0xD7FF && $character < 0xF900
205
-					|| $character < 0x20
206
-					|| $character > 0x7E && $character < 0xA0
207
-					|| $character > 0xEFFFD
208
-				)
209
-			) {
210
-				throw new Requests_Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character);
211
-			}
193
+            if (
194
+                // Non-shortest form sequences are invalid
195
+                    $length > 1 && $character <= 0x7F
196
+                || $length > 2 && $character <= 0x7FF
197
+                || $length > 3 && $character <= 0xFFFF
198
+                // Outside of range of ucschar codepoints
199
+                // Noncharacters
200
+                || ($character & 0xFFFE) === 0xFFFE
201
+                || $character >= 0xFDD0 && $character <= 0xFDEF
202
+                || (
203
+                    // Everything else not in ucschar
204
+                        $character > 0xD7FF && $character < 0xF900
205
+                    || $character < 0x20
206
+                    || $character > 0x7E && $character < 0xA0
207
+                    || $character > 0xEFFFD
208
+                )
209
+            ) {
210
+                throw new Requests_Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character);
211
+            }
212 212
 
213
-			$codepoints[] = $character;
214
-		}
213
+            $codepoints[] = $character;
214
+        }
215 215
 
216
-		return $codepoints;
217
-	}
216
+        return $codepoints;
217
+    }
218 218
 
219
-	/**
220
-	 * RFC3492-compliant encoder
221
-	 *
222
-	 * @internal Pseudo-code from Section 6.3 is commented with "#" next to relevant code
223
-	 * @throws Requests_Exception On character outside of the domain (never happens with Punycode) (`idna.character_outside_domain`)
224
-	 *
225
-	 * @param string $input UTF-8 encoded string to encode
226
-	 * @return string Punycode-encoded string
227
-	 */
228
-	public static function punycode_encode($input) {
229
-		$output = '';
219
+    /**
220
+     * RFC3492-compliant encoder
221
+     *
222
+     * @internal Pseudo-code from Section 6.3 is commented with "#" next to relevant code
223
+     * @throws Requests_Exception On character outside of the domain (never happens with Punycode) (`idna.character_outside_domain`)
224
+     *
225
+     * @param string $input UTF-8 encoded string to encode
226
+     * @return string Punycode-encoded string
227
+     */
228
+    public static function punycode_encode($input) {
229
+        $output = '';
230 230
 #		let n = initial_n
231
-		$n = self::BOOTSTRAP_INITIAL_N;
231
+        $n = self::BOOTSTRAP_INITIAL_N;
232 232
 #		let delta = 0
233
-		$delta = 0;
233
+        $delta = 0;
234 234
 #		let bias = initial_bias
235
-		$bias = self::BOOTSTRAP_INITIAL_BIAS;
235
+        $bias = self::BOOTSTRAP_INITIAL_BIAS;
236 236
 #		let h = b = the number of basic code points in the input
237
-		$h = $b = 0; // see loop
237
+        $h = $b = 0; // see loop
238 238
 #		copy them to the output in order
239
-		$codepoints = self::utf8_to_codepoints($input);
240
-		$extended = array();
239
+        $codepoints = self::utf8_to_codepoints($input);
240
+        $extended = array();
241 241
 
242
-		foreach ($codepoints as $char) {
243
-			if ($char < 128) {
244
-				// Character is valid ASCII
245
-				// TODO: this should also check if it's valid for a URL
246
-				$output .= chr($char);
247
-				$h++;
248
-			}
249
-			// Check if the character is non-ASCII, but below initial n
250
-			// This never occurs for Punycode, so ignore in coverage
251
-			// @codeCoverageIgnoreStart
252
-			elseif ($char < $n) {
253
-				throw new Requests_Exception('Invalid character', 'idna.character_outside_domain', $char);
254
-			}
255
-			// @codeCoverageIgnoreEnd
256
-			else {
257
-				$extended[$char] = true;
258
-			}
259
-		}
260
-		$extended = array_keys($extended);
261
-		sort($extended);
262
-		$b = $h;
242
+        foreach ($codepoints as $char) {
243
+            if ($char < 128) {
244
+                // Character is valid ASCII
245
+                // TODO: this should also check if it's valid for a URL
246
+                $output .= chr($char);
247
+                $h++;
248
+            }
249
+            // Check if the character is non-ASCII, but below initial n
250
+            // This never occurs for Punycode, so ignore in coverage
251
+            // @codeCoverageIgnoreStart
252
+            elseif ($char < $n) {
253
+                throw new Requests_Exception('Invalid character', 'idna.character_outside_domain', $char);
254
+            }
255
+            // @codeCoverageIgnoreEnd
256
+            else {
257
+                $extended[$char] = true;
258
+            }
259
+        }
260
+        $extended = array_keys($extended);
261
+        sort($extended);
262
+        $b = $h;
263 263
 #		[copy them] followed by a delimiter if b > 0
264
-		if (strlen($output) > 0) {
265
-			$output .= '-';
266
-		}
264
+        if (strlen($output) > 0) {
265
+            $output .= '-';
266
+        }
267 267
 #		{if the input contains a non-basic code point < n then fail}
268 268
 #		while h < length(input) do begin
269
-		while ($h < count($codepoints)) {
269
+        while ($h < count($codepoints)) {
270 270
 #			let m = the minimum code point >= n in the input
271
-			$m = array_shift($extended);
272
-			//printf('next code point to insert is %s' . PHP_EOL, dechex($m));
271
+            $m = array_shift($extended);
272
+            //printf('next code point to insert is %s' . PHP_EOL, dechex($m));
273 273
 #			let delta = delta + (m - n) * (h + 1), fail on overflow
274
-			$delta += ($m - $n) * ($h + 1);
274
+            $delta += ($m - $n) * ($h + 1);
275 275
 #			let n = m
276
-			$n = $m;
276
+            $n = $m;
277 277
 #			for each code point c in the input (in order) do begin
278
-			for ($num = 0; $num < count($codepoints); $num++) {
279
-				$c = $codepoints[$num];
278
+            for ($num = 0; $num < count($codepoints); $num++) {
279
+                $c = $codepoints[$num];
280 280
 #				if c < n then increment delta, fail on overflow
281
-				if ($c < $n) {
282
-					$delta++;
283
-				}
281
+                if ($c < $n) {
282
+                    $delta++;
283
+                }
284 284
 #				if c == n then begin
285
-				elseif ($c === $n) {
285
+                elseif ($c === $n) {
286 286
 #					let q = delta
287
-					$q = $delta;
287
+                    $q = $delta;
288 288
 #					for k = base to infinity in steps of base do begin
289
-					for ($k = self::BOOTSTRAP_BASE; ; $k += self::BOOTSTRAP_BASE) {
289
+                    for ($k = self::BOOTSTRAP_BASE; ; $k += self::BOOTSTRAP_BASE) {
290 290
 #						let t = tmin if k <= bias {+ tmin}, or
291 291
 #								tmax if k >= bias + tmax, or k - bias otherwise
292
-						if ($k <= ($bias + self::BOOTSTRAP_TMIN)) {
293
-							$t = self::BOOTSTRAP_TMIN;
294
-						}
295
-						elseif ($k >= ($bias + self::BOOTSTRAP_TMAX)) {
296
-							$t = self::BOOTSTRAP_TMAX;
297
-						}
298
-						else {
299
-							$t = $k - $bias;
300
-						}
292
+                        if ($k <= ($bias + self::BOOTSTRAP_TMIN)) {
293
+                            $t = self::BOOTSTRAP_TMIN;
294
+                        }
295
+                        elseif ($k >= ($bias + self::BOOTSTRAP_TMAX)) {
296
+                            $t = self::BOOTSTRAP_TMAX;
297
+                        }
298
+                        else {
299
+                            $t = $k - $bias;
300
+                        }
301 301
 #						if q < t then break
302
-						if ($q < $t) {
303
-							break;
304
-						}
302
+                        if ($q < $t) {
303
+                            break;
304
+                        }
305 305
 #						output the code point for digit t + ((q - t) mod (base - t))
306
-						$digit = $t + (($q - $t) % (self::BOOTSTRAP_BASE - $t));
307
-						$output .= self::digit_to_char($digit);
306
+                        $digit = $t + (($q - $t) % (self::BOOTSTRAP_BASE - $t));
307
+                        $output .= self::digit_to_char($digit);
308 308
 #						let q = (q - t) div (base - t)
309
-						$q = floor(($q - $t) / (self::BOOTSTRAP_BASE - $t));
309
+                        $q = floor(($q - $t) / (self::BOOTSTRAP_BASE - $t));
310 310
 #					end
311
-					}
311
+                    }
312 312
 #					output the code point for digit q
313
-					$output .= self::digit_to_char($q);
313
+                    $output .= self::digit_to_char($q);
314 314
 #					let bias = adapt(delta, h + 1, test h equals b?)
315
-					$bias = self::adapt($delta, $h + 1, $h === $b);
315
+                    $bias = self::adapt($delta, $h + 1, $h === $b);
316 316
 #					let delta = 0
317
-					$delta = 0;
317
+                    $delta = 0;
318 318
 #					increment h
319
-					$h++;
319
+                    $h++;
320 320
 #				end
321
-				}
321
+                }
322 322
 #			end
323
-			}
323
+            }
324 324
 #			increment delta and n
325
-			$delta++;
326
-			$n++;
325
+            $delta++;
326
+            $n++;
327 327
 #		end
328
-		}
328
+        }
329 329
 
330
-		return $output;
331
-	}
330
+        return $output;
331
+    }
332 332
 
333
-	/**
334
-	 * Convert a digit to its respective character
335
-	 *
336
-	 * @see https://tools.ietf.org/html/rfc3492#section-5
337
-	 * @throws Requests_Exception On invalid digit (`idna.invalid_digit`)
338
-	 *
339
-	 * @param int $digit Digit in the range 0-35
340
-	 * @return string Single character corresponding to digit
341
-	 */
342
-	protected static function digit_to_char($digit) {
343
-		// @codeCoverageIgnoreStart
344
-		// As far as I know, this never happens, but still good to be sure.
345
-		if ($digit < 0 || $digit > 35) {
346
-			throw new Requests_Exception(sprintf('Invalid digit %d', $digit), 'idna.invalid_digit', $digit);
347
-		}
348
-		// @codeCoverageIgnoreEnd
349
-		$digits = 'abcdefghijklmnopqrstuvwxyz0123456789';
350
-		return substr($digits, $digit, 1);
351
-	}
333
+    /**
334
+     * Convert a digit to its respective character
335
+     *
336
+     * @see https://tools.ietf.org/html/rfc3492#section-5
337
+     * @throws Requests_Exception On invalid digit (`idna.invalid_digit`)
338
+     *
339
+     * @param int $digit Digit in the range 0-35
340
+     * @return string Single character corresponding to digit
341
+     */
342
+    protected static function digit_to_char($digit) {
343
+        // @codeCoverageIgnoreStart
344
+        // As far as I know, this never happens, but still good to be sure.
345
+        if ($digit < 0 || $digit > 35) {
346
+            throw new Requests_Exception(sprintf('Invalid digit %d', $digit), 'idna.invalid_digit', $digit);
347
+        }
348
+        // @codeCoverageIgnoreEnd
349
+        $digits = 'abcdefghijklmnopqrstuvwxyz0123456789';
350
+        return substr($digits, $digit, 1);
351
+    }
352 352
 
353
-	/**
354
-	 * Adapt the bias
355
-	 *
356
-	 * @see https://tools.ietf.org/html/rfc3492#section-6.1
357
-	 * @param int $delta
358
-	 * @param int $numpoints
359
-	 * @param bool $firsttime
360
-	 * @return int New bias
361
-	 */
362
-	protected static function adapt($delta, $numpoints, $firsttime) {
353
+    /**
354
+     * Adapt the bias
355
+     *
356
+     * @see https://tools.ietf.org/html/rfc3492#section-6.1
357
+     * @param int $delta
358
+     * @param int $numpoints
359
+     * @param bool $firsttime
360
+     * @return int New bias
361
+     */
362
+    protected static function adapt($delta, $numpoints, $firsttime) {
363 363
 #	function adapt(delta,numpoints,firsttime):
364 364
 #		if firsttime then let delta = delta div damp
365
-		if ($firsttime) {
366
-			$delta = floor($delta / self::BOOTSTRAP_DAMP);
367
-		}
365
+        if ($firsttime) {
366
+            $delta = floor($delta / self::BOOTSTRAP_DAMP);
367
+        }
368 368
 #		else let delta = delta div 2
369
-		else {
370
-			$delta = floor($delta / 2);
371
-		}
369
+        else {
370
+            $delta = floor($delta / 2);
371
+        }
372 372
 #		let delta = delta + (delta div numpoints)
373
-		$delta += floor($delta / $numpoints);
373
+        $delta += floor($delta / $numpoints);
374 374
 #		let k = 0
375
-		$k = 0;
375
+        $k = 0;
376 376
 #		while delta > ((base - tmin) * tmax) div 2 do begin
377
-		$max = floor(((self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN) * self::BOOTSTRAP_TMAX) / 2);
378
-		while ($delta > $max) {
377
+        $max = floor(((self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN) * self::BOOTSTRAP_TMAX) / 2);
378
+        while ($delta > $max) {
379 379
 #			let delta = delta div (base - tmin)
380
-			$delta = floor($delta / (self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN));
380
+            $delta = floor($delta / (self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN));
381 381
 #			let k = k + base
382
-			$k += self::BOOTSTRAP_BASE;
382
+            $k += self::BOOTSTRAP_BASE;
383 383
 #		end
384
-		}
384
+        }
385 385
 #		return k + (((base - tmin + 1) * delta) div (delta + skew))
386
-		return $k + floor(((self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN + 1) * $delta) / ($delta + self::BOOTSTRAP_SKEW));
387
-	}
386
+        return $k + floor(((self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN + 1) * $delta) / ($delta + self::BOOTSTRAP_SKEW));
387
+    }
388 388
 }
389 389
\ No newline at end of file
Please login to merge, or discard this patch.
plugin/buycourses/src/Requests/Auth.php 1 patch
Indentation   +11 added lines, -11 removed lines patch added patch discarded remove patch
@@ -19,15 +19,15 @@
 block discarded – undo
19 19
  * @subpackage Authentication
20 20
  */
21 21
 interface Requests_Auth {
22
-	/**
23
-	 * Register hooks as needed
24
-	 *
25
-	 * This method is called in {@see Requests::request} when the user has set
26
-	 * an instance as the 'auth' option. Use this callback to register all the
27
-	 * hooks you'll need.
28
-	 *
29
-	 * @see Requests_Hooks::register
30
-	 * @param Requests_Hooks $hooks Hook system
31
-	 */
32
-	public function register(Requests_Hooks &$hooks);
22
+    /**
23
+     * Register hooks as needed
24
+     *
25
+     * This method is called in {@see Requests::request} when the user has set
26
+     * an instance as the 'auth' option. Use this callback to register all the
27
+     * hooks you'll need.
28
+     *
29
+     * @see Requests_Hooks::register
30
+     * @param Requests_Hooks $hooks Hook system
31
+     */
32
+    public function register(Requests_Hooks &$hooks);
33 33
 }
34 34
\ No newline at end of file
Please login to merge, or discard this patch.
plugin/buycourses/src/Requests/Response.php 1 patch
Indentation   +93 added lines, -93 removed lines patch added patch discarded remove patch
@@ -13,109 +13,109 @@
 block discarded – undo
13 13
  * @package Requests
14 14
  */
15 15
 class Requests_Response {
16
-	/**
17
-	 * Constructor
18
-	 */
19
-	public function __construct() {
20
-		$this->headers = new Requests_Response_Headers();
21
-		$this->cookies = new Requests_Cookie_Jar();
22
-	}
16
+    /**
17
+     * Constructor
18
+     */
19
+    public function __construct() {
20
+        $this->headers = new Requests_Response_Headers();
21
+        $this->cookies = new Requests_Cookie_Jar();
22
+    }
23 23
 
24
-	/**
25
-	 * Response body
26
-	 *
27
-	 * @var string
28
-	 */
29
-	public $body = '';
24
+    /**
25
+     * Response body
26
+     *
27
+     * @var string
28
+     */
29
+    public $body = '';
30 30
 
31
-	/**
32
-	 * Raw HTTP data from the transport
33
-	 *
34
-	 * @var string
35
-	 */
36
-	public $raw = '';
31
+    /**
32
+     * Raw HTTP data from the transport
33
+     *
34
+     * @var string
35
+     */
36
+    public $raw = '';
37 37
 
38
-	/**
39
-	 * Headers, as an associative array
40
-	 *
41
-	 * @var Requests_Response_Headers Array-like object representing headers
42
-	 */
43
-	public $headers = array();
38
+    /**
39
+     * Headers, as an associative array
40
+     *
41
+     * @var Requests_Response_Headers Array-like object representing headers
42
+     */
43
+    public $headers = array();
44 44
 
45
-	/**
46
-	 * Status code, false if non-blocking
47
-	 *
48
-	 * @var integer|boolean
49
-	 */
50
-	public $status_code = false;
45
+    /**
46
+     * Status code, false if non-blocking
47
+     *
48
+     * @var integer|boolean
49
+     */
50
+    public $status_code = false;
51 51
 
52
-	/**
53
-	 * Protocol version, false if non-blocking
54
-	 * @var float|boolean
55
-	 */
56
-	public $protocol_version = false;
52
+    /**
53
+     * Protocol version, false if non-blocking
54
+     * @var float|boolean
55
+     */
56
+    public $protocol_version = false;
57 57
 
58
-	/**
59
-	 * Whether the request succeeded or not
60
-	 *
61
-	 * @var boolean
62
-	 */
63
-	public $success = false;
58
+    /**
59
+     * Whether the request succeeded or not
60
+     *
61
+     * @var boolean
62
+     */
63
+    public $success = false;
64 64
 
65
-	/**
66
-	 * Number of redirects the request used
67
-	 *
68
-	 * @var integer
69
-	 */
70
-	public $redirects = 0;
65
+    /**
66
+     * Number of redirects the request used
67
+     *
68
+     * @var integer
69
+     */
70
+    public $redirects = 0;
71 71
 
72
-	/**
73
-	 * URL requested
74
-	 *
75
-	 * @var string
76
-	 */
77
-	public $url = '';
72
+    /**
73
+     * URL requested
74
+     *
75
+     * @var string
76
+     */
77
+    public $url = '';
78 78
 
79
-	/**
80
-	 * Previous requests (from redirects)
81
-	 *
82
-	 * @var array Array of Requests_Response objects
83
-	 */
84
-	public $history = array();
79
+    /**
80
+     * Previous requests (from redirects)
81
+     *
82
+     * @var array Array of Requests_Response objects
83
+     */
84
+    public $history = array();
85 85
 
86
-	/**
87
-	 * Cookies from the request
88
-	 *
89
-	 * @var Requests_Cookie_Jar Array-like object representing a cookie jar
90
-	 */
91
-	public $cookies = array();
86
+    /**
87
+     * Cookies from the request
88
+     *
89
+     * @var Requests_Cookie_Jar Array-like object representing a cookie jar
90
+     */
91
+    public $cookies = array();
92 92
 
93
-	/**
94
-	 * Is the response a redirect?
95
-	 *
96
-	 * @return boolean True if redirect (3xx status), false if not.
97
-	 */
98
-	public function is_redirect() {
99
-		$code = $this->status_code;
100
-		return in_array($code, array(300, 301, 302, 303, 307)) || $code > 307 && $code < 400;
101
-	}
93
+    /**
94
+     * Is the response a redirect?
95
+     *
96
+     * @return boolean True if redirect (3xx status), false if not.
97
+     */
98
+    public function is_redirect() {
99
+        $code = $this->status_code;
100
+        return in_array($code, array(300, 301, 302, 303, 307)) || $code > 307 && $code < 400;
101
+    }
102 102
 
103
-	/**
104
-	 * Throws an exception if the request was not successful
105
-	 *
106
-	 * @throws Requests_Exception If `$allow_redirects` is false, and code is 3xx (`response.no_redirects`)
107
-	 * @throws Requests_Exception_HTTP On non-successful status code. Exception class corresponds to code (e.g. {@see Requests_Exception_HTTP_404})
108
-	 * @param boolean $allow_redirects Set to false to throw on a 3xx as well
109
-	 */
110
-	public function throw_for_status($allow_redirects = true) {
111
-		if ($this->is_redirect()) {
112
-			if (!$allow_redirects) {
113
-				throw new Requests_Exception('Redirection not allowed', 'response.no_redirects', $this);
114
-			}
115
-		}
116
-		elseif (!$this->success) {
117
-			$exception = Requests_Exception_HTTP::get_class($this->status_code);
118
-			throw new $exception(null, $this);
119
-		}
120
-	}
103
+    /**
104
+     * Throws an exception if the request was not successful
105
+     *
106
+     * @throws Requests_Exception If `$allow_redirects` is false, and code is 3xx (`response.no_redirects`)
107
+     * @throws Requests_Exception_HTTP On non-successful status code. Exception class corresponds to code (e.g. {@see Requests_Exception_HTTP_404})
108
+     * @param boolean $allow_redirects Set to false to throw on a 3xx as well
109
+     */
110
+    public function throw_for_status($allow_redirects = true) {
111
+        if ($this->is_redirect()) {
112
+            if (!$allow_redirects) {
113
+                throw new Requests_Exception('Redirection not allowed', 'response.no_redirects', $this);
114
+            }
115
+        }
116
+        elseif (!$this->success) {
117
+            $exception = Requests_Exception_HTTP::get_class($this->status_code);
118
+            throw new $exception(null, $this);
119
+        }
120
+    }
121 121
 }
Please login to merge, or discard this patch.
plugin/buycourses/src/Requests/Auth/Basic.php 1 patch
Indentation   +62 added lines, -62 removed lines patch added patch discarded remove patch
@@ -16,73 +16,73 @@
 block discarded – undo
16 16
  * @subpackage Authentication
17 17
  */
18 18
 class Requests_Auth_Basic implements Requests_Auth {
19
-	/**
20
-	 * Username
21
-	 *
22
-	 * @var string
23
-	 */
24
-	public $user;
19
+    /**
20
+     * Username
21
+     *
22
+     * @var string
23
+     */
24
+    public $user;
25 25
 
26
-	/**
27
-	 * Password
28
-	 *
29
-	 * @var string
30
-	 */
31
-	public $pass;
26
+    /**
27
+     * Password
28
+     *
29
+     * @var string
30
+     */
31
+    public $pass;
32 32
 
33
-	/**
34
-	 * Constructor
35
-	 *
36
-	 * @throws Requests_Exception On incorrect number of arguments (`authbasicbadargs`)
37
-	 * @param array|null $args Array of user and password. Must have exactly two elements
38
-	 */
39
-	public function __construct($args = null) {
40
-		if (is_array($args)) {
41
-			if (count($args) !== 2) {
42
-				throw new Requests_Exception('Invalid number of arguments', 'authbasicbadargs');
43
-			}
33
+    /**
34
+     * Constructor
35
+     *
36
+     * @throws Requests_Exception On incorrect number of arguments (`authbasicbadargs`)
37
+     * @param array|null $args Array of user and password. Must have exactly two elements
38
+     */
39
+    public function __construct($args = null) {
40
+        if (is_array($args)) {
41
+            if (count($args) !== 2) {
42
+                throw new Requests_Exception('Invalid number of arguments', 'authbasicbadargs');
43
+            }
44 44
 
45
-			list($this->user, $this->pass) = $args;
46
-		}
47
-	}
45
+            list($this->user, $this->pass) = $args;
46
+        }
47
+    }
48 48
 
49
-	/**
50
-	 * Register the necessary callbacks
51
-	 *
52
-	 * @see curl_before_send
53
-	 * @see fsockopen_header
54
-	 * @param Requests_Hooks $hooks Hook system
55
-	 */
56
-	public function register(Requests_Hooks &$hooks) {
57
-		$hooks->register('curl.before_send', array(&$this, 'curl_before_send'));
58
-		$hooks->register('fsockopen.after_headers', array(&$this, 'fsockopen_header'));
59
-	}
49
+    /**
50
+     * Register the necessary callbacks
51
+     *
52
+     * @see curl_before_send
53
+     * @see fsockopen_header
54
+     * @param Requests_Hooks $hooks Hook system
55
+     */
56
+    public function register(Requests_Hooks &$hooks) {
57
+        $hooks->register('curl.before_send', array(&$this, 'curl_before_send'));
58
+        $hooks->register('fsockopen.after_headers', array(&$this, 'fsockopen_header'));
59
+    }
60 60
 
61
-	/**
62
-	 * Set cURL parameters before the data is sent
63
-	 *
64
-	 * @param resource $handle cURL resource
65
-	 */
66
-	public function curl_before_send(&$handle) {
67
-		curl_setopt($handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
68
-		curl_setopt($handle, CURLOPT_USERPWD, $this->getAuthString());
69
-	}
61
+    /**
62
+     * Set cURL parameters before the data is sent
63
+     *
64
+     * @param resource $handle cURL resource
65
+     */
66
+    public function curl_before_send(&$handle) {
67
+        curl_setopt($handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
68
+        curl_setopt($handle, CURLOPT_USERPWD, $this->getAuthString());
69
+    }
70 70
 
71
-	/**
72
-	 * Add extra headers to the request before sending
73
-	 *
74
-	 * @param string $out HTTP header string
75
-	 */
76
-	public function fsockopen_header(&$out) {
77
-		$out .= sprintf("Authorization: Basic %s\r\n", base64_encode($this->getAuthString()));
78
-	}
71
+    /**
72
+     * Add extra headers to the request before sending
73
+     *
74
+     * @param string $out HTTP header string
75
+     */
76
+    public function fsockopen_header(&$out) {
77
+        $out .= sprintf("Authorization: Basic %s\r\n", base64_encode($this->getAuthString()));
78
+    }
79 79
 
80
-	/**
81
-	 * Get the authentication string (user:pass)
82
-	 *
83
-	 * @return string
84
-	 */
85
-	public function getAuthString() {
86
-		return $this->user . ':' . $this->pass;
87
-	}
80
+    /**
81
+     * Get the authentication string (user:pass)
82
+     *
83
+     * @return string
84
+     */
85
+    public function getAuthString() {
86
+        return $this->user . ':' . $this->pass;
87
+    }
88 88
 }
89 89
\ No newline at end of file
Please login to merge, or discard this patch.
plugin/buycourses/src/Requests/Cookie/Jar.php 1 patch
Indentation   +159 added lines, -159 removed lines patch added patch discarded remove patch
@@ -13,163 +13,163 @@
 block discarded – undo
13 13
  * @subpackage Cookies
14 14
  */
15 15
 class Requests_Cookie_Jar implements ArrayAccess, IteratorAggregate {
16
-	/**
17
-	 * Actual item data
18
-	 *
19
-	 * @var array
20
-	 */
21
-	protected $cookies = array();
22
-
23
-	/**
24
-	 * Create a new jar
25
-	 *
26
-	 * @param array $cookies Existing cookie values
27
-	 */
28
-	public function __construct($cookies = array()) {
29
-		$this->cookies = $cookies;
30
-	}
31
-
32
-	/**
33
-	 * Normalise cookie data into a Requests_Cookie
34
-	 *
35
-	 * @param string|Requests_Cookie $cookie
36
-	 * @return Requests_Cookie
37
-	 */
38
-	public function normalize_cookie($cookie, $key = null) {
39
-		if ($cookie instanceof Requests_Cookie) {
40
-			return $cookie;
41
-		}
42
-
43
-		return Requests_Cookie::parse($cookie, $key);
44
-	}
45
-
46
-	/**
47
-	 * Normalise cookie data into a Requests_Cookie
48
-	 *
49
-	 * @codeCoverageIgnore
50
-	 * @deprecated Use {@see Requests_Cookie_Jar::normalize_cookie}
51
-	 * @return Requests_Cookie
52
-	 */
53
-	public function normalizeCookie($cookie, $key = null) {
54
-		return $this->normalize_cookie($cookie, $key);
55
-	}
56
-
57
-	/**
58
-	 * Check if the given item exists
59
-	 *
60
-	 * @param string $key Item key
61
-	 * @return boolean Does the item exist?
62
-	 */
63
-	public function offsetExists($key) {
64
-		return isset($this->cookies[$key]);
65
-	}
66
-
67
-	/**
68
-	 * Get the value for the item
69
-	 *
70
-	 * @param string $key Item key
71
-	 * @return string Item value
72
-	 */
73
-	public function offsetGet($key) {
74
-		if (!isset($this->cookies[$key])) {
75
-			return null;
76
-		}
77
-
78
-		return $this->cookies[$key];
79
-	}
80
-
81
-	/**
82
-	 * Set the given item
83
-	 *
84
-	 * @throws Requests_Exception On attempting to use dictionary as list (`invalidset`)
85
-	 *
86
-	 * @param string $key Item name
87
-	 * @param string $value Item value
88
-	 */
89
-	public function offsetSet($key, $value) {
90
-		if ($key === null) {
91
-			throw new Requests_Exception('Object is a dictionary, not a list', 'invalidset');
92
-		}
93
-
94
-		$this->cookies[$key] = $value;
95
-	}
96
-
97
-	/**
98
-	 * Unset the given header
99
-	 *
100
-	 * @param string $key
101
-	 */
102
-	public function offsetUnset($key) {
103
-		unset($this->cookies[$key]);
104
-	}
105
-
106
-	/**
107
-	 * Get an iterator for the data
108
-	 *
109
-	 * @return ArrayIterator
110
-	 */
111
-	public function getIterator() {
112
-		return new ArrayIterator($this->cookies);
113
-	}
114
-
115
-	/**
116
-	 * Register the cookie handler with the request's hooking system
117
-	 *
118
-	 * @param Requests_Hooker $hooks Hooking system
119
-	 */
120
-	public function register(Requests_Hooker $hooks) {
121
-		$hooks->register('requests.before_request', array($this, 'before_request'));
122
-		$hooks->register('requests.before_redirect_check', array($this, 'before_redirect_check'));
123
-	}
124
-
125
-	/**
126
-	 * Add Cookie header to a request if we have any
127
-	 *
128
-	 * As per RFC 6265, cookies are separated by '; '
129
-	 *
130
-	 * @param string $url
131
-	 * @param array $headers
132
-	 * @param array $data
133
-	 * @param string $type
134
-	 * @param array $options
135
-	 */
136
-	public function before_request($url, &$headers, &$data, &$type, &$options) {
137
-		if (!$url instanceof Requests_IRI) {
138
-			$url = new Requests_IRI($url);
139
-		}
140
-
141
-		if (!empty($this->cookies)) {
142
-			$cookies = array();
143
-			foreach ($this->cookies as $key => $cookie) {
144
-				$cookie = $this->normalize_cookie($cookie, $key);
145
-
146
-				// Skip expired cookies
147
-				if ($cookie->is_expired()) {
148
-					continue;
149
-				}
150
-
151
-				if ($cookie->domain_matches($url->host)) {
152
-					$cookies[] = $cookie->format_for_header();
153
-				}
154
-			}
155
-
156
-			$headers['Cookie'] = implode('; ', $cookies);
157
-		}
158
-	}
159
-
160
-	/**
161
-	 * Parse all cookies from a response and attach them to the response
162
-	 *
163
-	 * @var Requests_Response $response
164
-	 */
165
-	public function before_redirect_check(Requests_Response &$return) {
166
-		$url = $return->url;
167
-		if (!$url instanceof Requests_IRI) {
168
-			$url = new Requests_IRI($url);
169
-		}
170
-
171
-		$cookies = Requests_Cookie::parse_from_headers($return->headers, $url);
172
-		$this->cookies = array_merge($this->cookies, $cookies);
173
-		$return->cookies = $this;
174
-	}
16
+    /**
17
+     * Actual item data
18
+     *
19
+     * @var array
20
+     */
21
+    protected $cookies = array();
22
+
23
+    /**
24
+     * Create a new jar
25
+     *
26
+     * @param array $cookies Existing cookie values
27
+     */
28
+    public function __construct($cookies = array()) {
29
+        $this->cookies = $cookies;
30
+    }
31
+
32
+    /**
33
+     * Normalise cookie data into a Requests_Cookie
34
+     *
35
+     * @param string|Requests_Cookie $cookie
36
+     * @return Requests_Cookie
37
+     */
38
+    public function normalize_cookie($cookie, $key = null) {
39
+        if ($cookie instanceof Requests_Cookie) {
40
+            return $cookie;
41
+        }
42
+
43
+        return Requests_Cookie::parse($cookie, $key);
44
+    }
45
+
46
+    /**
47
+     * Normalise cookie data into a Requests_Cookie
48
+     *
49
+     * @codeCoverageIgnore
50
+     * @deprecated Use {@see Requests_Cookie_Jar::normalize_cookie}
51
+     * @return Requests_Cookie
52
+     */
53
+    public function normalizeCookie($cookie, $key = null) {
54
+        return $this->normalize_cookie($cookie, $key);
55
+    }
56
+
57
+    /**
58
+     * Check if the given item exists
59
+     *
60
+     * @param string $key Item key
61
+     * @return boolean Does the item exist?
62
+     */
63
+    public function offsetExists($key) {
64
+        return isset($this->cookies[$key]);
65
+    }
66
+
67
+    /**
68
+     * Get the value for the item
69
+     *
70
+     * @param string $key Item key
71
+     * @return string Item value
72
+     */
73
+    public function offsetGet($key) {
74
+        if (!isset($this->cookies[$key])) {
75
+            return null;
76
+        }
77
+
78
+        return $this->cookies[$key];
79
+    }
80
+
81
+    /**
82
+     * Set the given item
83
+     *
84
+     * @throws Requests_Exception On attempting to use dictionary as list (`invalidset`)
85
+     *
86
+     * @param string $key Item name
87
+     * @param string $value Item value
88
+     */
89
+    public function offsetSet($key, $value) {
90
+        if ($key === null) {
91
+            throw new Requests_Exception('Object is a dictionary, not a list', 'invalidset');
92
+        }
93
+
94
+        $this->cookies[$key] = $value;
95
+    }
96
+
97
+    /**
98
+     * Unset the given header
99
+     *
100
+     * @param string $key
101
+     */
102
+    public function offsetUnset($key) {
103
+        unset($this->cookies[$key]);
104
+    }
105
+
106
+    /**
107
+     * Get an iterator for the data
108
+     *
109
+     * @return ArrayIterator
110
+     */
111
+    public function getIterator() {
112
+        return new ArrayIterator($this->cookies);
113
+    }
114
+
115
+    /**
116
+     * Register the cookie handler with the request's hooking system
117
+     *
118
+     * @param Requests_Hooker $hooks Hooking system
119
+     */
120
+    public function register(Requests_Hooker $hooks) {
121
+        $hooks->register('requests.before_request', array($this, 'before_request'));
122
+        $hooks->register('requests.before_redirect_check', array($this, 'before_redirect_check'));
123
+    }
124
+
125
+    /**
126
+     * Add Cookie header to a request if we have any
127
+     *
128
+     * As per RFC 6265, cookies are separated by '; '
129
+     *
130
+     * @param string $url
131
+     * @param array $headers
132
+     * @param array $data
133
+     * @param string $type
134
+     * @param array $options
135
+     */
136
+    public function before_request($url, &$headers, &$data, &$type, &$options) {
137
+        if (!$url instanceof Requests_IRI) {
138
+            $url = new Requests_IRI($url);
139
+        }
140
+
141
+        if (!empty($this->cookies)) {
142
+            $cookies = array();
143
+            foreach ($this->cookies as $key => $cookie) {
144
+                $cookie = $this->normalize_cookie($cookie, $key);
145
+
146
+                // Skip expired cookies
147
+                if ($cookie->is_expired()) {
148
+                    continue;
149
+                }
150
+
151
+                if ($cookie->domain_matches($url->host)) {
152
+                    $cookies[] = $cookie->format_for_header();
153
+                }
154
+            }
155
+
156
+            $headers['Cookie'] = implode('; ', $cookies);
157
+        }
158
+    }
159
+
160
+    /**
161
+     * Parse all cookies from a response and attach them to the response
162
+     *
163
+     * @var Requests_Response $response
164
+     */
165
+    public function before_redirect_check(Requests_Response &$return) {
166
+        $url = $return->url;
167
+        if (!$url instanceof Requests_IRI) {
168
+            $url = new Requests_IRI($url);
169
+        }
170
+
171
+        $cookies = Requests_Cookie::parse_from_headers($return->headers, $url);
172
+        $this->cookies = array_merge($this->cookies, $cookies);
173
+        $return->cookies = $this;
174
+    }
175 175
 }
176 176
\ No newline at end of file
Please login to merge, or discard this patch.
plugin/buycourses/src/Requests/Session.php 1 patch
Indentation   +204 added lines, -204 removed lines patch added patch discarded remove patch
@@ -18,136 +18,136 @@  discard block
 block discarded – undo
18 18
  * @subpackage Session Handler
19 19
  */
20 20
 class Requests_Session {
21
-	/**
22
-	 * Base URL for requests
23
-	 *
24
-	 * URLs will be made absolute using this as the base
25
-	 * @var string|null
26
-	 */
27
-	public $url = null;
21
+    /**
22
+     * Base URL for requests
23
+     *
24
+     * URLs will be made absolute using this as the base
25
+     * @var string|null
26
+     */
27
+    public $url = null;
28 28
 
29
-	/**
30
-	 * Base headers for requests
31
-	 * @var array
32
-	 */
33
-	public $headers = array();
29
+    /**
30
+     * Base headers for requests
31
+     * @var array
32
+     */
33
+    public $headers = array();
34 34
 
35
-	/**
36
-	 * Base data for requests
37
-	 *
38
-	 * If both the base data and the per-request data are arrays, the data will
39
-	 * be merged before sending the request.
40
-	 *
41
-	 * @var array
42
-	 */
43
-	public $data = array();
35
+    /**
36
+     * Base data for requests
37
+     *
38
+     * If both the base data and the per-request data are arrays, the data will
39
+     * be merged before sending the request.
40
+     *
41
+     * @var array
42
+     */
43
+    public $data = array();
44 44
 
45
-	/**
46
-	 * Base options for requests
47
-	 *
48
-	 * The base options are merged with the per-request data for each request.
49
-	 * The only default option is a shared cookie jar between requests.
50
-	 *
51
-	 * Values here can also be set directly via properties on the Session
52
-	 * object, e.g. `$session->useragent = 'X';`
53
-	 *
54
-	 * @var array
55
-	 */
56
-	public $options = array();
45
+    /**
46
+     * Base options for requests
47
+     *
48
+     * The base options are merged with the per-request data for each request.
49
+     * The only default option is a shared cookie jar between requests.
50
+     *
51
+     * Values here can also be set directly via properties on the Session
52
+     * object, e.g. `$session->useragent = 'X';`
53
+     *
54
+     * @var array
55
+     */
56
+    public $options = array();
57 57
 
58
-	/**
59
-	 * Create a new session
60
-	 *
61
-	 * @param string|null $url Base URL for requests
62
-	 * @param array $headers Default headers for requests
63
-	 * @param array $data Default data for requests
64
-	 * @param array $options Default options for requests
65
-	 */
66
-	public function __construct($url = null, $headers = array(), $data = array(), $options = array()) {
67
-		$this->url = $url;
68
-		$this->headers = $headers;
69
-		$this->data = $data;
70
-		$this->options = $options;
58
+    /**
59
+     * Create a new session
60
+     *
61
+     * @param string|null $url Base URL for requests
62
+     * @param array $headers Default headers for requests
63
+     * @param array $data Default data for requests
64
+     * @param array $options Default options for requests
65
+     */
66
+    public function __construct($url = null, $headers = array(), $data = array(), $options = array()) {
67
+        $this->url = $url;
68
+        $this->headers = $headers;
69
+        $this->data = $data;
70
+        $this->options = $options;
71 71
 
72
-		if (empty($this->options['cookies'])) {
73
-			$this->options['cookies'] = new Requests_Cookie_Jar();
74
-		}
75
-	}
72
+        if (empty($this->options['cookies'])) {
73
+            $this->options['cookies'] = new Requests_Cookie_Jar();
74
+        }
75
+    }
76 76
 
77
-	/**
78
-	 * Get a property's value
79
-	 *
80
-	 * @param string $key Property key
81
-	 * @return mixed|null Property value, null if none found
82
-	 */
83
-	public function __get($key) {
84
-		if (isset($this->options[$key])) {
85
-			return $this->options[$key];
86
-		}
77
+    /**
78
+     * Get a property's value
79
+     *
80
+     * @param string $key Property key
81
+     * @return mixed|null Property value, null if none found
82
+     */
83
+    public function __get($key) {
84
+        if (isset($this->options[$key])) {
85
+            return $this->options[$key];
86
+        }
87 87
 
88
-		return null;
89
-	}
88
+        return null;
89
+    }
90 90
 
91
-	/**
92
-	 * Set a property's value
93
-	 *
94
-	 * @param string $key Property key
95
-	 * @param mixed $value Property value
96
-	 */
97
-	public function __set($key, $value) {
98
-		$this->options[$key] = $value;
99
-	}
91
+    /**
92
+     * Set a property's value
93
+     *
94
+     * @param string $key Property key
95
+     * @param mixed $value Property value
96
+     */
97
+    public function __set($key, $value) {
98
+        $this->options[$key] = $value;
99
+    }
100 100
 
101
-	/**
102
-	 * Remove a property's value
103
-	 *
104
-	 * @param string $key Property key
105
-	 */
106
-	public function __isset($key) {
107
-		return isset($this->options[$key]);
108
-	}
101
+    /**
102
+     * Remove a property's value
103
+     *
104
+     * @param string $key Property key
105
+     */
106
+    public function __isset($key) {
107
+        return isset($this->options[$key]);
108
+    }
109 109
 
110
-	/**
111
-	 * Remove a property's value
112
-	 *
113
-	 * @param string $key Property key
114
-	 */
115
-	public function __unset($key) {
116
-		if (isset($this->options[$key])) {
117
-			unset($this->options[$key]);
118
-		}
119
-	}
110
+    /**
111
+     * Remove a property's value
112
+     *
113
+     * @param string $key Property key
114
+     */
115
+    public function __unset($key) {
116
+        if (isset($this->options[$key])) {
117
+            unset($this->options[$key]);
118
+        }
119
+    }
120 120
 
121
-	/**#@+
121
+    /**#@+
122 122
 	 * @see request()
123 123
 	 * @param string $url
124 124
 	 * @param array $headers
125 125
 	 * @param array $options
126 126
 	 * @return Requests_Response
127 127
 	 */
128
-	/**
129
-	 * Send a GET request
130
-	 */
131
-	public function get($url, $headers = array(), $options = array()) {
132
-		return $this->request($url, $headers, null, Requests::GET, $options);
133
-	}
128
+    /**
129
+     * Send a GET request
130
+     */
131
+    public function get($url, $headers = array(), $options = array()) {
132
+        return $this->request($url, $headers, null, Requests::GET, $options);
133
+    }
134 134
 
135
-	/**
136
-	 * Send a HEAD request
137
-	 */
138
-	public function head($url, $headers = array(), $options = array()) {
139
-		return $this->request($url, $headers, null, Requests::HEAD, $options);
140
-	}
135
+    /**
136
+     * Send a HEAD request
137
+     */
138
+    public function head($url, $headers = array(), $options = array()) {
139
+        return $this->request($url, $headers, null, Requests::HEAD, $options);
140
+    }
141 141
 
142
-	/**
143
-	 * Send a DELETE request
144
-	 */
145
-	public function delete($url, $headers = array(), $options = array()) {
146
-		return $this->request($url, $headers, null, Requests::DELETE, $options);
147
-	}
148
-	/**#@-*/
142
+    /**
143
+     * Send a DELETE request
144
+     */
145
+    public function delete($url, $headers = array(), $options = array()) {
146
+        return $this->request($url, $headers, null, Requests::DELETE, $options);
147
+    }
148
+    /**#@-*/
149 149
 
150
-	/**#@+
150
+    /**#@+
151 151
 	 * @see request()
152 152
 	 * @param string $url
153 153
 	 * @param array $headers
@@ -155,112 +155,112 @@  discard block
 block discarded – undo
155 155
 	 * @param array $options
156 156
 	 * @return Requests_Response
157 157
 	 */
158
-	/**
159
-	 * Send a POST request
160
-	 */
161
-	public function post($url, $headers = array(), $data = array(), $options = array()) {
162
-		return $this->request($url, $headers, $data, Requests::POST, $options);
163
-	}
158
+    /**
159
+     * Send a POST request
160
+     */
161
+    public function post($url, $headers = array(), $data = array(), $options = array()) {
162
+        return $this->request($url, $headers, $data, Requests::POST, $options);
163
+    }
164 164
 
165
-	/**
166
-	 * Send a PUT request
167
-	 */
168
-	public function put($url, $headers = array(), $data = array(), $options = array()) {
169
-		return $this->request($url, $headers, $data, Requests::PUT, $options);
170
-	}
165
+    /**
166
+     * Send a PUT request
167
+     */
168
+    public function put($url, $headers = array(), $data = array(), $options = array()) {
169
+        return $this->request($url, $headers, $data, Requests::PUT, $options);
170
+    }
171 171
 
172
-	/**
173
-	 * Send a PATCH request
174
-	 *
175
-	 * Note: Unlike {@see post} and {@see put}, `$headers` is required, as the
176
-	 * specification recommends that should send an ETag
177
-	 *
178
-	 * @link https://tools.ietf.org/html/rfc5789
179
-	 */
180
-	public function patch($url, $headers, $data = array(), $options = array()) {
181
-		return $this->request($url, $headers, $data, Requests::PATCH, $options);
182
-	}
183
-	/**#@-*/
172
+    /**
173
+     * Send a PATCH request
174
+     *
175
+     * Note: Unlike {@see post} and {@see put}, `$headers` is required, as the
176
+     * specification recommends that should send an ETag
177
+     *
178
+     * @link https://tools.ietf.org/html/rfc5789
179
+     */
180
+    public function patch($url, $headers, $data = array(), $options = array()) {
181
+        return $this->request($url, $headers, $data, Requests::PATCH, $options);
182
+    }
183
+    /**#@-*/
184 184
 
185
-	/**
186
-	 * Main interface for HTTP requests
187
-	 *
188
-	 * This method initiates a request and sends it via a transport before
189
-	 * parsing.
190
-	 *
191
-	 * @see Requests::request()
192
-	 *
193
-	 * @throws Requests_Exception On invalid URLs (`nonhttp`)
194
-	 *
195
-	 * @param string $url URL to request
196
-	 * @param array $headers Extra headers to send with the request
197
-	 * @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests
198
-	 * @param string $type HTTP request type (use Requests constants)
199
-	 * @param array $options Options for the request (see {@see Requests::request})
200
-	 * @return Requests_Response
201
-	 */
202
-	public function request($url, $headers = array(), $data = array(), $type = Requests::GET, $options = array()) {
203
-		$request = $this->merge_request(compact('url', 'headers', 'data', 'options'));
185
+    /**
186
+     * Main interface for HTTP requests
187
+     *
188
+     * This method initiates a request and sends it via a transport before
189
+     * parsing.
190
+     *
191
+     * @see Requests::request()
192
+     *
193
+     * @throws Requests_Exception On invalid URLs (`nonhttp`)
194
+     *
195
+     * @param string $url URL to request
196
+     * @param array $headers Extra headers to send with the request
197
+     * @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests
198
+     * @param string $type HTTP request type (use Requests constants)
199
+     * @param array $options Options for the request (see {@see Requests::request})
200
+     * @return Requests_Response
201
+     */
202
+    public function request($url, $headers = array(), $data = array(), $type = Requests::GET, $options = array()) {
203
+        $request = $this->merge_request(compact('url', 'headers', 'data', 'options'));
204 204
 
205
-		return Requests::request($request['url'], $request['headers'], $request['data'], $type, $request['options']);
206
-	}
205
+        return Requests::request($request['url'], $request['headers'], $request['data'], $type, $request['options']);
206
+    }
207 207
 
208
-	/**
209
-	 * Send multiple HTTP requests simultaneously
210
-	 *
211
-	 * @see Requests::request_multiple()
212
-	 *
213
-	 * @param array $requests Requests data (see {@see Requests::request_multiple})
214
-	 * @param array $options Global and default options (see {@see Requests::request})
215
-	 * @return array Responses (either Requests_Response or a Requests_Exception object)
216
-	 */
217
-	public function request_multiple($requests, $options = array()) {
218
-		foreach ($requests as $key => $request) {
219
-			$requests[$key] = $this->merge_request($request, false);
220
-		}
208
+    /**
209
+     * Send multiple HTTP requests simultaneously
210
+     *
211
+     * @see Requests::request_multiple()
212
+     *
213
+     * @param array $requests Requests data (see {@see Requests::request_multiple})
214
+     * @param array $options Global and default options (see {@see Requests::request})
215
+     * @return array Responses (either Requests_Response or a Requests_Exception object)
216
+     */
217
+    public function request_multiple($requests, $options = array()) {
218
+        foreach ($requests as $key => $request) {
219
+            $requests[$key] = $this->merge_request($request, false);
220
+        }
221 221
 
222
-		$options = array_merge($this->options, $options);
222
+        $options = array_merge($this->options, $options);
223 223
 
224
-		// Disallow forcing the type, as that's a per request setting
225
-		unset($options['type']);
224
+        // Disallow forcing the type, as that's a per request setting
225
+        unset($options['type']);
226 226
 
227
-		return Requests::request_multiple($requests, $options);
228
-	}
227
+        return Requests::request_multiple($requests, $options);
228
+    }
229 229
 
230
-	/**
231
-	 * Merge a request's data with the default data
232
-	 *
233
-	 * @param array $request Request data (same form as {@see request_multiple})
234
-	 * @param boolean $merge_options Should we merge options as well?
235
-	 * @return array Request data
236
-	 */
237
-	protected function merge_request($request, $merge_options = true) {
238
-		if ($this->url !== null) {
239
-			$request['url'] = Requests_IRI::absolutize($this->url, $request['url']);
240
-			$request['url'] = $request['url']->uri;
241
-		}
230
+    /**
231
+     * Merge a request's data with the default data
232
+     *
233
+     * @param array $request Request data (same form as {@see request_multiple})
234
+     * @param boolean $merge_options Should we merge options as well?
235
+     * @return array Request data
236
+     */
237
+    protected function merge_request($request, $merge_options = true) {
238
+        if ($this->url !== null) {
239
+            $request['url'] = Requests_IRI::absolutize($this->url, $request['url']);
240
+            $request['url'] = $request['url']->uri;
241
+        }
242 242
 
243
-		if (empty($request['headers'])) {
244
-			$request['headers'] = array();
245
-		}
246
-		$request['headers'] = array_merge($this->headers, $request['headers']);
243
+        if (empty($request['headers'])) {
244
+            $request['headers'] = array();
245
+        }
246
+        $request['headers'] = array_merge($this->headers, $request['headers']);
247 247
 
248
-		if (empty($request['data'])) {
249
-			if (is_array($this->data)) {
250
-				$request['data'] = $this->data;
251
-			}
252
-		}
253
-		elseif (is_array($request['data']) && is_array($this->data)) {
254
-			$request['data'] = array_merge($this->data, $request['data']);
255
-		}
248
+        if (empty($request['data'])) {
249
+            if (is_array($this->data)) {
250
+                $request['data'] = $this->data;
251
+            }
252
+        }
253
+        elseif (is_array($request['data']) && is_array($this->data)) {
254
+            $request['data'] = array_merge($this->data, $request['data']);
255
+        }
256 256
 
257
-		if ($merge_options !== false) {
258
-			$request['options'] = array_merge($this->options, $request['options']);
257
+        if ($merge_options !== false) {
258
+            $request['options'] = array_merge($this->options, $request['options']);
259 259
 
260
-			// Disallow forcing the type, as that's a per request setting
261
-			unset($request['options']['type']);
262
-		}
260
+            // Disallow forcing the type, as that's a per request setting
261
+            unset($request['options']['type']);
262
+        }
263 263
 
264
-		return $request;
265
-	}
264
+        return $request;
265
+    }
266 266
 }
Please login to merge, or discard this patch.
plugin/buycourses/src/Requests/Cookie.php 1 patch
Indentation   +484 added lines, -484 removed lines patch added patch discarded remove patch
@@ -13,488 +13,488 @@
 block discarded – undo
13 13
  * @subpackage Cookies
14 14
  */
15 15
 class Requests_Cookie {
16
-	/**
17
-	 * Cookie name.
18
-	 *
19
-	 * @var string
20
-	 */
21
-	public $name;
22
-
23
-	/**
24
-	 * Cookie value.
25
-	 *
26
-	 * @var string
27
-	 */
28
-	public $value;
29
-
30
-	/**
31
-	 * Cookie attributes
32
-	 *
33
-	 * Valid keys are (currently) path, domain, expires, max-age, secure and
34
-	 * httponly.
35
-	 *
36
-	 * @var Requests_Utility_CaseInsensitiveDictionary|array Array-like object
37
-	 */
38
-	public $attributes = array();
39
-
40
-	/**
41
-	 * Cookie flags
42
-	 *
43
-	 * Valid keys are (currently) creation, last-access, persistent and
44
-	 * host-only.
45
-	 *
46
-	 * @var array
47
-	 */
48
-	public $flags = array();
49
-
50
-	/**
51
-	 * Reference time for relative calculations
52
-	 *
53
-	 * This is used in place of `time()` when calculating Max-Age expiration and
54
-	 * checking time validity.
55
-	 *
56
-	 * @var int
57
-	 */
58
-	public $reference_time = 0;
59
-
60
-	/**
61
-	 * Create a new cookie object
62
-	 *
63
-	 * @param string $name
64
-	 * @param string $value
65
-	 * @param array|Requests_Utility_CaseInsensitiveDictionary $attributes Associative array of attribute data
66
-	 */
67
-	public function __construct($name, $value, $attributes = array(), $flags = array(), $reference_time = null) {
68
-		$this->name = $name;
69
-		$this->value = $value;
70
-		$this->attributes = $attributes;
71
-		$default_flags = array(
72
-			'creation' => time(),
73
-			'last-access' => time(),
74
-			'persistent' => false,
75
-			'host-only' => true,
76
-		);
77
-		$this->flags = array_merge($default_flags, $flags);
78
-
79
-		$this->reference_time = time();
80
-		if ($reference_time !== null) {
81
-			$this->reference_time = $reference_time;
82
-		}
83
-
84
-		$this->normalize();
85
-	}
86
-
87
-	/**
88
-	 * Check if a cookie is expired.
89
-	 *
90
-	 * Checks the age against $this->reference_time to determine if the cookie
91
-	 * is expired.
92
-	 *
93
-	 * @return boolean True if expired, false if time is valid.
94
-	 */
95
-	public function is_expired() {
96
-		// RFC6265, s. 4.1.2.2:
97
-		// If a cookie has both the Max-Age and the Expires attribute, the Max-
98
-		// Age attribute has precedence and controls the expiration date of the
99
-		// cookie.
100
-		if (isset($this->attributes['max-age'])) {
101
-			$max_age = $this->attributes['max-age'];
102
-			return $max_age < $this->reference_time;
103
-		}
104
-
105
-		if (isset($this->attributes['expires'])) {
106
-			$expires = $this->attributes['expires'];
107
-			return $expires < $this->reference_time;
108
-		}
109
-
110
-		return false;
111
-	}
112
-
113
-	/**
114
-	 * Check if a cookie is valid for a given URI
115
-	 *
116
-	 * @param Requests_IRI $uri URI to check
117
-	 * @return boolean Whether the cookie is valid for the given URI
118
-	 */
119
-	public function uri_matches(Requests_IRI $uri) {
120
-		if (!$this->domain_matches($uri->host)) {
121
-			return false;
122
-		}
123
-
124
-		if (!$this->path_matches($uri->path)) {
125
-			return false;
126
-		}
127
-
128
-		return empty($this->attributes['secure']) || $uri->scheme === 'https';
129
-	}
130
-
131
-	/**
132
-	 * Check if a cookie is valid for a given domain
133
-	 *
134
-	 * @param string $string Domain to check
135
-	 * @return boolean Whether the cookie is valid for the given domain
136
-	 */
137
-	public function domain_matches($string) {
138
-		if (!isset($this->attributes['domain'])) {
139
-			// Cookies created manually; cookies created by Requests will set
140
-			// the domain to the requested domain
141
-			return true;
142
-		}
143
-
144
-		$domain_string = $this->attributes['domain'];
145
-		if ($domain_string === $string) {
146
-			// The domain string and the string are identical.
147
-			return true;
148
-		}
149
-
150
-		// If the cookie is marked as host-only and we don't have an exact
151
-		// match, reject the cookie
152
-		if ($this->flags['host-only'] === true) {
153
-			return false;
154
-		}
155
-
156
-		if (strlen($string) <= strlen($domain_string)) {
157
-			// For obvious reasons, the string cannot be a suffix if the domain
158
-			// is shorter than the domain string
159
-			return false;
160
-		}
161
-
162
-		if (substr($string, -1 * strlen($domain_string)) !== $domain_string) {
163
-			// The domain string should be a suffix of the string.
164
-			return false;
165
-		}
166
-
167
-		$prefix = substr($string, 0, strlen($string) - strlen($domain_string));
168
-		if (substr($prefix, -1) !== '.') {
169
-			// The last character of the string that is not included in the
170
-			// domain string should be a %x2E (".") character.
171
-			return false;
172
-		}
173
-
174
-		// The string should be a host name (i.e., not an IP address).
175
-		return !preg_match('#^(.+\.)\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$#', $string);
176
-	}
177
-
178
-	/**
179
-	 * Check if a cookie is valid for a given path
180
-	 *
181
-	 * From the path-match check in RFC 6265 section 5.1.4
182
-	 *
183
-	 * @param string $request_path Path to check
184
-	 * @return boolean Whether the cookie is valid for the given path
185
-	 */
186
-	public function path_matches($request_path) {
187
-		if (empty($request_path)) {
188
-			// Normalize empty path to root
189
-			$request_path = '/';
190
-		}
191
-
192
-		if (!isset($this->attributes['path'])) {
193
-			// Cookies created manually; cookies created by Requests will set
194
-			// the path to the requested path
195
-			return true;
196
-		}
197
-
198
-		$cookie_path = $this->attributes['path'];
199
-
200
-		if ($cookie_path === $request_path) {
201
-			// The cookie-path and the request-path are identical.
202
-			return true;
203
-		}
204
-
205
-		if (strlen($request_path) > strlen($cookie_path) && substr($request_path, 0, strlen($cookie_path)) === $cookie_path) {
206
-			if (substr($cookie_path, -1) === '/') {
207
-				// The cookie-path is a prefix of the request-path, and the last
208
-				// character of the cookie-path is %x2F ("/").
209
-				return true;
210
-			}
211
-
212
-			if (substr($request_path, strlen($cookie_path), 1) === '/') {
213
-				// The cookie-path is a prefix of the request-path, and the
214
-				// first character of the request-path that is not included in
215
-				// the cookie-path is a %x2F ("/") character.
216
-				return true;
217
-			}
218
-		}
219
-
220
-		return false;
221
-	}
222
-
223
-	/**
224
-	 * Normalize cookie and attributes
225
-	 *
226
-	 * @return boolean Whether the cookie was successfully normalized
227
-	 */
228
-	public function normalize() {
229
-		foreach ($this->attributes as $key => $value) {
230
-			$orig_value = $value;
231
-			$value = $this->normalize_attribute($key, $value);
232
-			if ($value === null) {
233
-				unset($this->attributes[$key]);
234
-				continue;
235
-			}
236
-
237
-			if ($value !== $orig_value) {
238
-				$this->attributes[$key] = $value;
239
-			}
240
-		}
241
-
242
-		return true;
243
-	}
244
-
245
-	/**
246
-	 * Parse an individual cookie attribute
247
-	 *
248
-	 * Handles parsing individual attributes from the cookie values.
249
-	 *
250
-	 * @param string $name Attribute name
251
-	 * @param string|boolean $value Attribute value (string value, or true if empty/flag)
252
-	 * @return mixed Value if available, or null if the attribute value is invalid (and should be skipped)
253
-	 */
254
-	protected function normalize_attribute($name, $value) {
255
-		switch (strtolower($name)) {
256
-			case 'expires':
257
-				// Expiration parsing, as per RFC 6265 section 5.2.1
258
-				if (is_int($value)) {
259
-					return $value;
260
-				}
261
-
262
-				$expiry_time = strtotime($value);
263
-				if ($expiry_time === false) {
264
-					return null;
265
-				}
266
-
267
-				return $expiry_time;
268
-
269
-			case 'max-age':
270
-				// Expiration parsing, as per RFC 6265 section 5.2.2
271
-				if (is_int($value)) {
272
-					return $value;
273
-				}
274
-
275
-				// Check that we have a valid age
276
-				if (!preg_match('/^-?\d+$/', $value)) {
277
-					return null;
278
-				}
279
-
280
-				$delta_seconds = (int) $value;
281
-				if ($delta_seconds <= 0) {
282
-					$expiry_time = 0;
283
-				}
284
-				else {
285
-					$expiry_time = $this->reference_time + $delta_seconds;
286
-				}
287
-
288
-				return $expiry_time;
289
-
290
-			case 'domain':
291
-				// Domain normalization, as per RFC 6265 section 5.2.3
292
-				if ($value[0] === '.') {
293
-					$value = substr($value, 1);
294
-				}
295
-
296
-				return $value;
297
-
298
-			default:
299
-				return $value;
300
-		}
301
-	}
302
-
303
-	/**
304
-	 * Format a cookie for a Cookie header
305
-	 *
306
-	 * This is used when sending cookies to a server.
307
-	 *
308
-	 * @return string Cookie formatted for Cookie header
309
-	 */
310
-	public function format_for_header() {
311
-		return sprintf('%s=%s', $this->name, $this->value);
312
-	}
313
-
314
-	/**
315
-	 * Format a cookie for a Cookie header
316
-	 *
317
-	 * @codeCoverageIgnore
318
-	 * @deprecated Use {@see Requests_Cookie::format_for_header}
319
-	 * @return string
320
-	 */
321
-	public function formatForHeader() {
322
-		return $this->format_for_header();
323
-	}
324
-
325
-	/**
326
-	 * Format a cookie for a Set-Cookie header
327
-	 *
328
-	 * This is used when sending cookies to clients. This isn't really
329
-	 * applicable to client-side usage, but might be handy for debugging.
330
-	 *
331
-	 * @return string Cookie formatted for Set-Cookie header
332
-	 */
333
-	public function format_for_set_cookie() {
334
-		$header_value = $this->format_for_header();
335
-		if (!empty($this->attributes)) {
336
-			$parts = array();
337
-			foreach ($this->attributes as $key => $value) {
338
-				// Ignore non-associative attributes
339
-				if (is_numeric($key)) {
340
-					$parts[] = $value;
341
-				}
342
-				else {
343
-					$parts[] = sprintf('%s=%s', $key, $value);
344
-				}
345
-			}
346
-
347
-			$header_value .= '; ' . implode('; ', $parts);
348
-		}
349
-		return $header_value;
350
-	}
351
-
352
-	/**
353
-	 * Format a cookie for a Set-Cookie header
354
-	 *
355
-	 * @codeCoverageIgnore
356
-	 * @deprecated Use {@see Requests_Cookie::format_for_set_cookie}
357
-	 * @return string
358
-	 */
359
-	public function formatForSetCookie() {
360
-		return $this->format_for_set_cookie();
361
-	}
362
-
363
-	/**
364
-	 * Get the cookie value
365
-	 *
366
-	 * Attributes and other data can be accessed via methods.
367
-	 */
368
-	public function __toString() {
369
-		return $this->value;
370
-	}
371
-
372
-	/**
373
-	 * Parse a cookie string into a cookie object
374
-	 *
375
-	 * Based on Mozilla's parsing code in Firefox and related projects, which
376
-	 * is an intentional deviation from RFC 2109 and RFC 2616. RFC 6265
377
-	 * specifies some of this handling, but not in a thorough manner.
378
-	 *
379
-	 * @param string Cookie header value (from a Set-Cookie header)
380
-	 * @return Requests_Cookie Parsed cookie object
381
-	 */
382
-	public static function parse($string, $name = '', $reference_time = null) {
383
-		$parts = explode(';', $string);
384
-		$kvparts = array_shift($parts);
385
-
386
-		if (!empty($name)) {
387
-			$value = $string;
388
-		}
389
-		elseif (strpos($kvparts, '=') === false) {
390
-			// Some sites might only have a value without the equals separator.
391
-			// Deviate from RFC 6265 and pretend it was actually a blank name
392
-			// (`=foo`)
393
-			//
394
-			// https://bugzilla.mozilla.org/show_bug.cgi?id=169091
395
-			$name = '';
396
-			$value = $kvparts;
397
-		}
398
-		else {
399
-			list($name, $value) = explode('=', $kvparts, 2);
400
-		}
401
-		$name = trim($name);
402
-		$value = trim($value);
403
-
404
-		// Attribute key are handled case-insensitively
405
-		$attributes = new Requests_Utility_CaseInsensitiveDictionary();
406
-
407
-		if (!empty($parts)) {
408
-			foreach ($parts as $part) {
409
-				if (strpos($part, '=') === false) {
410
-					$part_key = $part;
411
-					$part_value = true;
412
-				}
413
-				else {
414
-					list($part_key, $part_value) = explode('=', $part, 2);
415
-					$part_value = trim($part_value);
416
-				}
417
-
418
-				$part_key = trim($part_key);
419
-				$attributes[$part_key] = $part_value;
420
-			}
421
-		}
422
-
423
-		return new Requests_Cookie($name, $value, $attributes, array(), $reference_time);
424
-	}
425
-
426
-	/**
427
-	 * Parse all Set-Cookie headers from request headers
428
-	 *
429
-	 * @param Requests_Response_Headers $headers Headers to parse from
430
-	 * @param Requests_IRI|null $origin URI for comparing cookie origins
431
-	 * @param int|null $time Reference time for expiration calculation
432
-	 * @return array
433
-	 */
434
-	public static function parse_from_headers(Requests_Response_Headers $headers, Requests_IRI $origin = null, $time = null) {
435
-		$cookie_headers = $headers->getValues('Set-Cookie');
436
-		if (empty($cookie_headers)) {
437
-			return array();
438
-		}
439
-
440
-		$cookies = array();
441
-		foreach ($cookie_headers as $header) {
442
-			$parsed = self::parse($header, '', $time);
443
-
444
-			// Default domain/path attributes
445
-			if (empty($parsed->attributes['domain']) && !empty($origin)) {
446
-				$parsed->attributes['domain'] = $origin->host;
447
-				$parsed->flags['host-only'] = true;
448
-			}
449
-			else {
450
-				$parsed->flags['host-only'] = false;
451
-			}
452
-
453
-			$path_is_valid = (!empty($parsed->attributes['path']) && $parsed->attributes['path'][0] === '/');
454
-			if (!$path_is_valid && !empty($origin)) {
455
-				$path = $origin->path;
456
-
457
-				// Default path normalization as per RFC 6265 section 5.1.4
458
-				if (substr($path, 0, 1) !== '/') {
459
-					// If the uri-path is empty or if the first character of
460
-					// the uri-path is not a %x2F ("/") character, output
461
-					// %x2F ("/") and skip the remaining steps.
462
-					$path = '/';
463
-				}
464
-				elseif (substr_count($path, '/') === 1) {
465
-					// If the uri-path contains no more than one %x2F ("/")
466
-					// character, output %x2F ("/") and skip the remaining
467
-					// step.
468
-					$path = '/';
469
-				}
470
-				else {
471
-					// Output the characters of the uri-path from the first
472
-					// character up to, but not including, the right-most
473
-					// %x2F ("/").
474
-					$path = substr($path, 0, strrpos($path, '/'));
475
-				}
476
-				$parsed->attributes['path'] = $path;
477
-			}
478
-
479
-			// Reject invalid cookie domains
480
-			if (!empty($origin) && !$parsed->domain_matches($origin->host)) {
481
-				continue;
482
-			}
483
-
484
-			$cookies[$parsed->name] = $parsed;
485
-		}
486
-
487
-		return $cookies;
488
-	}
489
-
490
-	/**
491
-	 * Parse all Set-Cookie headers from request headers
492
-	 *
493
-	 * @codeCoverageIgnore
494
-	 * @deprecated Use {@see Requests_Cookie::parse_from_headers}
495
-	 * @return string
496
-	 */
497
-	public static function parseFromHeaders(Requests_Response_Headers $headers) {
498
-		return self::parse_from_headers($headers);
499
-	}
16
+    /**
17
+     * Cookie name.
18
+     *
19
+     * @var string
20
+     */
21
+    public $name;
22
+
23
+    /**
24
+     * Cookie value.
25
+     *
26
+     * @var string
27
+     */
28
+    public $value;
29
+
30
+    /**
31
+     * Cookie attributes
32
+     *
33
+     * Valid keys are (currently) path, domain, expires, max-age, secure and
34
+     * httponly.
35
+     *
36
+     * @var Requests_Utility_CaseInsensitiveDictionary|array Array-like object
37
+     */
38
+    public $attributes = array();
39
+
40
+    /**
41
+     * Cookie flags
42
+     *
43
+     * Valid keys are (currently) creation, last-access, persistent and
44
+     * host-only.
45
+     *
46
+     * @var array
47
+     */
48
+    public $flags = array();
49
+
50
+    /**
51
+     * Reference time for relative calculations
52
+     *
53
+     * This is used in place of `time()` when calculating Max-Age expiration and
54
+     * checking time validity.
55
+     *
56
+     * @var int
57
+     */
58
+    public $reference_time = 0;
59
+
60
+    /**
61
+     * Create a new cookie object
62
+     *
63
+     * @param string $name
64
+     * @param string $value
65
+     * @param array|Requests_Utility_CaseInsensitiveDictionary $attributes Associative array of attribute data
66
+     */
67
+    public function __construct($name, $value, $attributes = array(), $flags = array(), $reference_time = null) {
68
+        $this->name = $name;
69
+        $this->value = $value;
70
+        $this->attributes = $attributes;
71
+        $default_flags = array(
72
+            'creation' => time(),
73
+            'last-access' => time(),
74
+            'persistent' => false,
75
+            'host-only' => true,
76
+        );
77
+        $this->flags = array_merge($default_flags, $flags);
78
+
79
+        $this->reference_time = time();
80
+        if ($reference_time !== null) {
81
+            $this->reference_time = $reference_time;
82
+        }
83
+
84
+        $this->normalize();
85
+    }
86
+
87
+    /**
88
+     * Check if a cookie is expired.
89
+     *
90
+     * Checks the age against $this->reference_time to determine if the cookie
91
+     * is expired.
92
+     *
93
+     * @return boolean True if expired, false if time is valid.
94
+     */
95
+    public function is_expired() {
96
+        // RFC6265, s. 4.1.2.2:
97
+        // If a cookie has both the Max-Age and the Expires attribute, the Max-
98
+        // Age attribute has precedence and controls the expiration date of the
99
+        // cookie.
100
+        if (isset($this->attributes['max-age'])) {
101
+            $max_age = $this->attributes['max-age'];
102
+            return $max_age < $this->reference_time;
103
+        }
104
+
105
+        if (isset($this->attributes['expires'])) {
106
+            $expires = $this->attributes['expires'];
107
+            return $expires < $this->reference_time;
108
+        }
109
+
110
+        return false;
111
+    }
112
+
113
+    /**
114
+     * Check if a cookie is valid for a given URI
115
+     *
116
+     * @param Requests_IRI $uri URI to check
117
+     * @return boolean Whether the cookie is valid for the given URI
118
+     */
119
+    public function uri_matches(Requests_IRI $uri) {
120
+        if (!$this->domain_matches($uri->host)) {
121
+            return false;
122
+        }
123
+
124
+        if (!$this->path_matches($uri->path)) {
125
+            return false;
126
+        }
127
+
128
+        return empty($this->attributes['secure']) || $uri->scheme === 'https';
129
+    }
130
+
131
+    /**
132
+     * Check if a cookie is valid for a given domain
133
+     *
134
+     * @param string $string Domain to check
135
+     * @return boolean Whether the cookie is valid for the given domain
136
+     */
137
+    public function domain_matches($string) {
138
+        if (!isset($this->attributes['domain'])) {
139
+            // Cookies created manually; cookies created by Requests will set
140
+            // the domain to the requested domain
141
+            return true;
142
+        }
143
+
144
+        $domain_string = $this->attributes['domain'];
145
+        if ($domain_string === $string) {
146
+            // The domain string and the string are identical.
147
+            return true;
148
+        }
149
+
150
+        // If the cookie is marked as host-only and we don't have an exact
151
+        // match, reject the cookie
152
+        if ($this->flags['host-only'] === true) {
153
+            return false;
154
+        }
155
+
156
+        if (strlen($string) <= strlen($domain_string)) {
157
+            // For obvious reasons, the string cannot be a suffix if the domain
158
+            // is shorter than the domain string
159
+            return false;
160
+        }
161
+
162
+        if (substr($string, -1 * strlen($domain_string)) !== $domain_string) {
163
+            // The domain string should be a suffix of the string.
164
+            return false;
165
+        }
166
+
167
+        $prefix = substr($string, 0, strlen($string) - strlen($domain_string));
168
+        if (substr($prefix, -1) !== '.') {
169
+            // The last character of the string that is not included in the
170
+            // domain string should be a %x2E (".") character.
171
+            return false;
172
+        }
173
+
174
+        // The string should be a host name (i.e., not an IP address).
175
+        return !preg_match('#^(.+\.)\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$#', $string);
176
+    }
177
+
178
+    /**
179
+     * Check if a cookie is valid for a given path
180
+     *
181
+     * From the path-match check in RFC 6265 section 5.1.4
182
+     *
183
+     * @param string $request_path Path to check
184
+     * @return boolean Whether the cookie is valid for the given path
185
+     */
186
+    public function path_matches($request_path) {
187
+        if (empty($request_path)) {
188
+            // Normalize empty path to root
189
+            $request_path = '/';
190
+        }
191
+
192
+        if (!isset($this->attributes['path'])) {
193
+            // Cookies created manually; cookies created by Requests will set
194
+            // the path to the requested path
195
+            return true;
196
+        }
197
+
198
+        $cookie_path = $this->attributes['path'];
199
+
200
+        if ($cookie_path === $request_path) {
201
+            // The cookie-path and the request-path are identical.
202
+            return true;
203
+        }
204
+
205
+        if (strlen($request_path) > strlen($cookie_path) && substr($request_path, 0, strlen($cookie_path)) === $cookie_path) {
206
+            if (substr($cookie_path, -1) === '/') {
207
+                // The cookie-path is a prefix of the request-path, and the last
208
+                // character of the cookie-path is %x2F ("/").
209
+                return true;
210
+            }
211
+
212
+            if (substr($request_path, strlen($cookie_path), 1) === '/') {
213
+                // The cookie-path is a prefix of the request-path, and the
214
+                // first character of the request-path that is not included in
215
+                // the cookie-path is a %x2F ("/") character.
216
+                return true;
217
+            }
218
+        }
219
+
220
+        return false;
221
+    }
222
+
223
+    /**
224
+     * Normalize cookie and attributes
225
+     *
226
+     * @return boolean Whether the cookie was successfully normalized
227
+     */
228
+    public function normalize() {
229
+        foreach ($this->attributes as $key => $value) {
230
+            $orig_value = $value;
231
+            $value = $this->normalize_attribute($key, $value);
232
+            if ($value === null) {
233
+                unset($this->attributes[$key]);
234
+                continue;
235
+            }
236
+
237
+            if ($value !== $orig_value) {
238
+                $this->attributes[$key] = $value;
239
+            }
240
+        }
241
+
242
+        return true;
243
+    }
244
+
245
+    /**
246
+     * Parse an individual cookie attribute
247
+     *
248
+     * Handles parsing individual attributes from the cookie values.
249
+     *
250
+     * @param string $name Attribute name
251
+     * @param string|boolean $value Attribute value (string value, or true if empty/flag)
252
+     * @return mixed Value if available, or null if the attribute value is invalid (and should be skipped)
253
+     */
254
+    protected function normalize_attribute($name, $value) {
255
+        switch (strtolower($name)) {
256
+            case 'expires':
257
+                // Expiration parsing, as per RFC 6265 section 5.2.1
258
+                if (is_int($value)) {
259
+                    return $value;
260
+                }
261
+
262
+                $expiry_time = strtotime($value);
263
+                if ($expiry_time === false) {
264
+                    return null;
265
+                }
266
+
267
+                return $expiry_time;
268
+
269
+            case 'max-age':
270
+                // Expiration parsing, as per RFC 6265 section 5.2.2
271
+                if (is_int($value)) {
272
+                    return $value;
273
+                }
274
+
275
+                // Check that we have a valid age
276
+                if (!preg_match('/^-?\d+$/', $value)) {
277
+                    return null;
278
+                }
279
+
280
+                $delta_seconds = (int) $value;
281
+                if ($delta_seconds <= 0) {
282
+                    $expiry_time = 0;
283
+                }
284
+                else {
285
+                    $expiry_time = $this->reference_time + $delta_seconds;
286
+                }
287
+
288
+                return $expiry_time;
289
+
290
+            case 'domain':
291
+                // Domain normalization, as per RFC 6265 section 5.2.3
292
+                if ($value[0] === '.') {
293
+                    $value = substr($value, 1);
294
+                }
295
+
296
+                return $value;
297
+
298
+            default:
299
+                return $value;
300
+        }
301
+    }
302
+
303
+    /**
304
+     * Format a cookie for a Cookie header
305
+     *
306
+     * This is used when sending cookies to a server.
307
+     *
308
+     * @return string Cookie formatted for Cookie header
309
+     */
310
+    public function format_for_header() {
311
+        return sprintf('%s=%s', $this->name, $this->value);
312
+    }
313
+
314
+    /**
315
+     * Format a cookie for a Cookie header
316
+     *
317
+     * @codeCoverageIgnore
318
+     * @deprecated Use {@see Requests_Cookie::format_for_header}
319
+     * @return string
320
+     */
321
+    public function formatForHeader() {
322
+        return $this->format_for_header();
323
+    }
324
+
325
+    /**
326
+     * Format a cookie for a Set-Cookie header
327
+     *
328
+     * This is used when sending cookies to clients. This isn't really
329
+     * applicable to client-side usage, but might be handy for debugging.
330
+     *
331
+     * @return string Cookie formatted for Set-Cookie header
332
+     */
333
+    public function format_for_set_cookie() {
334
+        $header_value = $this->format_for_header();
335
+        if (!empty($this->attributes)) {
336
+            $parts = array();
337
+            foreach ($this->attributes as $key => $value) {
338
+                // Ignore non-associative attributes
339
+                if (is_numeric($key)) {
340
+                    $parts[] = $value;
341
+                }
342
+                else {
343
+                    $parts[] = sprintf('%s=%s', $key, $value);
344
+                }
345
+            }
346
+
347
+            $header_value .= '; ' . implode('; ', $parts);
348
+        }
349
+        return $header_value;
350
+    }
351
+
352
+    /**
353
+     * Format a cookie for a Set-Cookie header
354
+     *
355
+     * @codeCoverageIgnore
356
+     * @deprecated Use {@see Requests_Cookie::format_for_set_cookie}
357
+     * @return string
358
+     */
359
+    public function formatForSetCookie() {
360
+        return $this->format_for_set_cookie();
361
+    }
362
+
363
+    /**
364
+     * Get the cookie value
365
+     *
366
+     * Attributes and other data can be accessed via methods.
367
+     */
368
+    public function __toString() {
369
+        return $this->value;
370
+    }
371
+
372
+    /**
373
+     * Parse a cookie string into a cookie object
374
+     *
375
+     * Based on Mozilla's parsing code in Firefox and related projects, which
376
+     * is an intentional deviation from RFC 2109 and RFC 2616. RFC 6265
377
+     * specifies some of this handling, but not in a thorough manner.
378
+     *
379
+     * @param string Cookie header value (from a Set-Cookie header)
380
+     * @return Requests_Cookie Parsed cookie object
381
+     */
382
+    public static function parse($string, $name = '', $reference_time = null) {
383
+        $parts = explode(';', $string);
384
+        $kvparts = array_shift($parts);
385
+
386
+        if (!empty($name)) {
387
+            $value = $string;
388
+        }
389
+        elseif (strpos($kvparts, '=') === false) {
390
+            // Some sites might only have a value without the equals separator.
391
+            // Deviate from RFC 6265 and pretend it was actually a blank name
392
+            // (`=foo`)
393
+            //
394
+            // https://bugzilla.mozilla.org/show_bug.cgi?id=169091
395
+            $name = '';
396
+            $value = $kvparts;
397
+        }
398
+        else {
399
+            list($name, $value) = explode('=', $kvparts, 2);
400
+        }
401
+        $name = trim($name);
402
+        $value = trim($value);
403
+
404
+        // Attribute key are handled case-insensitively
405
+        $attributes = new Requests_Utility_CaseInsensitiveDictionary();
406
+
407
+        if (!empty($parts)) {
408
+            foreach ($parts as $part) {
409
+                if (strpos($part, '=') === false) {
410
+                    $part_key = $part;
411
+                    $part_value = true;
412
+                }
413
+                else {
414
+                    list($part_key, $part_value) = explode('=', $part, 2);
415
+                    $part_value = trim($part_value);
416
+                }
417
+
418
+                $part_key = trim($part_key);
419
+                $attributes[$part_key] = $part_value;
420
+            }
421
+        }
422
+
423
+        return new Requests_Cookie($name, $value, $attributes, array(), $reference_time);
424
+    }
425
+
426
+    /**
427
+     * Parse all Set-Cookie headers from request headers
428
+     *
429
+     * @param Requests_Response_Headers $headers Headers to parse from
430
+     * @param Requests_IRI|null $origin URI for comparing cookie origins
431
+     * @param int|null $time Reference time for expiration calculation
432
+     * @return array
433
+     */
434
+    public static function parse_from_headers(Requests_Response_Headers $headers, Requests_IRI $origin = null, $time = null) {
435
+        $cookie_headers = $headers->getValues('Set-Cookie');
436
+        if (empty($cookie_headers)) {
437
+            return array();
438
+        }
439
+
440
+        $cookies = array();
441
+        foreach ($cookie_headers as $header) {
442
+            $parsed = self::parse($header, '', $time);
443
+
444
+            // Default domain/path attributes
445
+            if (empty($parsed->attributes['domain']) && !empty($origin)) {
446
+                $parsed->attributes['domain'] = $origin->host;
447
+                $parsed->flags['host-only'] = true;
448
+            }
449
+            else {
450
+                $parsed->flags['host-only'] = false;
451
+            }
452
+
453
+            $path_is_valid = (!empty($parsed->attributes['path']) && $parsed->attributes['path'][0] === '/');
454
+            if (!$path_is_valid && !empty($origin)) {
455
+                $path = $origin->path;
456
+
457
+                // Default path normalization as per RFC 6265 section 5.1.4
458
+                if (substr($path, 0, 1) !== '/') {
459
+                    // If the uri-path is empty or if the first character of
460
+                    // the uri-path is not a %x2F ("/") character, output
461
+                    // %x2F ("/") and skip the remaining steps.
462
+                    $path = '/';
463
+                }
464
+                elseif (substr_count($path, '/') === 1) {
465
+                    // If the uri-path contains no more than one %x2F ("/")
466
+                    // character, output %x2F ("/") and skip the remaining
467
+                    // step.
468
+                    $path = '/';
469
+                }
470
+                else {
471
+                    // Output the characters of the uri-path from the first
472
+                    // character up to, but not including, the right-most
473
+                    // %x2F ("/").
474
+                    $path = substr($path, 0, strrpos($path, '/'));
475
+                }
476
+                $parsed->attributes['path'] = $path;
477
+            }
478
+
479
+            // Reject invalid cookie domains
480
+            if (!empty($origin) && !$parsed->domain_matches($origin->host)) {
481
+                continue;
482
+            }
483
+
484
+            $cookies[$parsed->name] = $parsed;
485
+        }
486
+
487
+        return $cookies;
488
+    }
489
+
490
+    /**
491
+     * Parse all Set-Cookie headers from request headers
492
+     *
493
+     * @codeCoverageIgnore
494
+     * @deprecated Use {@see Requests_Cookie::parse_from_headers}
495
+     * @return string
496
+     */
497
+    public static function parseFromHeaders(Requests_Response_Headers $headers) {
498
+        return self::parse_from_headers($headers);
499
+    }
500 500
 }
Please login to merge, or discard this patch.
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.