Completed
Push — master ( 68bd39...182d24 )
by adam
02:50
created

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
/*
4
This file is part of Peachy MediaWiki Bot API
5
6
Peachy is free software: you can redistribute it and/or modify
7
it under the terms of the GNU General Public License as published by
8
the Free Software Foundation, either version 3 of the License, or
9
(at your option) any later version.
10
11
This program is distributed in the hope that it will be useful,
12
but WITHOUT ANY WARRANTY; without even the implied warranty of
13
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
GNU General Public License for more details.
15
16
You should have received a copy of the GNU General Public License
17
along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
*/
19
20
/**
21
 * @file
22
 * HTTP object
23
 * Stores all cURL functions
24
 */
25
26
/**
27
 * HTTP Class, stores cURL functions
28
 */
29
class HTTP {
30
31
	/**
32
	 * Curl object
33
	 *
34
	 * @var resource a cURL handle
35
	 * @access private
36
	 */
37
	private $curl_instance;
38
39
	/**
40
	 * Hash to use for cookies
41
	 *
42
	 * @var string
43
	 * @access private
44
	 */
45
	private $cookie_hash;
46
47
	/**
48
	 * Whether or not to enable GET:, POST:, and DLOAD: messages being sent to the terminal.
49
	 *
50
	 * @var bool
51
	 * @access private
52
	 */
53
	private $echo;
54
55
	/**
56
	 * Useragent
57
	 *
58
	 * @var mixed
59
	 * @access private
60
	 */
61
	private $user_agent;
62
63
	/**
64
	 * Temporary file where cookies are stored
65
	 *
66
	 * @var mixed
67
	 * @access private
68
	 */
69
	private $cookie_jar;
70
71
	/**
72
	 * @var string|null
73
	 */
74
	private $lastHeader = null;
75
76
	/**
77
	 * Construction method for the HTTP class
78
	 *
79
	 * @access public
80
	 *
81
	 * @param bool $echo Whether or not to enable GET:, POST:, and DLOAD: messages being sent to the terminal. Default false;
82
	 *
83
	 * @note please consider using HTTP::getDefaultInstance() instead
84
	 *
85
	 * @throws RuntimeException
86
	 * @throws DependencyError
87
	 *
88
	 * @return HTTP
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
89
	 */
90
	public function __construct($echo = false)
91
	{
92
		if( !function_exists( 'curl_init' ) ) {
93
			throw new DependencyError( "cURL", "http://us2.php.net/manual/en/curl.requirements.php" );
0 ignored issues
show
'http://us2.php.net/manu.../curl.requirements.php' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
94
		}
95
96
		$this->echo = $echo;
97
		$this->curl_instance = curl_init();
98
		if( $this->curl_instance === false ) {
99
			throw new RuntimeException( 'Failed to initialize curl' );
100
		}
101
		$this->cookie_hash = md5( time() . '-' . rand( 0, 999 ) );
102
		$this->cookie_jar = sys_get_temp_dir() . 'peachy.cookies.' . $this->cookie_hash . '.dat';
103
104
		$userAgent = 'Peachy MediaWiki Bot API';
105
		if( defined( 'PEACHYVERSION' ) ) $userAgent .= ' Version ' . PEACHYVERSION;
106
		$this->setUserAgent( $userAgent );
107
108
		Hooks::runHook( 'HTTPNewCURLInstance', array( &$this, &$echo ) );
109
110
		$this->setCookieJar( $this->cookie_jar );
111
112
		curl_setopt( $this->curl_instance, CURLOPT_MAXCONNECTS, 100 );
113
		curl_setopt( $this->curl_instance, CURLOPT_CLOSEPOLICY, CURLCLOSEPOLICY_LEAST_RECENTLY_USED );
114
		curl_setopt( $this->curl_instance, CURLOPT_MAXREDIRS, 10 );
115
		$this->setCurlHeaders();
116
		curl_setopt( $this->curl_instance, CURLOPT_ENCODING, 'gzip' );
117
		curl_setopt( $this->curl_instance, CURLOPT_RETURNTRANSFER, 1 );
118
		curl_setopt( $this->curl_instance, CURLOPT_HEADER, 1 );
119
		curl_setopt( $this->curl_instance, CURLOPT_TIMEOUT, 10 );
120
		curl_setopt( $this->curl_instance, CURLOPT_CONNECTTIMEOUT, 10 );
121
122
		global $pgProxy;
123
		if( isset( $pgProxy ) && count( $pgProxy ) ) {
124
			curl_setopt( $this->curl_instance, CURLOPT_PROXY, $pgProxy['addr'] );
125
			if( isset( $pgProxy['type'] ) ) {
126
				curl_setopt( $this->curl_instance, CURLOPT_PROXYTYPE, $pgProxy['type'] );
127
			}
128
			if( isset( $pgProxy['userpass'] ) ) {
129
				curl_setopt( $this->curl_instance, CURLOPT_PROXYUSERPWD, $pgProxy['userpass'] );
130
			}
131
			if( isset( $pgProxy['port'] ) ) {
132
				curl_setopt( $this->curl_instance, CURLOPT_PROXYPORT, $pgProxy['port'] );
133
			}
134
		}
135
	}
136
137
	/**
138
	 * @param array $extraHeaders
139
     */
140
	private function setCurlHeaders( $extraHeaders = array() ) {
141
		curl_setopt( $this->curl_instance, CURLOPT_HTTPHEADER, array_merge( array( 'Expect:' ), $extraHeaders ) );
142
	}
143
144
	/**
145
	 * @param boolean $verifyssl
146
	 */
147
	private function setVerifySSL( $verifyssl = null ) {
148
		if( is_null( $verifyssl ) ) {
149
			global $verifyssl;
150
		}
151
		if( !$verifyssl ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $verifyssl of type null|boolean is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
152
			curl_setopt( $this->curl_instance, CURLOPT_SSL_VERIFYPEER, false );
153
			curl_setopt( $this->curl_instance, CURLOPT_SSL_VERIFYHOST, 0 );
154
		} else {
155
			curl_setopt( $this->curl_instance, CURLOPT_SSL_VERIFYPEER, true );
156
			//support for value of 1 will be removed in cURL 7.28.1
157
			curl_setopt( $this->curl_instance, CURLOPT_SSL_VERIFYHOST, 2 );
158
		}
159
	}
160
161
	/**
162
	 * @param string $cookie_file
163
	 */
164
	public function setCookieJar($cookie_file)
165
	{
166
		$this->cookie_jar = $cookie_file;
167
168
		Hooks::runHook( 'HTTPSetCookieJar', array( &$cookie_file ) );
169
170
		curl_setopt( $this->curl_instance, CURLOPT_COOKIEJAR, $cookie_file );
171
		curl_setopt( $this->curl_instance, CURLOPT_COOKIEFILE, $cookie_file );
172
	}
173
174
	/**
175
	 * @param null $user_agent
176
	 * @throws BadEntryError
177
	 * @throws HookError
178
     */
179
	public function setUserAgent($user_agent = null)
180
	{
181
		$this->user_agent = $user_agent;
182
183
		Hooks::runHook( 'HTTPSetUserAgent', array( &$user_agent ) );
184
185
		curl_setopt( $this->curl_instance, CURLOPT_USERAGENT, $user_agent );
186
	}
187
188
	/**
189
	 * @return string|bool Data. False on failure.
190
	 * @throws CURLError
191
	 */
192
	private function doCurlExecWithRetrys() {
193
		$data = false;
194
		for( $i = 0; $i <= 20; $i++ ){
195
			try{
196
				$response = curl_exec( $this->curl_instance );
197
				$header_size = curl_getinfo( $this->curl_instance, CURLINFO_HEADER_SIZE );
198
				$this->lastHeader = substr( $response, 0, $header_size );
199
				$data = substr( $response, $header_size );
200
			} catch( Exception $e ){
201
				if( curl_errno( $this->curl_instance ) != 0 ) {
202
					throw new CURLError( curl_errno( $this->curl_instance ), curl_error( $this->curl_instance ) );
203
				}
204
				if( $i == 20 ) {
205
					pecho( "Warning: A CURL error occurred.  Attempted 20 times.  Terminating attempts.", PECHO_WARN );
206
					return false;
207
				} else {
208
					pecho( "Warning: A CURL error occurred.  Details can be found in the PHP error log.  Retrying...", PECHO_WARN );
209
				}
210
				continue;
211
			}
212
			if( !is_null( $data ) && $data !== false ) {
213
				break;
214
			}
215
		}
216
		return $data;
217
	}
218
219
	/**
220
	 * Get an url with HTTP GET
221
	 *
222
	 * @access public
223
	 *
224
	 * @param string $url URL to get
225
	 * @param array|null $data Array of data to pass. Gets transformed into the URL inside the function. Default null.
226
	 * @param array $headers Array of headers to pass to curl
227
	 * @param bool $verifyssl override for the global verifyssl value
228
	 *
229
	 * @return bool|string Result
230
	 */
231
	public function get( $url, $data = null, $headers = array(), $verifyssl = null ) {
232
		global $argv, $displayGetOutData;
233
234
		if( is_string( $headers ) ) curl_setopt( $this->curl_instance, CURLOPT_HTTPHEADER, array( $headers ) );
235
		else $this->setCurlHeaders( $headers );
236
		$this->setVerifySSL( $verifyssl );
237
238
		curl_setopt( $this->curl_instance, CURLOPT_FOLLOWLOCATION, 1 );
239
		curl_setopt( $this->curl_instance, CURLOPT_HTTPGET, 1 );
240
		curl_setopt( $this->curl_instance, CURLOPT_POST, 0 );
241
242
		/*if( !is_null( $this->use_cookie ) ) {
243
			curl_setopt($this->curl_instance,CURLOPT_COOKIE, $this->use_cookie);
244
		}*/
245
246
		if( !is_null( $data ) && is_array( $data ) && !empty( $data ) ) {
247
			$url .= '?' . http_build_query( $data );
248
		}
249
250
		curl_setopt( $this->curl_instance, CURLOPT_URL, $url );
251
252
		if( ( !is_null( $argv ) && in_array( 'peachyecho', $argv ) ) || $this->echo ) {
253
			if( $displayGetOutData ) {
254
				pecho( "GET: $url\n", PECHO_NORMAL );
255
			}
256
		}
257
258
		Hooks::runHook( 'HTTPGet', array( &$this, &$url, &$data ) );
259
260
		return $this->doCurlExecWithRetrys();
261
262
	}
263
264
	/**
265
	 * Returns the HTTP code of the last request
266
	 *
267
	 * @access public
268
	 * @return int HTTP code
269
	 */
270
	public function get_HTTP_code()
271
	{
272
		$ci = curl_getinfo( $this->curl_instance );
273
		return $ci['http_code'];
274
	}
275
276
	/**
277
	 * Sends data via HTTP POST
278
	 *
279
	 * @access public
280
	 *
281
	 * @param string $url URL to send
282
	 * @param array $data Array of data to pass.
283
	 * @param array $headers Array of headers to pass to curl
284
	 *
285
	 * @param bool|null $verifyssl override for global verifyssl value
286
	 *
287
	 * @return bool|string Result
288
	 */
289
	public function post($url, $data, $headers = array(), $verifyssl = null)
290
	{
291
		global $argv, $displayPostOutData;
292
293
		if( is_string( $headers ) ) curl_setopt( $this->curl_instance, CURLOPT_HTTPHEADER, array( $headers ) );
294
		else $this->setCurlHeaders( $headers );
295
		$this->setVerifySSL( $verifyssl );
296
297
		curl_setopt( $this->curl_instance, CURLOPT_FOLLOWLOCATION, 0 );
298
		curl_setopt( $this->curl_instance, CURLOPT_HTTPGET, 0 );
299
		curl_setopt( $this->curl_instance, CURLOPT_POST, 1 );
300
		curl_setopt( $this->curl_instance, CURLOPT_POSTFIELDS, $data );
301
302
		/*if( !is_null( $this->use_cookie ) ) {
303
			curl_setopt($this->curl_instance,CURLOPT_COOKIE, $this->use_cookie);
304
		}*/
305
306
		curl_setopt( $this->curl_instance, CURLOPT_URL, $url );
307
308
		if( ( !is_null( $argv ) && in_array( 'peachyecho', $argv ) ) || $this->echo ) {
309
			if( $displayPostOutData ) {
310
				pecho( "POST: $url\n", PECHO_NORMAL );
311
			}
312
		}
313
314
		Hooks::runHook( 'HTTPPost', array( &$this, &$url, &$data ) );
315
316
		return $this->doCurlExecWithRetrys();
317
	}
318
319
	/**
320
	 * Downloads an URL to the local disk
321
	 *
322
	 * @access public
323
	 *
324
	 * @param string $url URL to get
325
	 * @param string $local Local filename to download to
326
	 * @param array $headers Array of headers to pass to curl
327
	 * @param bool|null $verifyssl
328
	 *
329
	 * @return bool
330
	 */
331
	function download( $url, $local, $headers = array(), $verifyssl = null ) {
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
Comprehensibility Best Practice introduced by
It is recommend to declare an explicit visibility for download.

Generally, we recommend to declare visibility for all methods in your source code. This has the advantage of clearly communication to other developers, and also yourself, how this method should be consumed.

If you are not sure which visibility to choose, it is a good idea to start with the most restrictive visibility, and then raise visibility as needed, i.e. start with private, and only raise it to protected if a sub-class needs to have access, or public if an external class needs access.

Loading history...
332
		global $argv;
333
334
		$out = fopen( $local, 'wb' );
335
336
		if( is_string( $headers ) ) curl_setopt( $this->curl_instance, CURLOPT_HTTPHEADER, array( $headers ) );
337
		else $this->setCurlHeaders( $headers );
338
		$this->setVerifySSL( $verifyssl );
339
340
		// curl_setopt($this->curl_instance, CURLOPT_FILE, $out);
341
		curl_setopt( $this->curl_instance, CURLOPT_HTTPGET, 1 );
342
		curl_setopt( $this->curl_instance, CURLOPT_POST, 0 );
343
		curl_setopt( $this->curl_instance, CURLOPT_URL, $url );
344
		curl_setopt( $this->curl_instance, CURLOPT_HEADER, 0 );
345
346
		if( ( !is_null( $argv ) && in_array( 'peachyecho', $argv ) ) || $this->echo ) {
347
			pecho( "DLOAD: $url\n", PECHO_NORMAL );
348
		}
349
350
		Hooks::runHook( 'HTTPDownload', array( &$this, &$url, &$local ) );
351
352
		fwrite( $out, $this->doCurlExecWithRetrys() );
353
		fclose( $out );
354
355
		return true;
356
357
	}
358
359
	/**
360
	 * Gets the Header for the last request made
361
	 * @return null|string
362
	 */
363
	public function getLastHeader() {
364
		return $this->lastHeader;
365
	}
366
367
	/**
368
	 * Destructor, deletes cookies and closes cURL class
369
	 *
370
	 * @access public
371
	 * @return void
372
	 */
373
	public function __destruct()
374
	{
375
		Hooks::runHook( 'HTTPClose', array( &$this ) );
376
377
		curl_close( $this->curl_instance );
378
379
		//@unlink($this->cookie_jar);
380
	}
381
382
	/**
383
	 * The below allows us to only have one instance of this class
384
	 */
385
	private static $defaultInstance = null;
386
	private static $defaultInstanceWithEcho = null;
387
388
	/**
389
	 * @param bool|false $echo
390
	 * @return HTTP|null
391
     */
392
	public static function getDefaultInstance($echo = false)
393
	{
394
		if( $echo ) {
395
			if( is_null( self::$defaultInstanceWithEcho ) ) {
396
				self::$defaultInstanceWithEcho = new Http( $echo );
397
			}
398
			return self::$defaultInstanceWithEcho;
399
		} else {
400
			if( is_null( self::$defaultInstance ) ) {
401
				self::$defaultInstance = new Http( $echo );
402
			}
403
			return self::$defaultInstance;
404
		}
405
	}
406
407
}