25 use Psr\Log\LoggerAwareInterface;
26 use Psr\Log\LoggerInterface;
27 use Psr\Log\NullLogger;
108 $this->logger = $options[
'logger'] ??
new NullLogger();
110 if ( !$this->parsedUrl || !self::isValidURI( $this->url ) ) {
116 if ( isset( $options[
'timeout'] ) && $options[
'timeout'] !=
'default' ) {
117 $this->timeout = $options[
'timeout'];
121 wfDeprecated( __METHOD__ .
' without the timeout option',
'1.35' );
122 $httpTimeout = MediaWikiServices::getInstance()->getMainConfig()->get(
123 MainConfigNames::HTTPTimeout );
124 $this->timeout = $httpTimeout;
126 if ( isset( $options[
'connectTimeout'] ) && $options[
'connectTimeout'] !=
'default' ) {
127 $this->connectTimeout = $options[
'connectTimeout'];
131 wfDeprecated( __METHOD__ .
' without the connectTimeout option',
'1.35' );
132 $httpConnectTimeout = MediaWikiServices::getInstance()->getMainConfig()->get(
133 MainConfigNames::HTTPConnectTimeout );
134 $this->connectTimeout = $httpConnectTimeout;
136 if ( isset( $options[
'userAgent'] ) ) {
139 if ( isset( $options[
'username'] ) && isset( $options[
'password'] ) ) {
142 'Basic ' . base64_encode( $options[
'username'] .
':' . $options[
'password'] )
145 if ( isset( $options[
'originalRequest'] ) ) {
149 $members = [
"postData",
"proxy",
"noProxy",
"sslVerifyHost",
"caInfo",
150 "method",
"followRedirects",
"maxRedirects",
"sslVerifyCert",
"callback" ];
152 foreach ( $members as $o ) {
153 if ( isset( $options[$o] ) ) {
156 if ( $o ==
'method' ) {
158 $options[$o] = strtoupper( $options[$o] );
160 $this->$o = $options[$o];
164 if ( $this->noProxy ) {
170 $this->profileName = $caller;
186 return function_exists(
'curl_init' ) ||
wfIniGetBool(
'allow_url_fopen' );
205 $this->postData = $args;
215 foreach ( $telemetry->getRequestHeaders() as
$header => $value ) {
226 $httpProxy = MediaWikiServices::getInstance()->getMainConfig()->get(
227 MainConfigNames::HTTPProxy );
228 $localHTTPProxy = MediaWikiServices::getInstance()->getMainConfig()->get(
229 MainConfigNames::LocalHTTPProxy );
231 if ( $this->noProxy ) {
237 if ( $this->proxy ) {
243 if ( self::isLocalURL( $this->url ) ) {
244 if ( $localHTTPProxy !==
false ) {
245 $this->setReverseProxy( $localHTTPProxy );
248 $this->proxy = (string)$httpProxy;
264 if ( $parsedProxy ===
false ) {
265 throw new Exception(
"Invalid reverseProxy configured: $proxy" );
268 $this->setHeader(
'Host', $this->parsedUrl[
'host'] );
270 $this->parsedUrl[
'scheme'] = $parsedProxy[
'scheme'];
271 $this->parsedUrl[
'host'] = $parsedProxy[
'host'];
272 if ( isset( $parsedProxy[
'port'] ) ) {
273 $this->parsedUrl[
'port'] = $parsedProxy[
'port'];
275 unset( $this->parsedUrl[
'port'] );
279 $this->noProxy =
true;
288 private static function isLocalURL( $url ) {
289 $commandLineMode = MediaWikiServices::getInstance()->getMainConfig()->get(
'CommandLineMode' );
290 $localVirtualHosts = MediaWikiServices::getInstance()->getMainConfig()->get(
291 MainConfigNames::LocalVirtualHosts );
292 if ( $commandLineMode ) {
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;
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];
573 $this->cookieJar = $jar;
582 if ( !$this->respHeaders ) {
583 $this->parseHeader();
586 return $this->cookieJar;
598 public function setCookie( $name, $value, array $attr = [] ) {
599 if ( !$this->cookieJar ) {
603 if ( $this->parsedUrl && !isset( $attr[
'domain'] ) ) {
604 $attr[
'domain'] = $this->parsedUrl[
'host'];
607 $this->cookieJar->
setCookie( $name, $value, $attr );
614 if ( !$this->cookieJar ) {
618 if ( isset( $this->respHeaders[
'set-cookie'] ) ) {
619 $url = parse_url( $this->getFinalUrl() );
620 if ( !isset( $url[
'host'] ) ) {
621 $this->status->fatal(
'http-invalid-url', $url );
623 foreach ( $this->respHeaders[
'set-cookie'] as $cookie ) {
624 $this->cookieJar->parseCookieResponseHeader( $cookie, $url[
'host'] );
647 $headers = $this->getResponseHeaders();
650 if ( isset( $headers[
'location'] ) ) {
651 $locations = $headers[
'location'];
653 $foundRelativeURI =
false;
654 $countLocations = count( $locations );
656 for ( $i = $countLocations - 1; $i >= 0; $i-- ) {
657 $url = parse_url( $locations[$i] );
659 if ( isset( $url[
'scheme'] ) && isset( $url[
'host'] ) ) {
660 $domain = $url[
'scheme'] .
'://' . $url[
'host'];
663 $foundRelativeURI =
true;
667 if ( !$foundRelativeURI ) {
668 return $locations[$countLocations - 1];
671 return $domain . $locations[$countLocations - 1];
673 $url = parse_url( $this->url );
674 if ( isset( $url[
'scheme'] ) && isset( $url[
'host'] ) ) {
675 return $url[
'scheme'] .
'://' . $url[
'host'] .
676 $locations[$countLocations - 1];
705 if ( $originalRequest instanceof
WebRequest ) {
707 'ip' => $originalRequest->getIP(),
708 'userAgent' => $originalRequest->getHeader(
'User-Agent' ),
711 !is_array( $originalRequest )
712 || array_diff( [
'ip',
'userAgent' ], array_keys( $originalRequest ) )
714 throw new InvalidArgumentException( __METHOD__ .
': $originalRequest must be a '
715 .
"WebRequest or an array with 'ip' and 'userAgent' keys" );
718 $this->reqHeaders[
'X-Forwarded-For'] = $originalRequest[
'ip'];
719 $this->reqHeaders[
'X-Original-User-Agent'] = $originalRequest[
'userAgent'];
739 return (
bool)preg_match(
740 '/^https?:\/\/[^\/\s]\S*$/D',
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 using $wgServer (or one of its alternatives).
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
wfAssembleUrl( $urlParts)
This function will reassemble a URL parsed with wfParseURL.
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.
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.
__construct( $url, array $options=[], $caller=__METHOD__, Profiler $profiler=null)
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.
static newFatal( $message,... $parameters)
Factory function for fatal errors.
static newGood( $value=null)
Factory function for good results.