26use Psr\Log\LoggerAwareInterface;
27use Psr\Log\LoggerInterface;
28use Psr\Log\NullLogger;
123 $url, array $options, $caller = __METHOD__, ?
Profiler $profiler =
null
125 $this->urlUtils = MediaWikiServices::getInstance()->getUrlUtils();
126 if ( !array_key_exists(
'timeout', $options )
127 || !array_key_exists(
'connectTimeout', $options ) ) {
128 throw new InvalidArgumentException(
"timeout and connectionTimeout options are required" );
130 $this->url = $this->urlUtils->expand(
$url,
PROTO_HTTP ) ??
false;
131 $this->parsedUrl = $this->urlUtils->parse( (
string)$this->url ) ??
false;
133 $this->logger = $options[
'logger'] ??
new NullLogger();
134 $this->timeout = $options[
'timeout'];
135 $this->connectTimeout = $options[
'connectTimeout'];
137 if ( !$this->parsedUrl || !self::isValidURI( $this->url ) ) {
138 $this->status = StatusValue::newFatal(
'http-invalid-url',
$url );
140 $this->status = StatusValue::newGood( 100 );
143 if ( isset( $options[
'userAgent'] ) ) {
146 if ( isset( $options[
'username'] ) && isset( $options[
'password'] ) ) {
149 'Basic ' . base64_encode( $options[
'username'] .
':' . $options[
'password'] )
152 if ( isset( $options[
'originalRequest'] ) ) {
156 $members = [
"postData",
"proxy",
"noProxy",
"sslVerifyHost",
"caInfo",
157 "method",
"followRedirects",
"maxRedirects",
"sslVerifyCert",
"callback" ];
159 foreach ( $members as $o ) {
160 if ( isset( $options[$o] ) ) {
163 if ( $o ==
'method' ) {
165 $options[$o] = strtoupper( $options[$o] );
167 $this->$o = $options[$o];
171 if ( $this->noProxy ) {
177 $this->profileName = $caller;
184 $this->logger = $logger;
193 return function_exists(
'curl_init' ) ||
wfIniGetBool(
'allow_url_fopen' );
202 return $this->content;
212 $this->postData = $args;
222 foreach ( $telemetry->getRequestHeaders() as
$header => $value ) {
233 $httpProxy = MediaWikiServices::getInstance()->getMainConfig()->get(
234 MainConfigNames::HTTPProxy );
235 $localHTTPProxy = MediaWikiServices::getInstance()->getMainConfig()->get(
236 MainConfigNames::LocalHTTPProxy );
238 if ( $this->noProxy ) {
244 if ( $this->proxy ) {
250 if ( self::isLocalURL( $this->url ) ) {
251 if ( $localHTTPProxy !==
false ) {
252 $this->setReverseProxy( $localHTTPProxy );
255 $this->proxy = (string)$httpProxy;
270 $parsedProxy = $this->urlUtils->parse( $proxy );
271 if ( $parsedProxy ===
null ) {
272 throw new InvalidArgumentException(
"Invalid reverseProxy configured: $proxy" );
275 $this->setHeader(
'Host', $this->parsedUrl[
'host'] );
277 $this->parsedUrl[
'scheme'] = $parsedProxy[
'scheme'];
278 $this->parsedUrl[
'host'] = $parsedProxy[
'host'];
279 if ( isset( $parsedProxy[
'port'] ) ) {
280 $this->parsedUrl[
'port'] = $parsedProxy[
'port'];
282 unset( $this->parsedUrl[
'port'] );
284 $this->url = UrlUtils::assemble( $this->parsedUrl );
286 $this->noProxy =
true;
295 private static function isLocalURL(
$url ) {
299 $localVirtualHosts = MediaWikiServices::getInstance()->getMainConfig()->get(
300 MainConfigNames::LocalVirtualHosts );
304 if ( preg_match(
'!^https?://([\w.-]+)[/:].*$!',
$url,
$matches ) ) {
307 $domainParts = explode(
'.', $host );
309 $domainParts = array_reverse( $domainParts );
312 $countParts = count( $domainParts );
313 for ( $i = 0; $i < $countParts; $i++ ) {
314 $domainPart = $domainParts[$i];
316 $domain = $domainPart;
318 $domain = $domainPart .
'.' . $domain;
321 if ( in_array( $domain, $localVirtualHosts ) ) {
334 $this->setHeader(
'User-Agent', $UA );
344 $this->reqHeaders[$name] = $value;
354 if ( $this->cookieJar ) {
355 $this->reqHeaders[
'Cookie'] =
356 $this->cookieJar->serializeToHttpRequest(
357 $this->parsedUrl[
'path'],
358 $this->parsedUrl[
'host']
362 foreach ( $this->reqHeaders as $name => $value ) {
363 $list[] =
"$name: $value";
388 $this->doSetCallback( $callback );
399 if ( $callback ===
null ) {
400 $callback = [ $this,
'read' ];
401 } elseif ( !is_callable( $callback ) ) {
402 $this->status->fatal(
'http-internal-error' );
403 throw new InvalidArgumentException( __METHOD__ .
': invalid callback' );
405 $this->callback = $callback;
417 public function read( $fh, $content ) {
418 $this->content .= $content;
419 return strlen( $content );
429 throw new LogicException(
'children must override this' );
435 if ( strtoupper( $this->method ) ==
"HEAD" ) {
436 $this->headersOnly =
true;
441 if ( !$this->callback ) {
442 $this->doSetCallback(
null );
445 if ( !isset( $this->reqHeaders[
'User-Agent'] ) ) {
446 $http = MediaWikiServices::getInstance()->getHttpRequestFactory();
447 $this->setUserAgent( $http->getUserAgent() );
460 if ( !$this->status->isOK() ) {
461 $this->respStatus =
'0 Error';
464 foreach ( $this->headerList as
$header ) {
465 if ( preg_match(
"#^HTTP/([0-9.]+) (.*)#",
$header, $match ) ) {
466 $this->respVersion = $match[1];
467 $this->respStatus = $match[2];
468 } elseif ( preg_match(
"#^[ \t]#",
$header ) ) {
469 $last = count( $this->respHeaders[$lastname] ) - 1;
470 $this->respHeaders[$lastname][$last] .=
"\r\n$header";
471 } elseif ( preg_match(
"#^([^:]*):[\t ]*(.*)#",
$header, $match ) ) {
472 $this->respHeaders[strtolower( $match[1] )][] = $match[2];
473 $lastname = strtolower( $match[1] );
477 $this->parseCookies();
488 if ( !$this->respHeaders ) {
489 $this->parseHeader();
492 if ( (
int)$this->respStatus > 0 && (
int)$this->respStatus < 400 ) {
493 $this->status->setResult(
true, (
int)$this->respStatus );
495 [ $code, $message ] = explode(
" ", $this->respStatus, 2 );
496 $this->status->setResult(
false, (
int)$this->respStatus );
497 $this->status->fatal(
"http-bad-status", $code, $message );
509 if ( !$this->respHeaders ) {
510 $this->parseHeader();
513 return (
int)$this->respStatus;
522 if ( !$this->respHeaders ) {
523 $this->parseHeader();
526 $status = (int)$this->respStatus;
528 if ( $status >= 300 && $status <= 303 ) {
545 if ( !$this->respHeaders ) {
546 $this->parseHeader();
549 return $this->respHeaders;
559 if ( !$this->respHeaders ) {
560 $this->parseHeader();
563 if ( isset( $this->respHeaders[strtolower(
$header )] ) ) {
564 $v = $this->respHeaders[strtolower(
$header )];
565 return $v[count( $v ) - 1];
579 $this->cookieJar = $jar;
588 if ( !$this->respHeaders ) {
589 $this->parseHeader();
592 return $this->cookieJar;
604 public function setCookie( $name, $value, array $attr = [] ) {
605 if ( !$this->cookieJar ) {
609 if ( $this->parsedUrl && !isset( $attr[
'domain'] ) ) {
610 $attr[
'domain'] = $this->parsedUrl[
'host'];
613 $this->cookieJar->
setCookie( $name, $value, $attr );
620 if ( !$this->cookieJar ) {
624 if ( isset( $this->respHeaders[
'set-cookie'] ) ) {
625 $url = parse_url( $this->getFinalUrl() );
626 if ( !isset(
$url[
'host'] ) ) {
627 $this->status->fatal(
'http-invalid-url',
$url );
629 foreach ( $this->respHeaders[
'set-cookie'] as $cookie ) {
630 $this->cookieJar->parseCookieResponseHeader( $cookie,
$url[
'host'] );
653 $headers = $this->getResponseHeaders();
656 if ( isset( $headers[
'location'] ) ) {
657 $locations = $headers[
'location'];
659 $foundRelativeURI =
false;
660 $countLocations = count( $locations );
662 for ( $i = $countLocations - 1; $i >= 0; $i-- ) {
663 $url = parse_url( $locations[$i] );
665 if ( isset(
$url[
'scheme'] ) && isset(
$url[
'host'] ) ) {
666 $domain =
$url[
'scheme'] .
'://' .
$url[
'host'];
669 $foundRelativeURI =
true;
673 if ( !$foundRelativeURI ) {
674 return $locations[$countLocations - 1];
677 return $domain . $locations[$countLocations - 1];
679 $url = parse_url( $this->url );
680 if ( isset(
$url[
'scheme'] ) && isset(
$url[
'host'] ) ) {
681 return $url[
'scheme'] .
'://' .
$url[
'host'] .
682 $locations[$countLocations - 1];
711 if ( $originalRequest instanceof
WebRequest ) {
713 'ip' => $originalRequest->getIP(),
714 'userAgent' => $originalRequest->getHeader(
'User-Agent' ),
717 !is_array( $originalRequest )
718 || array_diff( [
'ip',
'userAgent' ], array_keys( $originalRequest ) )
720 throw new InvalidArgumentException( __METHOD__ .
': $originalRequest must be a '
721 .
"WebRequest or an array with 'ip' and 'userAgent' keys" );
724 $this->reqHeaders[
'X-Forwarded-For'] = $originalRequest[
'ip'];
725 $this->reqHeaders[
'X-Original-User-Agent'] = $originalRequest[
'userAgent'];
745 return (
bool)preg_match(
746 '/^https?:\/\/[^\/\s]\S*$/D',
wfIniGetBool( $setting)
Safety wrapper around ini_get() for boolean settings.
Cookie jar to use with MWHttpRequest.
setCookie( $name, $value, $attr)
Set a cookie in the cookie jar.
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.
doSetCallback( $callback)
Worker function for setting callbacks.
__construct( $url, array $options, $caller=__METHOD__, ?Profiler $profiler=null)
setHeader( $name, $value)
Set an arbitrary header.
getCookieJar()
Returns the cookie jar in use.
setReverseProxy(string $proxy)
Enable use of a reverse proxy in which the hostname is passed as a "Host" header, and the request is ...
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 ...
parseHeader()
Parses the headers, including the HTTP status code and any Set-Cookie headers.
canFollowRedirects()
Returns true if the backend can follow redirects.
setCallback( $callback)
Set a read callback to accept data read from the HTTP request.
addTelemetry(TelemetryHeadersInterface $telemetry)
Add Telemetry information to the request.
static canMakeRequests()
Simple function to test if we can make any sort of requests at all, using cURL or fopen()
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.
A class containing constants representing the names of configuration variables.
Profiler base class that defines the interface and some shared functionality.
Generic operation result class Has warning/error list, boolean status and arbitrary value.