Completed
Push — master ( a65bfc...74ce99 )
by Maximilian
04:53
created

HTTP::get()   D

Complexity

Conditions 9
Paths 12

Size

Total Lines 32
Code Lines 16

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 32
rs 4.909
cc 9
eloc 16
nc 12
nop 4
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
Documentation introduced by
'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
	private function setCurlHeaders( $extraHeaders = array() ) {
138
		curl_setopt( $this->curl_instance, CURLOPT_HTTPHEADER, array_merge( array( 'Expect:' ), $extraHeaders ) );
139
	}
140
141
	/**
142
	 * @param boolean $verifyssl
143
	 */
144
	private function setVerifySSL( $verifyssl = null ) {
145
		if( is_null( $verifyssl ) ) {
146
			global $verifyssl;
147
		}
148
		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...
149
			curl_setopt( $this->curl_instance, CURLOPT_SSL_VERIFYPEER, false );
150
			curl_setopt( $this->curl_instance, CURLOPT_SSL_VERIFYHOST, 0 );
151
		} else {
152
			curl_setopt( $this->curl_instance, CURLOPT_SSL_VERIFYPEER, true );
153
			//support for value of 1 will be removed in cURL 7.28.1
154
			curl_setopt( $this->curl_instance, CURLOPT_SSL_VERIFYHOST, 2 );
155
		}
156
	}
157
158
	/**
159
	 * @param string $cookie_file
160
	 */
161
	public function setCookieJar($cookie_file)
162
	{
163
		$this->cookie_jar = $cookie_file;
164
165
		Hooks::runHook( 'HTTPSetCookieJar', array( &$cookie_file ) );
166
167
		curl_setopt( $this->curl_instance, CURLOPT_COOKIEJAR, $cookie_file );
168
		curl_setopt( $this->curl_instance, CURLOPT_COOKIEFILE, $cookie_file );
169
	}
170
171
	public function setUserAgent($user_agent = null)
172
	{
173
		$this->user_agent = $user_agent;
174
175
		Hooks::runHook( 'HTTPSetUserAgent', array( &$user_agent ) );
176
177
		curl_setopt( $this->curl_instance, CURLOPT_USERAGENT, $user_agent );
178
	}
179
180
	/**
181
	 * @return string|bool Data. False on failure.
182
	 * @throws CURLError
183
	 */
184
	private function doCurlExecWithRetrys() {
185
		$data = false;
186
		for( $i = 0; $i <= 20; $i++ ){
187
			try{
188
				$response = curl_exec( $this->curl_instance );
189
				$header_size = curl_getinfo( $this->curl_instance, CURLINFO_HEADER_SIZE );
190
				$this->lastHeader = substr( $response, 0, $header_size );
191
				$data = substr( $response, $header_size );
192
			} catch( Exception $e ){
193
				if( curl_errno( $this->curl_instance ) != 0 ) {
194
					throw new CURLError( curl_errno( $this->curl_instance ), curl_error( $this->curl_instance ) );
195
				}
196
				if( $i == 20 ) {
197
					pecho( "Warning: A CURL error occurred.  Attempted 20 times.  Terminating attempts.", PECHO_WARN );
198
					return false;
199
				} else {
200
					pecho( "Warning: A CURL error occurred.  Details can be found in the PHP error log.  Retrying...", PECHO_WARN );
201
				}
202
				continue;
203
			}
204
			if( !is_null( $data ) && $data !== false ) {
205
				break;
206
			}
207
		}
208
		return $data;
209
	}
210
211
	/**
212
	 * Get an url with HTTP GET
213
	 *
214
	 * @access public
215
	 *
216
	 * @param string $url URL to get
217
	 * @param array|null $data Array of data to pass. Gets transformed into the URL inside the function. Default null.
218
	 * @param array $headers Array of headers to pass to curl
219
	 * @param bool $verifyssl override for the global verifyssl value
220
	 *
221
	 * @return bool|string Result
222
	 */
223
	public function get( $url, $data = null, $headers = array(), $verifyssl = null ) {
224
		global $argv, $displayGetOutData;
225
226
		if( is_string( $headers ) ) curl_setopt( $this->curl_instance, CURLOPT_HTTPHEADER, array( $headers ) );
227
		else $this->setCurlHeaders( $headers );
228
		$this->setVerifySSL( $verifyssl );
229
230
		curl_setopt( $this->curl_instance, CURLOPT_FOLLOWLOCATION, 1 );
231
		curl_setopt( $this->curl_instance, CURLOPT_HTTPGET, 1 );
232
		curl_setopt( $this->curl_instance, CURLOPT_POST, 0 );
233
234
		/*if( !is_null( $this->use_cookie ) ) {
235
			curl_setopt($this->curl_instance,CURLOPT_COOKIE, $this->use_cookie);
236
		}*/
237
238
		if( !is_null( $data ) && is_array( $data ) && !empty( $data ) ) {
239
			$url .= '?' . http_build_query( $data );
240
		}
241
242
		curl_setopt( $this->curl_instance, CURLOPT_URL, $url );
243
244
		if( ( !is_null( $argv ) && in_array( 'peachyecho', $argv ) ) || $this->echo ) {
245
			if( $displayGetOutData ) {
246
				pecho( "GET: $url\n", PECHO_NORMAL );
247
			}
248
		}
249
250
		Hooks::runHook( 'HTTPGet', array( &$this, &$url, &$data ) );
251
252
		return $this->doCurlExecWithRetrys();
253
254
	}
255
256
	/**
257
	 * Returns the HTTP code of the last request
258
	 *
259
	 * @access public
260
	 * @return int HTTP code
261
	 */
262
	public function get_HTTP_code()
263
	{
264
		$ci = curl_getinfo( $this->curl_instance );
265
		return $ci['http_code'];
266
	}
267
268
	/**
269
	 * Sends data via HTTP POST
270
	 *
271
	 * @access public
272
	 *
273
	 * @param string $url URL to send
274
	 * @param array $data Array of data to pass.
275
	 * @param array $headers Array of headers to pass to curl
276
	 *
277
	 * @param bool|null $verifyssl override for global verifyssl value
278
	 *
279
	 * @return bool|string Result
280
	 */
281
	public function post($url, $data, $headers = array(), $verifyssl = null)
282
	{
283
		global $argv, $displayPostOutData;
284
285
		if( is_string( $headers ) ) curl_setopt( $this->curl_instance, CURLOPT_HTTPHEADER, array( $headers ) );
286
		else $this->setCurlHeaders( $headers );
287
		$this->setVerifySSL( $verifyssl );
288
289
		curl_setopt( $this->curl_instance, CURLOPT_FOLLOWLOCATION, 0 );
290
		curl_setopt( $this->curl_instance, CURLOPT_HTTPGET, 0 );
291
		curl_setopt( $this->curl_instance, CURLOPT_POST, 1 );
292
		curl_setopt( $this->curl_instance, CURLOPT_POSTFIELDS, $data );
293
294
		/*if( !is_null( $this->use_cookie ) ) {
295
			curl_setopt($this->curl_instance,CURLOPT_COOKIE, $this->use_cookie);
296
		}*/
297
298
		curl_setopt( $this->curl_instance, CURLOPT_URL, $url );
299
300
		if( ( !is_null( $argv ) && in_array( 'peachyecho', $argv ) ) || $this->echo ) {
301
			if( $displayPostOutData ) {
302
				pecho( "POST: $url\n", PECHO_NORMAL );
303
			}
304
		}
305
306
		Hooks::runHook( 'HTTPPost', array( &$this, &$url, &$data ) );
307
308
		return $this->doCurlExecWithRetrys();
309
	}
310
311
	/**
312
	 * Downloads an URL to the local disk
313
	 *
314
	 * @access public
315
	 *
316
	 * @param string $url URL to get
317
	 * @param string $local Local filename to download to
318
	 * @param array $headers Array of headers to pass to curl
319
	 * @param bool|null $verifyssl
320
	 *
321
	 * @return bool
322
	 */
323
	function download( $url, $local, $headers = array(), $verifyssl = null ) {
0 ignored issues
show
Best Practice introduced by
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...
324
		global $argv;
325
326
		$out = fopen( $local, 'wb' );
327
328
		if( is_string( $headers ) ) curl_setopt( $this->curl_instance, CURLOPT_HTTPHEADER, array( $headers ) );
329
		else $this->setCurlHeaders( $headers );
330
		$this->setVerifySSL( $verifyssl );
331
332
		// curl_setopt($this->curl_instance, CURLOPT_FILE, $out);
333
		curl_setopt( $this->curl_instance, CURLOPT_HTTPGET, 1 );
334
		curl_setopt( $this->curl_instance, CURLOPT_POST, 0 );
335
		curl_setopt( $this->curl_instance, CURLOPT_URL, $url );
336
		curl_setopt( $this->curl_instance, CURLOPT_HEADER, 0 );
337
338
		if( ( !is_null( $argv ) && in_array( 'peachyecho', $argv ) ) || $this->echo ) {
339
			pecho( "DLOAD: $url\n", PECHO_NORMAL );
340
		}
341
342
		Hooks::runHook( 'HTTPDownload', array( &$this, &$url, &$local ) );
343
344
		fwrite( $out, $this->doCurlExecWithRetrys() );
345
		fclose( $out );
346
347
		return true;
348
349
	}
350
351
	/**
352
	 * Gets the Header for the last request made
353
	 * @return null|string
354
	 */
355
	public function getLastHeader() {
356
		return $this->lastHeader;
357
	}
358
359
	/**
360
	 * Destructor, deletes cookies and closes cURL class
361
	 *
362
	 * @access public
363
	 * @return void
364
	 */
365
	public function __destruct()
366
	{
367
		Hooks::runHook( 'HTTPClose', array( &$this ) );
368
369
		curl_close( $this->curl_instance );
370
371
		//@unlink($this->cookie_jar);
372
	}
373
374
	/**
375
	 * The below allows us to only have one instance of this class
376
	 */
377
	private static $defaultInstance = null;
378
	private static $defaultInstanceWithEcho = null;
379
380
	public static function getDefaultInstance($echo = false)
381
	{
382
		if( $echo ) {
383
			if( is_null( self::$defaultInstanceWithEcho ) ) {
384
				self::$defaultInstanceWithEcho = new Http( $echo );
385
			}
386
			return self::$defaultInstanceWithEcho;
387
		} else {
388
			if( is_null( self::$defaultInstance ) ) {
389
				self::$defaultInstance = new Http( $echo );
390
			}
391
			return self::$defaultInstance;
392
		}
393
	}
394
395
}