Issues (2010)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

wp-includes/class-wp-http-curl.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * HTTP API: WP_Http_Curl class
4
 *
5
 * @package WordPress
6
 * @subpackage HTTP
7
 * @since 4.4.0
8
 */
9
10
/**
11
 * Core class used to integrate Curl as an HTTP transport.
12
 *
13
 * HTTP request method uses Curl extension to retrieve the url.
14
 *
15
 * Requires the Curl extension to be installed.
16
 *
17
 * @since 2.7.0
18
 */
19
class WP_Http_Curl {
20
21
	/**
22
	 * Temporary header storage for during requests.
23
	 *
24
	 * @since 3.2.0
25
	 * @access private
26
	 * @var string
27
	 */
28
	private $headers = '';
29
30
	/**
31
	 * Temporary body storage for during requests.
32
	 *
33
	 * @since 3.6.0
34
	 * @access private
35
	 * @var string
36
	 */
37
	private $body = '';
38
39
	/**
40
	 * The maximum amount of data to receive from the remote server.
41
	 *
42
	 * @since 3.6.0
43
	 * @access private
44
	 * @var int
45
	 */
46
	private $max_body_length = false;
47
48
	/**
49
	 * The file resource used for streaming to file.
50
	 *
51
	 * @since 3.6.0
52
	 * @access private
53
	 * @var resource
54
	 */
55
	private $stream_handle = false;
56
57
	/**
58
	 * The total bytes written in the current request.
59
	 *
60
	 * @since 4.1.0
61
	 * @access private
62
	 * @var int
63
	 */
64
	private $bytes_written_total = 0;
65
66
	/**
67
	 * Send a HTTP request to a URI using cURL extension.
68
	 *
69
	 * @access public
70
	 * @since 2.7.0
71
	 *
72
	 * @param string $url The request URL.
73
	 * @param string|array $args Optional. Override the defaults.
74
	 * @return array|WP_Error Array containing 'headers', 'body', 'response', 'cookies', 'filename'. A WP_Error instance upon error
75
	 */
76
	public function request($url, $args = array()) {
77
		$defaults = array(
78
			'method' => 'GET', 'timeout' => 5,
79
			'redirection' => 5, 'httpversion' => '1.0',
80
			'blocking' => true,
81
			'headers' => array(), 'body' => null, 'cookies' => array()
82
		);
83
84
		$r = wp_parse_args( $args, $defaults );
85
86 View Code Duplication
		if ( isset( $r['headers']['User-Agent'] ) ) {
87
			$r['user-agent'] = $r['headers']['User-Agent'];
88
			unset( $r['headers']['User-Agent'] );
89
		} elseif ( isset( $r['headers']['user-agent'] ) ) {
90
			$r['user-agent'] = $r['headers']['user-agent'];
91
			unset( $r['headers']['user-agent'] );
92
		}
93
94
		// Construct Cookie: header if any cookies are set.
95
		WP_Http::buildCookieHeader( $r );
96
97
		$handle = curl_init();
98
99
		// cURL offers really easy proxy support.
100
		$proxy = new WP_HTTP_Proxy();
101
102
		if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) {
103
104
			curl_setopt( $handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP );
105
			curl_setopt( $handle, CURLOPT_PROXY, $proxy->host() );
106
			curl_setopt( $handle, CURLOPT_PROXYPORT, $proxy->port() );
107
108
			if ( $proxy->use_authentication() ) {
109
				curl_setopt( $handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY );
110
				curl_setopt( $handle, CURLOPT_PROXYUSERPWD, $proxy->authentication() );
111
			}
112
		}
113
114
		$is_local = isset($r['local']) && $r['local'];
115
		$ssl_verify = isset($r['sslverify']) && $r['sslverify'];
116 View Code Duplication
		if ( $is_local ) {
117
			/** This filter is documented in wp-includes/class-wp-http-streams.php */
118
			$ssl_verify = apply_filters( 'https_local_ssl_verify', $ssl_verify );
119
		} elseif ( ! $is_local ) {
120
			/** This filter is documented in wp-includes/class-wp-http-streams.php */
121
			$ssl_verify = apply_filters( 'https_ssl_verify', $ssl_verify );
122
		}
123
124
		/*
125
		 * CURLOPT_TIMEOUT and CURLOPT_CONNECTTIMEOUT expect integers. Have to use ceil since.
126
		 * a value of 0 will allow an unlimited timeout.
127
		 */
128
		$timeout = (int) ceil( $r['timeout'] );
129
		curl_setopt( $handle, CURLOPT_CONNECTTIMEOUT, $timeout );
130
		curl_setopt( $handle, CURLOPT_TIMEOUT, $timeout );
131
132
		curl_setopt( $handle, CURLOPT_URL, $url);
133
		curl_setopt( $handle, CURLOPT_RETURNTRANSFER, true );
134
		curl_setopt( $handle, CURLOPT_SSL_VERIFYHOST, ( $ssl_verify === true ) ? 2 : false );
135
		curl_setopt( $handle, CURLOPT_SSL_VERIFYPEER, $ssl_verify );
136
137
		if ( $ssl_verify ) {
138
			curl_setopt( $handle, CURLOPT_CAINFO, $r['sslcertificates'] );
139
		}
140
141
		curl_setopt( $handle, CURLOPT_USERAGENT, $r['user-agent'] );
142
143
		/*
144
		 * The option doesn't work with safe mode or when open_basedir is set, and there's
145
		 * a bug #17490 with redirected POST requests, so handle redirections outside Curl.
146
		 */
147
		curl_setopt( $handle, CURLOPT_FOLLOWLOCATION, false );
148
		if ( defined( 'CURLOPT_PROTOCOLS' ) ) // PHP 5.2.10 / cURL 7.19.4
149
			curl_setopt( $handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS );
150
151
		switch ( $r['method'] ) {
152
			case 'HEAD':
153
				curl_setopt( $handle, CURLOPT_NOBODY, true );
154
				break;
155 View Code Duplication
			case 'POST':
156
				curl_setopt( $handle, CURLOPT_POST, true );
157
				curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] );
158
				break;
159 View Code Duplication
			case 'PUT':
160
				curl_setopt( $handle, CURLOPT_CUSTOMREQUEST, 'PUT' );
161
				curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] );
162
				break;
163
			default:
164
				curl_setopt( $handle, CURLOPT_CUSTOMREQUEST, $r['method'] );
165
				if ( ! is_null( $r['body'] ) )
166
					curl_setopt( $handle, CURLOPT_POSTFIELDS, $r['body'] );
167
				break;
168
		}
169
170
		if ( true === $r['blocking'] ) {
171
			curl_setopt( $handle, CURLOPT_HEADERFUNCTION, array( $this, 'stream_headers' ) );
172
			curl_setopt( $handle, CURLOPT_WRITEFUNCTION, array( $this, 'stream_body' ) );
173
		}
174
175
		curl_setopt( $handle, CURLOPT_HEADER, false );
176
177
		if ( isset( $r['limit_response_size'] ) )
178
			$this->max_body_length = intval( $r['limit_response_size'] );
179
		else
180
			$this->max_body_length = false;
0 ignored issues
show
Documentation Bug introduced by
The property $max_body_length was declared of type integer, but false is of type false. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
181
182
		// If streaming to a file open a file handle, and setup our curl streaming handler.
183
		if ( $r['stream'] ) {
184
			if ( ! WP_DEBUG )
185
				$this->stream_handle = @fopen( $r['filename'], 'w+' );
186
			else
187
				$this->stream_handle = fopen( $r['filename'], 'w+' );
188
			if ( ! $this->stream_handle )
189
				return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) );
190
		} else {
191
			$this->stream_handle = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type resource of property $stream_handle.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
192
		}
193
194
		if ( !empty( $r['headers'] ) ) {
195
			// cURL expects full header strings in each element.
196
			$headers = array();
197
			foreach ( $r['headers'] as $name => $value ) {
198
				$headers[] = "{$name}: $value";
199
			}
200
			curl_setopt( $handle, CURLOPT_HTTPHEADER, $headers );
201
		}
202
203
		if ( $r['httpversion'] == '1.0' )
204
			curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0 );
205
		else
206
			curl_setopt( $handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1 );
207
208
		/**
209
		 * Fires before the cURL request is executed.
210
		 *
211
		 * Cookies are not currently handled by the HTTP API. This action allows
212
		 * plugins to handle cookies themselves.
213
		 *
214
		 * @since 2.8.0
215
		 *
216
		 * @param resource &$handle The cURL handle returned by curl_init().
217
		 * @param array    $r       The HTTP request arguments.
218
		 * @param string   $url     The request URL.
219
		 */
220
		do_action_ref_array( 'http_api_curl', array( &$handle, $r, $url ) );
221
222
		// We don't need to return the body, so don't. Just execute request and return.
223
		if ( ! $r['blocking'] ) {
224
			curl_exec( $handle );
225
226
			if ( $curl_error = curl_error( $handle ) ) {
227
				curl_close( $handle );
228
				return new WP_Error( 'http_request_failed', $curl_error );
229
			}
230 View Code Duplication
			if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array( 301, 302 ) ) ) {
231
				curl_close( $handle );
232
				return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) );
233
			}
234
235
			curl_close( $handle );
236
			return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() );
237
		}
238
239
		curl_exec( $handle );
240
		$theHeaders = WP_Http::processHeaders( $this->headers, $url );
241
		$theBody = $this->body;
242
		$bytes_written_total = $this->bytes_written_total;
243
244
		$this->headers = '';
245
		$this->body = '';
246
		$this->bytes_written_total = 0;
247
248
		$curl_error = curl_errno( $handle );
249
250
		// If an error occurred, or, no response.
251
		if ( $curl_error || ( 0 == strlen( $theBody ) && empty( $theHeaders['headers'] ) ) ) {
252
			if ( CURLE_WRITE_ERROR /* 23 */ == $curl_error ) {
253
				if ( ! $this->max_body_length || $this->max_body_length != $bytes_written_total ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->max_body_length of type integer|false is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
254
					if ( $r['stream'] ) {
255
						curl_close( $handle );
256
						fclose( $this->stream_handle );
257
						return new WP_Error( 'http_request_failed', __( 'Failed to write request to temporary file.' ) );
258
					} else {
259
						curl_close( $handle );
260
						return new WP_Error( 'http_request_failed', curl_error( $handle ) );
261
					}
262
				}
263
			} else {
264
				if ( $curl_error = curl_error( $handle ) ) {
265
					curl_close( $handle );
266
					return new WP_Error( 'http_request_failed', $curl_error );
267
				}
268
			}
269 View Code Duplication
			if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array( 301, 302 ) ) ) {
270
				curl_close( $handle );
271
				return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) );
272
			}
273
		}
274
275
		curl_close( $handle );
276
277
		if ( $r['stream'] )
278
			fclose( $this->stream_handle );
279
280
		$response = array(
281
			'headers' => $theHeaders['headers'],
282
			'body' => null,
283
			'response' => $theHeaders['response'],
284
			'cookies' => $theHeaders['cookies'],
285
			'filename' => $r['filename']
286
		);
287
288
		// Handle redirects.
289
		if ( false !== ( $redirect_response = WP_HTTP::handle_redirects( $url, $r, $response ) ) )
290
			return $redirect_response;
291
292 View Code Duplication
		if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($theHeaders['headers']) )
293
			$theBody = WP_Http_Encoding::decompress( $theBody );
294
295
		$response['body'] = $theBody;
296
297
		return $response;
298
	}
299
300
	/**
301
	 * Grabs the headers of the cURL request.
302
	 *
303
	 * Each header is sent individually to this callback, so we append to the `$header` property
304
	 * for temporary storage
305
	 *
306
	 * @since 3.2.0
307
	 * @access private
308
	 *
309
	 * @param resource $handle  cURL handle.
310
	 * @param string   $headers cURL request headers.
311
	 * @return int Length of the request headers.
312
	 */
313
	private function stream_headers( $handle, $headers ) {
314
		$this->headers .= $headers;
315
		return strlen( $headers );
316
	}
317
318
	/**
319
	 * Grabs the body of the cURL request.
320
	 *
321
	 * The contents of the document are passed in chunks, so we append to the `$body`
322
	 * property for temporary storage. Returning a length shorter than the length of
323
	 * `$data` passed in will cause cURL to abort the request with `CURLE_WRITE_ERROR`.
324
	 *
325
	 * @since 3.6.0
326
	 * @access private
327
	 *
328
	 * @param resource $handle  cURL handle.
329
	 * @param string   $data    cURL request body.
330
	 * @return int Total bytes of data written.
331
	 */
332
	private function stream_body( $handle, $data ) {
333
		$data_length = strlen( $data );
334
335
		if ( $this->max_body_length && ( $this->bytes_written_total + $data_length ) > $this->max_body_length ) {
336
			$data_length = ( $this->max_body_length - $this->bytes_written_total );
337
			$data = substr( $data, 0, $data_length );
338
		}
339
340
		if ( $this->stream_handle ) {
341
			$bytes_written = fwrite( $this->stream_handle, $data );
342
		} else {
343
			$this->body .= $data;
344
			$bytes_written = $data_length;
345
		}
346
347
		$this->bytes_written_total += $bytes_written;
348
349
		// Upon event of this function returning less than strlen( $data ) curl will error with CURLE_WRITE_ERROR.
350
		return $bytes_written;
351
	}
352
353
	/**
354
	 * Determines whether this class can be used for retrieving a URL.
355
	 *
356
	 * @static
357
	 * @since 2.7.0
358
	 *
359
	 * @param array $args Optional. Array of request arguments. Default empty array.
360
	 * @return bool False means this class can not be used, true means it can.
361
	 */
362
	public static function test( $args = array() ) {
363
		if ( ! function_exists( 'curl_init' ) || ! function_exists( 'curl_exec' ) )
364
			return false;
365
366
		$is_ssl = isset( $args['ssl'] ) && $args['ssl'];
367
368
		if ( $is_ssl ) {
369
			$curl_version = curl_version();
370
			// Check whether this cURL version support SSL requests.
371
			if ( ! (CURL_VERSION_SSL & $curl_version['features']) )
372
				return false;
373
		}
374
375
		/**
376
		 * Filters whether cURL can be used as a transport for retrieving a URL.
377
		 *
378
		 * @since 2.7.0
379
		 *
380
		 * @param bool  $use_class Whether the class can be used. Default true.
381
		 * @param array $args      An array of request arguments.
382
		 */
383
		return apply_filters( 'use_curl_transport', true, $args );
384
	}
385
}
386