25use Psr\Log\LoggerAwareInterface;
26use Psr\Log\LoggerInterface;
27use Psr\Log\NullLogger;
103 $url, array $options = [], $caller = __METHOD__,
Profiler $profiler =
null
108 $this->logger = $options[
'logger'] ??
new NullLogger();
110 if ( !$this->parsedUrl || !self::isValidURI( $this->url ) ) {
111 $this->status = StatusValue::newFatal(
'http-invalid-url',
$url );
113 $this->status = StatusValue::newGood( 100 );
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;
177 $this->logger = $logger;
186 return function_exists(
'curl_init' ) ||
wfIniGetBool(
'allow_url_fopen' );
195 return $this->content;
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 InvalidArgumentException(
"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 ) {
292 $localVirtualHosts = MediaWikiServices::getInstance()->getMainConfig()->get(
293 MainConfigNames::LocalVirtualHosts );
297 if ( preg_match(
'!^https?://([\w.-]+)[/:].*$!', $url,
$matches ) ) {
300 $domainParts = explode(
'.', $host );
302 $domainParts = array_reverse( $domainParts );
305 $countParts = count( $domainParts );
306 for ( $i = 0; $i < $countParts; $i++ ) {
307 $domainPart = $domainParts[$i];
309 $domain = $domainPart;
311 $domain = $domainPart .
'.' . $domain;
314 if ( in_array( $domain, $localVirtualHosts ) ) {
327 $this->setHeader(
'User-Agent', $UA );
337 $this->reqHeaders[$name] = $value;
347 if ( $this->cookieJar ) {
348 $this->reqHeaders[
'Cookie'] =
349 $this->cookieJar->serializeToHttpRequest(
350 $this->parsedUrl[
'path'],
351 $this->parsedUrl[
'host']
355 foreach ( $this->reqHeaders as $name => $value ) {
356 $list[] =
"$name: $value";
381 $this->doSetCallback( $callback );
392 if ( $callback ===
null ) {
393 $callback = [ $this,
'read' ];
394 } elseif ( !is_callable( $callback ) ) {
395 $this->status->fatal(
'http-internal-error' );
396 throw new InvalidArgumentException( __METHOD__ .
': invalid callback' );
398 $this->callback = $callback;
410 public function read( $fh, $content ) {
411 $this->content .= $content;
412 return strlen( $content );
422 throw new LogicException(
'children must override this' );
428 if ( strtoupper( $this->method ) ==
"HEAD" ) {
429 $this->headersOnly =
true;
434 if ( !$this->callback ) {
435 $this->doSetCallback(
null );
438 if ( !isset( $this->reqHeaders[
'User-Agent'] ) ) {
439 $http = MediaWikiServices::getInstance()->getHttpRequestFactory();
440 $this->setUserAgent( $http->getUserAgent() );
453 if ( !$this->status->isOK() ) {
454 $this->respStatus =
'0 Error';
457 foreach ( $this->headerList as
$header ) {
458 if ( preg_match(
"#^HTTP/([0-9.]+) (.*)#",
$header, $match ) ) {
459 $this->respVersion = $match[1];
460 $this->respStatus = $match[2];
461 } elseif ( preg_match(
"#^[ \t]#",
$header ) ) {
462 $last = count( $this->respHeaders[$lastname] ) - 1;
463 $this->respHeaders[$lastname][$last] .=
"\r\n$header";
464 } elseif ( preg_match(
"#^([^:]*):[\t ]*(.*)#",
$header, $match ) ) {
465 $this->respHeaders[strtolower( $match[1] )][] = $match[2];
466 $lastname = strtolower( $match[1] );
470 $this->parseCookies();
481 if ( !$this->respHeaders ) {
482 $this->parseHeader();
485 if ( (
int)$this->respStatus > 0 && (
int)$this->respStatus < 400 ) {
486 $this->status->setResult(
true, (
int)$this->respStatus );
488 [ $code, $message ] = explode(
" ", $this->respStatus, 2 );
489 $this->status->setResult(
false, (
int)$this->respStatus );
490 $this->status->fatal(
"http-bad-status", $code, $message );
502 if ( !$this->respHeaders ) {
503 $this->parseHeader();
506 return (
int)$this->respStatus;
515 if ( !$this->respHeaders ) {
516 $this->parseHeader();
519 $status = (int)$this->respStatus;
521 if ( $status >= 300 && $status <= 303 ) {
538 if ( !$this->respHeaders ) {
539 $this->parseHeader();
542 return $this->respHeaders;
552 if ( !$this->respHeaders ) {
553 $this->parseHeader();
556 if ( isset( $this->respHeaders[strtolower(
$header )] ) ) {
557 $v = $this->respHeaders[strtolower(
$header )];
558 return $v[count( $v ) - 1];
572 $this->cookieJar = $jar;
581 if ( !$this->respHeaders ) {
582 $this->parseHeader();
585 return $this->cookieJar;
597 public function setCookie( $name, $value, array $attr = [] ) {
598 if ( !$this->cookieJar ) {
602 if ( $this->parsedUrl && !isset( $attr[
'domain'] ) ) {
603 $attr[
'domain'] = $this->parsedUrl[
'host'];
606 $this->cookieJar->
setCookie( $name, $value, $attr );
613 if ( !$this->cookieJar ) {
617 if ( isset( $this->respHeaders[
'set-cookie'] ) ) {
618 $url = parse_url( $this->getFinalUrl() );
619 if ( !isset( $url[
'host'] ) ) {
620 $this->status->fatal(
'http-invalid-url', $url );
622 foreach ( $this->respHeaders[
'set-cookie'] as $cookie ) {
623 $this->cookieJar->parseCookieResponseHeader( $cookie, $url[
'host'] );
646 $headers = $this->getResponseHeaders();
649 if ( isset( $headers[
'location'] ) ) {
650 $locations = $headers[
'location'];
652 $foundRelativeURI =
false;
653 $countLocations = count( $locations );
655 for ( $i = $countLocations - 1; $i >= 0; $i-- ) {
656 $url = parse_url( $locations[$i] );
658 if ( isset( $url[
'scheme'] ) && isset( $url[
'host'] ) ) {
659 $domain = $url[
'scheme'] .
'://' . $url[
'host'];
662 $foundRelativeURI =
true;
666 if ( !$foundRelativeURI ) {
667 return $locations[$countLocations - 1];
670 return $domain . $locations[$countLocations - 1];
672 $url = parse_url( $this->url );
673 if ( isset( $url[
'scheme'] ) && isset( $url[
'host'] ) ) {
674 return $url[
'scheme'] .
'://' . $url[
'host'] .
675 $locations[$countLocations - 1];
704 if ( $originalRequest instanceof
WebRequest ) {
706 'ip' => $originalRequest->getIP(),
707 'userAgent' => $originalRequest->getHeader(
'User-Agent' ),
710 !is_array( $originalRequest )
711 || array_diff( [
'ip',
'userAgent' ], array_keys( $originalRequest ) )
713 throw new InvalidArgumentException( __METHOD__ .
': $originalRequest must be a '
714 .
"WebRequest or an array with 'ip' and 'userAgent' keys" );
717 $this->reqHeaders[
'X-Forwarded-For'] = $originalRequest[
'ip'];
718 $this->reqHeaders[
'X-Original-User-Agent'] = $originalRequest[
'userAgent'];
738 return (
bool)preg_match(
739 '/^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.
Generic operation result class Has warning/error list, boolean status and arbitrary value.