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.

Socket::request()   F
last analyzed

Complexity

Conditions 21
Paths 5122

Size

Total Lines 114

Duplication

Lines 4
Ratio 3.51 %

Importance

Changes 0
Metric Value
cc 21
nc 5122
nop 6
dl 4
loc 114
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 Joomla\Http\Exception\InvalidResponseCodeException;
12
use Joomla\Http\TransportInterface;
13
use Joomla\Http\Response;
14
use Joomla\Uri\UriInterface;
15
use Joomla\Uri\Uri;
16
17
/**
18
 * HTTP transport class for using sockets directly.
19
 *
20
 * @since  1.0
21
 */
22
class Socket implements TransportInterface
23
{
24
	/**
25
	 * Reusable socket connections.
26
	 *
27
	 * @var    array
28
	 * @since  1.0
29
	 */
30
	protected $connections;
31
32
	/**
33
	 * The client options.
34
	 *
35
	 * @var    array|\ArrayAccess
36
	 * @since  1.0
37
	 */
38
	protected $options;
39
40
	/**
41
	 * Constructor.
42
	 *
43
	 * @param   array|\ArrayAccess  $options  Client options array.
44
	 *
45
	 * @since   1.0
46
	 * @throws  \RuntimeException
47
	 */
48
	public function __construct($options = array())
49
	{
50
		if (!self::isSupported())
51
		{
52
			throw new \RuntimeException('Cannot use a socket transport when fsockopen() is not available.');
53
		}
54
55
		if (!is_array($options) && !($options instanceof \ArrayAccess))
56
		{
57
			throw new \InvalidArgumentException(
58
				'The options param must be an array or implement the ArrayAccess interface.'
59
			);
60
		}
61
62
		$this->options = $options;
63
	}
64
65
	/**
66
	 * Send a request to the server and return a Response object with the response.
67
	 *
68
	 * @param   string        $method     The HTTP method for sending the request.
69
	 * @param   UriInterface  $uri        The URI to the resource to request.
70
	 * @param   mixed         $data       Either an associative array or a string to be sent with the request.
71
	 * @param   array         $headers    An array of request headers to send with the request.
72
	 * @param   integer       $timeout    Read timeout in seconds.
73
	 * @param   string        $userAgent  The optional user agent string to send with the request.
74
	 *
75
	 * @return  Response
76
	 *
77
	 * @since   1.0
78
	 * @throws  \RuntimeException
79
	 */
80
	public function request($method, UriInterface $uri, $data = null, array $headers = null, $timeout = null, $userAgent = null)
81
	{
82
		$connection = $this->connect($uri, $timeout);
83
84
		// Make sure the connection is alive and valid.
85
		if (is_resource($connection))
86
		{
87
			// Make sure the connection has not timed out.
88
			$meta = stream_get_meta_data($connection);
89
90
			if ($meta['timed_out'])
91
			{
92
				throw new \RuntimeException('Server connection timed out.');
93
			}
94
		}
95
		else
96
		{
97
			throw new \RuntimeException('Not connected to server.');
98
		}
99
100
		// Get the request path from the URI object.
101
		$path = $uri->toString(array('path', 'query'));
102
103
		// If we have data to send make sure our request is setup for it.
104
		if (!empty($data))
105
		{
106
			// If the data is not a scalar value encode it to be sent with the request.
107
			if (!is_scalar($data))
108
			{
109
				$data = http_build_query($data);
110
			}
111
112
			if (!isset($headers['Content-Type']))
113
			{
114
				$headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8';
115
			}
116
117
			// Add the relevant headers.
118
			$headers['Content-Length'] = strlen($data);
119
		}
120
121
		// Configure protocol version, use transport's default if not set otherwise.
122
		$protocolVersion = isset($this->options['protocolVersion']) ? $this->options['protocolVersion'] : '1.0';
123
124
		// Build the request payload.
125
		$request = array();
126
		$request[] = strtoupper($method) . ' ' . ((empty($path)) ? '/' : $path) . ' HTTP/' . $protocolVersion;
127
		$request[] = 'Host: ' . $uri->getHost();
128
129
		// If an explicit user agent is given use it.
130
		if (isset($userAgent))
131
		{
132
			$headers['User-Agent'] = $userAgent;
133
		}
134
135
		// If we have a username then we include basic authentication credentials.
136
		if ($uri->getUser())
137
		{
138
			$authString = $uri->getUser() . ':' . $uri->getPass();
139
			$headers['Authorization'] = 'Basic ' . base64_encode($authString);
140
		}
141
142
		// If there are custom headers to send add them to the request payload.
143
		if (is_array($headers))
144
		{
145
			foreach ($headers as $k => $v)
146
			{
147
				$request[] = $k . ': ' . $v;
148
			}
149
		}
150
151
		// Authentication, if needed
152 View Code Duplication
		if (isset($this->options['userauth']) && isset($this->options['passwordauth']))
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...
153
		{
154
			$request[] = 'Authorization: Basic ' . base64_encode($this->options['userauth'] . ':' . $this->options['passwordauth']);
155
		}
156
157
		// Set any custom transport options
158
		if (isset($this->options['transport.socket']))
159
		{
160
			foreach ($this->options['transport.socket'] as $value)
161
			{
162
				$request[] = $value;
163
			}
164
		}
165
166
		// If we have data to send add it to the request payload.
167
		if (!empty($data))
168
		{
169
			$request[] = null;
170
			$request[] = $data;
171
		}
172
173
		// Send the request to the server.
174
		fwrite($connection, implode("\r\n", $request) . "\r\n\r\n");
175
176
		// Get the response data from the server.
177
		$content = '';
178
179
		while (!feof($connection))
180
		{
181
			$content .= fgets($connection, 4096);
182
		}
183
184
		$content = $this->getResponse($content);
185
186
		// Follow Http redirects
187
		if ($content->code >= 301 && $content->code < 400 && isset($content->headers['Location']))
188
		{
189
			return $this->request($method, new Uri($content->headers['Location']), $data, $headers, $timeout, $userAgent);
190
		}
191
192
		return $content;
193
	}
194
195
	/**
196
	 * Method to get a response object from a server response.
197
	 *
198
	 * @param   string  $content  The complete server response, including headers.
199
	 *
200
	 * @return  Response
201
	 *
202
	 * @since   1.0
203
	 * @throws  \UnexpectedValueException
204
	 * @throws  InvalidResponseCodeException
205
	 */
206
	protected function getResponse($content)
207
	{
208
		// Create the response object.
209
		$return = new Response;
210
211
		if (empty($content))
212
		{
213
			throw new \UnexpectedValueException('No content in response.');
214
		}
215
216
		// Split the response into headers and body.
217
		$response = explode("\r\n\r\n", $content, 2);
218
219
		// Get the response headers as an array.
220
		$headers = explode("\r\n", $response[0]);
221
222
		// Set the body for the response.
223
		$return->body = empty($response[1]) ? '' : $response[1];
224
225
		// Get the response code from the first offset of the response headers.
226
		preg_match('/[0-9]{3}/', array_shift($headers), $matches);
227
		$code = $matches[0];
228
229
		if (is_numeric($code))
230
		{
231
			$return->code = (int) $code;
232
		}
233
		else
234
		// No valid response code was detected.
235
		{
236
			throw new InvalidResponseCodeException('No HTTP response code found.');
237
		}
238
239
		// Add the response headers to the response object.
240 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...
241
		{
242
			$pos = strpos($header, ':');
243
			$return->headers[trim(substr($header, 0, $pos))] = trim(substr($header, ($pos + 1)));
244
		}
245
246
		return $return;
247
	}
248
249
	/**
250
	 * Method to connect to a server and get the resource.
251
	 *
252
	 * @param   UriInterface  $uri      The URI to connect with.
253
	 * @param   integer       $timeout  Read timeout in seconds.
254
	 *
255
	 * @return  resource  Socket connection resource.
256
	 *
257
	 * @since   1.0
258
	 * @throws  \RuntimeException
259
	 */
260
	protected function connect(UriInterface $uri, $timeout = null)
261
	{
262
		$errno = null;
263
		$err = null;
264
265
		// Get the host from the uri.
266
		$host = ($uri->isSsl()) ? 'ssl://' . $uri->getHost() : $uri->getHost();
267
268
		// If the port is not explicitly set in the URI detect it.
269
		if (!$uri->getPort())
270
		{
271
			$port = ($uri->getScheme() == 'https') ? 443 : 80;
272
		}
273
274
		// Use the set port.
275
		else
276
		{
277
			$port = $uri->getPort();
278
		}
279
280
		// Build the connection key for resource memory caching.
281
		$key = md5($host . $port);
282
283
		// If the connection already exists, use it.
284
		if (!empty($this->connections[$key]) && is_resource($this->connections[$key]))
285
		{
286
			// Connection reached EOF, cannot be used anymore
287
			$meta = stream_get_meta_data($this->connections[$key]);
288
289
			if ($meta['eof'])
290
			{
291
				if (!fclose($this->connections[$key]))
292
				{
293
					throw new \RuntimeException('Cannot close connection');
294
				}
295
			}
296
297
			// Make sure the connection has not timed out.
298
			elseif (!$meta['timed_out'])
299
			{
300
				return $this->connections[$key];
301
			}
302
		}
303
304
		if (!is_numeric($timeout))
305
		{
306
			$timeout = ini_get('default_socket_timeout');
307
		}
308
309
		// Capture PHP errors
310
		$php_errormsg = '';
311
		$trackErrors = ini_get('track_errors');
312
		ini_set('track_errors', true);
313
314
		// PHP sends a warning if the uri does not exists; we silence it and throw an exception instead.
315
		// Attempt to connect to the server
316
		$connection = @fsockopen($host, $port, $errno, $err, $timeout);
317
318 View Code Duplication
		if (!$connection)
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...
319
		{
320
			if (!$php_errormsg)
321
			{
322
				// Error but nothing from php? Create our own
323
				$php_errormsg = sprintf('Could not connect to resource: %s', $uri, $err, $errno);
324
			}
325
326
			// Restore error tracking to give control to the exception handler
327
			ini_set('track_errors', $trackErrors);
328
329
			throw new \RuntimeException($php_errormsg);
330
		}
331
332
		// Restore error tracking to what it was before.
333
		ini_set('track_errors', $trackErrors);
334
335
		// Since the connection was successful let's store it in case we need to use it later.
336
		$this->connections[$key] = $connection;
337
338
		// If an explicit timeout is set, set it.
339
		if (isset($timeout))
340
		{
341
			stream_set_timeout($this->connections[$key], (int) $timeout);
342
		}
343
344
		return $this->connections[$key];
345
	}
346
347
	/**
348
	 * Method to check if http transport socket available for use
349
	 *
350
	 * @return  boolean   True if available else false
351
	 *
352
	 * @since   1.0
353
	 */
354
	public static function isSupported()
355
	{
356
		return function_exists('fsockopen') && is_callable('fsockopen');
357
	}
358
}
359