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;
180 public function setLogger( LoggerInterface $logger ): void {
181 $this->logger = $logger;
190 return function_exists(
'curl_init' ) ||
wfIniGetBool(
'allow_url_fopen' );
199 return $this->content;
209 $this->postData = $args;
219 foreach ( $telemetry->getRequestHeaders() as
$header => $value ) {
220 $this->setHeader(
$header, $value );
230 $httpProxy = MediaWikiServices::getInstance()->getMainConfig()->get(
231 MainConfigNames::HTTPProxy );
232 $localHTTPProxy = MediaWikiServices::getInstance()->getMainConfig()->get(
233 MainConfigNames::LocalHTTPProxy );
235 if ( $this->noProxy ) {
241 if ( $this->proxy ) {
247 if ( self::isLocalURL( $this->url ) ) {
248 if ( $localHTTPProxy !==
false ) {
249 $this->setReverseProxy( $localHTTPProxy );
252 $this->proxy = (string)$httpProxy;
267 $parsedProxy = $this->urlUtils->parse( $proxy );
268 if ( $parsedProxy ===
null ) {
269 throw new InvalidArgumentException(
"Invalid reverseProxy configured: $proxy" );
272 $this->setHeader(
'Host', $this->parsedUrl[
'host'] );
274 $this->parsedUrl[
'scheme'] = $parsedProxy[
'scheme'];
275 $this->parsedUrl[
'host'] = $parsedProxy[
'host'];
276 if ( isset( $parsedProxy[
'port'] ) ) {
277 $this->parsedUrl[
'port'] = $parsedProxy[
'port'];
279 unset( $this->parsedUrl[
'port'] );
281 $this->url = UrlUtils::assemble( $this->parsedUrl );
283 $this->noProxy =
true;
292 private static function isLocalURL(
$url ) {
293 $localVirtualHosts = MediaWikiServices::getInstance()->getMainConfig()->get(
294 MainConfigNames::LocalVirtualHosts );
298 if ( preg_match(
'!^https?://([\w.-]+)[/:].*$!',
$url,
$matches ) ) {
301 $domainParts = explode(
'.', $host );
303 $domainParts = array_reverse( $domainParts );
306 $countParts = count( $domainParts );
307 for ( $i = 0; $i < $countParts; $i++ ) {
308 $domainPart = $domainParts[$i];
310 $domain = $domainPart;
312 $domain = $domainPart .
'.' . $domain;
315 if ( in_array( $domain, $localVirtualHosts ) ) {
328 $this->setHeader(
'User-Agent', $UA );
338 $this->reqHeaders[$name] = $value;
348 if ( $this->cookieJar ) {
349 $this->reqHeaders[
'Cookie'] =
350 $this->cookieJar->serializeToHttpRequest(
351 $this->parsedUrl[
'path'],
352 $this->parsedUrl[
'host']
356 foreach ( $this->reqHeaders as $name => $value ) {
357 $list[] =
"$name: $value";
382 $this->doSetCallback( $callback );
393 if ( $callback ===
null ) {
394 $callback = [ $this,
'read' ];
395 } elseif ( !is_callable( $callback ) ) {
396 $this->status->fatal(
'http-internal-error' );
397 throw new InvalidArgumentException( __METHOD__ .
': invalid callback' );
399 $this->callback = $callback;
411 public function read( $fh, $content ) {
412 $this->content .= $content;
413 return strlen( $content );
423 throw new LogicException(
'children must override this' );
429 if ( strtoupper( $this->method ) ==
"HEAD" ) {
430 $this->headersOnly =
true;
435 if ( !$this->callback ) {
436 $this->doSetCallback(
null );
439 if ( !isset( $this->reqHeaders[
'User-Agent'] ) ) {
440 $http = MediaWikiServices::getInstance()->getHttpRequestFactory();
441 $this->setUserAgent( $http->getUserAgent() );
454 if ( !$this->status->isOK() ) {
455 $this->respStatus =
'0 Error';
458 foreach ( $this->headerList as
$header ) {
459 if ( preg_match(
"#^HTTP/([0-9.]+) (.*)#",
$header, $match ) ) {
460 $this->respVersion = $match[1];
461 $this->respStatus = $match[2];
462 } elseif ( preg_match(
"#^[ \t]#",
$header ) ) {
463 $last = count( $this->respHeaders[$lastname] ) - 1;
464 $this->respHeaders[$lastname][$last] .=
"\r\n$header";
465 } elseif ( preg_match(
"#^([^:]*):[\t ]*(.*)#",
$header, $match ) ) {
466 $this->respHeaders[strtolower( $match[1] )][] = $match[2];
467 $lastname = strtolower( $match[1] );
471 $this->parseCookies();
482 if ( !$this->respHeaders ) {
483 $this->parseHeader();
486 if ( (
int)$this->respStatus > 0 && (
int)$this->respStatus < 400 ) {
487 $this->status->setResult(
true, (
int)$this->respStatus );
489 [ $code, $message ] = explode(
" ", $this->respStatus, 2 );
490 $this->status->setResult(
false, (
int)$this->respStatus );
491 $this->status->fatal(
"http-bad-status", $code, $message );
503 if ( !$this->respHeaders ) {
504 $this->parseHeader();
507 return (
int)$this->respStatus;
516 if ( !$this->respHeaders ) {
517 $this->parseHeader();
520 $status = (int)$this->respStatus;
522 if ( $status >= 300 && $status <= 303 ) {
539 if ( !$this->respHeaders ) {
540 $this->parseHeader();
543 return $this->respHeaders;
553 if ( !$this->respHeaders ) {
554 $this->parseHeader();
557 if ( isset( $this->respHeaders[strtolower(
$header )] ) ) {
558 $v = $this->respHeaders[strtolower(
$header )];
559 return $v[count( $v ) - 1];
571 $this->cookieJar = $jar;
580 if ( !$this->respHeaders ) {
581 $this->parseHeader();
584 return $this->cookieJar;
596 public function setCookie( $name, $value, array $attr = [] ) {
597 if ( !$this->cookieJar ) {
601 if ( $this->parsedUrl && !isset( $attr[
'domain'] ) ) {
602 $attr[
'domain'] = $this->parsedUrl[
'host'];
605 $this->cookieJar->
setCookie( $name, $value, $attr );
612 if ( !$this->cookieJar ) {
616 if ( isset( $this->respHeaders[
'set-cookie'] ) ) {
617 $url = parse_url( $this->getFinalUrl() );
618 if ( !isset(
$url[
'host'] ) ) {
619 $this->status->fatal(
'http-invalid-url', $this->getFinalUrl() );
621 foreach ( $this->respHeaders[
'set-cookie'] as $cookie ) {
622 $this->cookieJar->parseCookieResponseHeader( $cookie,
$url[
'host'] );
645 $headers = $this->getResponseHeaders();
648 if ( isset( $headers[
'location'] ) ) {
649 $locations = $headers[
'location'];
651 $foundRelativeURI =
false;
652 $countLocations = count( $locations );
654 for ( $i = $countLocations - 1; $i >= 0; $i-- ) {
655 $url = parse_url( $locations[$i] );
657 if ( isset(
$url[
'scheme'] ) && isset(
$url[
'host'] ) ) {
658 $domain =
$url[
'scheme'] .
'://' .
$url[
'host'];
661 $foundRelativeURI =
true;
665 if ( !$foundRelativeURI ) {
666 return $locations[$countLocations - 1];
669 return $domain . $locations[$countLocations - 1];
671 $url = parse_url( $this->url );
672 if ( isset(
$url[
'scheme'] ) && isset(
$url[
'host'] ) ) {
673 return $url[
'scheme'] .
'://' .
$url[
'host'] .
674 $locations[$countLocations - 1];
703 if ( $originalRequest instanceof
WebRequest ) {
705 'ip' => $originalRequest->getIP(),
706 'userAgent' => $originalRequest->getHeader(
'User-Agent' ),
709 !is_array( $originalRequest )
710 || array_diff( [
'ip',
'userAgent' ], array_keys( $originalRequest ) )
712 throw new InvalidArgumentException( __METHOD__ .
': $originalRequest must be a '
713 .
"WebRequest or an array with 'ip' and 'userAgent' keys" );
716 $this->reqHeaders[
'X-Forwarded-For'] = $originalRequest[
'ip'];
717 $this->reqHeaders[
'X-Original-User-Agent'] = $originalRequest[
'userAgent'];
737 return (
bool)preg_match(
738 '/^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.