GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — develop (#1814)
by
unknown
11:57
created
classes/xml/xmlquery/tags/table/HintTableTag.class.php 1 patch
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -42,7 +42,7 @@  discard block
 block discarded – undo
42 42
 				, $this->alias ? ', \'' . $dbParser->escape($this->alias) . '\'' : ', null'
43 43
 				//, ', \'' . $dbParser->escape($this->index->name) .'\', \'' . $this->index->type .'\''
44 44
 		);
45
-		foreach($this->index as $indx)
45
+		foreach ($this->index as $indx)
46 46
 		{
47 47
 			$result .= "new IndexHint(";
48 48
 			$result .= '\'' . $dbParser->escape($indx->name) . '\', \'' . $indx->type . '\'' . ') , ';
@@ -54,7 +54,7 @@  discard block
 block discarded – undo
54 54
 
55 55
 	function getArguments()
56 56
 	{
57
-		if(!isset($this->conditionsTag))
57
+		if (!isset($this->conditionsTag))
58 58
 		{
59 59
 			return array();
60 60
 		}
Please login to merge, or discard this patch.
classes/xml/xmlquery/tags/table/TableTag.class.php 1 patch
Spacing   +5 added lines, -5 removed lines patch added patch discarded remove patch
@@ -74,7 +74,7 @@  discard block
 block discarded – undo
74 74
 		$this->name = $dbParser->parseTableName($table->attrs->name);
75 75
 
76 76
 		$this->alias = $table->attrs->alias;
77
-		if(!$this->alias)
77
+		if (!$this->alias)
78 78
 		{
79 79
 			$this->alias = $table->attrs->name;
80 80
 		}
@@ -83,7 +83,7 @@  discard block
 block discarded – undo
83 83
 
84 84
 		$this->conditions = $table->conditions;
85 85
 
86
-		if($this->isJoinTable())
86
+		if ($this->isJoinTable())
87 87
 		{
88 88
 			$this->conditionsTag = new JoinConditionsTag($this->conditions);
89 89
 		}
@@ -92,7 +92,7 @@  discard block
 block discarded – undo
92 92
 	function isJoinTable()
93 93
 	{
94 94
 		$joinList = array('left join' => 1, 'left outer join' => 1, 'right join' => 1, 'right outer join' => 1);
95
-		if(isset($joinList[$this->join_type]) && count($this->conditions))
95
+		if (isset($joinList[$this->join_type]) && count($this->conditions))
96 96
 		{
97 97
 			return true;
98 98
 		}
@@ -119,7 +119,7 @@  discard block
 block discarded – undo
119 119
 	{
120 120
 		$dbParser = DB::getParser();
121 121
 
122
-		if($this->isJoinTable())
122
+		if ($this->isJoinTable())
123 123
 		{
124 124
 			return sprintf('new JoinTable(\'%s\', \'%s\', "%s", %s)'
125 125
 							, $dbParser->escape($this->name)
@@ -133,7 +133,7 @@  discard block
 block discarded – undo
133 133
 
134 134
 	function getArguments()
135 135
 	{
136
-		if(!isset($this->conditionsTag))
136
+		if (!isset($this->conditionsTag))
137 137
 		{
138 138
 			return array();
139 139
 		}
Please login to merge, or discard this patch.
classes/xml/xmlquery/tags/table/TablesTag.class.php 2 patches
Spacing   +11 added lines, -11 removed lines patch added patch discarded remove patch
@@ -38,21 +38,21 @@  discard block
 block discarded – undo
38 38
 		$this->tables = array();
39 39
 
40 40
 		$xml_tables = $xml_tables_tag->table;
41
-		if(!is_array($xml_tables))
41
+		if (!is_array($xml_tables))
42 42
 		{
43 43
 			$xml_tables = array($xml_tables);
44 44
 		}
45 45
 
46
-		if($xml_index_hints_tag)
46
+		if ($xml_index_hints_tag)
47 47
 		{
48 48
 			$index_nodes = $xml_index_hints_tag->index;
49
-			if(!is_array($index_nodes))
49
+			if (!is_array($index_nodes))
50 50
 			{
51 51
 				$index_nodes = array($index_nodes);
52 52
 			}
53
-			foreach($index_nodes as $index_node)
53
+			foreach ($index_nodes as $index_node)
54 54
 			{
55
-				if(!isset($indexes[$index_node->attrs->table]))
55
+				if (!isset($indexes[$index_node->attrs->table]))
56 56
 				{
57 57
 					$indexes[$index_node->attrs->table] = array();
58 58
 				}
@@ -63,15 +63,15 @@  discard block
 block discarded – undo
63 63
 			}
64 64
 		}
65 65
 
66
-		foreach($xml_tables as $tag)
66
+		foreach ($xml_tables as $tag)
67 67
 		{
68
-			if($tag->attrs->query == 'true')
68
+			if ($tag->attrs->query == 'true')
69 69
 			{
70 70
 				$this->tables[] = new QueryTag($tag, true);
71 71
 			}
72 72
 			else
73 73
 			{
74
-				if(isset($indexes[$tag->attrs->name]) && $indexes[$tag->attrs->name])
74
+				if (isset($indexes[$tag->attrs->name]) && $indexes[$tag->attrs->name])
75 75
 				{
76 76
 					$this->tables[] = new HintTableTag($tag, $indexes[$tag->attrs->name]);
77 77
 				}
@@ -91,9 +91,9 @@  discard block
 block discarded – undo
91 91
 	function toString()
92 92
 	{
93 93
 		$output_tables = 'array(' . PHP_EOL;
94
-		foreach($this->tables as $table)
94
+		foreach ($this->tables as $table)
95 95
 		{
96
-			if(is_a($table, 'QueryTag'))
96
+			if (is_a($table, 'QueryTag'))
97 97
 			{
98 98
 				$output_tables .= $table->toString() . PHP_EOL . ',';
99 99
 			}
@@ -110,7 +110,7 @@  discard block
 block discarded – undo
110 110
 	function getArguments()
111 111
 	{
112 112
 		$arguments = array();
113
-		foreach($this->tables as $table)
113
+		foreach ($this->tables as $table)
114 114
 		{
115 115
 			$arguments = array_merge($arguments, $table->getArguments());
116 116
 		}
Please login to merge, or discard this patch.
Braces   +3 added lines, -6 removed lines patch added patch discarded remove patch
@@ -68,14 +68,12 @@  discard block
 block discarded – undo
68 68
 			if($tag->attrs->query == 'true')
69 69
 			{
70 70
 				$this->tables[] = new QueryTag($tag, true);
71
-			}
72
-			else
71
+			} else
73 72
 			{
74 73
 				if(isset($indexes[$tag->attrs->name]) && $indexes[$tag->attrs->name])
75 74
 				{
76 75
 					$this->tables[] = new HintTableTag($tag, $indexes[$tag->attrs->name]);
77
-				}
78
-				else
76
+				} else
79 77
 				{
80 78
 					$this->tables[] = new TableTag($tag);
81 79
 				}
@@ -96,8 +94,7 @@  discard block
 block discarded – undo
96 94
 			if(is_a($table, 'QueryTag'))
97 95
 			{
98 96
 				$output_tables .= $table->toString() . PHP_EOL . ',';
99
-			}
100
-			else
97
+			} else
101 98
 			{
102 99
 				$output_tables .= $table->getTableString() . PHP_EOL . ',';
103 100
 			}
Please login to merge, or discard this patch.
config/config.inc.php 2 patches
Spacing   +29 added lines, -29 removed lines patch added patch discarded remove patch
@@ -7,7 +7,7 @@  discard block
 block discarded – undo
7 7
  * @file   config/config.inc.php
8 8
  * @author NAVER ([email protected])
9 9
  */
10
-if(version_compare(PHP_VERSION, '5.4.0', '<'))
10
+if (version_compare(PHP_VERSION, '5.4.0', '<'))
11 11
 {
12 12
 	@error_reporting(E_ALL ^ E_NOTICE ^ E_DEPRECATED ^ E_WARNING);
13 13
 }
@@ -16,7 +16,7 @@  discard block
 block discarded – undo
16 16
 	@error_reporting(E_ALL ^ E_NOTICE ^ E_DEPRECATED ^ E_WARNING ^ E_STRICT);
17 17
 }
18 18
 
19
-if(!defined('__XE__'))
19
+if (!defined('__XE__'))
20 20
 {
21 21
 	exit();
22 22
 }
@@ -53,7 +53,7 @@  discard block
 block discarded – undo
53 53
 ini_set('session.use_only_cookies', 0);
54 54
 
55 55
 
56
-if(file_exists(_XE_PATH_ . 'config/package.inc.php'))
56
+if (file_exists(_XE_PATH_ . 'config/package.inc.php'))
57 57
 {
58 58
 	require _XE_PATH_ . 'config/package.inc.php';
59 59
 }
@@ -97,12 +97,12 @@  discard block
 block discarded – undo
97 97
  * define('__ENABLE_PHPUNIT_TEST__', 0);
98 98
  * define('__PROXY_SERVER__', 'http://domain:port/path');
99 99
  */
100
-if(file_exists(_XE_PATH_ . 'config/config.user.inc.php'))
100
+if (file_exists(_XE_PATH_ . 'config/config.user.inc.php'))
101 101
 {
102 102
 	require _XE_PATH_ . 'config/config.user.inc.php';
103 103
 }
104 104
 
105
-if(!defined('__DEBUG__'))
105
+if (!defined('__DEBUG__'))
106 106
 {
107 107
 	/**
108 108
 	 * output debug message(bit value)
@@ -117,7 +117,7 @@  discard block
 block discarded – undo
117 117
 	define('__DEBUG__', 0);
118 118
 }
119 119
 
120
-if(!defined('__DEBUG_OUTPUT__'))
120
+if (!defined('__DEBUG_OUTPUT__'))
121 121
 {
122 122
 	/**
123 123
 	 * output location of debug message
@@ -131,7 +131,7 @@  discard block
 block discarded – undo
131 131
 	define('__DEBUG_OUTPUT__', 0);
132 132
 }
133 133
 
134
-if(!defined('__DEBUG_PROTECT__'))
134
+if (!defined('__DEBUG_PROTECT__'))
135 135
 {
136 136
 	/**
137 137
 	 * output comments of the firePHP console and browser
@@ -144,7 +144,7 @@  discard block
 block discarded – undo
144 144
 	define('__DEBUG_PROTECT__', 1);
145 145
 }
146 146
 
147
-if(!defined('__DEBUG_PROTECT_IP__'))
147
+if (!defined('__DEBUG_PROTECT_IP__'))
148 148
 {
149 149
 	/**
150 150
 	 * Set a ip address to allow debug
@@ -152,7 +152,7 @@  discard block
 block discarded – undo
152 152
 	define('__DEBUG_PROTECT_IP__', '127.0.0.1');
153 153
 }
154 154
 
155
-if(!defined('__DEBUG_DB_OUTPUT__'))
155
+if (!defined('__DEBUG_DB_OUTPUT__'))
156 156
 {
157 157
 	/**
158 158
 	 * DB error message definition
@@ -165,7 +165,7 @@  discard block
 block discarded – undo
165 165
 	define('__DEBUG_DB_OUTPUT__', 0);
166 166
 }
167 167
 
168
-if(!defined('__LOG_SLOW_QUERY__'))
168
+if (!defined('__LOG_SLOW_QUERY__'))
169 169
 {
170 170
 	/**
171 171
 	 * Query log for only timeout query among DB queries
@@ -179,7 +179,7 @@  discard block
 block discarded – undo
179 179
 	define('__LOG_SLOW_QUERY__', 0);
180 180
 }
181 181
 
182
-if(!defined('__LOG_SLOW_TRIGGER__'))
182
+if (!defined('__LOG_SLOW_TRIGGER__'))
183 183
 {
184 184
 	/**
185 185
 	 * Trigger excute time log
@@ -193,7 +193,7 @@  discard block
 block discarded – undo
193 193
 	define('__LOG_SLOW_TRIGGER__', 0);
194 194
 }
195 195
 
196
-if(!defined('__LOG_SLOW_ADDON__'))
196
+if (!defined('__LOG_SLOW_ADDON__'))
197 197
 {
198 198
 	/**
199 199
 	 * Addon excute time log
@@ -207,7 +207,7 @@  discard block
 block discarded – undo
207 207
 	define('__LOG_SLOW_ADDON__', 0);
208 208
 }
209 209
 
210
-if(!defined('__LOG_SLOW_WIDGET__'))
210
+if (!defined('__LOG_SLOW_WIDGET__'))
211 211
 {
212 212
 	/**
213 213
 	 * Widget excute time log
@@ -221,7 +221,7 @@  discard block
 block discarded – undo
221 221
 	define('__LOG_SLOW_WIDGET__', 0);
222 222
 }
223 223
 
224
-if(!defined('__DEBUG_QUERY__'))
224
+if (!defined('__DEBUG_QUERY__'))
225 225
 {
226 226
 	/**
227 227
 	 * Leave DB query information
@@ -234,7 +234,7 @@  discard block
 block discarded – undo
234 234
 	define('__DEBUG_QUERY__', 0);
235 235
 }
236 236
 
237
-if(!defined('__OB_GZHANDLER_ENABLE__'))
237
+if (!defined('__OB_GZHANDLER_ENABLE__'))
238 238
 {
239 239
 	/**
240 240
 	 * option to enable/disable a compression feature using ob_gzhandler
@@ -248,7 +248,7 @@  discard block
 block discarded – undo
248 248
 	define('__OB_GZHANDLER_ENABLE__', 1);
249 249
 }
250 250
 
251
-if(!defined('__ENABLE_PHPUNIT_TEST__'))
251
+if (!defined('__ENABLE_PHPUNIT_TEST__'))
252 252
 {
253 253
 	/**
254 254
 	 * decide to use/not use the php unit test (Path/tests/index.php)
@@ -261,7 +261,7 @@  discard block
 block discarded – undo
261 261
 	define('__ENABLE_PHPUNIT_TEST__', 0);
262 262
 }
263 263
 
264
-if(!defined('__PROXY_SERVER__'))
264
+if (!defined('__PROXY_SERVER__'))
265 265
 {
266 266
 	/**
267 267
 	 * __PROXY_SERVER__ has server information to request to the external through the target server
@@ -271,13 +271,13 @@  discard block
 block discarded – undo
271 271
 }
272 272
 
273 273
 // Require specific files when using Firebug console output
274
-if((__DEBUG_OUTPUT__ == 2) && version_compare(PHP_VERSION, '6.0.0') === -1)
274
+if ((__DEBUG_OUTPUT__ == 2) && version_compare(PHP_VERSION, '6.0.0') === -1)
275 275
 {
276 276
 	require _XE_PATH_ . 'libs/FirePHPCore/FirePHP.class.php';
277 277
 }
278 278
 
279 279
 // Set Timezone as server time
280
-if(version_compare(PHP_VERSION, '5.3.0') >= 0)
280
+if (version_compare(PHP_VERSION, '5.3.0') >= 0)
281 281
 {
282 282
 	date_default_timezone_set(@date_default_timezone_get());
283 283
 }
@@ -285,11 +285,11 @@  discard block
 block discarded – undo
285 285
 // Require a function-defined-file for simple use
286 286
 require(_XE_PATH_ . 'config/func.inc.php');
287 287
 
288
-if(__DEBUG__) {
288
+if (__DEBUG__) {
289 289
 	define('__StartTime__', getMicroTime());
290 290
 }
291 291
 
292
-if(__DEBUG__) {
292
+if (__DEBUG__) {
293 293
 	$GLOBALS['__elapsed_class_load__'] = 0;
294 294
 }
295 295
 
@@ -391,38 +391,38 @@  discard block
 block discarded – undo
391 391
 
392 392
 function __xe_autoload($class_name)
393 393
 {
394
-	if(__DEBUG__) {
394
+	if (__DEBUG__) {
395 395
 		$time_at = getMicroTime();
396 396
 	}
397 397
 
398
-	if(isset($GLOBALS['__xe_autoload_file_map'][strtolower($class_name)]))
398
+	if (isset($GLOBALS['__xe_autoload_file_map'][strtolower($class_name)]))
399 399
 	{
400 400
 		require _XE_PATH_ . $GLOBALS['__xe_autoload_file_map'][strtolower($class_name)];
401 401
 	}
402
-	elseif(preg_match('/^([a-zA-Z0-9_]+?)(Admin)?(View|Controller|Model|Api|Wap|Mobile)?$/', $class_name, $matches))
402
+	elseif (preg_match('/^([a-zA-Z0-9_]+?)(Admin)?(View|Controller|Model|Api|Wap|Mobile)?$/', $class_name, $matches))
403 403
 	{
404 404
 		$candidate_filename = array();
405 405
 		$candidate_filename[] = 'modules/' . $matches[1] . '/' . $matches[1];
406
-		if(isset($matches[2]) && $matches[2]) $candidate_filename[] = 'admin';
406
+		if (isset($matches[2]) && $matches[2]) $candidate_filename[] = 'admin';
407 407
 		$candidate_filename[] = (isset($matches[3]) && $matches[3]) ? strtolower($matches[3]) : 'class';
408 408
 		$candidate_filename[] = 'php';
409 409
 
410 410
 		$candidate_filename = implode('.', $candidate_filename);
411 411
 
412
-		if(file_exists(_XE_PATH_ . $candidate_filename))
412
+		if (file_exists(_XE_PATH_ . $candidate_filename))
413 413
 		{
414 414
 			require _XE_PATH_ . $candidate_filename;
415 415
 		}
416 416
 	}
417 417
 
418
-	if(__DEBUG__) {
418
+	if (__DEBUG__) {
419 419
 		$GLOBALS['__elapsed_class_load__'] += getMicroTime() - $time_at;
420 420
 	}
421 421
 }
422 422
 spl_autoload_register('__xe_autoload');
423 423
 
424
-if(file_exists(_XE_PATH_  . '/vendor/autoload.php')) {
425
-	require _XE_PATH_  . '/vendor/autoload.php';
424
+if (file_exists(_XE_PATH_ . '/vendor/autoload.php')) {
425
+	require _XE_PATH_ . '/vendor/autoload.php';
426 426
 }
427 427
 /* End of file config.inc.php */
428 428
 /* Location: ./config/config.inc.php */
Please login to merge, or discard this patch.
Braces   +6 added lines, -7 removed lines patch added patch discarded remove patch
@@ -10,8 +10,7 @@  discard block
 block discarded – undo
10 10
 if(version_compare(PHP_VERSION, '5.4.0', '<'))
11 11
 {
12 12
 	@error_reporting(E_ALL ^ E_NOTICE ^ E_DEPRECATED ^ E_WARNING);
13
-}
14
-else
13
+} else
15 14
 {
16 15
 	@error_reporting(E_ALL ^ E_NOTICE ^ E_DEPRECATED ^ E_WARNING ^ E_STRICT);
17 16
 }
@@ -56,8 +55,7 @@  discard block
 block discarded – undo
56 55
 if(file_exists(_XE_PATH_ . 'config/package.inc.php'))
57 56
 {
58 57
 	require _XE_PATH_ . 'config/package.inc.php';
59
-}
60
-else
58
+} else
61 59
 {
62 60
 	/**
63 61
 	 * Package type
@@ -398,12 +396,13 @@  discard block
 block discarded – undo
398 396
 	if(isset($GLOBALS['__xe_autoload_file_map'][strtolower($class_name)]))
399 397
 	{
400 398
 		require _XE_PATH_ . $GLOBALS['__xe_autoload_file_map'][strtolower($class_name)];
401
-	}
402
-	elseif(preg_match('/^([a-zA-Z0-9_]+?)(Admin)?(View|Controller|Model|Api|Wap|Mobile)?$/', $class_name, $matches))
399
+	} elseif(preg_match('/^([a-zA-Z0-9_]+?)(Admin)?(View|Controller|Model|Api|Wap|Mobile)?$/', $class_name, $matches))
403 400
 	{
404 401
 		$candidate_filename = array();
405 402
 		$candidate_filename[] = 'modules/' . $matches[1] . '/' . $matches[1];
406
-		if(isset($matches[2]) && $matches[2]) $candidate_filename[] = 'admin';
403
+		if(isset($matches[2]) && $matches[2]) {
404
+			$candidate_filename[] = 'admin';
405
+		}
407 406
 		$candidate_filename[] = (isset($matches[3]) && $matches[3]) ? strtolower($matches[3]) : 'class';
408 407
 		$candidate_filename[] = 'php';
409 408
 
Please login to merge, or discard this patch.
index.php 2 patches
Spacing   +4 added lines, -4 removed lines patch added patch discarded remove patch
@@ -32,7 +32,7 @@  discard block
 block discarded – undo
32 32
 /**
33 33
  * @brief Declare constants for generic use and for checking to avoid a direct call from the Web
34 34
  **/
35
-define('__XE__',   TRUE);
35
+define('__XE__', TRUE);
36 36
 /**
37 37
  * @brief Include the necessary configuration files
38 38
  **/
@@ -48,18 +48,18 @@  discard block
 block discarded – undo
48 48
 /**
49 49
  * @brief If default_url is set and it is different from the current url, attempt to redirect for SSO authentication and then process the module
50 50
  **/
51
-if($oContext->checkSSO())
51
+if ($oContext->checkSSO())
52 52
 {
53 53
 	$oModuleHandler = new ModuleHandler();
54 54
 
55 55
 	try
56 56
 	{
57
-		if($oModuleHandler->init())
57
+		if ($oModuleHandler->init())
58 58
 		{
59 59
 			$oModuleHandler->displayContent($oModuleHandler->procModule());
60 60
 		}
61 61
 	}
62
-	catch(Exception $e)
62
+	catch (Exception $e)
63 63
 	{
64 64
 		htmlHeader();
65 65
 		echo Context::getLang($e->getMessage());
Please login to merge, or discard this patch.
Braces   +1 added lines, -2 removed lines patch added patch discarded remove patch
@@ -58,8 +58,7 @@
 block discarded – undo
58 58
 		{
59 59
 			$oModuleHandler->displayContent($oModuleHandler->procModule());
60 60
 		}
61
-	}
62
-	catch(Exception $e)
61
+	} catch(Exception $e)
63 62
 	{
64 63
 		htmlHeader();
65 64
 		echo Context::getLang($e->getMessage());
Please login to merge, or discard this patch.
libs/PEAR.1.9.5/HTTP/Request2/Adapter.php 1 patch
Indentation   +103 added lines, -103 removed lines patch added patch discarded remove patch
@@ -1,22 +1,22 @@  discard block
 block discarded – undo
1 1
 <?php
2 2
 /**
3
- * Base class for HTTP_Request2 adapters
4
- *
5
- * PHP version 5
6
- *
7
- * LICENSE
8
- *
9
- * This source file is subject to BSD 3-Clause License that is bundled
10
- * with this package in the file LICENSE and available at the URL
11
- * https://raw.github.com/pear/HTTP_Request2/trunk/docs/LICENSE
12
- *
13
- * @category  HTTP
14
- * @package   HTTP_Request2
15
- * @author    Alexey Borzov <[email protected]>
16
- * @copyright 2008-2014 Alexey Borzov <[email protected]>
17
- * @license   http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
18
- * @link      http://pear.php.net/package/HTTP_Request2
19
- */
3
+	 * Base class for HTTP_Request2 adapters
4
+	 *
5
+	 * PHP version 5
6
+	 *
7
+	 * LICENSE
8
+	 *
9
+	 * This source file is subject to BSD 3-Clause License that is bundled
10
+	 * with this package in the file LICENSE and available at the URL
11
+	 * https://raw.github.com/pear/HTTP_Request2/trunk/docs/LICENSE
12
+	 *
13
+	 * @category  HTTP
14
+	 * @package   HTTP_Request2
15
+	 * @author    Alexey Borzov <[email protected]>
16
+	 * @copyright 2008-2014 Alexey Borzov <[email protected]>
17
+	 * @license   http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
18
+	 * @link      http://pear.php.net/package/HTTP_Request2
19
+	 */
20 20
 
21 21
 /**
22 22
  * Class representing a HTTP response
@@ -39,99 +39,99 @@  discard block
 block discarded – undo
39 39
  */
40 40
 abstract class HTTP_Request2_Adapter
41 41
 {
42
-    /**
43
-     * A list of methods that MUST NOT have a request body, per RFC 2616
44
-     * @var  array
45
-     */
46
-    protected static $bodyDisallowed = array('TRACE');
42
+	/**
43
+	 * A list of methods that MUST NOT have a request body, per RFC 2616
44
+	 * @var  array
45
+	 */
46
+	protected static $bodyDisallowed = array('TRACE');
47 47
 
48
-    /**
49
-     * Methods having defined semantics for request body
50
-     *
51
-     * Content-Length header (indicating that the body follows, section 4.3 of
52
-     * RFC 2616) will be sent for these methods even if no body was added
53
-     *
54
-     * @var  array
55
-     * @link http://pear.php.net/bugs/bug.php?id=12900
56
-     * @link http://pear.php.net/bugs/bug.php?id=14740
57
-     */
58
-    protected static $bodyRequired = array('POST', 'PUT');
48
+	/**
49
+	 * Methods having defined semantics for request body
50
+	 *
51
+	 * Content-Length header (indicating that the body follows, section 4.3 of
52
+	 * RFC 2616) will be sent for these methods even if no body was added
53
+	 *
54
+	 * @var  array
55
+	 * @link http://pear.php.net/bugs/bug.php?id=12900
56
+	 * @link http://pear.php.net/bugs/bug.php?id=14740
57
+	 */
58
+	protected static $bodyRequired = array('POST', 'PUT');
59 59
 
60
-    /**
61
-     * Request being sent
62
-     * @var  HTTP_Request2
63
-     */
64
-    protected $request;
60
+	/**
61
+	 * Request being sent
62
+	 * @var  HTTP_Request2
63
+	 */
64
+	protected $request;
65 65
 
66
-    /**
67
-     * Request body
68
-     * @var  string|resource|HTTP_Request2_MultipartBody
69
-     * @see  HTTP_Request2::getBody()
70
-     */
71
-    protected $requestBody;
66
+	/**
67
+	 * Request body
68
+	 * @var  string|resource|HTTP_Request2_MultipartBody
69
+	 * @see  HTTP_Request2::getBody()
70
+	 */
71
+	protected $requestBody;
72 72
 
73
-    /**
74
-     * Length of the request body
75
-     * @var  integer
76
-     */
77
-    protected $contentLength;
73
+	/**
74
+	 * Length of the request body
75
+	 * @var  integer
76
+	 */
77
+	protected $contentLength;
78 78
 
79
-    /**
80
-     * Sends request to the remote server and returns its response
81
-     *
82
-     * @param HTTP_Request2 $request HTTP request message
83
-     *
84
-     * @return   HTTP_Request2_Response
85
-     * @throws   HTTP_Request2_Exception
86
-     */
87
-    abstract public function sendRequest(HTTP_Request2 $request);
79
+	/**
80
+	 * Sends request to the remote server and returns its response
81
+	 *
82
+	 * @param HTTP_Request2 $request HTTP request message
83
+	 *
84
+	 * @return   HTTP_Request2_Response
85
+	 * @throws   HTTP_Request2_Exception
86
+	 */
87
+	abstract public function sendRequest(HTTP_Request2 $request);
88 88
 
89
-    /**
90
-     * Calculates length of the request body, adds proper headers
91
-     *
92
-     * @param array &$headers associative array of request headers, this method
93
-     *                        will add proper 'Content-Length' and 'Content-Type'
94
-     *                        headers to this array (or remove them if not needed)
95
-     */
96
-    protected function calculateRequestLength(&$headers)
97
-    {
98
-        $this->requestBody = $this->request->getBody();
89
+	/**
90
+	 * Calculates length of the request body, adds proper headers
91
+	 *
92
+	 * @param array &$headers associative array of request headers, this method
93
+	 *                        will add proper 'Content-Length' and 'Content-Type'
94
+	 *                        headers to this array (or remove them if not needed)
95
+	 */
96
+	protected function calculateRequestLength(&$headers)
97
+	{
98
+		$this->requestBody = $this->request->getBody();
99 99
 
100
-        if (is_string($this->requestBody)) {
101
-            $this->contentLength = strlen($this->requestBody);
102
-        } elseif (is_resource($this->requestBody)) {
103
-            $stat = fstat($this->requestBody);
104
-            $this->contentLength = $stat['size'];
105
-            rewind($this->requestBody);
106
-        } else {
107
-            $this->contentLength = $this->requestBody->getLength();
108
-            $headers['content-type'] = 'multipart/form-data; boundary=' .
109
-                                       $this->requestBody->getBoundary();
110
-            $this->requestBody->rewind();
111
-        }
100
+		if (is_string($this->requestBody)) {
101
+			$this->contentLength = strlen($this->requestBody);
102
+		} elseif (is_resource($this->requestBody)) {
103
+			$stat = fstat($this->requestBody);
104
+			$this->contentLength = $stat['size'];
105
+			rewind($this->requestBody);
106
+		} else {
107
+			$this->contentLength = $this->requestBody->getLength();
108
+			$headers['content-type'] = 'multipart/form-data; boundary=' .
109
+									   $this->requestBody->getBoundary();
110
+			$this->requestBody->rewind();
111
+		}
112 112
 
113
-        if (in_array($this->request->getMethod(), self::$bodyDisallowed)
114
-            || 0 == $this->contentLength
115
-        ) {
116
-            // No body: send a Content-Length header nonetheless (request #12900),
117
-            // but do that only for methods that require a body (bug #14740)
118
-            if (in_array($this->request->getMethod(), self::$bodyRequired)) {
119
-                $headers['content-length'] = 0;
120
-            } else {
121
-                unset($headers['content-length']);
122
-                // if the method doesn't require a body and doesn't have a
123
-                // body, don't send a Content-Type header. (request #16799)
124
-                unset($headers['content-type']);
125
-            }
126
-        } else {
127
-            if (empty($headers['content-type'])) {
128
-                $headers['content-type'] = 'application/x-www-form-urlencoded';
129
-            }
130
-            // Content-Length should not be sent for chunked Transfer-Encoding (bug #20125)
131
-            if (!isset($headers['transfer-encoding'])) {
132
-                $headers['content-length'] = $this->contentLength;
133
-            }
134
-        }
135
-    }
113
+		if (in_array($this->request->getMethod(), self::$bodyDisallowed)
114
+			|| 0 == $this->contentLength
115
+		) {
116
+			// No body: send a Content-Length header nonetheless (request #12900),
117
+			// but do that only for methods that require a body (bug #14740)
118
+			if (in_array($this->request->getMethod(), self::$bodyRequired)) {
119
+				$headers['content-length'] = 0;
120
+			} else {
121
+				unset($headers['content-length']);
122
+				// if the method doesn't require a body and doesn't have a
123
+				// body, don't send a Content-Type header. (request #16799)
124
+				unset($headers['content-type']);
125
+			}
126
+		} else {
127
+			if (empty($headers['content-type'])) {
128
+				$headers['content-type'] = 'application/x-www-form-urlencoded';
129
+			}
130
+			// Content-Length should not be sent for chunked Transfer-Encoding (bug #20125)
131
+			if (!isset($headers['transfer-encoding'])) {
132
+				$headers['content-length'] = $this->contentLength;
133
+			}
134
+		}
135
+	}
136 136
 }
137 137
 ?>
Please login to merge, or discard this patch.
libs/PEAR.1.9.5/HTTP/Request2/Adapter/Curl.php 3 patches
Indentation   +545 added lines, -545 removed lines patch added patch discarded remove patch
@@ -1,22 +1,22 @@  discard block
 block discarded – undo
1 1
 <?php
2 2
 /**
3
- * Adapter for HTTP_Request2 wrapping around cURL extension
4
- *
5
- * PHP version 5
6
- *
7
- * LICENSE
8
- *
9
- * This source file is subject to BSD 3-Clause License that is bundled
10
- * with this package in the file LICENSE and available at the URL
11
- * https://raw.github.com/pear/HTTP_Request2/trunk/docs/LICENSE
12
- *
13
- * @category  HTTP
14
- * @package   HTTP_Request2
15
- * @author    Alexey Borzov <[email protected]>
16
- * @copyright 2008-2014 Alexey Borzov <[email protected]>
17
- * @license   http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
18
- * @link      http://pear.php.net/package/HTTP_Request2
19
- */
3
+	 * Adapter for HTTP_Request2 wrapping around cURL extension
4
+	 *
5
+	 * PHP version 5
6
+	 *
7
+	 * LICENSE
8
+	 *
9
+	 * This source file is subject to BSD 3-Clause License that is bundled
10
+	 * with this package in the file LICENSE and available at the URL
11
+	 * https://raw.github.com/pear/HTTP_Request2/trunk/docs/LICENSE
12
+	 *
13
+	 * @category  HTTP
14
+	 * @package   HTTP_Request2
15
+	 * @author    Alexey Borzov <[email protected]>
16
+	 * @copyright 2008-2014 Alexey Borzov <[email protected]>
17
+	 * @license   http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
18
+	 * @link      http://pear.php.net/package/HTTP_Request2
19
+	 */
20 20
 
21 21
 /**
22 22
  * Base class for HTTP_Request2 adapters
@@ -35,533 +35,533 @@  discard block
 block discarded – undo
35 35
  */
36 36
 class HTTP_Request2_Adapter_Curl extends HTTP_Request2_Adapter
37 37
 {
38
-    /**
39
-     * Mapping of header names to cURL options
40
-     * @var  array
41
-     */
42
-    protected static $headerMap = array(
43
-        'accept-encoding' => CURLOPT_ENCODING,
44
-        'cookie'          => CURLOPT_COOKIE,
45
-        'referer'         => CURLOPT_REFERER,
46
-        'user-agent'      => CURLOPT_USERAGENT
47
-    );
48
-
49
-    /**
50
-     * Mapping of SSL context options to cURL options
51
-     * @var  array
52
-     */
53
-    protected static $sslContextMap = array(
54
-        'ssl_verify_peer' => CURLOPT_SSL_VERIFYPEER,
55
-        'ssl_cafile'      => CURLOPT_CAINFO,
56
-        'ssl_capath'      => CURLOPT_CAPATH,
57
-        'ssl_local_cert'  => CURLOPT_SSLCERT,
58
-        'ssl_passphrase'  => CURLOPT_SSLCERTPASSWD
59
-    );
60
-
61
-    /**
62
-     * Mapping of CURLE_* constants to Exception subclasses and error codes
63
-     * @var  array
64
-     */
65
-    protected static $errorMap = array(
66
-        CURLE_UNSUPPORTED_PROTOCOL  => array('HTTP_Request2_MessageException',
67
-                                             HTTP_Request2_Exception::NON_HTTP_REDIRECT),
68
-        CURLE_COULDNT_RESOLVE_PROXY => array('HTTP_Request2_ConnectionException'),
69
-        CURLE_COULDNT_RESOLVE_HOST  => array('HTTP_Request2_ConnectionException'),
70
-        CURLE_COULDNT_CONNECT       => array('HTTP_Request2_ConnectionException'),
71
-        // error returned from write callback
72
-        CURLE_WRITE_ERROR           => array('HTTP_Request2_MessageException',
73
-                                             HTTP_Request2_Exception::NON_HTTP_REDIRECT),
74
-        CURLE_OPERATION_TIMEOUTED   => array('HTTP_Request2_MessageException',
75
-                                             HTTP_Request2_Exception::TIMEOUT),
76
-        CURLE_HTTP_RANGE_ERROR      => array('HTTP_Request2_MessageException'),
77
-        CURLE_SSL_CONNECT_ERROR     => array('HTTP_Request2_ConnectionException'),
78
-        CURLE_LIBRARY_NOT_FOUND     => array('HTTP_Request2_LogicException',
79
-                                             HTTP_Request2_Exception::MISCONFIGURATION),
80
-        CURLE_FUNCTION_NOT_FOUND    => array('HTTP_Request2_LogicException',
81
-                                             HTTP_Request2_Exception::MISCONFIGURATION),
82
-        CURLE_ABORTED_BY_CALLBACK   => array('HTTP_Request2_MessageException',
83
-                                             HTTP_Request2_Exception::NON_HTTP_REDIRECT),
84
-        CURLE_TOO_MANY_REDIRECTS    => array('HTTP_Request2_MessageException',
85
-                                             HTTP_Request2_Exception::TOO_MANY_REDIRECTS),
86
-        CURLE_SSL_PEER_CERTIFICATE  => array('HTTP_Request2_ConnectionException'),
87
-        CURLE_GOT_NOTHING           => array('HTTP_Request2_MessageException'),
88
-        CURLE_SSL_ENGINE_NOTFOUND   => array('HTTP_Request2_LogicException',
89
-                                             HTTP_Request2_Exception::MISCONFIGURATION),
90
-        CURLE_SSL_ENGINE_SETFAILED  => array('HTTP_Request2_LogicException',
91
-                                             HTTP_Request2_Exception::MISCONFIGURATION),
92
-        CURLE_SEND_ERROR            => array('HTTP_Request2_MessageException'),
93
-        CURLE_RECV_ERROR            => array('HTTP_Request2_MessageException'),
94
-        CURLE_SSL_CERTPROBLEM       => array('HTTP_Request2_LogicException',
95
-                                             HTTP_Request2_Exception::INVALID_ARGUMENT),
96
-        CURLE_SSL_CIPHER            => array('HTTP_Request2_ConnectionException'),
97
-        CURLE_SSL_CACERT            => array('HTTP_Request2_ConnectionException'),
98
-        CURLE_BAD_CONTENT_ENCODING  => array('HTTP_Request2_MessageException'),
99
-    );
100
-
101
-    /**
102
-     * Response being received
103
-     * @var  HTTP_Request2_Response
104
-     */
105
-    protected $response;
106
-
107
-    /**
108
-     * Whether 'sentHeaders' event was sent to observers
109
-     * @var  boolean
110
-     */
111
-    protected $eventSentHeaders = false;
112
-
113
-    /**
114
-     * Whether 'receivedHeaders' event was sent to observers
115
-     * @var boolean
116
-     */
117
-    protected $eventReceivedHeaders = false;
118
-
119
-    /**
120
-     * Position within request body
121
-     * @var  integer
122
-     * @see  callbackReadBody()
123
-     */
124
-    protected $position = 0;
125
-
126
-    /**
127
-     * Information about last transfer, as returned by curl_getinfo()
128
-     * @var  array
129
-     */
130
-    protected $lastInfo;
131
-
132
-    /**
133
-     * Creates a subclass of HTTP_Request2_Exception from curl error data
134
-     *
135
-     * @param resource $ch curl handle
136
-     *
137
-     * @return HTTP_Request2_Exception
138
-     */
139
-    protected static function wrapCurlError($ch)
140
-    {
141
-        $nativeCode = curl_errno($ch);
142
-        $message    = 'Curl error: ' . curl_error($ch);
143
-        if (!isset(self::$errorMap[$nativeCode])) {
144
-            return new HTTP_Request2_Exception($message, 0, $nativeCode);
145
-        } else {
146
-            $class = self::$errorMap[$nativeCode][0];
147
-            $code  = empty(self::$errorMap[$nativeCode][1])
148
-                     ? 0 : self::$errorMap[$nativeCode][1];
149
-            return new $class($message, $code, $nativeCode);
150
-        }
151
-    }
152
-
153
-    /**
154
-     * Sends request to the remote server and returns its response
155
-     *
156
-     * @param HTTP_Request2 $request HTTP request message
157
-     *
158
-     * @return   HTTP_Request2_Response
159
-     * @throws   HTTP_Request2_Exception
160
-     */
161
-    public function sendRequest(HTTP_Request2 $request)
162
-    {
163
-        if (!extension_loaded('curl')) {
164
-            throw new HTTP_Request2_LogicException(
165
-                'cURL extension not available', HTTP_Request2_Exception::MISCONFIGURATION
166
-            );
167
-        }
168
-
169
-        $this->request              = $request;
170
-        $this->response             = null;
171
-        $this->position             = 0;
172
-        $this->eventSentHeaders     = false;
173
-        $this->eventReceivedHeaders = false;
174
-
175
-        try {
176
-            if (false === curl_exec($ch = $this->createCurlHandle())) {
177
-                $e = self::wrapCurlError($ch);
178
-            }
179
-        } catch (Exception $e) {
180
-        }
181
-        if (isset($ch)) {
182
-            $this->lastInfo = curl_getinfo($ch);
183
-            curl_close($ch);
184
-        }
185
-
186
-        $response = $this->response;
187
-        unset($this->request, $this->requestBody, $this->response);
188
-
189
-        if (!empty($e)) {
190
-            throw $e;
191
-        }
192
-
193
-        if ($jar = $request->getCookieJar()) {
194
-            $jar->addCookiesFromResponse($response, $request->getUrl());
195
-        }
196
-
197
-        if (0 < $this->lastInfo['size_download']) {
198
-            $request->setLastEvent('receivedBody', $response);
199
-        }
200
-        return $response;
201
-    }
202
-
203
-    /**
204
-     * Returns information about last transfer
205
-     *
206
-     * @return   array   associative array as returned by curl_getinfo()
207
-     */
208
-    public function getInfo()
209
-    {
210
-        return $this->lastInfo;
211
-    }
212
-
213
-    /**
214
-     * Creates a new cURL handle and populates it with data from the request
215
-     *
216
-     * @return   resource    a cURL handle, as created by curl_init()
217
-     * @throws   HTTP_Request2_LogicException
218
-     * @throws   HTTP_Request2_NotImplementedException
219
-     */
220
-    protected function createCurlHandle()
221
-    {
222
-        $ch = curl_init();
223
-
224
-        curl_setopt_array($ch, array(
225
-            // setup write callbacks
226
-            CURLOPT_HEADERFUNCTION => array($this, 'callbackWriteHeader'),
227
-            CURLOPT_WRITEFUNCTION  => array($this, 'callbackWriteBody'),
228
-            // buffer size
229
-            CURLOPT_BUFFERSIZE     => $this->request->getConfig('buffer_size'),
230
-            // connection timeout
231
-            CURLOPT_CONNECTTIMEOUT => $this->request->getConfig('connect_timeout'),
232
-            // save full outgoing headers, in case someone is interested
233
-            CURLINFO_HEADER_OUT    => true,
234
-            // request url
235
-            CURLOPT_URL            => $this->request->getUrl()->getUrl()
236
-        ));
237
-
238
-        // set up redirects
239
-        if (!$this->request->getConfig('follow_redirects')) {
240
-            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
241
-        } else {
242
-            if (!@curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true)) {
243
-                throw new HTTP_Request2_LogicException(
244
-                    'Redirect support in curl is unavailable due to open_basedir or safe_mode setting',
245
-                    HTTP_Request2_Exception::MISCONFIGURATION
246
-                );
247
-            }
248
-            curl_setopt($ch, CURLOPT_MAXREDIRS, $this->request->getConfig('max_redirects'));
249
-            // limit redirects to http(s), works in 5.2.10+
250
-            if (defined('CURLOPT_REDIR_PROTOCOLS')) {
251
-                curl_setopt($ch, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
252
-            }
253
-            // works in 5.3.2+, http://bugs.php.net/bug.php?id=49571
254
-            if ($this->request->getConfig('strict_redirects') && defined('CURLOPT_POSTREDIR')) {
255
-                curl_setopt($ch, CURLOPT_POSTREDIR, 3);
256
-            }
257
-        }
258
-
259
-        // set local IP via CURLOPT_INTERFACE (request #19515)
260
-        if ($ip = $this->request->getConfig('local_ip')) {
261
-            curl_setopt($ch, CURLOPT_INTERFACE, $ip);
262
-        }
263
-
264
-        // request timeout
265
-        if ($timeout = $this->request->getConfig('timeout')) {
266
-            curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
267
-        }
268
-
269
-        // set HTTP version
270
-        switch ($this->request->getConfig('protocol_version')) {
271
-        case '1.0':
272
-            curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
273
-            break;
274
-        case '1.1':
275
-            curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
276
-        }
277
-
278
-        // set request method
279
-        switch ($this->request->getMethod()) {
280
-        case HTTP_Request2::METHOD_GET:
281
-            curl_setopt($ch, CURLOPT_HTTPGET, true);
282
-            break;
283
-        case HTTP_Request2::METHOD_POST:
284
-            curl_setopt($ch, CURLOPT_POST, true);
285
-            break;
286
-        case HTTP_Request2::METHOD_HEAD:
287
-            curl_setopt($ch, CURLOPT_NOBODY, true);
288
-            break;
289
-        case HTTP_Request2::METHOD_PUT:
290
-            curl_setopt($ch, CURLOPT_UPLOAD, true);
291
-            break;
292
-        default:
293
-            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->request->getMethod());
294
-        }
295
-
296
-        // set proxy, if needed
297
-        if ($host = $this->request->getConfig('proxy_host')) {
298
-            if (!($port = $this->request->getConfig('proxy_port'))) {
299
-                throw new HTTP_Request2_LogicException(
300
-                    'Proxy port not provided', HTTP_Request2_Exception::MISSING_VALUE
301
-                );
302
-            }
303
-            curl_setopt($ch, CURLOPT_PROXY, $host . ':' . $port);
304
-            if ($user = $this->request->getConfig('proxy_user')) {
305
-                curl_setopt(
306
-                    $ch, CURLOPT_PROXYUSERPWD,
307
-                    $user . ':' . $this->request->getConfig('proxy_password')
308
-                );
309
-                switch ($this->request->getConfig('proxy_auth_scheme')) {
310
-                case HTTP_Request2::AUTH_BASIC:
311
-                    curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC);
312
-                    break;
313
-                case HTTP_Request2::AUTH_DIGEST:
314
-                    curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_DIGEST);
315
-                }
316
-            }
317
-            if ($type = $this->request->getConfig('proxy_type')) {
318
-                switch ($type) {
319
-                case 'http':
320
-                    curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
321
-                    break;
322
-                case 'socks5':
323
-                    curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
324
-                    break;
325
-                default:
326
-                    throw new HTTP_Request2_NotImplementedException(
327
-                        "Proxy type '{$type}' is not supported"
328
-                    );
329
-                }
330
-            }
331
-        }
332
-
333
-        // set authentication data
334
-        if ($auth = $this->request->getAuth()) {
335
-            curl_setopt($ch, CURLOPT_USERPWD, $auth['user'] . ':' . $auth['password']);
336
-            switch ($auth['scheme']) {
337
-            case HTTP_Request2::AUTH_BASIC:
338
-                curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
339
-                break;
340
-            case HTTP_Request2::AUTH_DIGEST:
341
-                curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
342
-            }
343
-        }
344
-
345
-        // set SSL options
346
-        foreach ($this->request->getConfig() as $name => $value) {
347
-            if ('ssl_verify_host' == $name && null !== $value) {
348
-                curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $value? 2: 0);
349
-            } elseif (isset(self::$sslContextMap[$name]) && null !== $value) {
350
-                curl_setopt($ch, self::$sslContextMap[$name], $value);
351
-            }
352
-        }
353
-
354
-        $headers = $this->request->getHeaders();
355
-        // make cURL automagically send proper header
356
-        if (!isset($headers['accept-encoding'])) {
357
-            $headers['accept-encoding'] = '';
358
-        }
359
-
360
-        if (($jar = $this->request->getCookieJar())
361
-            && ($cookies = $jar->getMatching($this->request->getUrl(), true))
362
-        ) {
363
-            $headers['cookie'] = (empty($headers['cookie'])? '': $headers['cookie'] . '; ') . $cookies;
364
-        }
365
-
366
-        // set headers having special cURL keys
367
-        foreach (self::$headerMap as $name => $option) {
368
-            if (isset($headers[$name])) {
369
-                curl_setopt($ch, $option, $headers[$name]);
370
-                unset($headers[$name]);
371
-            }
372
-        }
373
-
374
-        $this->calculateRequestLength($headers);
375
-        if (isset($headers['content-length']) || isset($headers['transfer-encoding'])) {
376
-            $this->workaroundPhpBug47204($ch, $headers);
377
-        }
378
-
379
-        // set headers not having special keys
380
-        $headersFmt = array();
381
-        foreach ($headers as $name => $value) {
382
-            $canonicalName = implode('-', array_map('ucfirst', explode('-', $name)));
383
-            $headersFmt[]  = $canonicalName . ': ' . $value;
384
-        }
385
-        curl_setopt($ch, CURLOPT_HTTPHEADER, $headersFmt);
386
-
387
-        return $ch;
388
-    }
389
-
390
-    /**
391
-     * Workaround for PHP bug #47204 that prevents rewinding request body
392
-     *
393
-     * The workaround consists of reading the entire request body into memory
394
-     * and setting it as CURLOPT_POSTFIELDS, so it isn't recommended for large
395
-     * file uploads, use Socket adapter instead.
396
-     *
397
-     * @param resource $ch       cURL handle
398
-     * @param array    &$headers Request headers
399
-     */
400
-    protected function workaroundPhpBug47204($ch, &$headers)
401
-    {
402
-        // no redirects, no digest auth -> probably no rewind needed
403
-        if (!$this->request->getConfig('follow_redirects')
404
-            && (!($auth = $this->request->getAuth())
405
-                || HTTP_Request2::AUTH_DIGEST != $auth['scheme'])
406
-        ) {
407
-            curl_setopt($ch, CURLOPT_READFUNCTION, array($this, 'callbackReadBody'));
408
-
409
-        } else {
410
-            // rewind may be needed, read the whole body into memory
411
-            if ($this->requestBody instanceof HTTP_Request2_MultipartBody) {
412
-                $this->requestBody = $this->requestBody->__toString();
413
-
414
-            } elseif (is_resource($this->requestBody)) {
415
-                $fp = $this->requestBody;
416
-                $this->requestBody = '';
417
-                while (!feof($fp)) {
418
-                    $this->requestBody .= fread($fp, 16384);
419
-                }
420
-            }
421
-            // curl hangs up if content-length is present
422
-            unset($headers['content-length']);
423
-            curl_setopt($ch, CURLOPT_POSTFIELDS, $this->requestBody);
424
-        }
425
-    }
426
-
427
-    /**
428
-     * Callback function called by cURL for reading the request body
429
-     *
430
-     * @param resource $ch     cURL handle
431
-     * @param resource $fd     file descriptor (not used)
432
-     * @param integer  $length maximum length of data to return
433
-     *
434
-     * @return   string      part of the request body, up to $length bytes
435
-     */
436
-    protected function callbackReadBody($ch, $fd, $length)
437
-    {
438
-        if (!$this->eventSentHeaders) {
439
-            $this->request->setLastEvent(
440
-                'sentHeaders', curl_getinfo($ch, CURLINFO_HEADER_OUT)
441
-            );
442
-            $this->eventSentHeaders = true;
443
-        }
444
-        if (in_array($this->request->getMethod(), self::$bodyDisallowed)
445
-            || 0 == $this->contentLength || $this->position >= $this->contentLength
446
-        ) {
447
-            return '';
448
-        }
449
-        if (is_string($this->requestBody)) {
450
-            $string = substr($this->requestBody, $this->position, $length);
451
-        } elseif (is_resource($this->requestBody)) {
452
-            $string = fread($this->requestBody, $length);
453
-        } else {
454
-            $string = $this->requestBody->read($length);
455
-        }
456
-        $this->request->setLastEvent('sentBodyPart', strlen($string));
457
-        $this->position += strlen($string);
458
-        return $string;
459
-    }
460
-
461
-    /**
462
-     * Callback function called by cURL for saving the response headers
463
-     *
464
-     * @param resource $ch     cURL handle
465
-     * @param string   $string response header (with trailing CRLF)
466
-     *
467
-     * @return   integer     number of bytes saved
468
-     * @see      HTTP_Request2_Response::parseHeaderLine()
469
-     */
470
-    protected function callbackWriteHeader($ch, $string)
471
-    {
472
-        // we may receive a second set of headers if doing e.g. digest auth
473
-        if ($this->eventReceivedHeaders || !$this->eventSentHeaders) {
474
-            // don't bother with 100-Continue responses (bug #15785)
475
-            if (!$this->eventSentHeaders
476
-                || $this->response->getStatus() >= 200
477
-            ) {
478
-                $this->request->setLastEvent(
479
-                    'sentHeaders', curl_getinfo($ch, CURLINFO_HEADER_OUT)
480
-                );
481
-            }
482
-            $upload = curl_getinfo($ch, CURLINFO_SIZE_UPLOAD);
483
-            // if body wasn't read by a callback, send event with total body size
484
-            if ($upload > $this->position) {
485
-                $this->request->setLastEvent(
486
-                    'sentBodyPart', $upload - $this->position
487
-                );
488
-                $this->position = $upload;
489
-            }
490
-            if ($upload && (!$this->eventSentHeaders
491
-                            || $this->response->getStatus() >= 200)
492
-            ) {
493
-                $this->request->setLastEvent('sentBody', $upload);
494
-            }
495
-            $this->eventSentHeaders = true;
496
-            // we'll need a new response object
497
-            if ($this->eventReceivedHeaders) {
498
-                $this->eventReceivedHeaders = false;
499
-                $this->response             = null;
500
-            }
501
-        }
502
-        if (empty($this->response)) {
503
-            $this->response = new HTTP_Request2_Response(
504
-                $string, false, curl_getinfo($ch, CURLINFO_EFFECTIVE_URL)
505
-            );
506
-        } else {
507
-            $this->response->parseHeaderLine($string);
508
-            if ('' == trim($string)) {
509
-                // don't bother with 100-Continue responses (bug #15785)
510
-                if (200 <= $this->response->getStatus()) {
511
-                    $this->request->setLastEvent('receivedHeaders', $this->response);
512
-                }
513
-
514
-                if ($this->request->getConfig('follow_redirects') && $this->response->isRedirect()) {
515
-                    $redirectUrl = new Net_URL2($this->response->getHeader('location'));
516
-
517
-                    // for versions lower than 5.2.10, check the redirection URL protocol
518
-                    if (!defined('CURLOPT_REDIR_PROTOCOLS') && $redirectUrl->isAbsolute()
519
-                        && !in_array($redirectUrl->getScheme(), array('http', 'https'))
520
-                    ) {
521
-                        return -1;
522
-                    }
523
-
524
-                    if ($jar = $this->request->getCookieJar()) {
525
-                        $jar->addCookiesFromResponse($this->response, $this->request->getUrl());
526
-                        if (!$redirectUrl->isAbsolute()) {
527
-                            $redirectUrl = $this->request->getUrl()->resolve($redirectUrl);
528
-                        }
529
-                        if ($cookies = $jar->getMatching($redirectUrl, true)) {
530
-                            curl_setopt($ch, CURLOPT_COOKIE, $cookies);
531
-                        }
532
-                    }
533
-                }
534
-                $this->eventReceivedHeaders = true;
535
-            }
536
-        }
537
-        return strlen($string);
538
-    }
539
-
540
-    /**
541
-     * Callback function called by cURL for saving the response body
542
-     *
543
-     * @param resource $ch     cURL handle (not used)
544
-     * @param string   $string part of the response body
545
-     *
546
-     * @return   integer     number of bytes saved
547
-     * @throws   HTTP_Request2_MessageException
548
-     * @see      HTTP_Request2_Response::appendBody()
549
-     */
550
-    protected function callbackWriteBody($ch, $string)
551
-    {
552
-        // cURL calls WRITEFUNCTION callback without calling HEADERFUNCTION if
553
-        // response doesn't start with proper HTTP status line (see bug #15716)
554
-        if (empty($this->response)) {
555
-            throw new HTTP_Request2_MessageException(
556
-                "Malformed response: {$string}",
557
-                HTTP_Request2_Exception::MALFORMED_RESPONSE
558
-            );
559
-        }
560
-        if ($this->request->getConfig('store_body')) {
561
-            $this->response->appendBody($string);
562
-        }
563
-        $this->request->setLastEvent('receivedBodyPart', $string);
564
-        return strlen($string);
565
-    }
38
+	/**
39
+	 * Mapping of header names to cURL options
40
+	 * @var  array
41
+	 */
42
+	protected static $headerMap = array(
43
+		'accept-encoding' => CURLOPT_ENCODING,
44
+		'cookie'          => CURLOPT_COOKIE,
45
+		'referer'         => CURLOPT_REFERER,
46
+		'user-agent'      => CURLOPT_USERAGENT
47
+	);
48
+
49
+	/**
50
+	 * Mapping of SSL context options to cURL options
51
+	 * @var  array
52
+	 */
53
+	protected static $sslContextMap = array(
54
+		'ssl_verify_peer' => CURLOPT_SSL_VERIFYPEER,
55
+		'ssl_cafile'      => CURLOPT_CAINFO,
56
+		'ssl_capath'      => CURLOPT_CAPATH,
57
+		'ssl_local_cert'  => CURLOPT_SSLCERT,
58
+		'ssl_passphrase'  => CURLOPT_SSLCERTPASSWD
59
+	);
60
+
61
+	/**
62
+	 * Mapping of CURLE_* constants to Exception subclasses and error codes
63
+	 * @var  array
64
+	 */
65
+	protected static $errorMap = array(
66
+		CURLE_UNSUPPORTED_PROTOCOL  => array('HTTP_Request2_MessageException',
67
+											 HTTP_Request2_Exception::NON_HTTP_REDIRECT),
68
+		CURLE_COULDNT_RESOLVE_PROXY => array('HTTP_Request2_ConnectionException'),
69
+		CURLE_COULDNT_RESOLVE_HOST  => array('HTTP_Request2_ConnectionException'),
70
+		CURLE_COULDNT_CONNECT       => array('HTTP_Request2_ConnectionException'),
71
+		// error returned from write callback
72
+		CURLE_WRITE_ERROR           => array('HTTP_Request2_MessageException',
73
+											 HTTP_Request2_Exception::NON_HTTP_REDIRECT),
74
+		CURLE_OPERATION_TIMEOUTED   => array('HTTP_Request2_MessageException',
75
+											 HTTP_Request2_Exception::TIMEOUT),
76
+		CURLE_HTTP_RANGE_ERROR      => array('HTTP_Request2_MessageException'),
77
+		CURLE_SSL_CONNECT_ERROR     => array('HTTP_Request2_ConnectionException'),
78
+		CURLE_LIBRARY_NOT_FOUND     => array('HTTP_Request2_LogicException',
79
+											 HTTP_Request2_Exception::MISCONFIGURATION),
80
+		CURLE_FUNCTION_NOT_FOUND    => array('HTTP_Request2_LogicException',
81
+											 HTTP_Request2_Exception::MISCONFIGURATION),
82
+		CURLE_ABORTED_BY_CALLBACK   => array('HTTP_Request2_MessageException',
83
+											 HTTP_Request2_Exception::NON_HTTP_REDIRECT),
84
+		CURLE_TOO_MANY_REDIRECTS    => array('HTTP_Request2_MessageException',
85
+											 HTTP_Request2_Exception::TOO_MANY_REDIRECTS),
86
+		CURLE_SSL_PEER_CERTIFICATE  => array('HTTP_Request2_ConnectionException'),
87
+		CURLE_GOT_NOTHING           => array('HTTP_Request2_MessageException'),
88
+		CURLE_SSL_ENGINE_NOTFOUND   => array('HTTP_Request2_LogicException',
89
+											 HTTP_Request2_Exception::MISCONFIGURATION),
90
+		CURLE_SSL_ENGINE_SETFAILED  => array('HTTP_Request2_LogicException',
91
+											 HTTP_Request2_Exception::MISCONFIGURATION),
92
+		CURLE_SEND_ERROR            => array('HTTP_Request2_MessageException'),
93
+		CURLE_RECV_ERROR            => array('HTTP_Request2_MessageException'),
94
+		CURLE_SSL_CERTPROBLEM       => array('HTTP_Request2_LogicException',
95
+											 HTTP_Request2_Exception::INVALID_ARGUMENT),
96
+		CURLE_SSL_CIPHER            => array('HTTP_Request2_ConnectionException'),
97
+		CURLE_SSL_CACERT            => array('HTTP_Request2_ConnectionException'),
98
+		CURLE_BAD_CONTENT_ENCODING  => array('HTTP_Request2_MessageException'),
99
+	);
100
+
101
+	/**
102
+	 * Response being received
103
+	 * @var  HTTP_Request2_Response
104
+	 */
105
+	protected $response;
106
+
107
+	/**
108
+	 * Whether 'sentHeaders' event was sent to observers
109
+	 * @var  boolean
110
+	 */
111
+	protected $eventSentHeaders = false;
112
+
113
+	/**
114
+	 * Whether 'receivedHeaders' event was sent to observers
115
+	 * @var boolean
116
+	 */
117
+	protected $eventReceivedHeaders = false;
118
+
119
+	/**
120
+	 * Position within request body
121
+	 * @var  integer
122
+	 * @see  callbackReadBody()
123
+	 */
124
+	protected $position = 0;
125
+
126
+	/**
127
+	 * Information about last transfer, as returned by curl_getinfo()
128
+	 * @var  array
129
+	 */
130
+	protected $lastInfo;
131
+
132
+	/**
133
+	 * Creates a subclass of HTTP_Request2_Exception from curl error data
134
+	 *
135
+	 * @param resource $ch curl handle
136
+	 *
137
+	 * @return HTTP_Request2_Exception
138
+	 */
139
+	protected static function wrapCurlError($ch)
140
+	{
141
+		$nativeCode = curl_errno($ch);
142
+		$message    = 'Curl error: ' . curl_error($ch);
143
+		if (!isset(self::$errorMap[$nativeCode])) {
144
+			return new HTTP_Request2_Exception($message, 0, $nativeCode);
145
+		} else {
146
+			$class = self::$errorMap[$nativeCode][0];
147
+			$code  = empty(self::$errorMap[$nativeCode][1])
148
+					 ? 0 : self::$errorMap[$nativeCode][1];
149
+			return new $class($message, $code, $nativeCode);
150
+		}
151
+	}
152
+
153
+	/**
154
+	 * Sends request to the remote server and returns its response
155
+	 *
156
+	 * @param HTTP_Request2 $request HTTP request message
157
+	 *
158
+	 * @return   HTTP_Request2_Response
159
+	 * @throws   HTTP_Request2_Exception
160
+	 */
161
+	public function sendRequest(HTTP_Request2 $request)
162
+	{
163
+		if (!extension_loaded('curl')) {
164
+			throw new HTTP_Request2_LogicException(
165
+				'cURL extension not available', HTTP_Request2_Exception::MISCONFIGURATION
166
+			);
167
+		}
168
+
169
+		$this->request              = $request;
170
+		$this->response             = null;
171
+		$this->position             = 0;
172
+		$this->eventSentHeaders     = false;
173
+		$this->eventReceivedHeaders = false;
174
+
175
+		try {
176
+			if (false === curl_exec($ch = $this->createCurlHandle())) {
177
+				$e = self::wrapCurlError($ch);
178
+			}
179
+		} catch (Exception $e) {
180
+		}
181
+		if (isset($ch)) {
182
+			$this->lastInfo = curl_getinfo($ch);
183
+			curl_close($ch);
184
+		}
185
+
186
+		$response = $this->response;
187
+		unset($this->request, $this->requestBody, $this->response);
188
+
189
+		if (!empty($e)) {
190
+			throw $e;
191
+		}
192
+
193
+		if ($jar = $request->getCookieJar()) {
194
+			$jar->addCookiesFromResponse($response, $request->getUrl());
195
+		}
196
+
197
+		if (0 < $this->lastInfo['size_download']) {
198
+			$request->setLastEvent('receivedBody', $response);
199
+		}
200
+		return $response;
201
+	}
202
+
203
+	/**
204
+	 * Returns information about last transfer
205
+	 *
206
+	 * @return   array   associative array as returned by curl_getinfo()
207
+	 */
208
+	public function getInfo()
209
+	{
210
+		return $this->lastInfo;
211
+	}
212
+
213
+	/**
214
+	 * Creates a new cURL handle and populates it with data from the request
215
+	 *
216
+	 * @return   resource    a cURL handle, as created by curl_init()
217
+	 * @throws   HTTP_Request2_LogicException
218
+	 * @throws   HTTP_Request2_NotImplementedException
219
+	 */
220
+	protected function createCurlHandle()
221
+	{
222
+		$ch = curl_init();
223
+
224
+		curl_setopt_array($ch, array(
225
+			// setup write callbacks
226
+			CURLOPT_HEADERFUNCTION => array($this, 'callbackWriteHeader'),
227
+			CURLOPT_WRITEFUNCTION  => array($this, 'callbackWriteBody'),
228
+			// buffer size
229
+			CURLOPT_BUFFERSIZE     => $this->request->getConfig('buffer_size'),
230
+			// connection timeout
231
+			CURLOPT_CONNECTTIMEOUT => $this->request->getConfig('connect_timeout'),
232
+			// save full outgoing headers, in case someone is interested
233
+			CURLINFO_HEADER_OUT    => true,
234
+			// request url
235
+			CURLOPT_URL            => $this->request->getUrl()->getUrl()
236
+		));
237
+
238
+		// set up redirects
239
+		if (!$this->request->getConfig('follow_redirects')) {
240
+			curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
241
+		} else {
242
+			if (!@curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true)) {
243
+				throw new HTTP_Request2_LogicException(
244
+					'Redirect support in curl is unavailable due to open_basedir or safe_mode setting',
245
+					HTTP_Request2_Exception::MISCONFIGURATION
246
+				);
247
+			}
248
+			curl_setopt($ch, CURLOPT_MAXREDIRS, $this->request->getConfig('max_redirects'));
249
+			// limit redirects to http(s), works in 5.2.10+
250
+			if (defined('CURLOPT_REDIR_PROTOCOLS')) {
251
+				curl_setopt($ch, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
252
+			}
253
+			// works in 5.3.2+, http://bugs.php.net/bug.php?id=49571
254
+			if ($this->request->getConfig('strict_redirects') && defined('CURLOPT_POSTREDIR')) {
255
+				curl_setopt($ch, CURLOPT_POSTREDIR, 3);
256
+			}
257
+		}
258
+
259
+		// set local IP via CURLOPT_INTERFACE (request #19515)
260
+		if ($ip = $this->request->getConfig('local_ip')) {
261
+			curl_setopt($ch, CURLOPT_INTERFACE, $ip);
262
+		}
263
+
264
+		// request timeout
265
+		if ($timeout = $this->request->getConfig('timeout')) {
266
+			curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
267
+		}
268
+
269
+		// set HTTP version
270
+		switch ($this->request->getConfig('protocol_version')) {
271
+		case '1.0':
272
+			curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
273
+			break;
274
+		case '1.1':
275
+			curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
276
+		}
277
+
278
+		// set request method
279
+		switch ($this->request->getMethod()) {
280
+		case HTTP_Request2::METHOD_GET:
281
+			curl_setopt($ch, CURLOPT_HTTPGET, true);
282
+			break;
283
+		case HTTP_Request2::METHOD_POST:
284
+			curl_setopt($ch, CURLOPT_POST, true);
285
+			break;
286
+		case HTTP_Request2::METHOD_HEAD:
287
+			curl_setopt($ch, CURLOPT_NOBODY, true);
288
+			break;
289
+		case HTTP_Request2::METHOD_PUT:
290
+			curl_setopt($ch, CURLOPT_UPLOAD, true);
291
+			break;
292
+		default:
293
+			curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->request->getMethod());
294
+		}
295
+
296
+		// set proxy, if needed
297
+		if ($host = $this->request->getConfig('proxy_host')) {
298
+			if (!($port = $this->request->getConfig('proxy_port'))) {
299
+				throw new HTTP_Request2_LogicException(
300
+					'Proxy port not provided', HTTP_Request2_Exception::MISSING_VALUE
301
+				);
302
+			}
303
+			curl_setopt($ch, CURLOPT_PROXY, $host . ':' . $port);
304
+			if ($user = $this->request->getConfig('proxy_user')) {
305
+				curl_setopt(
306
+					$ch, CURLOPT_PROXYUSERPWD,
307
+					$user . ':' . $this->request->getConfig('proxy_password')
308
+				);
309
+				switch ($this->request->getConfig('proxy_auth_scheme')) {
310
+				case HTTP_Request2::AUTH_BASIC:
311
+					curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC);
312
+					break;
313
+				case HTTP_Request2::AUTH_DIGEST:
314
+					curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_DIGEST);
315
+				}
316
+			}
317
+			if ($type = $this->request->getConfig('proxy_type')) {
318
+				switch ($type) {
319
+				case 'http':
320
+					curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
321
+					break;
322
+				case 'socks5':
323
+					curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
324
+					break;
325
+				default:
326
+					throw new HTTP_Request2_NotImplementedException(
327
+						"Proxy type '{$type}' is not supported"
328
+					);
329
+				}
330
+			}
331
+		}
332
+
333
+		// set authentication data
334
+		if ($auth = $this->request->getAuth()) {
335
+			curl_setopt($ch, CURLOPT_USERPWD, $auth['user'] . ':' . $auth['password']);
336
+			switch ($auth['scheme']) {
337
+			case HTTP_Request2::AUTH_BASIC:
338
+				curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
339
+				break;
340
+			case HTTP_Request2::AUTH_DIGEST:
341
+				curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
342
+			}
343
+		}
344
+
345
+		// set SSL options
346
+		foreach ($this->request->getConfig() as $name => $value) {
347
+			if ('ssl_verify_host' == $name && null !== $value) {
348
+				curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $value? 2: 0);
349
+			} elseif (isset(self::$sslContextMap[$name]) && null !== $value) {
350
+				curl_setopt($ch, self::$sslContextMap[$name], $value);
351
+			}
352
+		}
353
+
354
+		$headers = $this->request->getHeaders();
355
+		// make cURL automagically send proper header
356
+		if (!isset($headers['accept-encoding'])) {
357
+			$headers['accept-encoding'] = '';
358
+		}
359
+
360
+		if (($jar = $this->request->getCookieJar())
361
+			&& ($cookies = $jar->getMatching($this->request->getUrl(), true))
362
+		) {
363
+			$headers['cookie'] = (empty($headers['cookie'])? '': $headers['cookie'] . '; ') . $cookies;
364
+		}
365
+
366
+		// set headers having special cURL keys
367
+		foreach (self::$headerMap as $name => $option) {
368
+			if (isset($headers[$name])) {
369
+				curl_setopt($ch, $option, $headers[$name]);
370
+				unset($headers[$name]);
371
+			}
372
+		}
373
+
374
+		$this->calculateRequestLength($headers);
375
+		if (isset($headers['content-length']) || isset($headers['transfer-encoding'])) {
376
+			$this->workaroundPhpBug47204($ch, $headers);
377
+		}
378
+
379
+		// set headers not having special keys
380
+		$headersFmt = array();
381
+		foreach ($headers as $name => $value) {
382
+			$canonicalName = implode('-', array_map('ucfirst', explode('-', $name)));
383
+			$headersFmt[]  = $canonicalName . ': ' . $value;
384
+		}
385
+		curl_setopt($ch, CURLOPT_HTTPHEADER, $headersFmt);
386
+
387
+		return $ch;
388
+	}
389
+
390
+	/**
391
+	 * Workaround for PHP bug #47204 that prevents rewinding request body
392
+	 *
393
+	 * The workaround consists of reading the entire request body into memory
394
+	 * and setting it as CURLOPT_POSTFIELDS, so it isn't recommended for large
395
+	 * file uploads, use Socket adapter instead.
396
+	 *
397
+	 * @param resource $ch       cURL handle
398
+	 * @param array    &$headers Request headers
399
+	 */
400
+	protected function workaroundPhpBug47204($ch, &$headers)
401
+	{
402
+		// no redirects, no digest auth -> probably no rewind needed
403
+		if (!$this->request->getConfig('follow_redirects')
404
+			&& (!($auth = $this->request->getAuth())
405
+				|| HTTP_Request2::AUTH_DIGEST != $auth['scheme'])
406
+		) {
407
+			curl_setopt($ch, CURLOPT_READFUNCTION, array($this, 'callbackReadBody'));
408
+
409
+		} else {
410
+			// rewind may be needed, read the whole body into memory
411
+			if ($this->requestBody instanceof HTTP_Request2_MultipartBody) {
412
+				$this->requestBody = $this->requestBody->__toString();
413
+
414
+			} elseif (is_resource($this->requestBody)) {
415
+				$fp = $this->requestBody;
416
+				$this->requestBody = '';
417
+				while (!feof($fp)) {
418
+					$this->requestBody .= fread($fp, 16384);
419
+				}
420
+			}
421
+			// curl hangs up if content-length is present
422
+			unset($headers['content-length']);
423
+			curl_setopt($ch, CURLOPT_POSTFIELDS, $this->requestBody);
424
+		}
425
+	}
426
+
427
+	/**
428
+	 * Callback function called by cURL for reading the request body
429
+	 *
430
+	 * @param resource $ch     cURL handle
431
+	 * @param resource $fd     file descriptor (not used)
432
+	 * @param integer  $length maximum length of data to return
433
+	 *
434
+	 * @return   string      part of the request body, up to $length bytes
435
+	 */
436
+	protected function callbackReadBody($ch, $fd, $length)
437
+	{
438
+		if (!$this->eventSentHeaders) {
439
+			$this->request->setLastEvent(
440
+				'sentHeaders', curl_getinfo($ch, CURLINFO_HEADER_OUT)
441
+			);
442
+			$this->eventSentHeaders = true;
443
+		}
444
+		if (in_array($this->request->getMethod(), self::$bodyDisallowed)
445
+			|| 0 == $this->contentLength || $this->position >= $this->contentLength
446
+		) {
447
+			return '';
448
+		}
449
+		if (is_string($this->requestBody)) {
450
+			$string = substr($this->requestBody, $this->position, $length);
451
+		} elseif (is_resource($this->requestBody)) {
452
+			$string = fread($this->requestBody, $length);
453
+		} else {
454
+			$string = $this->requestBody->read($length);
455
+		}
456
+		$this->request->setLastEvent('sentBodyPart', strlen($string));
457
+		$this->position += strlen($string);
458
+		return $string;
459
+	}
460
+
461
+	/**
462
+	 * Callback function called by cURL for saving the response headers
463
+	 *
464
+	 * @param resource $ch     cURL handle
465
+	 * @param string   $string response header (with trailing CRLF)
466
+	 *
467
+	 * @return   integer     number of bytes saved
468
+	 * @see      HTTP_Request2_Response::parseHeaderLine()
469
+	 */
470
+	protected function callbackWriteHeader($ch, $string)
471
+	{
472
+		// we may receive a second set of headers if doing e.g. digest auth
473
+		if ($this->eventReceivedHeaders || !$this->eventSentHeaders) {
474
+			// don't bother with 100-Continue responses (bug #15785)
475
+			if (!$this->eventSentHeaders
476
+				|| $this->response->getStatus() >= 200
477
+			) {
478
+				$this->request->setLastEvent(
479
+					'sentHeaders', curl_getinfo($ch, CURLINFO_HEADER_OUT)
480
+				);
481
+			}
482
+			$upload = curl_getinfo($ch, CURLINFO_SIZE_UPLOAD);
483
+			// if body wasn't read by a callback, send event with total body size
484
+			if ($upload > $this->position) {
485
+				$this->request->setLastEvent(
486
+					'sentBodyPart', $upload - $this->position
487
+				);
488
+				$this->position = $upload;
489
+			}
490
+			if ($upload && (!$this->eventSentHeaders
491
+							|| $this->response->getStatus() >= 200)
492
+			) {
493
+				$this->request->setLastEvent('sentBody', $upload);
494
+			}
495
+			$this->eventSentHeaders = true;
496
+			// we'll need a new response object
497
+			if ($this->eventReceivedHeaders) {
498
+				$this->eventReceivedHeaders = false;
499
+				$this->response             = null;
500
+			}
501
+		}
502
+		if (empty($this->response)) {
503
+			$this->response = new HTTP_Request2_Response(
504
+				$string, false, curl_getinfo($ch, CURLINFO_EFFECTIVE_URL)
505
+			);
506
+		} else {
507
+			$this->response->parseHeaderLine($string);
508
+			if ('' == trim($string)) {
509
+				// don't bother with 100-Continue responses (bug #15785)
510
+				if (200 <= $this->response->getStatus()) {
511
+					$this->request->setLastEvent('receivedHeaders', $this->response);
512
+				}
513
+
514
+				if ($this->request->getConfig('follow_redirects') && $this->response->isRedirect()) {
515
+					$redirectUrl = new Net_URL2($this->response->getHeader('location'));
516
+
517
+					// for versions lower than 5.2.10, check the redirection URL protocol
518
+					if (!defined('CURLOPT_REDIR_PROTOCOLS') && $redirectUrl->isAbsolute()
519
+						&& !in_array($redirectUrl->getScheme(), array('http', 'https'))
520
+					) {
521
+						return -1;
522
+					}
523
+
524
+					if ($jar = $this->request->getCookieJar()) {
525
+						$jar->addCookiesFromResponse($this->response, $this->request->getUrl());
526
+						if (!$redirectUrl->isAbsolute()) {
527
+							$redirectUrl = $this->request->getUrl()->resolve($redirectUrl);
528
+						}
529
+						if ($cookies = $jar->getMatching($redirectUrl, true)) {
530
+							curl_setopt($ch, CURLOPT_COOKIE, $cookies);
531
+						}
532
+					}
533
+				}
534
+				$this->eventReceivedHeaders = true;
535
+			}
536
+		}
537
+		return strlen($string);
538
+	}
539
+
540
+	/**
541
+	 * Callback function called by cURL for saving the response body
542
+	 *
543
+	 * @param resource $ch     cURL handle (not used)
544
+	 * @param string   $string part of the response body
545
+	 *
546
+	 * @return   integer     number of bytes saved
547
+	 * @throws   HTTP_Request2_MessageException
548
+	 * @see      HTTP_Request2_Response::appendBody()
549
+	 */
550
+	protected function callbackWriteBody($ch, $string)
551
+	{
552
+		// cURL calls WRITEFUNCTION callback without calling HEADERFUNCTION if
553
+		// response doesn't start with proper HTTP status line (see bug #15716)
554
+		if (empty($this->response)) {
555
+			throw new HTTP_Request2_MessageException(
556
+				"Malformed response: {$string}",
557
+				HTTP_Request2_Exception::MALFORMED_RESPONSE
558
+			);
559
+		}
560
+		if ($this->request->getConfig('store_body')) {
561
+			$this->response->appendBody($string);
562
+		}
563
+		$this->request->setLastEvent('receivedBodyPart', $string);
564
+		return strlen($string);
565
+	}
566 566
 }
567 567
 ?>
Please login to merge, or discard this patch.
Switch Indentation   +39 added lines, -39 removed lines patch added patch discarded remove patch
@@ -268,29 +268,29 @@  discard block
 block discarded – undo
268 268
 
269 269
         // set HTTP version
270 270
         switch ($this->request->getConfig('protocol_version')) {
271
-        case '1.0':
272
-            curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
273
-            break;
274
-        case '1.1':
275
-            curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
271
+        	case '1.0':
272
+            	curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
273
+            	break;
274
+        	case '1.1':
275
+            	curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
276 276
         }
277 277
 
278 278
         // set request method
279 279
         switch ($this->request->getMethod()) {
280
-        case HTTP_Request2::METHOD_GET:
281
-            curl_setopt($ch, CURLOPT_HTTPGET, true);
282
-            break;
283
-        case HTTP_Request2::METHOD_POST:
284
-            curl_setopt($ch, CURLOPT_POST, true);
285
-            break;
286
-        case HTTP_Request2::METHOD_HEAD:
287
-            curl_setopt($ch, CURLOPT_NOBODY, true);
288
-            break;
289
-        case HTTP_Request2::METHOD_PUT:
290
-            curl_setopt($ch, CURLOPT_UPLOAD, true);
291
-            break;
292
-        default:
293
-            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->request->getMethod());
280
+        	case HTTP_Request2::METHOD_GET:
281
+            	curl_setopt($ch, CURLOPT_HTTPGET, true);
282
+            	break;
283
+        	case HTTP_Request2::METHOD_POST:
284
+            	curl_setopt($ch, CURLOPT_POST, true);
285
+            	break;
286
+        	case HTTP_Request2::METHOD_HEAD:
287
+            	curl_setopt($ch, CURLOPT_NOBODY, true);
288
+            	break;
289
+        	case HTTP_Request2::METHOD_PUT:
290
+            	curl_setopt($ch, CURLOPT_UPLOAD, true);
291
+            	break;
292
+        	default:
293
+            	curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->request->getMethod());
294 294
         }
295 295
 
296 296
         // set proxy, if needed
@@ -307,25 +307,25 @@  discard block
 block discarded – undo
307 307
                     $user . ':' . $this->request->getConfig('proxy_password')
308 308
                 );
309 309
                 switch ($this->request->getConfig('proxy_auth_scheme')) {
310
-                case HTTP_Request2::AUTH_BASIC:
311
-                    curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC);
312
-                    break;
313
-                case HTTP_Request2::AUTH_DIGEST:
314
-                    curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_DIGEST);
310
+                	case HTTP_Request2::AUTH_BASIC:
311
+                    	curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC);
312
+                    	break;
313
+                	case HTTP_Request2::AUTH_DIGEST:
314
+                    	curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_DIGEST);
315 315
                 }
316 316
             }
317 317
             if ($type = $this->request->getConfig('proxy_type')) {
318 318
                 switch ($type) {
319
-                case 'http':
320
-                    curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
321
-                    break;
322
-                case 'socks5':
323
-                    curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
324
-                    break;
325
-                default:
326
-                    throw new HTTP_Request2_NotImplementedException(
327
-                        "Proxy type '{$type}' is not supported"
328
-                    );
319
+                	case 'http':
320
+                    	curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
321
+                    	break;
322
+                	case 'socks5':
323
+                    	curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
324
+                    	break;
325
+                	default:
326
+                    	throw new HTTP_Request2_NotImplementedException(
327
+                        	"Proxy type '{$type}' is not supported"
328
+                    	);
329 329
                 }
330 330
             }
331 331
         }
@@ -334,11 +334,11 @@  discard block
 block discarded – undo
334 334
         if ($auth = $this->request->getAuth()) {
335 335
             curl_setopt($ch, CURLOPT_USERPWD, $auth['user'] . ':' . $auth['password']);
336 336
             switch ($auth['scheme']) {
337
-            case HTTP_Request2::AUTH_BASIC:
338
-                curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
339
-                break;
340
-            case HTTP_Request2::AUTH_DIGEST:
341
-                curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
337
+            	case HTTP_Request2::AUTH_BASIC:
338
+                	curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
339
+                	break;
340
+            	case HTTP_Request2::AUTH_DIGEST:
341
+                	curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
342 342
             }
343 343
         }
344 344
 
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -345,7 +345,7 @@  discard block
 block discarded – undo
345 345
         // set SSL options
346 346
         foreach ($this->request->getConfig() as $name => $value) {
347 347
             if ('ssl_verify_host' == $name && null !== $value) {
348
-                curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $value? 2: 0);
348
+                curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $value ? 2 : 0);
349 349
             } elseif (isset(self::$sslContextMap[$name]) && null !== $value) {
350 350
                 curl_setopt($ch, self::$sslContextMap[$name], $value);
351 351
             }
@@ -360,7 +360,7 @@  discard block
 block discarded – undo
360 360
         if (($jar = $this->request->getCookieJar())
361 361
             && ($cookies = $jar->getMatching($this->request->getUrl(), true))
362 362
         ) {
363
-            $headers['cookie'] = (empty($headers['cookie'])? '': $headers['cookie'] . '; ') . $cookies;
363
+            $headers['cookie'] = (empty($headers['cookie']) ? '' : $headers['cookie'] . '; ') . $cookies;
364 364
         }
365 365
 
366 366
         // set headers having special cURL keys
Please login to merge, or discard this patch.
libs/PEAR.1.9.5/HTTP/Request2/Adapter/Mock.php 2 patches
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -70,7 +70,7 @@
 block discarded – undo
70 70
      */
71 71
     public function sendRequest(HTTP_Request2 $request)
72 72
     {
73
-        $requestUrl = (string)$request->getUrl();
73
+        $requestUrl = (string) $request->getUrl();
74 74
         $response   = null;
75 75
         foreach ($this->responses as $k => $v) {
76 76
             if (!$v[1] || $requestUrl == $v[1]) {
Please login to merge, or discard this patch.
Indentation   +106 added lines, -106 removed lines patch added patch discarded remove patch
@@ -49,118 +49,118 @@
 block discarded – undo
49 49
  */
50 50
 class HTTP_Request2_Adapter_Mock extends HTTP_Request2_Adapter
51 51
 {
52
-    /**
53
-     * A queue of responses to be returned by sendRequest()
54
-     * @var  array
55
-     */
56
-    protected $responses = array();
52
+	/**
53
+	 * A queue of responses to be returned by sendRequest()
54
+	 * @var  array
55
+	 */
56
+	protected $responses = array();
57 57
 
58
-    /**
59
-     * Returns the next response from the queue built by addResponse()
60
-     *
61
-     * Only responses without explicit URLs or with URLs equal to request URL
62
-     * will be considered. If matching response is not found or the queue is
63
-     * empty then default empty response with status 400 will be returned,
64
-     * if an Exception object was added to the queue it will be thrown.
65
-     *
66
-     * @param HTTP_Request2 $request HTTP request message
67
-     *
68
-     * @return   HTTP_Request2_Response
69
-     * @throws   Exception
70
-     */
71
-    public function sendRequest(HTTP_Request2 $request)
72
-    {
73
-        $requestUrl = (string)$request->getUrl();
74
-        $response   = null;
75
-        foreach ($this->responses as $k => $v) {
76
-            if (!$v[1] || $requestUrl == $v[1]) {
77
-                $response = $v[0];
78
-                array_splice($this->responses, $k, 1);
79
-                break;
80
-            }
81
-        }
82
-        if (!$response) {
83
-            return self::createResponseFromString("HTTP/1.1 400 Bad Request\r\n\r\n");
58
+	/**
59
+	 * Returns the next response from the queue built by addResponse()
60
+	 *
61
+	 * Only responses without explicit URLs or with URLs equal to request URL
62
+	 * will be considered. If matching response is not found or the queue is
63
+	 * empty then default empty response with status 400 will be returned,
64
+	 * if an Exception object was added to the queue it will be thrown.
65
+	 *
66
+	 * @param HTTP_Request2 $request HTTP request message
67
+	 *
68
+	 * @return   HTTP_Request2_Response
69
+	 * @throws   Exception
70
+	 */
71
+	public function sendRequest(HTTP_Request2 $request)
72
+	{
73
+		$requestUrl = (string)$request->getUrl();
74
+		$response   = null;
75
+		foreach ($this->responses as $k => $v) {
76
+			if (!$v[1] || $requestUrl == $v[1]) {
77
+				$response = $v[0];
78
+				array_splice($this->responses, $k, 1);
79
+				break;
80
+			}
81
+		}
82
+		if (!$response) {
83
+			return self::createResponseFromString("HTTP/1.1 400 Bad Request\r\n\r\n");
84 84
 
85
-        } elseif ($response instanceof HTTP_Request2_Response) {
86
-            return $response;
85
+		} elseif ($response instanceof HTTP_Request2_Response) {
86
+			return $response;
87 87
 
88
-        } else {
89
-            // rethrow the exception
90
-            $class   = get_class($response);
91
-            $message = $response->getMessage();
92
-            $code    = $response->getCode();
93
-            throw new $class($message, $code);
94
-        }
95
-    }
88
+		} else {
89
+			// rethrow the exception
90
+			$class   = get_class($response);
91
+			$message = $response->getMessage();
92
+			$code    = $response->getCode();
93
+			throw new $class($message, $code);
94
+		}
95
+	}
96 96
 
97
-    /**
98
-     * Adds response to the queue
99
-     *
100
-     * @param mixed  $response either a string, a pointer to an open file,
101
-     *                         an instance of HTTP_Request2_Response or Exception
102
-     * @param string $url      A request URL this response should be valid for
103
-     *                         (see {@link http://pear.php.net/bugs/bug.php?id=19276})
104
-     *
105
-     * @throws   HTTP_Request2_Exception
106
-     */
107
-    public function addResponse($response, $url = null)
108
-    {
109
-        if (is_string($response)) {
110
-            $response = self::createResponseFromString($response);
111
-        } elseif (is_resource($response)) {
112
-            $response = self::createResponseFromFile($response);
113
-        } elseif (!$response instanceof HTTP_Request2_Response &&
114
-                  !$response instanceof Exception
115
-        ) {
116
-            throw new HTTP_Request2_Exception('Parameter is not a valid response');
117
-        }
118
-        $this->responses[] = array($response, $url);
119
-    }
97
+	/**
98
+	 * Adds response to the queue
99
+	 *
100
+	 * @param mixed  $response either a string, a pointer to an open file,
101
+	 *                         an instance of HTTP_Request2_Response or Exception
102
+	 * @param string $url      A request URL this response should be valid for
103
+	 *                         (see {@link http://pear.php.net/bugs/bug.php?id=19276})
104
+	 *
105
+	 * @throws   HTTP_Request2_Exception
106
+	 */
107
+	public function addResponse($response, $url = null)
108
+	{
109
+		if (is_string($response)) {
110
+			$response = self::createResponseFromString($response);
111
+		} elseif (is_resource($response)) {
112
+			$response = self::createResponseFromFile($response);
113
+		} elseif (!$response instanceof HTTP_Request2_Response &&
114
+				  !$response instanceof Exception
115
+		) {
116
+			throw new HTTP_Request2_Exception('Parameter is not a valid response');
117
+		}
118
+		$this->responses[] = array($response, $url);
119
+	}
120 120
 
121
-    /**
122
-     * Creates a new HTTP_Request2_Response object from a string
123
-     *
124
-     * @param string $str string containing HTTP response message
125
-     *
126
-     * @return   HTTP_Request2_Response
127
-     * @throws   HTTP_Request2_Exception
128
-     */
129
-    public static function createResponseFromString($str)
130
-    {
131
-        $parts       = preg_split('!(\r?\n){2}!m', $str, 2);
132
-        $headerLines = explode("\n", $parts[0]);
133
-        $response    = new HTTP_Request2_Response(array_shift($headerLines));
134
-        foreach ($headerLines as $headerLine) {
135
-            $response->parseHeaderLine($headerLine);
136
-        }
137
-        $response->parseHeaderLine('');
138
-        if (isset($parts[1])) {
139
-            $response->appendBody($parts[1]);
140
-        }
141
-        return $response;
142
-    }
121
+	/**
122
+	 * Creates a new HTTP_Request2_Response object from a string
123
+	 *
124
+	 * @param string $str string containing HTTP response message
125
+	 *
126
+	 * @return   HTTP_Request2_Response
127
+	 * @throws   HTTP_Request2_Exception
128
+	 */
129
+	public static function createResponseFromString($str)
130
+	{
131
+		$parts       = preg_split('!(\r?\n){2}!m', $str, 2);
132
+		$headerLines = explode("\n", $parts[0]);
133
+		$response    = new HTTP_Request2_Response(array_shift($headerLines));
134
+		foreach ($headerLines as $headerLine) {
135
+			$response->parseHeaderLine($headerLine);
136
+		}
137
+		$response->parseHeaderLine('');
138
+		if (isset($parts[1])) {
139
+			$response->appendBody($parts[1]);
140
+		}
141
+		return $response;
142
+	}
143 143
 
144
-    /**
145
-     * Creates a new HTTP_Request2_Response object from a file
146
-     *
147
-     * @param resource $fp file pointer returned by fopen()
148
-     *
149
-     * @return   HTTP_Request2_Response
150
-     * @throws   HTTP_Request2_Exception
151
-     */
152
-    public static function createResponseFromFile($fp)
153
-    {
154
-        $response = new HTTP_Request2_Response(fgets($fp));
155
-        do {
156
-            $headerLine = fgets($fp);
157
-            $response->parseHeaderLine($headerLine);
158
-        } while ('' != trim($headerLine));
144
+	/**
145
+	 * Creates a new HTTP_Request2_Response object from a file
146
+	 *
147
+	 * @param resource $fp file pointer returned by fopen()
148
+	 *
149
+	 * @return   HTTP_Request2_Response
150
+	 * @throws   HTTP_Request2_Exception
151
+	 */
152
+	public static function createResponseFromFile($fp)
153
+	{
154
+		$response = new HTTP_Request2_Response(fgets($fp));
155
+		do {
156
+			$headerLine = fgets($fp);
157
+			$response->parseHeaderLine($headerLine);
158
+		} while ('' != trim($headerLine));
159 159
 
160
-        while (!feof($fp)) {
161
-            $response->appendBody(fread($fp, 8192));
162
-        }
163
-        return $response;
164
-    }
160
+		while (!feof($fp)) {
161
+			$response->appendBody(fread($fp, 8192));
162
+		}
163
+		return $response;
164
+	}
165 165
 }
166 166
 ?>
167 167
\ No newline at end of file
Please login to merge, or discard this patch.
libs/PEAR.1.9.5/HTTP/Request2/Adapter/Socket.php 3 patches
Switch Indentation   +52 added lines, -52 removed lines patch added patch discarded remove patch
@@ -732,34 +732,34 @@  discard block
 block discarded – undo
732 732
             return;
733 733
         }
734 734
         switch ($auth['scheme']) {
735
-        case HTTP_Request2::AUTH_BASIC:
736
-            $headers['authorization'] = 'Basic ' . base64_encode(
737
-                $auth['user'] . ':' . $auth['password']
738
-            );
739
-            break;
740
-
741
-        case HTTP_Request2::AUTH_DIGEST:
742
-            unset($this->serverChallenge);
743
-            $fullUrl = ('/' == $requestUrl[0])?
744
-                       $this->request->getUrl()->getScheme() . '://' .
745
-                        $requestHost . $requestUrl:
746
-                       $requestUrl;
747
-            foreach (array_keys(self::$challenges) as $key) {
748
-                if ($key == substr($fullUrl, 0, strlen($key))) {
749
-                    $headers['authorization'] = $this->createDigestResponse(
750
-                        $auth['user'], $auth['password'],
751
-                        $requestUrl, self::$challenges[$key]
752
-                    );
753
-                    $this->serverChallenge =& self::$challenges[$key];
754
-                    break;
755
-                }
756
-            }
757
-            break;
758
-
759
-        default:
760
-            throw new HTTP_Request2_NotImplementedException(
761
-                "Unknown HTTP authentication scheme '{$auth['scheme']}'"
762
-            );
735
+        	case HTTP_Request2::AUTH_BASIC:
736
+            	$headers['authorization'] = 'Basic ' . base64_encode(
737
+                	$auth['user'] . ':' . $auth['password']
738
+            	);
739
+            	break;
740
+
741
+        	case HTTP_Request2::AUTH_DIGEST:
742
+            	unset($this->serverChallenge);
743
+            	$fullUrl = ('/' == $requestUrl[0])?
744
+                       	$this->request->getUrl()->getScheme() . '://' .
745
+                        	$requestHost . $requestUrl:
746
+                       	$requestUrl;
747
+            	foreach (array_keys(self::$challenges) as $key) {
748
+                	if ($key == substr($fullUrl, 0, strlen($key))) {
749
+                    	$headers['authorization'] = $this->createDigestResponse(
750
+                        	$auth['user'], $auth['password'],
751
+                        	$requestUrl, self::$challenges[$key]
752
+                    	);
753
+                    	$this->serverChallenge =& self::$challenges[$key];
754
+                    	break;
755
+                	}
756
+            	}
757
+            	break;
758
+
759
+        	default:
760
+            	throw new HTTP_Request2_NotImplementedException(
761
+                	"Unknown HTTP authentication scheme '{$auth['scheme']}'"
762
+            	);
763 763
         }
764 764
     }
765 765
 
@@ -783,30 +783,30 @@  discard block
 block discarded – undo
783 783
 
784 784
         $password = $this->request->getConfig('proxy_password');
785 785
         switch ($this->request->getConfig('proxy_auth_scheme')) {
786
-        case HTTP_Request2::AUTH_BASIC:
787
-            $headers['proxy-authorization'] = 'Basic ' . base64_encode(
788
-                $user . ':' . $password
789
-            );
790
-            break;
791
-
792
-        case HTTP_Request2::AUTH_DIGEST:
793
-            unset($this->proxyChallenge);
794
-            $proxyUrl = 'proxy://' . $this->request->getConfig('proxy_host') .
795
-                        ':' . $this->request->getConfig('proxy_port');
796
-            if (!empty(self::$challenges[$proxyUrl])) {
797
-                $headers['proxy-authorization'] = $this->createDigestResponse(
798
-                    $user, $password,
799
-                    $requestUrl, self::$challenges[$proxyUrl]
800
-                );
801
-                $this->proxyChallenge =& self::$challenges[$proxyUrl];
802
-            }
803
-            break;
804
-
805
-        default:
806
-            throw new HTTP_Request2_NotImplementedException(
807
-                "Unknown HTTP authentication scheme '" .
808
-                $this->request->getConfig('proxy_auth_scheme') . "'"
809
-            );
786
+        	case HTTP_Request2::AUTH_BASIC:
787
+            	$headers['proxy-authorization'] = 'Basic ' . base64_encode(
788
+                	$user . ':' . $password
789
+            	);
790
+            	break;
791
+
792
+        	case HTTP_Request2::AUTH_DIGEST:
793
+            	unset($this->proxyChallenge);
794
+            	$proxyUrl = 'proxy://' . $this->request->getConfig('proxy_host') .
795
+                        	':' . $this->request->getConfig('proxy_port');
796
+            	if (!empty(self::$challenges[$proxyUrl])) {
797
+                	$headers['proxy-authorization'] = $this->createDigestResponse(
798
+                    	$user, $password,
799
+                    	$requestUrl, self::$challenges[$proxyUrl]
800
+                	);
801
+                	$this->proxyChallenge =& self::$challenges[$proxyUrl];
802
+            	}
803
+            	break;
804
+
805
+        	default:
806
+            	throw new HTTP_Request2_NotImplementedException(
807
+                	"Unknown HTTP authentication scheme '" .
808
+                	$this->request->getConfig('proxy_auth_scheme') . "'"
809
+            	);
810 810
         }
811 811
     }
812 812
 
Please login to merge, or discard this patch.
Spacing   +23 added lines, -26 removed lines patch added patch discarded remove patch
@@ -199,7 +199,7 @@  discard block
 block discarded – undo
199 199
         $headers = $this->request->getHeaders();
200 200
         $reqHost = $this->request->getUrl()->getHost();
201 201
         if (!($reqPort = $this->request->getUrl()->getPort())) {
202
-            $reqPort = $secure? 443: 80;
202
+            $reqPort = $secure ? 443 : 80;
203 203
         }
204 204
 
205 205
         $httpProxy = $socksProxy = false;
@@ -281,12 +281,12 @@  discard block
 block discarded – undo
281 281
 
282 282
         // Changing SSL context options after connection is established does *not*
283 283
         // work, we need a new connection if options change
284
-        $remote    = ((!$secure || $httpProxy || $socksProxy)? 'tcp://': 'ssl://')
284
+        $remote    = ((!$secure || $httpProxy || $socksProxy) ? 'tcp://' : 'ssl://')
285 285
                      . $host . ':' . $port;
286 286
         $socketKey = $remote . (
287 287
                         ($secure && $httpProxy || $socksProxy)
288 288
                         ? "->{$reqHost}:{$reqPort}" : ''
289
-                     ) . (empty($options)? '': ':' . serialize($options));
289
+                     ) . (empty($options) ? '' : ':' . serialize($options));
290 290
         unset($this->socket);
291 291
 
292 292
         // We use persistent connections and have a connected socket?
@@ -294,7 +294,7 @@  discard block
 block discarded – undo
294 294
         if ($keepAlive && !empty(self::$sockets[$socketKey])
295 295
             && !self::$sockets[$socketKey]->eof()
296 296
         ) {
297
-            $this->socket =& self::$sockets[$socketKey];
297
+            $this->socket = & self::$sockets[$socketKey];
298 298
 
299 299
         } else {
300 300
             if ($socksProxy) {
@@ -324,8 +324,8 @@  discard block
 block discarded – undo
324 324
                     $remote, $this->request->getConfig('connect_timeout'), $options
325 325
                 );
326 326
             }
327
-            $this->request->setLastEvent('connect', empty($conninfo)? $remote: $conninfo);
328
-            self::$sockets[$socketKey] =& $this->socket;
327
+            $this->request->setLastEvent('connect', empty($conninfo) ? $remote : $conninfo);
328
+            self::$sockets[$socketKey] = & $this->socket;
329 329
         }
330 330
         $this->socket->setDeadline($deadline, $this->request->getConfig('timeout'));
331 331
         return $keepAlive;
@@ -383,7 +383,7 @@  discard block
 block discarded – undo
383 383
                        // no body possible for such responses, see also request #17031
384 384
                        || HTTP_Request2::METHOD_HEAD == $this->request->getMethod()
385 385
                        || in_array($response->getStatus(), array(204, 304));
386
-        $persistent  = 'keep-alive' == strtolower($response->getHeader('connection')) ||
386
+        $persistent = 'keep-alive' == strtolower($response->getHeader('connection')) ||
387 387
                        (null === $response->getHeader('connection') &&
388 388
                         '1.1' == $response->getVersion());
389 389
         return $requestKeepAlive && $lengthKnown && $persistent;
@@ -522,7 +522,7 @@  discard block
 block discarded – undo
522 522
                 // probably credentials are invalid
523 523
                 $ret = false;
524 524
             }
525
-            self::$challenges[$prefix] =& $challenge;
525
+            self::$challenges[$prefix] = & $challenge;
526 526
         }
527 527
         return $ret;
528 528
     }
@@ -709,12 +709,10 @@  discard block
 block discarded – undo
709 709
                'nonce="' . $challenge['nonce'] . '", ' .
710 710
                'uri="' . $url . '", ' .
711 711
                'response="' . $digest . '"' .
712
-               (!empty($challenge['opaque'])?
713
-                ', opaque="' . $challenge['opaque'] . '"':
714
-                '') .
715
-               (!empty($challenge['qop'])?
716
-                ', qop="auth", nc=' . $nc . ', cnonce="' . $challenge['cnonce'] . '"':
717
-                '');
712
+               (!empty($challenge['opaque']) ?
713
+                ', opaque="' . $challenge['opaque'] . '"' : '') .
714
+               (!empty($challenge['qop']) ?
715
+                ', qop="auth", nc=' . $nc . ', cnonce="' . $challenge['cnonce'] . '"' : '');
718 716
     }
719 717
 
720 718
     /**
@@ -740,17 +738,16 @@  discard block
 block discarded – undo
740 738
 
741 739
         case HTTP_Request2::AUTH_DIGEST:
742 740
             unset($this->serverChallenge);
743
-            $fullUrl = ('/' == $requestUrl[0])?
741
+            $fullUrl = ('/' == $requestUrl[0]) ?
744 742
                        $this->request->getUrl()->getScheme() . '://' .
745
-                        $requestHost . $requestUrl:
746
-                       $requestUrl;
743
+                        $requestHost . $requestUrl : $requestUrl;
747 744
             foreach (array_keys(self::$challenges) as $key) {
748 745
                 if ($key == substr($fullUrl, 0, strlen($key))) {
749 746
                     $headers['authorization'] = $this->createDigestResponse(
750 747
                         $auth['user'], $auth['password'],
751 748
                         $requestUrl, self::$challenges[$key]
752 749
                     );
753
-                    $this->serverChallenge =& self::$challenges[$key];
750
+                    $this->serverChallenge = & self::$challenges[$key];
754 751
                     break;
755 752
                 }
756 753
             }
@@ -798,7 +795,7 @@  discard block
 block discarded – undo
798 795
                     $user, $password,
799 796
                     $requestUrl, self::$challenges[$proxyUrl]
800 797
                 );
801
-                $this->proxyChallenge =& self::$challenges[$proxyUrl];
798
+                $this->proxyChallenge = & self::$challenges[$proxyUrl];
802 799
             }
803 800
             break;
804 801
 
@@ -824,9 +821,9 @@  discard block
 block discarded – undo
824 821
         $connect = HTTP_Request2::METHOD_CONNECT == $this->request->getMethod();
825 822
         $host    = $url->getHost();
826 823
 
827
-        $defaultPort = 0 == strcasecmp($url->getScheme(), 'https')? 443: 80;
824
+        $defaultPort = 0 == strcasecmp($url->getScheme(), 'https') ? 443 : 80;
828 825
         if (($port = $url->getPort()) && $port != $defaultPort || $connect) {
829
-            $host .= ':' . (empty($port)? $defaultPort: $port);
826
+            $host .= ':' . (empty($port) ? $defaultPort : $port);
830 827
         }
831 828
         // Do not overwrite explicitly set 'Host' header, see bug #16146
832 829
         if (!isset($headers['host'])) {
@@ -847,7 +844,7 @@  discard block
 block discarded – undo
847 844
             }
848 845
             $path        = $url->getPath();
849 846
             $query       = $url->getQuery();
850
-            $requestUrl .= (empty($path)? '/': $path) . (empty($query)? '': '?' . $query);
847
+            $requestUrl .= (empty($path) ? '/' : $path) . (empty($query) ? '' : '?' . $query);
851 848
         }
852 849
 
853 850
         if ('1.1' == $this->request->getConfig('protocol_version')
@@ -858,7 +855,7 @@  discard block
 block discarded – undo
858 855
         if (($jar = $this->request->getCookieJar())
859 856
             && ($cookies = $jar->getMatching($this->request->getUrl(), true))
860 857
         ) {
861
-            $headers['cookie'] = (empty($headers['cookie'])? '': $headers['cookie'] . '; ') . $cookies;
858
+            $headers['cookie'] = (empty($headers['cookie']) ? '' : $headers['cookie'] . '; ') . $cookies;
862 859
         }
863 860
 
864 861
         $this->addAuthorizationHeader($headers, $host, $requestUrl);
@@ -913,7 +910,7 @@  discard block
 block discarded – undo
913 910
                                . self::REGEXP_TOKEN . '|'
914 911
                                . self::REGEXP_QUOTED_STRING . ')\s*(?:'
915 912
                                . $expectParam . ')*)?';
916
-            $expectItem      = '!(100-continue|' . $expectExtension . ')!A';
913
+            $expectItem = '!(100-continue|' . $expectExtension . ')!A';
917 914
 
918 915
             $pos    = 0;
919 916
             $length = strlen($headers['expect']);
@@ -933,7 +930,7 @@  discard block
 block discarded – undo
933 930
                 } else {
934 931
                     $pos += strlen($m[0]);
935 932
                     if (strcasecmp('100-continue', $m[0])) {
936
-                        $expectations[]  = $m[0];
933
+                        $expectations[] = $m[0];
937 934
                     }
938 935
                 }
939 936
             }
@@ -1048,7 +1045,7 @@  discard block
 block discarded – undo
1048 1045
             // 3. ... If a message is received with both a
1049 1046
             // Transfer-Encoding header field and a Content-Length header field,
1050 1047
             // the latter MUST be ignored.
1051
-            $toRead = ($chunked || null === $length)? null: $length;
1048
+            $toRead = ($chunked || null === $length) ? null : $length;
1052 1049
             $this->chunkLength = 0;
1053 1050
 
1054 1051
             while (!$this->socket->eof() && (is_null($toRead) || 0 < $toRead)) {
Please login to merge, or discard this patch.
Indentation   +1077 added lines, -1077 removed lines patch added patch discarded remove patch
@@ -39,1083 +39,1083 @@
 block discarded – undo
39 39
  */
40 40
 class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
41 41
 {
42
-    /**
43
-     * Regular expression for 'token' rule from RFC 2616
44
-     */
45
-    const REGEXP_TOKEN = '[^\x00-\x1f\x7f-\xff()<>@,;:\\\\"/\[\]?={}\s]+';
46
-
47
-    /**
48
-     * Regular expression for 'quoted-string' rule from RFC 2616
49
-     */
50
-    const REGEXP_QUOTED_STRING = '"(?>[^"\\\\]+|\\\\.)*"';
51
-
52
-    /**
53
-     * Connected sockets, needed for Keep-Alive support
54
-     * @var  array
55
-     * @see  connect()
56
-     */
57
-    protected static $sockets = array();
58
-
59
-    /**
60
-     * Data for digest authentication scheme
61
-     *
62
-     * The keys for the array are URL prefixes.
63
-     *
64
-     * The values are associative arrays with data (realm, nonce, nonce-count,
65
-     * opaque...) needed for digest authentication. Stored here to prevent making
66
-     * duplicate requests to digest-protected resources after we have already
67
-     * received the challenge.
68
-     *
69
-     * @var  array
70
-     */
71
-    protected static $challenges = array();
72
-
73
-    /**
74
-     * Connected socket
75
-     * @var  HTTP_Request2_SocketWrapper
76
-     * @see  connect()
77
-     */
78
-    protected $socket;
79
-
80
-    /**
81
-     * Challenge used for server digest authentication
82
-     * @var  array
83
-     */
84
-    protected $serverChallenge;
85
-
86
-    /**
87
-     * Challenge used for proxy digest authentication
88
-     * @var  array
89
-     */
90
-    protected $proxyChallenge;
91
-
92
-    /**
93
-     * Remaining length of the current chunk, when reading chunked response
94
-     * @var  integer
95
-     * @see  readChunked()
96
-     */
97
-    protected $chunkLength = 0;
98
-
99
-    /**
100
-     * Remaining amount of redirections to follow
101
-     *
102
-     * Starts at 'max_redirects' configuration parameter and is reduced on each
103
-     * subsequent redirect. An Exception will be thrown once it reaches zero.
104
-     *
105
-     * @var  integer
106
-     */
107
-    protected $redirectCountdown = null;
108
-
109
-    /**
110
-     * Whether to wait for "100 Continue" response before sending request body
111
-     * @var bool
112
-     */
113
-    protected $expect100Continue = false;
114
-
115
-    /**
116
-     * Sends request to the remote server and returns its response
117
-     *
118
-     * @param HTTP_Request2 $request HTTP request message
119
-     *
120
-     * @return   HTTP_Request2_Response
121
-     * @throws   HTTP_Request2_Exception
122
-     */
123
-    public function sendRequest(HTTP_Request2 $request)
124
-    {
125
-        $this->request = $request;
126
-
127
-        try {
128
-            $keepAlive = $this->connect();
129
-            $headers   = $this->prepareHeaders();
130
-            $this->socket->write($headers);
131
-            // provide request headers to the observer, see request #7633
132
-            $this->request->setLastEvent('sentHeaders', $headers);
133
-
134
-            if (!$this->expect100Continue) {
135
-                $this->writeBody();
136
-                $response = $this->readResponse();
137
-
138
-            } else {
139
-                $response = $this->readResponse();
140
-                if (!$response || 100 == $response->getStatus()) {
141
-                    $this->expect100Continue = false;
142
-                    // either got "100 Continue" or timed out -> send body
143
-                    $this->writeBody();
144
-                    $response = $this->readResponse();
145
-                }
146
-            }
147
-
148
-
149
-            if ($jar = $request->getCookieJar()) {
150
-                $jar->addCookiesFromResponse($response, $request->getUrl());
151
-            }
152
-
153
-            if (!$this->canKeepAlive($keepAlive, $response)) {
154
-                $this->disconnect();
155
-            }
156
-
157
-            if ($this->shouldUseProxyDigestAuth($response)) {
158
-                return $this->sendRequest($request);
159
-            }
160
-            if ($this->shouldUseServerDigestAuth($response)) {
161
-                return $this->sendRequest($request);
162
-            }
163
-            if ($authInfo = $response->getHeader('authentication-info')) {
164
-                $this->updateChallenge($this->serverChallenge, $authInfo);
165
-            }
166
-            if ($proxyInfo = $response->getHeader('proxy-authentication-info')) {
167
-                $this->updateChallenge($this->proxyChallenge, $proxyInfo);
168
-            }
169
-
170
-        } catch (Exception $e) {
171
-            $this->disconnect();
172
-        }
173
-
174
-        unset($this->request, $this->requestBody);
175
-
176
-        if (!empty($e)) {
177
-            $this->redirectCountdown = null;
178
-            throw $e;
179
-        }
180
-
181
-        if (!$request->getConfig('follow_redirects') || !$response->isRedirect()) {
182
-            $this->redirectCountdown = null;
183
-            return $response;
184
-        } else {
185
-            return $this->handleRedirect($request, $response);
186
-        }
187
-    }
188
-
189
-    /**
190
-     * Connects to the remote server
191
-     *
192
-     * @return   bool    whether the connection can be persistent
193
-     * @throws   HTTP_Request2_Exception
194
-     */
195
-    protected function connect()
196
-    {
197
-        $secure  = 0 == strcasecmp($this->request->getUrl()->getScheme(), 'https');
198
-        $tunnel  = HTTP_Request2::METHOD_CONNECT == $this->request->getMethod();
199
-        $headers = $this->request->getHeaders();
200
-        $reqHost = $this->request->getUrl()->getHost();
201
-        if (!($reqPort = $this->request->getUrl()->getPort())) {
202
-            $reqPort = $secure? 443: 80;
203
-        }
204
-
205
-        $httpProxy = $socksProxy = false;
206
-        if (!($host = $this->request->getConfig('proxy_host'))) {
207
-            $host = $reqHost;
208
-            $port = $reqPort;
209
-        } else {
210
-            if (!($port = $this->request->getConfig('proxy_port'))) {
211
-                throw new HTTP_Request2_LogicException(
212
-                    'Proxy port not provided',
213
-                    HTTP_Request2_Exception::MISSING_VALUE
214
-                );
215
-            }
216
-            if ('http' == ($type = $this->request->getConfig('proxy_type'))) {
217
-                $httpProxy = true;
218
-            } elseif ('socks5' == $type) {
219
-                $socksProxy = true;
220
-            } else {
221
-                throw new HTTP_Request2_NotImplementedException(
222
-                    "Proxy type '{$type}' is not supported"
223
-                );
224
-            }
225
-        }
226
-
227
-        if ($tunnel && !$httpProxy) {
228
-            throw new HTTP_Request2_LogicException(
229
-                "Trying to perform CONNECT request without proxy",
230
-                HTTP_Request2_Exception::MISSING_VALUE
231
-            );
232
-        }
233
-        if ($secure && !in_array('ssl', stream_get_transports())) {
234
-            throw new HTTP_Request2_LogicException(
235
-                'Need OpenSSL support for https:// requests',
236
-                HTTP_Request2_Exception::MISCONFIGURATION
237
-            );
238
-        }
239
-
240
-        // RFC 2068, section 19.7.1: A client MUST NOT send the Keep-Alive
241
-        // connection token to a proxy server...
242
-        if ($httpProxy && !$secure && !empty($headers['connection'])
243
-            && 'Keep-Alive' == $headers['connection']
244
-        ) {
245
-            $this->request->setHeader('connection');
246
-        }
247
-
248
-        $keepAlive = ('1.1' == $this->request->getConfig('protocol_version') &&
249
-                      empty($headers['connection'])) ||
250
-                     (!empty($headers['connection']) &&
251
-                      'Keep-Alive' == $headers['connection']);
252
-
253
-        $options = array();
254
-        if ($ip = $this->request->getConfig('local_ip')) {
255
-            $options['socket'] = array(
256
-                'bindto' => (false === strpos($ip, ':') ? $ip : '[' . $ip . ']') . ':0'
257
-            );
258
-        }
259
-        if ($secure || $tunnel) {
260
-            $options['ssl'] = array();
261
-            foreach ($this->request->getConfig() as $name => $value) {
262
-                if ('ssl_' == substr($name, 0, 4) && null !== $value) {
263
-                    if ('ssl_verify_host' == $name) {
264
-                        if ($value) {
265
-                            $options['ssl']['CN_match'] = $reqHost;
266
-                        }
267
-                    } else {
268
-                        $options['ssl'][substr($name, 4)] = $value;
269
-                    }
270
-                }
271
-            }
272
-            ksort($options['ssl']);
273
-        }
274
-
275
-        // Use global request timeout if given, see feature requests #5735, #8964
276
-        if ($timeout = $this->request->getConfig('timeout')) {
277
-            $deadline = time() + $timeout;
278
-        } else {
279
-            $deadline = null;
280
-        }
281
-
282
-        // Changing SSL context options after connection is established does *not*
283
-        // work, we need a new connection if options change
284
-        $remote    = ((!$secure || $httpProxy || $socksProxy)? 'tcp://': 'ssl://')
285
-                     . $host . ':' . $port;
286
-        $socketKey = $remote . (
287
-                        ($secure && $httpProxy || $socksProxy)
288
-                        ? "->{$reqHost}:{$reqPort}" : ''
289
-                     ) . (empty($options)? '': ':' . serialize($options));
290
-        unset($this->socket);
291
-
292
-        // We use persistent connections and have a connected socket?
293
-        // Ensure that the socket is still connected, see bug #16149
294
-        if ($keepAlive && !empty(self::$sockets[$socketKey])
295
-            && !self::$sockets[$socketKey]->eof()
296
-        ) {
297
-            $this->socket =& self::$sockets[$socketKey];
298
-
299
-        } else {
300
-            if ($socksProxy) {
301
-                require_once 'HTTP/Request2/SOCKS5.php';
302
-
303
-                $this->socket = new HTTP_Request2_SOCKS5(
304
-                    $remote, $this->request->getConfig('connect_timeout'),
305
-                    $options, $this->request->getConfig('proxy_user'),
306
-                    $this->request->getConfig('proxy_password')
307
-                );
308
-                // handle request timeouts ASAP
309
-                $this->socket->setDeadline($deadline, $this->request->getConfig('timeout'));
310
-                $this->socket->connect($reqHost, $reqPort);
311
-                if (!$secure) {
312
-                    $conninfo = "tcp://{$reqHost}:{$reqPort} via {$remote}";
313
-                } else {
314
-                    $this->socket->enableCrypto();
315
-                    $conninfo = "ssl://{$reqHost}:{$reqPort} via {$remote}";
316
-                }
317
-
318
-            } elseif ($secure && $httpProxy && !$tunnel) {
319
-                $this->establishTunnel();
320
-                $conninfo = "ssl://{$reqHost}:{$reqPort} via {$remote}";
321
-
322
-            } else {
323
-                $this->socket = new HTTP_Request2_SocketWrapper(
324
-                    $remote, $this->request->getConfig('connect_timeout'), $options
325
-                );
326
-            }
327
-            $this->request->setLastEvent('connect', empty($conninfo)? $remote: $conninfo);
328
-            self::$sockets[$socketKey] =& $this->socket;
329
-        }
330
-        $this->socket->setDeadline($deadline, $this->request->getConfig('timeout'));
331
-        return $keepAlive;
332
-    }
333
-
334
-    /**
335
-     * Establishes a tunnel to a secure remote server via HTTP CONNECT request
336
-     *
337
-     * This method will fail if 'ssl_verify_peer' is enabled. Probably because PHP
338
-     * sees that we are connected to a proxy server (duh!) rather than the server
339
-     * that presents its certificate.
340
-     *
341
-     * @link     http://tools.ietf.org/html/rfc2817#section-5.2
342
-     * @throws   HTTP_Request2_Exception
343
-     */
344
-    protected function establishTunnel()
345
-    {
346
-        $donor   = new self;
347
-        $connect = new HTTP_Request2(
348
-            $this->request->getUrl(), HTTP_Request2::METHOD_CONNECT,
349
-            array_merge($this->request->getConfig(), array('adapter' => $donor))
350
-        );
351
-        $response = $connect->send();
352
-        // Need any successful (2XX) response
353
-        if (200 > $response->getStatus() || 300 <= $response->getStatus()) {
354
-            throw new HTTP_Request2_ConnectionException(
355
-                'Failed to connect via HTTPS proxy. Proxy response: ' .
356
-                $response->getStatus() . ' ' . $response->getReasonPhrase()
357
-            );
358
-        }
359
-        $this->socket = $donor->socket;
360
-        $this->socket->enableCrypto();
361
-    }
362
-
363
-    /**
364
-     * Checks whether current connection may be reused or should be closed
365
-     *
366
-     * @param boolean                $requestKeepAlive whether connection could
367
-     *                               be persistent in the first place
368
-     * @param HTTP_Request2_Response $response         response object to check
369
-     *
370
-     * @return   boolean
371
-     */
372
-    protected function canKeepAlive($requestKeepAlive, HTTP_Request2_Response $response)
373
-    {
374
-        // Do not close socket on successful CONNECT request
375
-        if (HTTP_Request2::METHOD_CONNECT == $this->request->getMethod()
376
-            && 200 <= $response->getStatus() && 300 > $response->getStatus()
377
-        ) {
378
-            return true;
379
-        }
380
-
381
-        $lengthKnown = 'chunked' == strtolower($response->getHeader('transfer-encoding'))
382
-                       || null !== $response->getHeader('content-length')
383
-                       // no body possible for such responses, see also request #17031
384
-                       || HTTP_Request2::METHOD_HEAD == $this->request->getMethod()
385
-                       || in_array($response->getStatus(), array(204, 304));
386
-        $persistent  = 'keep-alive' == strtolower($response->getHeader('connection')) ||
387
-                       (null === $response->getHeader('connection') &&
388
-                        '1.1' == $response->getVersion());
389
-        return $requestKeepAlive && $lengthKnown && $persistent;
390
-    }
391
-
392
-    /**
393
-     * Disconnects from the remote server
394
-     */
395
-    protected function disconnect()
396
-    {
397
-        if (!empty($this->socket)) {
398
-            $this->socket = null;
399
-            $this->request->setLastEvent('disconnect');
400
-        }
401
-    }
402
-
403
-    /**
404
-     * Handles HTTP redirection
405
-     *
406
-     * This method will throw an Exception if redirect to a non-HTTP(S) location
407
-     * is attempted, also if number of redirects performed already is equal to
408
-     * 'max_redirects' configuration parameter.
409
-     *
410
-     * @param HTTP_Request2          $request  Original request
411
-     * @param HTTP_Request2_Response $response Response containing redirect
412
-     *
413
-     * @return   HTTP_Request2_Response      Response from a new location
414
-     * @throws   HTTP_Request2_Exception
415
-     */
416
-    protected function handleRedirect(
417
-        HTTP_Request2 $request, HTTP_Request2_Response $response
418
-    ) {
419
-        if (is_null($this->redirectCountdown)) {
420
-            $this->redirectCountdown = $request->getConfig('max_redirects');
421
-        }
422
-        if (0 == $this->redirectCountdown) {
423
-            $this->redirectCountdown = null;
424
-            // Copying cURL behaviour
425
-            throw new HTTP_Request2_MessageException(
426
-                'Maximum (' . $request->getConfig('max_redirects') . ') redirects followed',
427
-                HTTP_Request2_Exception::TOO_MANY_REDIRECTS
428
-            );
429
-        }
430
-        $redirectUrl = new Net_URL2(
431
-            $response->getHeader('location'),
432
-            array(Net_URL2::OPTION_USE_BRACKETS => $request->getConfig('use_brackets'))
433
-        );
434
-        // refuse non-HTTP redirect
435
-        if ($redirectUrl->isAbsolute()
436
-            && !in_array($redirectUrl->getScheme(), array('http', 'https'))
437
-        ) {
438
-            $this->redirectCountdown = null;
439
-            throw new HTTP_Request2_MessageException(
440
-                'Refusing to redirect to a non-HTTP URL ' . $redirectUrl->__toString(),
441
-                HTTP_Request2_Exception::NON_HTTP_REDIRECT
442
-            );
443
-        }
444
-        // Theoretically URL should be absolute (see http://tools.ietf.org/html/rfc2616#section-14.30),
445
-        // but in practice it is often not
446
-        if (!$redirectUrl->isAbsolute()) {
447
-            $redirectUrl = $request->getUrl()->resolve($redirectUrl);
448
-        }
449
-        $redirect = clone $request;
450
-        $redirect->setUrl($redirectUrl);
451
-        if (303 == $response->getStatus()
452
-            || (!$request->getConfig('strict_redirects')
453
-                && in_array($response->getStatus(), array(301, 302)))
454
-        ) {
455
-            $redirect->setMethod(HTTP_Request2::METHOD_GET);
456
-            $redirect->setBody('');
457
-        }
458
-
459
-        if (0 < $this->redirectCountdown) {
460
-            $this->redirectCountdown--;
461
-        }
462
-        return $this->sendRequest($redirect);
463
-    }
464
-
465
-    /**
466
-     * Checks whether another request should be performed with server digest auth
467
-     *
468
-     * Several conditions should be satisfied for it to return true:
469
-     *   - response status should be 401
470
-     *   - auth credentials should be set in the request object
471
-     *   - response should contain WWW-Authenticate header with digest challenge
472
-     *   - there is either no challenge stored for this URL or new challenge
473
-     *     contains stale=true parameter (in other case we probably just failed
474
-     *     due to invalid username / password)
475
-     *
476
-     * The method stores challenge values in $challenges static property
477
-     *
478
-     * @param HTTP_Request2_Response $response response to check
479
-     *
480
-     * @return   boolean whether another request should be performed
481
-     * @throws   HTTP_Request2_Exception in case of unsupported challenge parameters
482
-     */
483
-    protected function shouldUseServerDigestAuth(HTTP_Request2_Response $response)
484
-    {
485
-        // no sense repeating a request if we don't have credentials
486
-        if (401 != $response->getStatus() || !$this->request->getAuth()) {
487
-            return false;
488
-        }
489
-        if (!$challenge = $this->parseDigestChallenge($response->getHeader('www-authenticate'))) {
490
-            return false;
491
-        }
492
-
493
-        $url    = $this->request->getUrl();
494
-        $scheme = $url->getScheme();
495
-        $host   = $scheme . '://' . $url->getHost();
496
-        if ($port = $url->getPort()) {
497
-            if ((0 == strcasecmp($scheme, 'http') && 80 != $port)
498
-                || (0 == strcasecmp($scheme, 'https') && 443 != $port)
499
-            ) {
500
-                $host .= ':' . $port;
501
-            }
502
-        }
503
-
504
-        if (!empty($challenge['domain'])) {
505
-            $prefixes = array();
506
-            foreach (preg_split('/\\s+/', $challenge['domain']) as $prefix) {
507
-                // don't bother with different servers
508
-                if ('/' == substr($prefix, 0, 1)) {
509
-                    $prefixes[] = $host . $prefix;
510
-                }
511
-            }
512
-        }
513
-        if (empty($prefixes)) {
514
-            $prefixes = array($host . '/');
515
-        }
516
-
517
-        $ret = true;
518
-        foreach ($prefixes as $prefix) {
519
-            if (!empty(self::$challenges[$prefix])
520
-                && (empty($challenge['stale']) || strcasecmp('true', $challenge['stale']))
521
-            ) {
522
-                // probably credentials are invalid
523
-                $ret = false;
524
-            }
525
-            self::$challenges[$prefix] =& $challenge;
526
-        }
527
-        return $ret;
528
-    }
529
-
530
-    /**
531
-     * Checks whether another request should be performed with proxy digest auth
532
-     *
533
-     * Several conditions should be satisfied for it to return true:
534
-     *   - response status should be 407
535
-     *   - proxy auth credentials should be set in the request object
536
-     *   - response should contain Proxy-Authenticate header with digest challenge
537
-     *   - there is either no challenge stored for this proxy or new challenge
538
-     *     contains stale=true parameter (in other case we probably just failed
539
-     *     due to invalid username / password)
540
-     *
541
-     * The method stores challenge values in $challenges static property
542
-     *
543
-     * @param HTTP_Request2_Response $response response to check
544
-     *
545
-     * @return   boolean whether another request should be performed
546
-     * @throws   HTTP_Request2_Exception in case of unsupported challenge parameters
547
-     */
548
-    protected function shouldUseProxyDigestAuth(HTTP_Request2_Response $response)
549
-    {
550
-        if (407 != $response->getStatus() || !$this->request->getConfig('proxy_user')) {
551
-            return false;
552
-        }
553
-        if (!($challenge = $this->parseDigestChallenge($response->getHeader('proxy-authenticate')))) {
554
-            return false;
555
-        }
556
-
557
-        $key = 'proxy://' . $this->request->getConfig('proxy_host') .
558
-               ':' . $this->request->getConfig('proxy_port');
559
-
560
-        if (!empty(self::$challenges[$key])
561
-            && (empty($challenge['stale']) || strcasecmp('true', $challenge['stale']))
562
-        ) {
563
-            $ret = false;
564
-        } else {
565
-            $ret = true;
566
-        }
567
-        self::$challenges[$key] = $challenge;
568
-        return $ret;
569
-    }
570
-
571
-    /**
572
-     * Extracts digest method challenge from (WWW|Proxy)-Authenticate header value
573
-     *
574
-     * There is a problem with implementation of RFC 2617: several of the parameters
575
-     * are defined as quoted-string there and thus may contain backslash escaped
576
-     * double quotes (RFC 2616, section 2.2). However, RFC 2617 defines unq(X) as
577
-     * just value of quoted-string X without surrounding quotes, it doesn't speak
578
-     * about removing backslash escaping.
579
-     *
580
-     * Now realm parameter is user-defined and human-readable, strange things
581
-     * happen when it contains quotes:
582
-     *   - Apache allows quotes in realm, but apparently uses realm value without
583
-     *     backslashes for digest computation
584
-     *   - Squid allows (manually escaped) quotes there, but it is impossible to
585
-     *     authorize with either escaped or unescaped quotes used in digest,
586
-     *     probably it can't parse the response (?)
587
-     *   - Both IE and Firefox display realm value with backslashes in
588
-     *     the password popup and apparently use the same value for digest
589
-     *
590
-     * HTTP_Request2 follows IE and Firefox (and hopefully RFC 2617) in
591
-     * quoted-string handling, unfortunately that means failure to authorize
592
-     * sometimes
593
-     *
594
-     * @param string $headerValue value of WWW-Authenticate or Proxy-Authenticate header
595
-     *
596
-     * @return   mixed   associative array with challenge parameters, false if
597
-     *                   no challenge is present in header value
598
-     * @throws   HTTP_Request2_NotImplementedException in case of unsupported challenge parameters
599
-     */
600
-    protected function parseDigestChallenge($headerValue)
601
-    {
602
-        $authParam   = '(' . self::REGEXP_TOKEN . ')\\s*=\\s*(' .
603
-                       self::REGEXP_TOKEN . '|' . self::REGEXP_QUOTED_STRING . ')';
604
-        $challenge   = "!(?<=^|\\s|,)Digest ({$authParam}\\s*(,\\s*|$))+!";
605
-        if (!preg_match($challenge, $headerValue, $matches)) {
606
-            return false;
607
-        }
608
-
609
-        preg_match_all('!' . $authParam . '!', $matches[0], $params);
610
-        $paramsAry   = array();
611
-        $knownParams = array('realm', 'domain', 'nonce', 'opaque', 'stale',
612
-                             'algorithm', 'qop');
613
-        for ($i = 0; $i < count($params[0]); $i++) {
614
-            // section 3.2.1: Any unrecognized directive MUST be ignored.
615
-            if (in_array($params[1][$i], $knownParams)) {
616
-                if ('"' == substr($params[2][$i], 0, 1)) {
617
-                    $paramsAry[$params[1][$i]] = substr($params[2][$i], 1, -1);
618
-                } else {
619
-                    $paramsAry[$params[1][$i]] = $params[2][$i];
620
-                }
621
-            }
622
-        }
623
-        // we only support qop=auth
624
-        if (!empty($paramsAry['qop'])
625
-            && !in_array('auth', array_map('trim', explode(',', $paramsAry['qop'])))
626
-        ) {
627
-            throw new HTTP_Request2_NotImplementedException(
628
-                "Only 'auth' qop is currently supported in digest authentication, " .
629
-                "server requested '{$paramsAry['qop']}'"
630
-            );
631
-        }
632
-        // we only support algorithm=MD5
633
-        if (!empty($paramsAry['algorithm']) && 'MD5' != $paramsAry['algorithm']) {
634
-            throw new HTTP_Request2_NotImplementedException(
635
-                "Only 'MD5' algorithm is currently supported in digest authentication, " .
636
-                "server requested '{$paramsAry['algorithm']}'"
637
-            );
638
-        }
639
-
640
-        return $paramsAry;
641
-    }
642
-
643
-    /**
644
-     * Parses [Proxy-]Authentication-Info header value and updates challenge
645
-     *
646
-     * @param array  &$challenge  challenge to update
647
-     * @param string $headerValue value of [Proxy-]Authentication-Info header
648
-     *
649
-     * @todo     validate server rspauth response
650
-     */
651
-    protected function updateChallenge(&$challenge, $headerValue)
652
-    {
653
-        $authParam   = '!(' . self::REGEXP_TOKEN . ')\\s*=\\s*(' .
654
-                       self::REGEXP_TOKEN . '|' . self::REGEXP_QUOTED_STRING . ')!';
655
-        $paramsAry   = array();
656
-
657
-        preg_match_all($authParam, $headerValue, $params);
658
-        for ($i = 0; $i < count($params[0]); $i++) {
659
-            if ('"' == substr($params[2][$i], 0, 1)) {
660
-                $paramsAry[$params[1][$i]] = substr($params[2][$i], 1, -1);
661
-            } else {
662
-                $paramsAry[$params[1][$i]] = $params[2][$i];
663
-            }
664
-        }
665
-        // for now, just update the nonce value
666
-        if (!empty($paramsAry['nextnonce'])) {
667
-            $challenge['nonce'] = $paramsAry['nextnonce'];
668
-            $challenge['nc']    = 1;
669
-        }
670
-    }
671
-
672
-    /**
673
-     * Creates a value for [Proxy-]Authorization header when using digest authentication
674
-     *
675
-     * @param string $user       user name
676
-     * @param string $password   password
677
-     * @param string $url        request URL
678
-     * @param array  &$challenge digest challenge parameters
679
-     *
680
-     * @return   string  value of [Proxy-]Authorization request header
681
-     * @link     http://tools.ietf.org/html/rfc2617#section-3.2.2
682
-     */
683
-    protected function createDigestResponse($user, $password, $url, &$challenge)
684
-    {
685
-        if (false !== ($q = strpos($url, '?'))
686
-            && $this->request->getConfig('digest_compat_ie')
687
-        ) {
688
-            $url = substr($url, 0, $q);
689
-        }
690
-
691
-        $a1 = md5($user . ':' . $challenge['realm'] . ':' . $password);
692
-        $a2 = md5($this->request->getMethod() . ':' . $url);
693
-
694
-        if (empty($challenge['qop'])) {
695
-            $digest = md5($a1 . ':' . $challenge['nonce'] . ':' . $a2);
696
-        } else {
697
-            $challenge['cnonce'] = 'Req2.' . rand();
698
-            if (empty($challenge['nc'])) {
699
-                $challenge['nc'] = 1;
700
-            }
701
-            $nc     = sprintf('%08x', $challenge['nc']++);
702
-            $digest = md5(
703
-                $a1 . ':' . $challenge['nonce'] . ':' . $nc . ':' .
704
-                $challenge['cnonce'] . ':auth:' . $a2
705
-            );
706
-        }
707
-        return 'Digest username="' . str_replace(array('\\', '"'), array('\\\\', '\\"'), $user) . '", ' .
708
-               'realm="' . $challenge['realm'] . '", ' .
709
-               'nonce="' . $challenge['nonce'] . '", ' .
710
-               'uri="' . $url . '", ' .
711
-               'response="' . $digest . '"' .
712
-               (!empty($challenge['opaque'])?
713
-                ', opaque="' . $challenge['opaque'] . '"':
714
-                '') .
715
-               (!empty($challenge['qop'])?
716
-                ', qop="auth", nc=' . $nc . ', cnonce="' . $challenge['cnonce'] . '"':
717
-                '');
718
-    }
719
-
720
-    /**
721
-     * Adds 'Authorization' header (if needed) to request headers array
722
-     *
723
-     * @param array  &$headers    request headers
724
-     * @param string $requestHost request host (needed for digest authentication)
725
-     * @param string $requestUrl  request URL (needed for digest authentication)
726
-     *
727
-     * @throws   HTTP_Request2_NotImplementedException
728
-     */
729
-    protected function addAuthorizationHeader(&$headers, $requestHost, $requestUrl)
730
-    {
731
-        if (!($auth = $this->request->getAuth())) {
732
-            return;
733
-        }
734
-        switch ($auth['scheme']) {
735
-        case HTTP_Request2::AUTH_BASIC:
736
-            $headers['authorization'] = 'Basic ' . base64_encode(
737
-                $auth['user'] . ':' . $auth['password']
738
-            );
739
-            break;
740
-
741
-        case HTTP_Request2::AUTH_DIGEST:
742
-            unset($this->serverChallenge);
743
-            $fullUrl = ('/' == $requestUrl[0])?
744
-                       $this->request->getUrl()->getScheme() . '://' .
745
-                        $requestHost . $requestUrl:
746
-                       $requestUrl;
747
-            foreach (array_keys(self::$challenges) as $key) {
748
-                if ($key == substr($fullUrl, 0, strlen($key))) {
749
-                    $headers['authorization'] = $this->createDigestResponse(
750
-                        $auth['user'], $auth['password'],
751
-                        $requestUrl, self::$challenges[$key]
752
-                    );
753
-                    $this->serverChallenge =& self::$challenges[$key];
754
-                    break;
755
-                }
756
-            }
757
-            break;
758
-
759
-        default:
760
-            throw new HTTP_Request2_NotImplementedException(
761
-                "Unknown HTTP authentication scheme '{$auth['scheme']}'"
762
-            );
763
-        }
764
-    }
765
-
766
-    /**
767
-     * Adds 'Proxy-Authorization' header (if needed) to request headers array
768
-     *
769
-     * @param array  &$headers   request headers
770
-     * @param string $requestUrl request URL (needed for digest authentication)
771
-     *
772
-     * @throws   HTTP_Request2_NotImplementedException
773
-     */
774
-    protected function addProxyAuthorizationHeader(&$headers, $requestUrl)
775
-    {
776
-        if (!$this->request->getConfig('proxy_host')
777
-            || !($user = $this->request->getConfig('proxy_user'))
778
-            || (0 == strcasecmp('https', $this->request->getUrl()->getScheme())
779
-                && HTTP_Request2::METHOD_CONNECT != $this->request->getMethod())
780
-        ) {
781
-            return;
782
-        }
783
-
784
-        $password = $this->request->getConfig('proxy_password');
785
-        switch ($this->request->getConfig('proxy_auth_scheme')) {
786
-        case HTTP_Request2::AUTH_BASIC:
787
-            $headers['proxy-authorization'] = 'Basic ' . base64_encode(
788
-                $user . ':' . $password
789
-            );
790
-            break;
791
-
792
-        case HTTP_Request2::AUTH_DIGEST:
793
-            unset($this->proxyChallenge);
794
-            $proxyUrl = 'proxy://' . $this->request->getConfig('proxy_host') .
795
-                        ':' . $this->request->getConfig('proxy_port');
796
-            if (!empty(self::$challenges[$proxyUrl])) {
797
-                $headers['proxy-authorization'] = $this->createDigestResponse(
798
-                    $user, $password,
799
-                    $requestUrl, self::$challenges[$proxyUrl]
800
-                );
801
-                $this->proxyChallenge =& self::$challenges[$proxyUrl];
802
-            }
803
-            break;
804
-
805
-        default:
806
-            throw new HTTP_Request2_NotImplementedException(
807
-                "Unknown HTTP authentication scheme '" .
808
-                $this->request->getConfig('proxy_auth_scheme') . "'"
809
-            );
810
-        }
811
-    }
812
-
813
-
814
-    /**
815
-     * Creates the string with the Request-Line and request headers
816
-     *
817
-     * @return   string
818
-     * @throws   HTTP_Request2_Exception
819
-     */
820
-    protected function prepareHeaders()
821
-    {
822
-        $headers = $this->request->getHeaders();
823
-        $url     = $this->request->getUrl();
824
-        $connect = HTTP_Request2::METHOD_CONNECT == $this->request->getMethod();
825
-        $host    = $url->getHost();
826
-
827
-        $defaultPort = 0 == strcasecmp($url->getScheme(), 'https')? 443: 80;
828
-        if (($port = $url->getPort()) && $port != $defaultPort || $connect) {
829
-            $host .= ':' . (empty($port)? $defaultPort: $port);
830
-        }
831
-        // Do not overwrite explicitly set 'Host' header, see bug #16146
832
-        if (!isset($headers['host'])) {
833
-            $headers['host'] = $host;
834
-        }
835
-
836
-        if ($connect) {
837
-            $requestUrl = $host;
838
-
839
-        } else {
840
-            if (!$this->request->getConfig('proxy_host')
841
-                || 'http' != $this->request->getConfig('proxy_type')
842
-                || 0 == strcasecmp($url->getScheme(), 'https')
843
-            ) {
844
-                $requestUrl = '';
845
-            } else {
846
-                $requestUrl = $url->getScheme() . '://' . $host;
847
-            }
848
-            $path        = $url->getPath();
849
-            $query       = $url->getQuery();
850
-            $requestUrl .= (empty($path)? '/': $path) . (empty($query)? '': '?' . $query);
851
-        }
852
-
853
-        if ('1.1' == $this->request->getConfig('protocol_version')
854
-            && extension_loaded('zlib') && !isset($headers['accept-encoding'])
855
-        ) {
856
-            $headers['accept-encoding'] = 'gzip, deflate';
857
-        }
858
-        if (($jar = $this->request->getCookieJar())
859
-            && ($cookies = $jar->getMatching($this->request->getUrl(), true))
860
-        ) {
861
-            $headers['cookie'] = (empty($headers['cookie'])? '': $headers['cookie'] . '; ') . $cookies;
862
-        }
863
-
864
-        $this->addAuthorizationHeader($headers, $host, $requestUrl);
865
-        $this->addProxyAuthorizationHeader($headers, $requestUrl);
866
-        $this->calculateRequestLength($headers);
867
-        if ('1.1' == $this->request->getConfig('protocol_version')) {
868
-            $this->updateExpectHeader($headers);
869
-        } else {
870
-            $this->expect100Continue = false;
871
-        }
872
-
873
-        $headersStr = $this->request->getMethod() . ' ' . $requestUrl . ' HTTP/' .
874
-                      $this->request->getConfig('protocol_version') . "\r\n";
875
-        foreach ($headers as $name => $value) {
876
-            $canonicalName = implode('-', array_map('ucfirst', explode('-', $name)));
877
-            $headersStr   .= $canonicalName . ': ' . $value . "\r\n";
878
-        }
879
-        return $headersStr . "\r\n";
880
-    }
881
-
882
-    /**
883
-     * Adds or removes 'Expect: 100-continue' header from request headers
884
-     *
885
-     * Also sets the $expect100Continue property. Parsing of existing header
886
-     * is somewhat needed due to its complex structure and due to the
887
-     * requirement in section 8.2.3 of RFC 2616:
888
-     * > A client MUST NOT send an Expect request-header field (section
889
-     * > 14.20) with the "100-continue" expectation if it does not intend
890
-     * > to send a request body.
891
-     *
892
-     * @param array &$headers Array of headers prepared for the request
893
-     *
894
-     * @throws HTTP_Request2_LogicException
895
-     * @link http://pear.php.net/bugs/bug.php?id=19233
896
-     * @link http://tools.ietf.org/html/rfc2616#section-8.2.3
897
-     */
898
-    protected function updateExpectHeader(&$headers)
899
-    {
900
-        $this->expect100Continue = false;
901
-        $expectations = array();
902
-        if (isset($headers['expect'])) {
903
-            if ('' === $headers['expect']) {
904
-                // empty 'Expect' header is technically invalid, so just get rid of it
905
-                unset($headers['expect']);
906
-                return;
907
-            }
908
-            // build regexp to parse the value of existing Expect header
909
-            $expectParam     = ';\s*' . self::REGEXP_TOKEN . '(?:\s*=\s*(?:'
910
-                               . self::REGEXP_TOKEN . '|'
911
-                               . self::REGEXP_QUOTED_STRING . '))?\s*';
912
-            $expectExtension = self::REGEXP_TOKEN . '(?:\s*=\s*(?:'
913
-                               . self::REGEXP_TOKEN . '|'
914
-                               . self::REGEXP_QUOTED_STRING . ')\s*(?:'
915
-                               . $expectParam . ')*)?';
916
-            $expectItem      = '!(100-continue|' . $expectExtension . ')!A';
917
-
918
-            $pos    = 0;
919
-            $length = strlen($headers['expect']);
920
-
921
-            while ($pos < $length) {
922
-                $pos += strspn($headers['expect'], " \t", $pos);
923
-                if (',' === substr($headers['expect'], $pos, 1)) {
924
-                    $pos++;
925
-                    continue;
926
-
927
-                } elseif (!preg_match($expectItem, $headers['expect'], $m, 0, $pos)) {
928
-                    throw new HTTP_Request2_LogicException(
929
-                        "Cannot parse value '{$headers['expect']}' of Expect header",
930
-                        HTTP_Request2_Exception::INVALID_ARGUMENT
931
-                    );
932
-
933
-                } else {
934
-                    $pos += strlen($m[0]);
935
-                    if (strcasecmp('100-continue', $m[0])) {
936
-                        $expectations[]  = $m[0];
937
-                    }
938
-                }
939
-            }
940
-        }
941
-
942
-        if (1024 < $this->contentLength) {
943
-            $expectations[] = '100-continue';
944
-            $this->expect100Continue = true;
945
-        }
946
-
947
-        if (empty($expectations)) {
948
-            unset($headers['expect']);
949
-        } else {
950
-            $headers['expect'] = implode(',', $expectations);
951
-        }
952
-    }
953
-
954
-    /**
955
-     * Sends the request body
956
-     *
957
-     * @throws   HTTP_Request2_MessageException
958
-     */
959
-    protected function writeBody()
960
-    {
961
-        if (in_array($this->request->getMethod(), self::$bodyDisallowed)
962
-            || 0 == $this->contentLength
963
-        ) {
964
-            return;
965
-        }
966
-
967
-        $position   = 0;
968
-        $bufferSize = $this->request->getConfig('buffer_size');
969
-        $headers    = $this->request->getHeaders();
970
-        $chunked    = isset($headers['transfer-encoding']);
971
-        while ($position < $this->contentLength) {
972
-            if (is_string($this->requestBody)) {
973
-                $str = substr($this->requestBody, $position, $bufferSize);
974
-            } elseif (is_resource($this->requestBody)) {
975
-                $str = fread($this->requestBody, $bufferSize);
976
-            } else {
977
-                $str = $this->requestBody->read($bufferSize);
978
-            }
979
-            if (!$chunked) {
980
-                $this->socket->write($str);
981
-            } else {
982
-                $this->socket->write(dechex(strlen($str)) . "\r\n{$str}\r\n");
983
-            }
984
-            // Provide the length of written string to the observer, request #7630
985
-            $this->request->setLastEvent('sentBodyPart', strlen($str));
986
-            $position += strlen($str);
987
-        }
988
-
989
-        // write zero-length chunk
990
-        if ($chunked) {
991
-            $this->socket->write("0\r\n\r\n");
992
-        }
993
-        $this->request->setLastEvent('sentBody', $this->contentLength);
994
-    }
995
-
996
-    /**
997
-     * Reads the remote server's response
998
-     *
999
-     * @return   HTTP_Request2_Response
1000
-     * @throws   HTTP_Request2_Exception
1001
-     */
1002
-    protected function readResponse()
1003
-    {
1004
-        $bufferSize = $this->request->getConfig('buffer_size');
1005
-        // http://tools.ietf.org/html/rfc2616#section-8.2.3
1006
-        // ...the client SHOULD NOT wait for an indefinite period before sending the request body
1007
-        $timeout    = $this->expect100Continue ? 1 : null;
1008
-
1009
-        do {
1010
-            try {
1011
-                $response = new HTTP_Request2_Response(
1012
-                    $this->socket->readLine($bufferSize, $timeout), true, $this->request->getUrl()
1013
-                );
1014
-                do {
1015
-                    $headerLine = $this->socket->readLine($bufferSize);
1016
-                    $response->parseHeaderLine($headerLine);
1017
-                } while ('' != $headerLine);
1018
-
1019
-            } catch (HTTP_Request2_MessageException $e) {
1020
-                if (HTTP_Request2_Exception::TIMEOUT === $e->getCode()
1021
-                    && $this->expect100Continue
1022
-                ) {
1023
-                    return null;
1024
-                }
1025
-                throw $e;
1026
-            }
1027
-            if ($this->expect100Continue && 100 == $response->getStatus()) {
1028
-                return $response;
1029
-            }
1030
-        } while (in_array($response->getStatus(), array(100, 101)));
1031
-
1032
-        $this->request->setLastEvent('receivedHeaders', $response);
1033
-
1034
-        // No body possible in such responses
1035
-        if (HTTP_Request2::METHOD_HEAD == $this->request->getMethod()
1036
-            || (HTTP_Request2::METHOD_CONNECT == $this->request->getMethod()
1037
-                && 200 <= $response->getStatus() && 300 > $response->getStatus())
1038
-            || in_array($response->getStatus(), array(204, 304))
1039
-        ) {
1040
-            return $response;
1041
-        }
1042
-
1043
-        $chunked = 'chunked' == $response->getHeader('transfer-encoding');
1044
-        $length  = $response->getHeader('content-length');
1045
-        $hasBody = false;
1046
-        if ($chunked || null === $length || 0 < intval($length)) {
1047
-            // RFC 2616, section 4.4:
1048
-            // 3. ... If a message is received with both a
1049
-            // Transfer-Encoding header field and a Content-Length header field,
1050
-            // the latter MUST be ignored.
1051
-            $toRead = ($chunked || null === $length)? null: $length;
1052
-            $this->chunkLength = 0;
1053
-
1054
-            while (!$this->socket->eof() && (is_null($toRead) || 0 < $toRead)) {
1055
-                if ($chunked) {
1056
-                    $data = $this->readChunked($bufferSize);
1057
-                } elseif (is_null($toRead)) {
1058
-                    $data = $this->socket->read($bufferSize);
1059
-                } else {
1060
-                    $data    = $this->socket->read(min($toRead, $bufferSize));
1061
-                    $toRead -= strlen($data);
1062
-                }
1063
-                if ('' == $data && (!$this->chunkLength || $this->socket->eof())) {
1064
-                    break;
1065
-                }
1066
-
1067
-                $hasBody = true;
1068
-                if ($this->request->getConfig('store_body')) {
1069
-                    $response->appendBody($data);
1070
-                }
1071
-                if (!in_array($response->getHeader('content-encoding'), array('identity', null))) {
1072
-                    $this->request->setLastEvent('receivedEncodedBodyPart', $data);
1073
-                } else {
1074
-                    $this->request->setLastEvent('receivedBodyPart', $data);
1075
-                }
1076
-            }
1077
-        }
1078
-
1079
-        if ($hasBody) {
1080
-            $this->request->setLastEvent('receivedBody', $response);
1081
-        }
1082
-        return $response;
1083
-    }
1084
-
1085
-    /**
1086
-     * Reads a part of response body encoded with chunked Transfer-Encoding
1087
-     *
1088
-     * @param int $bufferSize buffer size to use for reading
1089
-     *
1090
-     * @return   string
1091
-     * @throws   HTTP_Request2_MessageException
1092
-     */
1093
-    protected function readChunked($bufferSize)
1094
-    {
1095
-        // at start of the next chunk?
1096
-        if (0 == $this->chunkLength) {
1097
-            $line = $this->socket->readLine($bufferSize);
1098
-            if (!preg_match('/^([0-9a-f]+)/i', $line, $matches)) {
1099
-                throw new HTTP_Request2_MessageException(
1100
-                    "Cannot decode chunked response, invalid chunk length '{$line}'",
1101
-                    HTTP_Request2_Exception::DECODE_ERROR
1102
-                );
1103
-            } else {
1104
-                $this->chunkLength = hexdec($matches[1]);
1105
-                // Chunk with zero length indicates the end
1106
-                if (0 == $this->chunkLength) {
1107
-                    $this->socket->readLine($bufferSize);
1108
-                    return '';
1109
-                }
1110
-            }
1111
-        }
1112
-        $data = $this->socket->read(min($this->chunkLength, $bufferSize));
1113
-        $this->chunkLength -= strlen($data);
1114
-        if (0 == $this->chunkLength) {
1115
-            $this->socket->readLine($bufferSize); // Trailing CRLF
1116
-        }
1117
-        return $data;
1118
-    }
42
+	/**
43
+	 * Regular expression for 'token' rule from RFC 2616
44
+	 */
45
+	const REGEXP_TOKEN = '[^\x00-\x1f\x7f-\xff()<>@,;:\\\\"/\[\]?={}\s]+';
46
+
47
+	/**
48
+	 * Regular expression for 'quoted-string' rule from RFC 2616
49
+	 */
50
+	const REGEXP_QUOTED_STRING = '"(?>[^"\\\\]+|\\\\.)*"';
51
+
52
+	/**
53
+	 * Connected sockets, needed for Keep-Alive support
54
+	 * @var  array
55
+	 * @see  connect()
56
+	 */
57
+	protected static $sockets = array();
58
+
59
+	/**
60
+	 * Data for digest authentication scheme
61
+	 *
62
+	 * The keys for the array are URL prefixes.
63
+	 *
64
+	 * The values are associative arrays with data (realm, nonce, nonce-count,
65
+	 * opaque...) needed for digest authentication. Stored here to prevent making
66
+	 * duplicate requests to digest-protected resources after we have already
67
+	 * received the challenge.
68
+	 *
69
+	 * @var  array
70
+	 */
71
+	protected static $challenges = array();
72
+
73
+	/**
74
+	 * Connected socket
75
+	 * @var  HTTP_Request2_SocketWrapper
76
+	 * @see  connect()
77
+	 */
78
+	protected $socket;
79
+
80
+	/**
81
+	 * Challenge used for server digest authentication
82
+	 * @var  array
83
+	 */
84
+	protected $serverChallenge;
85
+
86
+	/**
87
+	 * Challenge used for proxy digest authentication
88
+	 * @var  array
89
+	 */
90
+	protected $proxyChallenge;
91
+
92
+	/**
93
+	 * Remaining length of the current chunk, when reading chunked response
94
+	 * @var  integer
95
+	 * @see  readChunked()
96
+	 */
97
+	protected $chunkLength = 0;
98
+
99
+	/**
100
+	 * Remaining amount of redirections to follow
101
+	 *
102
+	 * Starts at 'max_redirects' configuration parameter and is reduced on each
103
+	 * subsequent redirect. An Exception will be thrown once it reaches zero.
104
+	 *
105
+	 * @var  integer
106
+	 */
107
+	protected $redirectCountdown = null;
108
+
109
+	/**
110
+	 * Whether to wait for "100 Continue" response before sending request body
111
+	 * @var bool
112
+	 */
113
+	protected $expect100Continue = false;
114
+
115
+	/**
116
+	 * Sends request to the remote server and returns its response
117
+	 *
118
+	 * @param HTTP_Request2 $request HTTP request message
119
+	 *
120
+	 * @return   HTTP_Request2_Response
121
+	 * @throws   HTTP_Request2_Exception
122
+	 */
123
+	public function sendRequest(HTTP_Request2 $request)
124
+	{
125
+		$this->request = $request;
126
+
127
+		try {
128
+			$keepAlive = $this->connect();
129
+			$headers   = $this->prepareHeaders();
130
+			$this->socket->write($headers);
131
+			// provide request headers to the observer, see request #7633
132
+			$this->request->setLastEvent('sentHeaders', $headers);
133
+
134
+			if (!$this->expect100Continue) {
135
+				$this->writeBody();
136
+				$response = $this->readResponse();
137
+
138
+			} else {
139
+				$response = $this->readResponse();
140
+				if (!$response || 100 == $response->getStatus()) {
141
+					$this->expect100Continue = false;
142
+					// either got "100 Continue" or timed out -> send body
143
+					$this->writeBody();
144
+					$response = $this->readResponse();
145
+				}
146
+			}
147
+
148
+
149
+			if ($jar = $request->getCookieJar()) {
150
+				$jar->addCookiesFromResponse($response, $request->getUrl());
151
+			}
152
+
153
+			if (!$this->canKeepAlive($keepAlive, $response)) {
154
+				$this->disconnect();
155
+			}
156
+
157
+			if ($this->shouldUseProxyDigestAuth($response)) {
158
+				return $this->sendRequest($request);
159
+			}
160
+			if ($this->shouldUseServerDigestAuth($response)) {
161
+				return $this->sendRequest($request);
162
+			}
163
+			if ($authInfo = $response->getHeader('authentication-info')) {
164
+				$this->updateChallenge($this->serverChallenge, $authInfo);
165
+			}
166
+			if ($proxyInfo = $response->getHeader('proxy-authentication-info')) {
167
+				$this->updateChallenge($this->proxyChallenge, $proxyInfo);
168
+			}
169
+
170
+		} catch (Exception $e) {
171
+			$this->disconnect();
172
+		}
173
+
174
+		unset($this->request, $this->requestBody);
175
+
176
+		if (!empty($e)) {
177
+			$this->redirectCountdown = null;
178
+			throw $e;
179
+		}
180
+
181
+		if (!$request->getConfig('follow_redirects') || !$response->isRedirect()) {
182
+			$this->redirectCountdown = null;
183
+			return $response;
184
+		} else {
185
+			return $this->handleRedirect($request, $response);
186
+		}
187
+	}
188
+
189
+	/**
190
+	 * Connects to the remote server
191
+	 *
192
+	 * @return   bool    whether the connection can be persistent
193
+	 * @throws   HTTP_Request2_Exception
194
+	 */
195
+	protected function connect()
196
+	{
197
+		$secure  = 0 == strcasecmp($this->request->getUrl()->getScheme(), 'https');
198
+		$tunnel  = HTTP_Request2::METHOD_CONNECT == $this->request->getMethod();
199
+		$headers = $this->request->getHeaders();
200
+		$reqHost = $this->request->getUrl()->getHost();
201
+		if (!($reqPort = $this->request->getUrl()->getPort())) {
202
+			$reqPort = $secure? 443: 80;
203
+		}
204
+
205
+		$httpProxy = $socksProxy = false;
206
+		if (!($host = $this->request->getConfig('proxy_host'))) {
207
+			$host = $reqHost;
208
+			$port = $reqPort;
209
+		} else {
210
+			if (!($port = $this->request->getConfig('proxy_port'))) {
211
+				throw new HTTP_Request2_LogicException(
212
+					'Proxy port not provided',
213
+					HTTP_Request2_Exception::MISSING_VALUE
214
+				);
215
+			}
216
+			if ('http' == ($type = $this->request->getConfig('proxy_type'))) {
217
+				$httpProxy = true;
218
+			} elseif ('socks5' == $type) {
219
+				$socksProxy = true;
220
+			} else {
221
+				throw new HTTP_Request2_NotImplementedException(
222
+					"Proxy type '{$type}' is not supported"
223
+				);
224
+			}
225
+		}
226
+
227
+		if ($tunnel && !$httpProxy) {
228
+			throw new HTTP_Request2_LogicException(
229
+				"Trying to perform CONNECT request without proxy",
230
+				HTTP_Request2_Exception::MISSING_VALUE
231
+			);
232
+		}
233
+		if ($secure && !in_array('ssl', stream_get_transports())) {
234
+			throw new HTTP_Request2_LogicException(
235
+				'Need OpenSSL support for https:// requests',
236
+				HTTP_Request2_Exception::MISCONFIGURATION
237
+			);
238
+		}
239
+
240
+		// RFC 2068, section 19.7.1: A client MUST NOT send the Keep-Alive
241
+		// connection token to a proxy server...
242
+		if ($httpProxy && !$secure && !empty($headers['connection'])
243
+			&& 'Keep-Alive' == $headers['connection']
244
+		) {
245
+			$this->request->setHeader('connection');
246
+		}
247
+
248
+		$keepAlive = ('1.1' == $this->request->getConfig('protocol_version') &&
249
+					  empty($headers['connection'])) ||
250
+					 (!empty($headers['connection']) &&
251
+					  'Keep-Alive' == $headers['connection']);
252
+
253
+		$options = array();
254
+		if ($ip = $this->request->getConfig('local_ip')) {
255
+			$options['socket'] = array(
256
+				'bindto' => (false === strpos($ip, ':') ? $ip : '[' . $ip . ']') . ':0'
257
+			);
258
+		}
259
+		if ($secure || $tunnel) {
260
+			$options['ssl'] = array();
261
+			foreach ($this->request->getConfig() as $name => $value) {
262
+				if ('ssl_' == substr($name, 0, 4) && null !== $value) {
263
+					if ('ssl_verify_host' == $name) {
264
+						if ($value) {
265
+							$options['ssl']['CN_match'] = $reqHost;
266
+						}
267
+					} else {
268
+						$options['ssl'][substr($name, 4)] = $value;
269
+					}
270
+				}
271
+			}
272
+			ksort($options['ssl']);
273
+		}
274
+
275
+		// Use global request timeout if given, see feature requests #5735, #8964
276
+		if ($timeout = $this->request->getConfig('timeout')) {
277
+			$deadline = time() + $timeout;
278
+		} else {
279
+			$deadline = null;
280
+		}
281
+
282
+		// Changing SSL context options after connection is established does *not*
283
+		// work, we need a new connection if options change
284
+		$remote    = ((!$secure || $httpProxy || $socksProxy)? 'tcp://': 'ssl://')
285
+					 . $host . ':' . $port;
286
+		$socketKey = $remote . (
287
+						($secure && $httpProxy || $socksProxy)
288
+						? "->{$reqHost}:{$reqPort}" : ''
289
+					 ) . (empty($options)? '': ':' . serialize($options));
290
+		unset($this->socket);
291
+
292
+		// We use persistent connections and have a connected socket?
293
+		// Ensure that the socket is still connected, see bug #16149
294
+		if ($keepAlive && !empty(self::$sockets[$socketKey])
295
+			&& !self::$sockets[$socketKey]->eof()
296
+		) {
297
+			$this->socket =& self::$sockets[$socketKey];
298
+
299
+		} else {
300
+			if ($socksProxy) {
301
+				require_once 'HTTP/Request2/SOCKS5.php';
302
+
303
+				$this->socket = new HTTP_Request2_SOCKS5(
304
+					$remote, $this->request->getConfig('connect_timeout'),
305
+					$options, $this->request->getConfig('proxy_user'),
306
+					$this->request->getConfig('proxy_password')
307
+				);
308
+				// handle request timeouts ASAP
309
+				$this->socket->setDeadline($deadline, $this->request->getConfig('timeout'));
310
+				$this->socket->connect($reqHost, $reqPort);
311
+				if (!$secure) {
312
+					$conninfo = "tcp://{$reqHost}:{$reqPort} via {$remote}";
313
+				} else {
314
+					$this->socket->enableCrypto();
315
+					$conninfo = "ssl://{$reqHost}:{$reqPort} via {$remote}";
316
+				}
317
+
318
+			} elseif ($secure && $httpProxy && !$tunnel) {
319
+				$this->establishTunnel();
320
+				$conninfo = "ssl://{$reqHost}:{$reqPort} via {$remote}";
321
+
322
+			} else {
323
+				$this->socket = new HTTP_Request2_SocketWrapper(
324
+					$remote, $this->request->getConfig('connect_timeout'), $options
325
+				);
326
+			}
327
+			$this->request->setLastEvent('connect', empty($conninfo)? $remote: $conninfo);
328
+			self::$sockets[$socketKey] =& $this->socket;
329
+		}
330
+		$this->socket->setDeadline($deadline, $this->request->getConfig('timeout'));
331
+		return $keepAlive;
332
+	}
333
+
334
+	/**
335
+	 * Establishes a tunnel to a secure remote server via HTTP CONNECT request
336
+	 *
337
+	 * This method will fail if 'ssl_verify_peer' is enabled. Probably because PHP
338
+	 * sees that we are connected to a proxy server (duh!) rather than the server
339
+	 * that presents its certificate.
340
+	 *
341
+	 * @link     http://tools.ietf.org/html/rfc2817#section-5.2
342
+	 * @throws   HTTP_Request2_Exception
343
+	 */
344
+	protected function establishTunnel()
345
+	{
346
+		$donor   = new self;
347
+		$connect = new HTTP_Request2(
348
+			$this->request->getUrl(), HTTP_Request2::METHOD_CONNECT,
349
+			array_merge($this->request->getConfig(), array('adapter' => $donor))
350
+		);
351
+		$response = $connect->send();
352
+		// Need any successful (2XX) response
353
+		if (200 > $response->getStatus() || 300 <= $response->getStatus()) {
354
+			throw new HTTP_Request2_ConnectionException(
355
+				'Failed to connect via HTTPS proxy. Proxy response: ' .
356
+				$response->getStatus() . ' ' . $response->getReasonPhrase()
357
+			);
358
+		}
359
+		$this->socket = $donor->socket;
360
+		$this->socket->enableCrypto();
361
+	}
362
+
363
+	/**
364
+	 * Checks whether current connection may be reused or should be closed
365
+	 *
366
+	 * @param boolean                $requestKeepAlive whether connection could
367
+	 *                               be persistent in the first place
368
+	 * @param HTTP_Request2_Response $response         response object to check
369
+	 *
370
+	 * @return   boolean
371
+	 */
372
+	protected function canKeepAlive($requestKeepAlive, HTTP_Request2_Response $response)
373
+	{
374
+		// Do not close socket on successful CONNECT request
375
+		if (HTTP_Request2::METHOD_CONNECT == $this->request->getMethod()
376
+			&& 200 <= $response->getStatus() && 300 > $response->getStatus()
377
+		) {
378
+			return true;
379
+		}
380
+
381
+		$lengthKnown = 'chunked' == strtolower($response->getHeader('transfer-encoding'))
382
+					   || null !== $response->getHeader('content-length')
383
+					   // no body possible for such responses, see also request #17031
384
+					   || HTTP_Request2::METHOD_HEAD == $this->request->getMethod()
385
+					   || in_array($response->getStatus(), array(204, 304));
386
+		$persistent  = 'keep-alive' == strtolower($response->getHeader('connection')) ||
387
+					   (null === $response->getHeader('connection') &&
388
+						'1.1' == $response->getVersion());
389
+		return $requestKeepAlive && $lengthKnown && $persistent;
390
+	}
391
+
392
+	/**
393
+	 * Disconnects from the remote server
394
+	 */
395
+	protected function disconnect()
396
+	{
397
+		if (!empty($this->socket)) {
398
+			$this->socket = null;
399
+			$this->request->setLastEvent('disconnect');
400
+		}
401
+	}
402
+
403
+	/**
404
+	 * Handles HTTP redirection
405
+	 *
406
+	 * This method will throw an Exception if redirect to a non-HTTP(S) location
407
+	 * is attempted, also if number of redirects performed already is equal to
408
+	 * 'max_redirects' configuration parameter.
409
+	 *
410
+	 * @param HTTP_Request2          $request  Original request
411
+	 * @param HTTP_Request2_Response $response Response containing redirect
412
+	 *
413
+	 * @return   HTTP_Request2_Response      Response from a new location
414
+	 * @throws   HTTP_Request2_Exception
415
+	 */
416
+	protected function handleRedirect(
417
+		HTTP_Request2 $request, HTTP_Request2_Response $response
418
+	) {
419
+		if (is_null($this->redirectCountdown)) {
420
+			$this->redirectCountdown = $request->getConfig('max_redirects');
421
+		}
422
+		if (0 == $this->redirectCountdown) {
423
+			$this->redirectCountdown = null;
424
+			// Copying cURL behaviour
425
+			throw new HTTP_Request2_MessageException(
426
+				'Maximum (' . $request->getConfig('max_redirects') . ') redirects followed',
427
+				HTTP_Request2_Exception::TOO_MANY_REDIRECTS
428
+			);
429
+		}
430
+		$redirectUrl = new Net_URL2(
431
+			$response->getHeader('location'),
432
+			array(Net_URL2::OPTION_USE_BRACKETS => $request->getConfig('use_brackets'))
433
+		);
434
+		// refuse non-HTTP redirect
435
+		if ($redirectUrl->isAbsolute()
436
+			&& !in_array($redirectUrl->getScheme(), array('http', 'https'))
437
+		) {
438
+			$this->redirectCountdown = null;
439
+			throw new HTTP_Request2_MessageException(
440
+				'Refusing to redirect to a non-HTTP URL ' . $redirectUrl->__toString(),
441
+				HTTP_Request2_Exception::NON_HTTP_REDIRECT
442
+			);
443
+		}
444
+		// Theoretically URL should be absolute (see http://tools.ietf.org/html/rfc2616#section-14.30),
445
+		// but in practice it is often not
446
+		if (!$redirectUrl->isAbsolute()) {
447
+			$redirectUrl = $request->getUrl()->resolve($redirectUrl);
448
+		}
449
+		$redirect = clone $request;
450
+		$redirect->setUrl($redirectUrl);
451
+		if (303 == $response->getStatus()
452
+			|| (!$request->getConfig('strict_redirects')
453
+				&& in_array($response->getStatus(), array(301, 302)))
454
+		) {
455
+			$redirect->setMethod(HTTP_Request2::METHOD_GET);
456
+			$redirect->setBody('');
457
+		}
458
+
459
+		if (0 < $this->redirectCountdown) {
460
+			$this->redirectCountdown--;
461
+		}
462
+		return $this->sendRequest($redirect);
463
+	}
464
+
465
+	/**
466
+	 * Checks whether another request should be performed with server digest auth
467
+	 *
468
+	 * Several conditions should be satisfied for it to return true:
469
+	 *   - response status should be 401
470
+	 *   - auth credentials should be set in the request object
471
+	 *   - response should contain WWW-Authenticate header with digest challenge
472
+	 *   - there is either no challenge stored for this URL or new challenge
473
+	 *     contains stale=true parameter (in other case we probably just failed
474
+	 *     due to invalid username / password)
475
+	 *
476
+	 * The method stores challenge values in $challenges static property
477
+	 *
478
+	 * @param HTTP_Request2_Response $response response to check
479
+	 *
480
+	 * @return   boolean whether another request should be performed
481
+	 * @throws   HTTP_Request2_Exception in case of unsupported challenge parameters
482
+	 */
483
+	protected function shouldUseServerDigestAuth(HTTP_Request2_Response $response)
484
+	{
485
+		// no sense repeating a request if we don't have credentials
486
+		if (401 != $response->getStatus() || !$this->request->getAuth()) {
487
+			return false;
488
+		}
489
+		if (!$challenge = $this->parseDigestChallenge($response->getHeader('www-authenticate'))) {
490
+			return false;
491
+		}
492
+
493
+		$url    = $this->request->getUrl();
494
+		$scheme = $url->getScheme();
495
+		$host   = $scheme . '://' . $url->getHost();
496
+		if ($port = $url->getPort()) {
497
+			if ((0 == strcasecmp($scheme, 'http') && 80 != $port)
498
+				|| (0 == strcasecmp($scheme, 'https') && 443 != $port)
499
+			) {
500
+				$host .= ':' . $port;
501
+			}
502
+		}
503
+
504
+		if (!empty($challenge['domain'])) {
505
+			$prefixes = array();
506
+			foreach (preg_split('/\\s+/', $challenge['domain']) as $prefix) {
507
+				// don't bother with different servers
508
+				if ('/' == substr($prefix, 0, 1)) {
509
+					$prefixes[] = $host . $prefix;
510
+				}
511
+			}
512
+		}
513
+		if (empty($prefixes)) {
514
+			$prefixes = array($host . '/');
515
+		}
516
+
517
+		$ret = true;
518
+		foreach ($prefixes as $prefix) {
519
+			if (!empty(self::$challenges[$prefix])
520
+				&& (empty($challenge['stale']) || strcasecmp('true', $challenge['stale']))
521
+			) {
522
+				// probably credentials are invalid
523
+				$ret = false;
524
+			}
525
+			self::$challenges[$prefix] =& $challenge;
526
+		}
527
+		return $ret;
528
+	}
529
+
530
+	/**
531
+	 * Checks whether another request should be performed with proxy digest auth
532
+	 *
533
+	 * Several conditions should be satisfied for it to return true:
534
+	 *   - response status should be 407
535
+	 *   - proxy auth credentials should be set in the request object
536
+	 *   - response should contain Proxy-Authenticate header with digest challenge
537
+	 *   - there is either no challenge stored for this proxy or new challenge
538
+	 *     contains stale=true parameter (in other case we probably just failed
539
+	 *     due to invalid username / password)
540
+	 *
541
+	 * The method stores challenge values in $challenges static property
542
+	 *
543
+	 * @param HTTP_Request2_Response $response response to check
544
+	 *
545
+	 * @return   boolean whether another request should be performed
546
+	 * @throws   HTTP_Request2_Exception in case of unsupported challenge parameters
547
+	 */
548
+	protected function shouldUseProxyDigestAuth(HTTP_Request2_Response $response)
549
+	{
550
+		if (407 != $response->getStatus() || !$this->request->getConfig('proxy_user')) {
551
+			return false;
552
+		}
553
+		if (!($challenge = $this->parseDigestChallenge($response->getHeader('proxy-authenticate')))) {
554
+			return false;
555
+		}
556
+
557
+		$key = 'proxy://' . $this->request->getConfig('proxy_host') .
558
+			   ':' . $this->request->getConfig('proxy_port');
559
+
560
+		if (!empty(self::$challenges[$key])
561
+			&& (empty($challenge['stale']) || strcasecmp('true', $challenge['stale']))
562
+		) {
563
+			$ret = false;
564
+		} else {
565
+			$ret = true;
566
+		}
567
+		self::$challenges[$key] = $challenge;
568
+		return $ret;
569
+	}
570
+
571
+	/**
572
+	 * Extracts digest method challenge from (WWW|Proxy)-Authenticate header value
573
+	 *
574
+	 * There is a problem with implementation of RFC 2617: several of the parameters
575
+	 * are defined as quoted-string there and thus may contain backslash escaped
576
+	 * double quotes (RFC 2616, section 2.2). However, RFC 2617 defines unq(X) as
577
+	 * just value of quoted-string X without surrounding quotes, it doesn't speak
578
+	 * about removing backslash escaping.
579
+	 *
580
+	 * Now realm parameter is user-defined and human-readable, strange things
581
+	 * happen when it contains quotes:
582
+	 *   - Apache allows quotes in realm, but apparently uses realm value without
583
+	 *     backslashes for digest computation
584
+	 *   - Squid allows (manually escaped) quotes there, but it is impossible to
585
+	 *     authorize with either escaped or unescaped quotes used in digest,
586
+	 *     probably it can't parse the response (?)
587
+	 *   - Both IE and Firefox display realm value with backslashes in
588
+	 *     the password popup and apparently use the same value for digest
589
+	 *
590
+	 * HTTP_Request2 follows IE and Firefox (and hopefully RFC 2617) in
591
+	 * quoted-string handling, unfortunately that means failure to authorize
592
+	 * sometimes
593
+	 *
594
+	 * @param string $headerValue value of WWW-Authenticate or Proxy-Authenticate header
595
+	 *
596
+	 * @return   mixed   associative array with challenge parameters, false if
597
+	 *                   no challenge is present in header value
598
+	 * @throws   HTTP_Request2_NotImplementedException in case of unsupported challenge parameters
599
+	 */
600
+	protected function parseDigestChallenge($headerValue)
601
+	{
602
+		$authParam   = '(' . self::REGEXP_TOKEN . ')\\s*=\\s*(' .
603
+					   self::REGEXP_TOKEN . '|' . self::REGEXP_QUOTED_STRING . ')';
604
+		$challenge   = "!(?<=^|\\s|,)Digest ({$authParam}\\s*(,\\s*|$))+!";
605
+		if (!preg_match($challenge, $headerValue, $matches)) {
606
+			return false;
607
+		}
608
+
609
+		preg_match_all('!' . $authParam . '!', $matches[0], $params);
610
+		$paramsAry   = array();
611
+		$knownParams = array('realm', 'domain', 'nonce', 'opaque', 'stale',
612
+							 'algorithm', 'qop');
613
+		for ($i = 0; $i < count($params[0]); $i++) {
614
+			// section 3.2.1: Any unrecognized directive MUST be ignored.
615
+			if (in_array($params[1][$i], $knownParams)) {
616
+				if ('"' == substr($params[2][$i], 0, 1)) {
617
+					$paramsAry[$params[1][$i]] = substr($params[2][$i], 1, -1);
618
+				} else {
619
+					$paramsAry[$params[1][$i]] = $params[2][$i];
620
+				}
621
+			}
622
+		}
623
+		// we only support qop=auth
624
+		if (!empty($paramsAry['qop'])
625
+			&& !in_array('auth', array_map('trim', explode(',', $paramsAry['qop'])))
626
+		) {
627
+			throw new HTTP_Request2_NotImplementedException(
628
+				"Only 'auth' qop is currently supported in digest authentication, " .
629
+				"server requested '{$paramsAry['qop']}'"
630
+			);
631
+		}
632
+		// we only support algorithm=MD5
633
+		if (!empty($paramsAry['algorithm']) && 'MD5' != $paramsAry['algorithm']) {
634
+			throw new HTTP_Request2_NotImplementedException(
635
+				"Only 'MD5' algorithm is currently supported in digest authentication, " .
636
+				"server requested '{$paramsAry['algorithm']}'"
637
+			);
638
+		}
639
+
640
+		return $paramsAry;
641
+	}
642
+
643
+	/**
644
+	 * Parses [Proxy-]Authentication-Info header value and updates challenge
645
+	 *
646
+	 * @param array  &$challenge  challenge to update
647
+	 * @param string $headerValue value of [Proxy-]Authentication-Info header
648
+	 *
649
+	 * @todo     validate server rspauth response
650
+	 */
651
+	protected function updateChallenge(&$challenge, $headerValue)
652
+	{
653
+		$authParam   = '!(' . self::REGEXP_TOKEN . ')\\s*=\\s*(' .
654
+					   self::REGEXP_TOKEN . '|' . self::REGEXP_QUOTED_STRING . ')!';
655
+		$paramsAry   = array();
656
+
657
+		preg_match_all($authParam, $headerValue, $params);
658
+		for ($i = 0; $i < count($params[0]); $i++) {
659
+			if ('"' == substr($params[2][$i], 0, 1)) {
660
+				$paramsAry[$params[1][$i]] = substr($params[2][$i], 1, -1);
661
+			} else {
662
+				$paramsAry[$params[1][$i]] = $params[2][$i];
663
+			}
664
+		}
665
+		// for now, just update the nonce value
666
+		if (!empty($paramsAry['nextnonce'])) {
667
+			$challenge['nonce'] = $paramsAry['nextnonce'];
668
+			$challenge['nc']    = 1;
669
+		}
670
+	}
671
+
672
+	/**
673
+	 * Creates a value for [Proxy-]Authorization header when using digest authentication
674
+	 *
675
+	 * @param string $user       user name
676
+	 * @param string $password   password
677
+	 * @param string $url        request URL
678
+	 * @param array  &$challenge digest challenge parameters
679
+	 *
680
+	 * @return   string  value of [Proxy-]Authorization request header
681
+	 * @link     http://tools.ietf.org/html/rfc2617#section-3.2.2
682
+	 */
683
+	protected function createDigestResponse($user, $password, $url, &$challenge)
684
+	{
685
+		if (false !== ($q = strpos($url, '?'))
686
+			&& $this->request->getConfig('digest_compat_ie')
687
+		) {
688
+			$url = substr($url, 0, $q);
689
+		}
690
+
691
+		$a1 = md5($user . ':' . $challenge['realm'] . ':' . $password);
692
+		$a2 = md5($this->request->getMethod() . ':' . $url);
693
+
694
+		if (empty($challenge['qop'])) {
695
+			$digest = md5($a1 . ':' . $challenge['nonce'] . ':' . $a2);
696
+		} else {
697
+			$challenge['cnonce'] = 'Req2.' . rand();
698
+			if (empty($challenge['nc'])) {
699
+				$challenge['nc'] = 1;
700
+			}
701
+			$nc     = sprintf('%08x', $challenge['nc']++);
702
+			$digest = md5(
703
+				$a1 . ':' . $challenge['nonce'] . ':' . $nc . ':' .
704
+				$challenge['cnonce'] . ':auth:' . $a2
705
+			);
706
+		}
707
+		return 'Digest username="' . str_replace(array('\\', '"'), array('\\\\', '\\"'), $user) . '", ' .
708
+			   'realm="' . $challenge['realm'] . '", ' .
709
+			   'nonce="' . $challenge['nonce'] . '", ' .
710
+			   'uri="' . $url . '", ' .
711
+			   'response="' . $digest . '"' .
712
+			   (!empty($challenge['opaque'])?
713
+				', opaque="' . $challenge['opaque'] . '"':
714
+				'') .
715
+			   (!empty($challenge['qop'])?
716
+				', qop="auth", nc=' . $nc . ', cnonce="' . $challenge['cnonce'] . '"':
717
+				'');
718
+	}
719
+
720
+	/**
721
+	 * Adds 'Authorization' header (if needed) to request headers array
722
+	 *
723
+	 * @param array  &$headers    request headers
724
+	 * @param string $requestHost request host (needed for digest authentication)
725
+	 * @param string $requestUrl  request URL (needed for digest authentication)
726
+	 *
727
+	 * @throws   HTTP_Request2_NotImplementedException
728
+	 */
729
+	protected function addAuthorizationHeader(&$headers, $requestHost, $requestUrl)
730
+	{
731
+		if (!($auth = $this->request->getAuth())) {
732
+			return;
733
+		}
734
+		switch ($auth['scheme']) {
735
+		case HTTP_Request2::AUTH_BASIC:
736
+			$headers['authorization'] = 'Basic ' . base64_encode(
737
+				$auth['user'] . ':' . $auth['password']
738
+			);
739
+			break;
740
+
741
+		case HTTP_Request2::AUTH_DIGEST:
742
+			unset($this->serverChallenge);
743
+			$fullUrl = ('/' == $requestUrl[0])?
744
+					   $this->request->getUrl()->getScheme() . '://' .
745
+						$requestHost . $requestUrl:
746
+					   $requestUrl;
747
+			foreach (array_keys(self::$challenges) as $key) {
748
+				if ($key == substr($fullUrl, 0, strlen($key))) {
749
+					$headers['authorization'] = $this->createDigestResponse(
750
+						$auth['user'], $auth['password'],
751
+						$requestUrl, self::$challenges[$key]
752
+					);
753
+					$this->serverChallenge =& self::$challenges[$key];
754
+					break;
755
+				}
756
+			}
757
+			break;
758
+
759
+		default:
760
+			throw new HTTP_Request2_NotImplementedException(
761
+				"Unknown HTTP authentication scheme '{$auth['scheme']}'"
762
+			);
763
+		}
764
+	}
765
+
766
+	/**
767
+	 * Adds 'Proxy-Authorization' header (if needed) to request headers array
768
+	 *
769
+	 * @param array  &$headers   request headers
770
+	 * @param string $requestUrl request URL (needed for digest authentication)
771
+	 *
772
+	 * @throws   HTTP_Request2_NotImplementedException
773
+	 */
774
+	protected function addProxyAuthorizationHeader(&$headers, $requestUrl)
775
+	{
776
+		if (!$this->request->getConfig('proxy_host')
777
+			|| !($user = $this->request->getConfig('proxy_user'))
778
+			|| (0 == strcasecmp('https', $this->request->getUrl()->getScheme())
779
+				&& HTTP_Request2::METHOD_CONNECT != $this->request->getMethod())
780
+		) {
781
+			return;
782
+		}
783
+
784
+		$password = $this->request->getConfig('proxy_password');
785
+		switch ($this->request->getConfig('proxy_auth_scheme')) {
786
+		case HTTP_Request2::AUTH_BASIC:
787
+			$headers['proxy-authorization'] = 'Basic ' . base64_encode(
788
+				$user . ':' . $password
789
+			);
790
+			break;
791
+
792
+		case HTTP_Request2::AUTH_DIGEST:
793
+			unset($this->proxyChallenge);
794
+			$proxyUrl = 'proxy://' . $this->request->getConfig('proxy_host') .
795
+						':' . $this->request->getConfig('proxy_port');
796
+			if (!empty(self::$challenges[$proxyUrl])) {
797
+				$headers['proxy-authorization'] = $this->createDigestResponse(
798
+					$user, $password,
799
+					$requestUrl, self::$challenges[$proxyUrl]
800
+				);
801
+				$this->proxyChallenge =& self::$challenges[$proxyUrl];
802
+			}
803
+			break;
804
+
805
+		default:
806
+			throw new HTTP_Request2_NotImplementedException(
807
+				"Unknown HTTP authentication scheme '" .
808
+				$this->request->getConfig('proxy_auth_scheme') . "'"
809
+			);
810
+		}
811
+	}
812
+
813
+
814
+	/**
815
+	 * Creates the string with the Request-Line and request headers
816
+	 *
817
+	 * @return   string
818
+	 * @throws   HTTP_Request2_Exception
819
+	 */
820
+	protected function prepareHeaders()
821
+	{
822
+		$headers = $this->request->getHeaders();
823
+		$url     = $this->request->getUrl();
824
+		$connect = HTTP_Request2::METHOD_CONNECT == $this->request->getMethod();
825
+		$host    = $url->getHost();
826
+
827
+		$defaultPort = 0 == strcasecmp($url->getScheme(), 'https')? 443: 80;
828
+		if (($port = $url->getPort()) && $port != $defaultPort || $connect) {
829
+			$host .= ':' . (empty($port)? $defaultPort: $port);
830
+		}
831
+		// Do not overwrite explicitly set 'Host' header, see bug #16146
832
+		if (!isset($headers['host'])) {
833
+			$headers['host'] = $host;
834
+		}
835
+
836
+		if ($connect) {
837
+			$requestUrl = $host;
838
+
839
+		} else {
840
+			if (!$this->request->getConfig('proxy_host')
841
+				|| 'http' != $this->request->getConfig('proxy_type')
842
+				|| 0 == strcasecmp($url->getScheme(), 'https')
843
+			) {
844
+				$requestUrl = '';
845
+			} else {
846
+				$requestUrl = $url->getScheme() . '://' . $host;
847
+			}
848
+			$path        = $url->getPath();
849
+			$query       = $url->getQuery();
850
+			$requestUrl .= (empty($path)? '/': $path) . (empty($query)? '': '?' . $query);
851
+		}
852
+
853
+		if ('1.1' == $this->request->getConfig('protocol_version')
854
+			&& extension_loaded('zlib') && !isset($headers['accept-encoding'])
855
+		) {
856
+			$headers['accept-encoding'] = 'gzip, deflate';
857
+		}
858
+		if (($jar = $this->request->getCookieJar())
859
+			&& ($cookies = $jar->getMatching($this->request->getUrl(), true))
860
+		) {
861
+			$headers['cookie'] = (empty($headers['cookie'])? '': $headers['cookie'] . '; ') . $cookies;
862
+		}
863
+
864
+		$this->addAuthorizationHeader($headers, $host, $requestUrl);
865
+		$this->addProxyAuthorizationHeader($headers, $requestUrl);
866
+		$this->calculateRequestLength($headers);
867
+		if ('1.1' == $this->request->getConfig('protocol_version')) {
868
+			$this->updateExpectHeader($headers);
869
+		} else {
870
+			$this->expect100Continue = false;
871
+		}
872
+
873
+		$headersStr = $this->request->getMethod() . ' ' . $requestUrl . ' HTTP/' .
874
+					  $this->request->getConfig('protocol_version') . "\r\n";
875
+		foreach ($headers as $name => $value) {
876
+			$canonicalName = implode('-', array_map('ucfirst', explode('-', $name)));
877
+			$headersStr   .= $canonicalName . ': ' . $value . "\r\n";
878
+		}
879
+		return $headersStr . "\r\n";
880
+	}
881
+
882
+	/**
883
+	 * Adds or removes 'Expect: 100-continue' header from request headers
884
+	 *
885
+	 * Also sets the $expect100Continue property. Parsing of existing header
886
+	 * is somewhat needed due to its complex structure and due to the
887
+	 * requirement in section 8.2.3 of RFC 2616:
888
+	 * > A client MUST NOT send an Expect request-header field (section
889
+	 * > 14.20) with the "100-continue" expectation if it does not intend
890
+	 * > to send a request body.
891
+	 *
892
+	 * @param array &$headers Array of headers prepared for the request
893
+	 *
894
+	 * @throws HTTP_Request2_LogicException
895
+	 * @link http://pear.php.net/bugs/bug.php?id=19233
896
+	 * @link http://tools.ietf.org/html/rfc2616#section-8.2.3
897
+	 */
898
+	protected function updateExpectHeader(&$headers)
899
+	{
900
+		$this->expect100Continue = false;
901
+		$expectations = array();
902
+		if (isset($headers['expect'])) {
903
+			if ('' === $headers['expect']) {
904
+				// empty 'Expect' header is technically invalid, so just get rid of it
905
+				unset($headers['expect']);
906
+				return;
907
+			}
908
+			// build regexp to parse the value of existing Expect header
909
+			$expectParam     = ';\s*' . self::REGEXP_TOKEN . '(?:\s*=\s*(?:'
910
+							   . self::REGEXP_TOKEN . '|'
911
+							   . self::REGEXP_QUOTED_STRING . '))?\s*';
912
+			$expectExtension = self::REGEXP_TOKEN . '(?:\s*=\s*(?:'
913
+							   . self::REGEXP_TOKEN . '|'
914
+							   . self::REGEXP_QUOTED_STRING . ')\s*(?:'
915
+							   . $expectParam . ')*)?';
916
+			$expectItem      = '!(100-continue|' . $expectExtension . ')!A';
917
+
918
+			$pos    = 0;
919
+			$length = strlen($headers['expect']);
920
+
921
+			while ($pos < $length) {
922
+				$pos += strspn($headers['expect'], " \t", $pos);
923
+				if (',' === substr($headers['expect'], $pos, 1)) {
924
+					$pos++;
925
+					continue;
926
+
927
+				} elseif (!preg_match($expectItem, $headers['expect'], $m, 0, $pos)) {
928
+					throw new HTTP_Request2_LogicException(
929
+						"Cannot parse value '{$headers['expect']}' of Expect header",
930
+						HTTP_Request2_Exception::INVALID_ARGUMENT
931
+					);
932
+
933
+				} else {
934
+					$pos += strlen($m[0]);
935
+					if (strcasecmp('100-continue', $m[0])) {
936
+						$expectations[]  = $m[0];
937
+					}
938
+				}
939
+			}
940
+		}
941
+
942
+		if (1024 < $this->contentLength) {
943
+			$expectations[] = '100-continue';
944
+			$this->expect100Continue = true;
945
+		}
946
+
947
+		if (empty($expectations)) {
948
+			unset($headers['expect']);
949
+		} else {
950
+			$headers['expect'] = implode(',', $expectations);
951
+		}
952
+	}
953
+
954
+	/**
955
+	 * Sends the request body
956
+	 *
957
+	 * @throws   HTTP_Request2_MessageException
958
+	 */
959
+	protected function writeBody()
960
+	{
961
+		if (in_array($this->request->getMethod(), self::$bodyDisallowed)
962
+			|| 0 == $this->contentLength
963
+		) {
964
+			return;
965
+		}
966
+
967
+		$position   = 0;
968
+		$bufferSize = $this->request->getConfig('buffer_size');
969
+		$headers    = $this->request->getHeaders();
970
+		$chunked    = isset($headers['transfer-encoding']);
971
+		while ($position < $this->contentLength) {
972
+			if (is_string($this->requestBody)) {
973
+				$str = substr($this->requestBody, $position, $bufferSize);
974
+			} elseif (is_resource($this->requestBody)) {
975
+				$str = fread($this->requestBody, $bufferSize);
976
+			} else {
977
+				$str = $this->requestBody->read($bufferSize);
978
+			}
979
+			if (!$chunked) {
980
+				$this->socket->write($str);
981
+			} else {
982
+				$this->socket->write(dechex(strlen($str)) . "\r\n{$str}\r\n");
983
+			}
984
+			// Provide the length of written string to the observer, request #7630
985
+			$this->request->setLastEvent('sentBodyPart', strlen($str));
986
+			$position += strlen($str);
987
+		}
988
+
989
+		// write zero-length chunk
990
+		if ($chunked) {
991
+			$this->socket->write("0\r\n\r\n");
992
+		}
993
+		$this->request->setLastEvent('sentBody', $this->contentLength);
994
+	}
995
+
996
+	/**
997
+	 * Reads the remote server's response
998
+	 *
999
+	 * @return   HTTP_Request2_Response
1000
+	 * @throws   HTTP_Request2_Exception
1001
+	 */
1002
+	protected function readResponse()
1003
+	{
1004
+		$bufferSize = $this->request->getConfig('buffer_size');
1005
+		// http://tools.ietf.org/html/rfc2616#section-8.2.3
1006
+		// ...the client SHOULD NOT wait for an indefinite period before sending the request body
1007
+		$timeout    = $this->expect100Continue ? 1 : null;
1008
+
1009
+		do {
1010
+			try {
1011
+				$response = new HTTP_Request2_Response(
1012
+					$this->socket->readLine($bufferSize, $timeout), true, $this->request->getUrl()
1013
+				);
1014
+				do {
1015
+					$headerLine = $this->socket->readLine($bufferSize);
1016
+					$response->parseHeaderLine($headerLine);
1017
+				} while ('' != $headerLine);
1018
+
1019
+			} catch (HTTP_Request2_MessageException $e) {
1020
+				if (HTTP_Request2_Exception::TIMEOUT === $e->getCode()
1021
+					&& $this->expect100Continue
1022
+				) {
1023
+					return null;
1024
+				}
1025
+				throw $e;
1026
+			}
1027
+			if ($this->expect100Continue && 100 == $response->getStatus()) {
1028
+				return $response;
1029
+			}
1030
+		} while (in_array($response->getStatus(), array(100, 101)));
1031
+
1032
+		$this->request->setLastEvent('receivedHeaders', $response);
1033
+
1034
+		// No body possible in such responses
1035
+		if (HTTP_Request2::METHOD_HEAD == $this->request->getMethod()
1036
+			|| (HTTP_Request2::METHOD_CONNECT == $this->request->getMethod()
1037
+				&& 200 <= $response->getStatus() && 300 > $response->getStatus())
1038
+			|| in_array($response->getStatus(), array(204, 304))
1039
+		) {
1040
+			return $response;
1041
+		}
1042
+
1043
+		$chunked = 'chunked' == $response->getHeader('transfer-encoding');
1044
+		$length  = $response->getHeader('content-length');
1045
+		$hasBody = false;
1046
+		if ($chunked || null === $length || 0 < intval($length)) {
1047
+			// RFC 2616, section 4.4:
1048
+			// 3. ... If a message is received with both a
1049
+			// Transfer-Encoding header field and a Content-Length header field,
1050
+			// the latter MUST be ignored.
1051
+			$toRead = ($chunked || null === $length)? null: $length;
1052
+			$this->chunkLength = 0;
1053
+
1054
+			while (!$this->socket->eof() && (is_null($toRead) || 0 < $toRead)) {
1055
+				if ($chunked) {
1056
+					$data = $this->readChunked($bufferSize);
1057
+				} elseif (is_null($toRead)) {
1058
+					$data = $this->socket->read($bufferSize);
1059
+				} else {
1060
+					$data    = $this->socket->read(min($toRead, $bufferSize));
1061
+					$toRead -= strlen($data);
1062
+				}
1063
+				if ('' == $data && (!$this->chunkLength || $this->socket->eof())) {
1064
+					break;
1065
+				}
1066
+
1067
+				$hasBody = true;
1068
+				if ($this->request->getConfig('store_body')) {
1069
+					$response->appendBody($data);
1070
+				}
1071
+				if (!in_array($response->getHeader('content-encoding'), array('identity', null))) {
1072
+					$this->request->setLastEvent('receivedEncodedBodyPart', $data);
1073
+				} else {
1074
+					$this->request->setLastEvent('receivedBodyPart', $data);
1075
+				}
1076
+			}
1077
+		}
1078
+
1079
+		if ($hasBody) {
1080
+			$this->request->setLastEvent('receivedBody', $response);
1081
+		}
1082
+		return $response;
1083
+	}
1084
+
1085
+	/**
1086
+	 * Reads a part of response body encoded with chunked Transfer-Encoding
1087
+	 *
1088
+	 * @param int $bufferSize buffer size to use for reading
1089
+	 *
1090
+	 * @return   string
1091
+	 * @throws   HTTP_Request2_MessageException
1092
+	 */
1093
+	protected function readChunked($bufferSize)
1094
+	{
1095
+		// at start of the next chunk?
1096
+		if (0 == $this->chunkLength) {
1097
+			$line = $this->socket->readLine($bufferSize);
1098
+			if (!preg_match('/^([0-9a-f]+)/i', $line, $matches)) {
1099
+				throw new HTTP_Request2_MessageException(
1100
+					"Cannot decode chunked response, invalid chunk length '{$line}'",
1101
+					HTTP_Request2_Exception::DECODE_ERROR
1102
+				);
1103
+			} else {
1104
+				$this->chunkLength = hexdec($matches[1]);
1105
+				// Chunk with zero length indicates the end
1106
+				if (0 == $this->chunkLength) {
1107
+					$this->socket->readLine($bufferSize);
1108
+					return '';
1109
+				}
1110
+			}
1111
+		}
1112
+		$data = $this->socket->read(min($this->chunkLength, $bufferSize));
1113
+		$this->chunkLength -= strlen($data);
1114
+		if (0 == $this->chunkLength) {
1115
+			$this->socket->readLine($bufferSize); // Trailing CRLF
1116
+		}
1117
+		return $data;
1118
+	}
1119 1119
 }
1120 1120
 
1121 1121
 ?>
1122 1122
\ No newline at end of file
Please login to merge, or discard this patch.