b82de8d982bde0b5f7c7ac7d243f3e938cf49107
7 * Various HTTP related functions
12 * Perform an HTTP request
13 * @param $method string HTTP method. Usually GET/POST
14 * @param $url string Full URL to act on
15 * @param $opts options to pass to HttpRequest object
16 * @returns mixed (bool)false on failure or a string on success
18 public static function request( $method, $url, $opts = array() ) {
19 $opts['method'] = strtoupper( $method );
20 if ( !array_key_exists( 'timeout', $opts ) ) {
21 $opts['timeout'] = 'default';
23 $req = HttpRequest
::factory( $url, $opts );
24 $status = $req->execute();
25 if ( $status->isOK() ) {
33 * Simple wrapper for Http::request( 'GET' )
34 * @see Http::request()
36 public static function get( $url, $timeout = 'default', $opts = array() ) {
37 $opts['timeout'] = $timeout;
38 return Http
::request( 'GET', $url, $opts );
42 * Simple wrapper for Http::request( 'POST' )
43 * @see Http::request()
45 public static function post( $url, $opts = array() ) {
46 return Http
::request( 'POST', $url, $opts );
50 * Check if the URL can be served by localhost
51 * @param $url string Full url to check
54 public static function isLocalURL( $url ) {
55 global $wgCommandLineMode, $wgConf;
56 if ( $wgCommandLineMode ) {
62 if ( preg_match( '!^http://([\w.-]+)[/:].*$!', $url, $matches ) ) {
65 $domainParts = explode( '.', $host );
66 // Check if this domain or any superdomain is listed in $wgConf as a local virtual host
67 $domainParts = array_reverse( $domainParts );
68 for ( $i = 0; $i < count( $domainParts ); $i++
) {
69 $domainPart = $domainParts[$i];
71 $domain = $domainPart;
73 $domain = $domainPart . '.' . $domain;
75 if ( $wgConf->isLocalVHost( $domain ) ) {
84 * A standard user-agent we can use for external requests.
87 public static function userAgent() {
89 return "MediaWiki/$wgVersion";
93 * Checks that the given URI is a valid one
94 * @param $uri Mixed: URI to check for validity
97 public static function isValidURI( $uri ) {
99 '/(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/',
107 * This wrapper class will call out to curl (if available) or fallback
108 * to regular PHP if necessary for handling internal HTTP requests.
112 protected $timeout = 'default';
113 protected $headersOnly = null;
114 protected $postdata = null;
115 protected $proxy = null;
116 protected $no_proxy = false;
117 protected $sslVerifyHost = true;
118 protected $caInfo = null;
119 protected $method = "GET";
121 protected $parsed_url;
125 * @param $url string url to use
126 * @param $options array (optional) extra params to pass
127 * Possible keys for the array:
139 function __construct( $url = null, $opt = array() ) {
140 global $wgHTTPTimeout, $wgTitle;
143 $this->parsed_url
= parse_url( $url );
145 if ( !ini_get( 'allow_url_fopen' ) ) {
146 throw new MWException( 'allow_url_fopen needs to be enabled for http requests to work' );
147 } elseif ( !Http
::isValidURI( $this->url
) ) {
148 throw new MWException( 'Invalid URL' );
150 $this->status
= Status
::newGood( 100 ); // continue
153 if ( array_key_exists( 'timeout', $opt ) && $opt['timeout'] != 'default' ) {
154 $this->timeout
= $opt['timeout'];
156 $this->timeout
= $wgHTTPTimeout;
159 $members = array( "targetFilePath", "requestKey", "headersOnly", "postdata",
160 "proxy", "no_proxy", "sslVerifyHost", "caInfo", "method" );
161 foreach ( $members as $o ) {
162 if ( array_key_exists( $o, $opt ) ) {
163 $this->$o = $opt[$o];
167 if ( is_array( $this->postdata
) ) {
168 $this->postdata
= wfArrayToCGI( $this->postdata
);
171 $this->initRequest();
173 if ( !$this->no_proxy
) {
177 # Set the referer to $wgTitle, even in command-line mode
178 # This is useful for interwiki transclusion, where the foreign
179 # server wants to know what the referring page is.
180 # $_SERVER['REQUEST_URI'] gives a less reliable indication of the
182 if ( is_object( $wgTitle ) ) {
183 $this->setReferrer( $wgTitle->getFullURL() );
188 * For backwards compatibility, we provide a __toString method so
189 * that any code that expects a string result from Http::Get()
190 * will see the content of the request.
192 function __toString() {
193 return $this->content
;
197 * Generate a new request object
198 * @see HttpRequest::__construct
200 public static function factory( $url, $opt ) {
201 global $wgHTTPEngine;
202 $engine = $wgHTTPEngine;
204 if ( !$wgHTTPEngine ) {
205 $wgHTTPEngine = function_exists( 'curl_init' ) ?
'curl' : 'php';
206 } elseif ( $wgHTTPEngine == 'curl' && !function_exists( 'curl_init' ) ) {
207 throw new MWException( 'FIXME' );
210 switch( $wgHTTPEngine ) {
212 return new CurlHttpRequest( $url, $opt );
214 return new PhpHttpRequest( $url, $opt );
216 throw new MWException( 'The setting of $wgHTTPEngine is not valid.' );
220 public function getContent() {
221 return $this->content
;
224 public function initRequest() { }
225 public function proxySetup() { }
226 public function setReferrer( $url ) { }
227 public function setCallback( $cb ) { }
228 public function read( $fh, $content ) { }
229 public function getCode() { }
230 public function execute() { }
234 * HttpRequest implemented using internal curl compiled into PHP
236 class CurlHttpRequest
extends HttpRequest
{
237 protected $curlHandle;
238 protected $curlCBSet;
240 public function initRequest() {
241 $this->curlHandle
= curl_init( $this->url
);
244 public function proxySetup() {
247 if ( is_string( $this->proxy
) ) {
248 curl_setopt( $this->curlHandle
, CURLOPT_PROXY
, $this->proxy
);
249 } else if ( Http
::isLocalURL( $this->url
) ) { /* Not sure this makes any sense. */
250 curl_setopt( $this->curlHandle
, CURLOPT_PROXY
, 'localhost:80' );
251 } else if ( $wgHTTPProxy ) {
252 curl_setopt( $this->curlHandle
, CURLOPT_PROXY
, $wgHTTPProxy );
256 public function setCallback( $cb ) {
257 if ( !$this->curlCBSet
) {
258 $this->curlCBSet
= true;
259 curl_setopt( $this->curlHandle
, CURLOPT_WRITEFUNCTION
, $cb );
263 public function execute() {
264 if ( !$this->status
->isOK() ) {
265 return $this->status
;
268 $this->setCallback( array( $this, 'read' ) );
270 curl_setopt( $this->curlHandle
, CURLOPT_TIMEOUT
, $this->timeout
);
271 curl_setopt( $this->curlHandle
, CURLOPT_USERAGENT
, Http
::userAgent() );
272 curl_setopt( $this->curlHandle
, CURLOPT_HTTP_VERSION
, CURL_HTTP_VERSION_1_0
);
274 if ( $this->sslVerifyHost
) {
275 curl_setopt( $this->curlHandle
, CURLOPT_SSL_VERIFYHOST
, $this->sslVerifyHost
);
278 if ( $this->caInfo
) {
279 curl_setopt( $this->curlHandle
, CURLOPT_CAINFO
, $this->caInfo
);
282 if ( $this->headersOnly
) {
283 curl_setopt( $this->curlHandle
, CURLOPT_NOBODY
, true );
284 curl_setopt( $this->curlHandle
, CURLOPT_HEADER
, true );
285 } elseif ( $this->method
== 'POST' ) {
286 curl_setopt( $this->curlHandle
, CURLOPT_POST
, true );
287 curl_setopt( $this->curlHandle
, CURLOPT_POSTFIELDS
, $this->postdata
);
288 // Suppress 'Expect: 100-continue' header, as some servers
289 // will reject it with a 417 and Curl won't auto retry
290 // with HTTP 1.0 fallback
291 curl_setopt( $this->curlHandle
, CURLOPT_HTTPHEADER
, array( 'Expect:' ) );
293 curl_setopt( $this->curlHandle
, CURLOPT_CUSTOMREQUEST
, $this->method
);
297 if ( false === curl_exec( $this->curlHandle
) ) {
298 $this->status
->fatal( 'Error sending request (#$1): $2',
299 curl_errno( $this->curlHandle
),
300 curl_error( $this->curlHandle
) );
302 } catch ( Exception
$e ) {
303 $errno = curl_errno( $this->curlHandle
);
304 if ( $errno != CURLE_OK
) {
305 $errstr = curl_error( $this->curlHandle
);
306 $this->status
->fatal( 'CURL error code $1: $2', $errno, $errstr );
310 curl_close( $this->curlHandle
);
312 return $this->status
;
315 public function read( $curlH, $content ) {
316 $this->content
.= $content;
317 return strlen( $content );
320 public function getCode() {
321 # Don't return truncated output
322 $code = curl_getinfo( $this->curlHandle
, CURLINFO_HTTP_CODE
);
324 $this->status
->setResult( true, $code );
326 $this->status
->setResult( false, $code );
331 class PhpHttpRequest
extends HttpRequest
{
336 public function initRequest() {
337 $this->setCallback( array( $this, 'read' ) );
339 $this->reqHeaders
[] = "User-Agent: " . Http
::userAgent();
340 $this->reqHeaders
[] = "Accept: */*";
341 if ( $this->method
== 'POST' ) {
342 // Required for HTTP 1.0 POSTs
343 $this->reqHeaders
[] = "Content-Length: " . strlen( $this->postdata
);
344 $this->reqHeaders
[] = "Content-type: application/x-www-form-urlencoded";
347 if ( $this->parsed_url
['scheme'] != 'http' ) {
348 $this->status
->fatal( "Only http:// is supported currently." );
352 protected function urlToTcp( $url ) {
353 $parsed_url = parse_url( $url );
355 return 'tcp://' . $parsed_url['host'] . ':' . $parsed_url['port'];
358 public function proxySetup() {
361 if ( Http
::isLocalURL( $this->url
) ) {
362 $this->proxy
= 'http://localhost:80/';
363 } elseif ( $wgHTTPProxy ) {
364 $this->proxy
= $wgHTTPProxy ;
368 public function setReferrer( $url ) {
369 $this->reqHeaders
[] = "Referer: $url";
372 public function setCallback( $cb ) {
373 $this->callback
= $cb;
376 public function read( $fh, $contents ) {
377 if ( $this->headersOnly
) {
380 $this->content
.= $contents;
382 return strlen( $contents );
385 public function execute() {
386 if ( !$this->status
->isOK() ) {
387 return $this->status
;
391 if ( $this->proxy
&& !$this->no_proxy
) {
392 $opts['proxy'] = $this->urlToTCP( $this->proxy
);
393 $opts['request_fulluri'] = true;
396 $opts['method'] = $this->method
;
397 $opts['timeout'] = $this->timeout
;
398 $opts['header'] = implode( "\r\n", $this->reqHeaders
);
399 // FOR NOW: Force everyone to HTTP 1.0
400 /* if ( version_compare( "5.3.0", phpversion(), ">" ) ) { */
401 $opts['protocol_version'] = "1.0";
403 /* $opts['protocol_version'] = "1.1"; */
406 if ( $this->postdata
) {
407 $opts['content'] = $this->postdata
;
410 $context = stream_context_create( array( 'http' => $opts ) );
412 $this->fh
= fopen( $this->url
, "r", false, $context );
413 } catch ( Exception
$e ) {
414 $this->status
->fatal( $e->getMessage() );
415 return $this->status
;
418 $result = stream_get_meta_data( $this->fh
);
419 if ( $result['timed_out'] ) {
420 $this->status
->error( 'The request timed out' );
423 $this->headers
= $result['wrapper_data'];
427 $contents = fread( $this->fh
, 8192 );
428 $size = call_user_func_array( $this->callback
, array( $this->fh
, $contents ) );
429 $end = ( $size == 0 ) ||
feof( $this->fh
);
433 return $this->status
;