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
Push — develop ( 06e328...3bdb15 )
by
unknown
14s
created

Curl::mapProtocolVersion()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 11
nc 7
nop 1
dl 0
loc 20
rs 8.8571
c 0
b 0
f 0
1
<?php
2
/**
3
 * Part of the Joomla Framework Http Package
4
 *
5
 * @copyright  Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
6
 * @license    GNU General Public License version 2 or later; see LICENSE
7
 */
8
9
namespace Joomla\Http\Transport;
10
11
use Composer\CaBundle\CaBundle;
12
use Joomla\Http\Exception\InvalidResponseCodeException;
13
use Joomla\Http\TransportInterface;
14
use Joomla\Http\Response;
15
use Joomla\Uri\UriInterface;
16
17
/**
18
 * HTTP transport class for using cURL.
19
 *
20
 * @since  1.0
21
 */
22
class Curl implements TransportInterface
23
{
24
	/**
25
	 * The client options.
26
	 *
27
	 * @var    array|\ArrayAccess
28
	 * @since  1.0
29
	 */
30
	protected $options;
31
32
	/**
33
	 * Constructor. CURLOPT_FOLLOWLOCATION must be disabled when open_basedir or safe_mode are enabled.
34
	 *
35
	 * @param   array|\ArrayAccess  $options  Client options array.
36
	 *
37
	 * @link    https://secure.php.net/manual/en/function.curl-setopt.php
38
	 * @since   1.0
39
	 * @throws  \InvalidArgumentException
40
	 * @throws  \RuntimeException
41
	 */
42 View Code Duplication
	public function __construct($options = array())
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
43
	{
44
		if (!function_exists('curl_init') || !is_callable('curl_init'))
45
		{
46
			throw new \RuntimeException('Cannot use a cURL transport when curl_init() is not available.');
47
		}
48
49
		if (!is_array($options) && !($options instanceof \ArrayAccess))
50
		{
51
			throw new \InvalidArgumentException(
52
				'The options param must be an array or implement the ArrayAccess interface.'
53
			);
54
		}
55
56
		$this->options = $options;
57
	}
58
59
	/**
60
	 * Send a request to the server and return a Response object with the response.
61
	 *
62
	 * @param   string        $method     The HTTP method for sending the request.
63
	 * @param   UriInterface  $uri        The URI to the resource to request.
64
	 * @param   mixed         $data       Either an associative array or a string to be sent with the request.
65
	 * @param   array         $headers    An array of request headers to send with the request.
66
	 * @param   integer       $timeout    Read timeout in seconds.
67
	 * @param   string        $userAgent  The optional user agent string to send with the request.
68
	 *
69
	 * @return  Response
70
	 *
71
	 * @since   1.0
72
	 * @throws  \RuntimeException
73
	 */
74
	public function request($method, UriInterface $uri, $data = null, array $headers = null, $timeout = null, $userAgent = null)
75
	{
76
		// Setup the cURL handle.
77
		$ch = curl_init();
78
79
		$options = array();
80
81
		// Set the request method.
82
		switch (strtoupper($method))
83
		{
84
			case 'GET':
85
				$options[CURLOPT_HTTPGET] = true;
86
				break;
87
88
			case 'POST':
89
				$options[CURLOPT_POST] = true;
90
				break;
91
92
			default:
93
				$options[CURLOPT_CUSTOMREQUEST] = strtoupper($method);
94
				break;
95
		}
96
97
		// Don't wait for body when $method is HEAD
98
		$options[CURLOPT_NOBODY] = ($method === 'HEAD');
99
100
		// Initialize the certificate store
101
		$options[CURLOPT_CAINFO] = isset($this->options['curl.certpath']) ? $this->options['curl.certpath'] : CaBundle::getSystemCaRootBundlePath();
102
103
		// If data exists let's encode it and make sure our Content-type header is set.
104
		if (isset($data))
105
		{
106
			// If the data is a scalar value simply add it to the cURL post fields.
107
			if (is_scalar($data) || (isset($headers['Content-Type']) && strpos($headers['Content-Type'], 'multipart/form-data') === 0))
108
			{
109
				$options[CURLOPT_POSTFIELDS] = $data;
110
			}
111
			else
112
			// Otherwise we need to encode the value first.
113
			{
114
				$options[CURLOPT_POSTFIELDS] = http_build_query($data);
115
			}
116
117
			if (!isset($headers['Content-Type']))
118
			{
119
				$headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8';
120
			}
121
122
			// Add the relevant headers.
123
			if (is_scalar($options[CURLOPT_POSTFIELDS]))
124
			{
125
				$headers['Content-Length'] = strlen($options[CURLOPT_POSTFIELDS]);
126
			}
127
		}
128
129
		// Build the headers string for the request.
130
		$headerArray = array();
131
132
		if (isset($headers))
133
		{
134
			foreach ($headers as $key => $value)
135
			{
136
				$headerArray[] = $key . ': ' . $value;
137
			}
138
139
			// Add the headers string into the stream context options array.
140
			$options[CURLOPT_HTTPHEADER] = $headerArray;
141
		}
142
143
		// Curl needs the accepted encoding header as option
144
		if (isset($headers['Accept-Encoding']))
145
		{
146
			$options[CURLOPT_ENCODING] = $headers['Accept-Encoding'];
147
		}
148
149
		// If an explicit timeout is given user it.
150
		if (isset($timeout))
151
		{
152
			$options[CURLOPT_TIMEOUT] = (int) $timeout;
153
			$options[CURLOPT_CONNECTTIMEOUT] = (int) $timeout;
154
		}
155
156
		// If an explicit user agent is given use it.
157
		if (isset($userAgent))
158
		{
159
			$options[CURLOPT_USERAGENT] = $userAgent;
160
		}
161
162
		// Set the request URL.
163
		$options[CURLOPT_URL] = (string) $uri;
164
165
		// We want our headers. :-)
166
		$options[CURLOPT_HEADER] = true;
167
168
		// Return it... echoing it would be tacky.
169
		$options[CURLOPT_RETURNTRANSFER] = true;
170
171
		// Override the Expect header to prevent cURL from confusing itself in its own stupidity.
172
		// Link: http://the-stickman.com/web-development/php-and-curl-disabling-100-continue-header/
173
		$options[CURLOPT_HTTPHEADER][] = 'Expect:';
174
175
		// Follow redirects if server config allows
176
		if ($this->redirectsAllowed())
177
		{
178
			$options[CURLOPT_FOLLOWLOCATION] = (bool) isset($this->options['follow_location']) ? $this->options['follow_location'] : true;
179
		}
180
181
		// Authentication, if needed
182
		if (isset($this->options['userauth']) && isset($this->options['passwordauth']))
183
		{
184
			$options[CURLOPT_USERPWD]  = $this->options['userauth'] . ':' . $this->options['passwordauth'];
185
			$options[CURLOPT_HTTPAUTH] = CURLAUTH_BASIC;
186
		}
187
188
		// Configure protocol version
189
		if (isset($this->options['protocolVersion']))
190
		{
191
			$options[CURLOPT_HTTP_VERSION] = $this->mapProtocolVersion($this->options['protocolVersion']);
192
		}
193
194
		// Set any custom transport options
195
		if (isset($this->options['transport.curl']))
196
		{
197
			foreach ($this->options['transport.curl'] as $key => $value)
198
			{
199
				$options[$key] = $value;
200
			}
201
		}
202
203
		// Set the cURL options.
204
		curl_setopt_array($ch, $options);
205
206
		// Execute the request and close the connection.
207
		$content = curl_exec($ch);
208
209
		// Check if the content is a string. If it is not, it must be an error.
210
		if (!is_string($content))
211
		{
212
			$message = curl_error($ch);
213
214
			if (empty($message))
215
			{
216
				// Error but nothing from cURL? Create our own
217
				$message = 'No HTTP response received';
218
			}
219
220
			throw new \RuntimeException($message);
221
		}
222
223
		// Get the request information.
224
		$info = curl_getinfo($ch);
225
226
		// Close the connection.
227
		curl_close($ch);
228
229
		return $this->getResponse($content, $info);
230
	}
231
232
	/**
233
	 * Method to get a response object from a server response.
234
	 *
235
	 * @param   string  $content  The complete server response, including headers
236
	 *                            as a string if the response has no errors.
237
	 * @param   array   $info     The cURL request information.
238
	 *
239
	 * @return  Response
240
	 *
241
	 * @since   1.0
242
	 * @throws  InvalidResponseCodeException
243
	 */
244
	protected function getResponse($content, $info)
245
	{
246
		// Create the response object.
247
		$return = new Response;
248
249
		// Try to get header size
250
		if (isset($info['header_size']))
251
		{
252
			$headerString = trim(substr($content, 0, $info['header_size']));
253
			$headerArray  = explode("\r\n\r\n", $headerString);
254
255
			// Get the last set of response headers as an array.
256
			$headers = explode("\r\n", array_pop($headerArray));
257
258
			// Set the body for the response.
259
			$return->body = substr($content, $info['header_size']);
260
		}
261
		// Fallback and try to guess header count by redirect count
262
		else
263
		{
264
			// Get the number of redirects that occurred.
265
			$redirects = isset($info['redirect_count']) ? $info['redirect_count'] : 0;
266
267
			/*
268
			 * Split the response into headers and body. If cURL encountered redirects, the headers for the redirected requests will
269
			 * also be included. So we split the response into header + body + the number of redirects and only use the last two
270
			 * sections which should be the last set of headers and the actual body.
271
			 */
272
			$response = explode("\r\n\r\n", $content, 2 + $redirects);
273
274
			// Set the body for the response.
275
			$return->body = array_pop($response);
276
277
			// Get the last set of response headers as an array.
278
			$headers = explode("\r\n", array_pop($response));
279
		}
280
281
		// Get the response code from the first offset of the response headers.
282
		preg_match('/[0-9]{3}/', array_shift($headers), $matches);
283
284
		$code = count($matches) ? $matches[0] : null;
285
286
		if (is_numeric($code))
287
		{
288
			$return->code = (int) $code;
289
		}
290
291
		// No valid response code was detected.
292
		else
293
		{
294
			throw new InvalidResponseCodeException('No HTTP response code found.');
295
		}
296
297
		// Add the response headers to the response object.
298 View Code Duplication
		foreach ($headers as $header)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
299
		{
300
			$pos = strpos($header, ':');
301
			$return->headers[trim(substr($header, 0, $pos))] = trim(substr($header, ($pos + 1)));
302
		}
303
304
		return $return;
305
	}
306
307
	/**
308
	 * Method to check if HTTP transport cURL is available for use
309
	 *
310
	 * @return  boolean  True if available, else false
311
	 *
312
	 * @since   1.0
313
	 */
314
	public static function isSupported()
315
	{
316
		return function_exists('curl_version') && curl_version();
317
	}
318
319
	/**
320
	 * Get the cURL constant for a HTTP protocol version
321
	 *
322
	 * @param   string  $version  The HTTP protocol version to use
323
	 *
324
	 * @return  integer
325
	 *
326
	 * @since   1.3.1
327
	 */
328
	private function mapProtocolVersion($version)
329
	{
330
		switch ($version)
331
		{
332
			case '1.0':
333
				return CURL_HTTP_VERSION_1_0;
334
335
			case '1.1':
336
				return CURL_HTTP_VERSION_1_1;
337
338
			case '2.0':
339
			case '2':
340
				if (defined('CURL_HTTP_VERSION_2'))
341
				{
342
					return CURL_HTTP_VERSION_2;
343
				}
344
		}
345
346
		return CURL_HTTP_VERSION_NONE;
347
	}
348
349
	/**
350
	 * Check if redirects are allowed
351
	 *
352
	 * @return  boolean
353
	 *
354
	 * @since   1.2.1
355
	 */
356
	private function redirectsAllowed()
357
	{
358
		// There are no issues on PHP 5.6 and later
359
		if (version_compare(PHP_VERSION, '5.6', '>='))
360
		{
361
			return true;
362
		}
363
364
		// For PHP 5.3, redirects are not allowed if safe_mode and open_basedir are enabled
365
		if (PHP_MAJOR_VERSION === 5 && PHP_MINOR_VERSION === 3)
366
		{
367
			if (!ini_get('safe_mode') && !ini_get('open_basedir'))
368
			{
369
				return true;
370
			}
371
		}
372
373
		// For PHP 5.4 and 5.5, we only need to check if open_basedir is disabled
374
		return !ini_get('open_basedir');
375
	}
376
}
377