21use Psr\Log\LoggerInterface;
22use Psr\Log\LoggerAwareInterface;
23use Psr\Log\NullLogger;
99 $url, array $options = [], $caller = __METHOD__,
Profiler $profiler =
null
106 $this->logger = $options[
'logger'] ??
new NullLogger();
109 $this->status = StatusValue::newFatal(
'http-invalid-url',
$url );
111 $this->status = StatusValue::newGood( 100 );
114 if ( isset( $options[
'timeout'] ) && $options[
'timeout'] !=
'default' ) {
115 $this->timeout = $options[
'timeout'];
119 if ( isset( $options[
'connectTimeout'] ) && $options[
'connectTimeout'] !=
'default' ) {
120 $this->connectTimeout = $options[
'connectTimeout'];
124 if ( isset( $options[
'userAgent'] ) ) {
127 if ( isset( $options[
'username'] ) && isset( $options[
'password'] ) ) {
130 'Basic ' . base64_encode( $options[
'username'] .
':' . $options[
'password'] )
133 if ( isset( $options[
'originalRequest'] ) ) {
137 $this->
setHeader(
'X-Request-Id', WebRequest::getRequestId() );
139 $members = [
"postData",
"proxy",
"noProxy",
"sslVerifyHost",
"caInfo",
140 "method",
"followRedirects",
"maxRedirects",
"sslVerifyCert",
"callback" ];
142 foreach ( $members as $o ) {
143 if ( isset( $options[$o] ) ) {
146 if ( $o ==
'method' ) {
148 $options[$o] = strtoupper( $options[$o] );
150 $this->$o = $options[$o];
154 if ( $this->noProxy ) {
160 $this->profileName = $caller;
167 $this->logger = $logger;
176 return function_exists(
'curl_init' ) ||
wfIniGetBool(
'allow_url_fopen' );
189 public static function factory( $url, array $options =
null, $caller = __METHOD__ ) {
190 if ( $options ===
null ) {
193 return \MediaWiki\MediaWikiServices::getInstance()
194 ->getHttpRequestFactory()
195 ->create(
$url, $options, $caller );
214 $this->postData =
$args;
224 if ( $this->proxy && !$this->noProxy ) {
230 if ( self::isLocalURL( $this->url ) || $this->noProxy ) {
253 if ( preg_match(
'!^https?://([\w.-]+)[/:].*$!',
$url,
$matches ) ) {
256 $domainParts = explode(
'.', $host );
258 $domainParts = array_reverse( $domainParts );
261 $countParts = count( $domainParts );
262 for ( $i = 0; $i < $countParts; $i++ ) {
263 $domainPart = $domainParts[$i];
265 $domain = $domainPart;
267 $domain = $domainPart .
'.' . $domain;
294 $this->reqHeaders[$name] = $value;
304 if ( $this->cookieJar ) {
305 $this->reqHeaders[
'Cookie'] =
306 $this->cookieJar->serializeToHttpRequest(
307 $this->parsedUrl[
'path'],
308 $this->parsedUrl[
'host']
312 foreach ( $this->reqHeaders as $name => $value ) {
313 $list[] =
"$name: $value";
352 $this->status->fatal(
'http-internal-error' );
353 throw new InvalidArgumentException( __METHOD__ .
': invalid callback' );
379 throw new LogicException(
'children must override this' );
385 if ( strtoupper( $this->method ) ==
"HEAD" ) {
386 $this->headersOnly =
true;
391 if ( !$this->callback ) {
395 if ( !isset( $this->reqHeaders[
'User-Agent'] ) ) {
409 if ( !$this->status->isOK() ) {
410 $this->respStatus =
'0 Error';
413 foreach ( $this->headerList as
$header ) {
414 if ( preg_match(
"#^HTTP/([0-9.]+) (.*)#",
$header, $match ) ) {
415 $this->respVersion = $match[1];
416 $this->respStatus = $match[2];
417 } elseif ( preg_match(
"#^[ \t]#",
$header ) ) {
418 $last = count( $this->respHeaders[$lastname] ) - 1;
419 $this->respHeaders[$lastname][
$last] .=
"\r\n$header";
420 } elseif ( preg_match(
"#^([^:]*):[\t ]*(.*)#",
$header, $match ) ) {
421 $this->respHeaders[strtolower( $match[1] )][] = $match[2];
422 $lastname = strtolower( $match[1] );
437 if ( !$this->respHeaders ) {
441 if ( ( (
int)$this->respStatus > 0 && (
int)$this->respStatus < 400 ) ) {
442 $this->status->setResult(
true, (
int)$this->respStatus );
444 list( $code, $message ) = explode(
" ", $this->respStatus, 2 );
445 $this->status->setResult(
false, (
int)$this->respStatus );
446 $this->status->fatal(
"http-bad-status", $code, $message );
458 if ( !$this->respHeaders ) {
462 return (
int)$this->respStatus;
471 if ( !$this->respHeaders ) {
475 $status = (int)$this->respStatus;
494 if ( !$this->respHeaders ) {
498 return $this->respHeaders;
508 if ( !$this->respHeaders ) {
512 if ( isset( $this->respHeaders[strtolower(
$header )] ) ) {
513 $v = $this->respHeaders[strtolower(
$header )];
514 return $v[count( $v ) - 1];
528 $this->cookieJar = $jar;
537 if ( !$this->respHeaders ) {
541 return $this->cookieJar;
553 public function setCookie( $name, $value, array $attr = [] ) {
554 if ( !$this->cookieJar ) {
558 if ( $this->parsedUrl && !isset( $attr[
'domain'] ) ) {
559 $attr[
'domain'] = $this->parsedUrl[
'host'];
562 $this->cookieJar->
setCookie( $name, $value, $attr );
569 if ( !$this->cookieJar ) {
573 if ( isset( $this->respHeaders[
'set-cookie'] ) ) {
575 foreach ( $this->respHeaders[
'set-cookie'] as $cookie ) {
576 $this->cookieJar->parseCookieResponseHeader( $cookie,
$url[
'host'] );
601 if ( isset( $headers[
'location'] ) ) {
602 $locations = $headers[
'location'];
604 $foundRelativeURI =
false;
605 $countLocations = count( $locations );
607 for ( $i = $countLocations - 1; $i >= 0; $i-- ) {
608 $url = parse_url( $locations[$i] );
610 if ( isset(
$url[
'host'] ) ) {
611 $domain =
$url[
'scheme'] .
'://' .
$url[
'host'];
614 $foundRelativeURI =
true;
618 if ( !$foundRelativeURI ) {
619 return $locations[$countLocations - 1];
622 return $domain . $locations[$countLocations - 1];
624 $url = parse_url( $this->url );
625 if ( isset(
$url[
'host'] ) ) {
626 return $url[
'scheme'] .
'://' .
$url[
'host'] .
627 $locations[$countLocations - 1];
656 if ( $originalRequest instanceof
WebRequest ) {
658 'ip' => $originalRequest->getIP(),
659 'userAgent' => $originalRequest->getHeader(
'User-Agent' ),
662 !is_array( $originalRequest )
663 || array_diff( [
'ip',
'userAgent' ], array_keys( $originalRequest ) )
665 throw new InvalidArgumentException( __METHOD__ .
': $originalRequest must be a '
666 .
"WebRequest or an array with 'ip' and 'userAgent' keys" );
669 $this->reqHeaders[
'X-Forwarded-For'] = $originalRequest[
'ip'];
670 $this->reqHeaders[
'X-Original-User-Agent'] = $originalRequest[
'userAgent'];
690 return (
bool)preg_match(
691 '/^https?:\/\/[^\/\s]\S*$/D',
int $wgHTTPTimeout
Timeout for HTTP requests done internally, in seconds.
$wgHTTPConnectTimeout
Timeout for connections done internally (in seconds) Only works for curl.
$wgHTTPProxy
Proxy to use for CURL requests.
$wgLocalVirtualHosts
Local virtual hosts.
global $wgCommandLineMode
wfParseUrl( $url)
parse_url() work-alike, but non-broken.
wfIniGetBool( $setting)
Safety wrapper around ini_get() for boolean settings.
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
Cookie jar to use with MWHttpRequest.
setCookie( $name, $value, $attr)
Set a cookie in the cookie jar.
static isValidURI( $uri)
Check that the given URI is a valid one.
static userAgent()
A standard user-agent we can use for external requests.
This wrapper class will call out to curl (if available) or fallback to regular PHP if necessary for h...
getContent()
Get the body, or content, of the response to the request.
setLogger(LoggerInterface $logger)
setCookie( $name, $value, array $attr=[])
Sets a cookie.
getResponseHeaders()
Returns an associative array of response headers after the request has been executed.
static isLocalURL( $url)
Check if the URL can be served by localhost.
doSetCallback( $callback)
Worker function for setting callbacks.
setHeader( $name, $value)
Set an arbitrary header.
getCookieJar()
Returns the cookie jar in use.
setOriginalRequest( $originalRequest)
Set information about the original request.
isRedirect()
Returns true if the last status code was a redirect.
read( $fh, $content)
A generic callback to read the body of the response from a remote server.
getFinalUrl()
Returns the final URL after all redirections.
setStatus()
Sets HTTPRequest status member to a fatal value with the error message if the returned integer value ...
setUserAgent( $UA)
Set the user agent.
parseHeader()
Parses the headers, including the HTTP status code and any Set-Cookie headers.
canFollowRedirects()
Returns true if the backend can follow redirects.
__construct( $url, array $options=[], $caller=__METHOD__, Profiler $profiler=null)
setCallback( $callback)
Set a read callback to accept data read from the HTTP request.
static canMakeRequests()
Simple function to test if we can make any sort of requests at all, using cURL or fopen()
static factory( $url, array $options=null, $caller=__METHOD__)
Generate a new request object.
static isValidURI( $uri)
Check that the given URI is a valid one.
getStatus()
Get the integer value of the HTTP status code (e.g.
const SUPPORTS_FILE_POSTS
execute()
Take care of whatever is necessary to perform the URI request.
getResponseHeader( $header)
Returns the value of the given response header.
proxySetup()
Take care of setting up the proxy (do nothing if "noProxy" is set)
setData(array $args)
Set the parameters of the request.
parseCookies()
Parse the cookies in the response headers and store them in the cookie jar.
getHeaderList()
Get an array of the headers.
setCookieJar(CookieJar $jar)
Tells the MWHttpRequest object to use this pre-loaded CookieJar.
Profiler base class that defines the interface and some trivial functionality.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...