MWHttpRequest: Restore ability to pass null for $options
[lhc/web/wiklou.git] / includes / http / MWHttpRequest.php
index 08883ae..70691b4 100644 (file)
@@ -18,7 +18,6 @@
  * @file
  */
 
-use MediaWiki\Logger\LoggerFactory;
 use Psr\Log\LoggerInterface;
 use Psr\Log\LoggerAwareInterface;
 use Psr\Log\NullLogger;
@@ -30,11 +29,15 @@ use Psr\Log\NullLogger;
  * Renamed from HttpRequest to MWHttpRequest to avoid conflict with
  * PHP's HTTP extension.
  */
-class MWHttpRequest implements LoggerAwareInterface {
+abstract class MWHttpRequest implements LoggerAwareInterface {
        const SUPPORTS_FILE_POSTS = false;
 
-       protected $content;
+       /**
+        * @var int|string
+        */
        protected $timeout = 'default';
+
+       protected $content;
        protected $headersOnly = null;
        protected $postData = null;
        protected $proxy = null;
@@ -46,9 +49,11 @@ class MWHttpRequest implements LoggerAwareInterface {
        protected $reqHeaders = [];
        protected $url;
        protected $parsedUrl;
+       /** @var callable */
        protected $callback;
        protected $maxRedirects = 5;
        protected $followRedirects = false;
+       protected $connectTimeout;
 
        /**
         * @var CookieJar
@@ -60,7 +65,8 @@ class MWHttpRequest implements LoggerAwareInterface {
        protected $respStatus = "200 Ok";
        protected $respHeaders = [];
 
-       public $status;
+       /** @var StatusValue */
+       protected $status;
 
        /**
         * @var Profiler
@@ -73,7 +79,7 @@ class MWHttpRequest implements LoggerAwareInterface {
        protected $profileName;
 
        /**
-        * @var LoggerInterface;
+        * @var LoggerInterface
         */
        protected $logger;
 
@@ -83,8 +89,8 @@ class MWHttpRequest implements LoggerAwareInterface {
         * @param string $caller The method making this request, for profiling
         * @param Profiler $profiler An instance of the profiler for profiling, or null
         */
-       protected function __construct(
-               $url, $options = [], $caller = __METHOD__, $profiler = null
+       public function __construct(
+               $url, array $options = [], $caller = __METHOD__, $profiler = null
        ) {
                global $wgHTTPTimeout, $wgHTTPConnectTimeout;
 
@@ -98,9 +104,9 @@ class MWHttpRequest implements LoggerAwareInterface {
                }
 
                if ( !$this->parsedUrl || !Http::isValidURI( $this->url ) ) {
-                       $this->status = Status::newFatal( 'http-invalid-url', $url );
+                       $this->status = StatusValue::newFatal( 'http-invalid-url', $url );
                } else {
-                       $this->status = Status::newGood( 100 ); // continue
+                       $this->status = StatusValue::newGood( 100 ); // continue
                }
 
                if ( isset( $options['timeout'] ) && $options['timeout'] != 'default' ) {
@@ -116,6 +122,15 @@ class MWHttpRequest implements LoggerAwareInterface {
                if ( isset( $options['userAgent'] ) ) {
                        $this->setUserAgent( $options['userAgent'] );
                }
+               if ( isset( $options['username'] ) && isset( $options['password'] ) ) {
+                       $this->setHeader(
+                               'Authorization',
+                               'Basic ' . base64_encode( $options['username'] . ':' . $options['password'] )
+                       );
+               }
+               if ( isset( $options['originalRequest'] ) ) {
+                       $this->setOriginalRequest( $options['originalRequest'] );
+               }
 
                $members = [ "postData", "proxy", "noProxy", "sslVerifyHost", "caInfo",
                                "method", "followRedirects", "maxRedirects", "sslVerifyCert", "callback" ];
@@ -123,7 +138,7 @@ class MWHttpRequest implements LoggerAwareInterface {
                foreach ( $members as $o ) {
                        if ( isset( $options[$o] ) ) {
                                // ensure that MWHttpRequest::method is always
-                               // uppercased. Bug 36137
+                               // uppercased. T38137
                                if ( $o == 'method' ) {
                                        $options[$o] = strtoupper( $options[$o] );
                                }
@@ -158,44 +173,21 @@ class MWHttpRequest implements LoggerAwareInterface {
 
        /**
         * Generate a new request object
+        * Deprecated: @see HttpRequestFactory::create
         * @param string $url Url to use
-        * @param array $options (optional) extra params to pass (see Http::request())
+        * @param array|null $options (optional) extra params to pass (see Http::request())
         * @param string $caller The method making this request, for profiling
-        * @throws MWException
-        * @return CurlHttpRequest|PhpHttpRequest
+        * @throws DomainException
+        * @return MWHttpRequest
         * @see MWHttpRequest::__construct
         */
-       public static function factory( $url, $options = null, $caller = __METHOD__ ) {
-               if ( !Http::$httpEngine ) {
-                       Http::$httpEngine = function_exists( 'curl_init' ) ? 'curl' : 'php';
-               } elseif ( Http::$httpEngine == 'curl' && !function_exists( 'curl_init' ) ) {
-                       throw new MWException( __METHOD__ . ': curl (http://php.net/curl) is not installed, but' .
-                               ' Http::$httpEngine is set to "curl"' );
-               }
-
-               if ( !is_array( $options ) ) {
+       public static function factory( $url, array $options = null, $caller = __METHOD__ ) {
+               if ( $options === null ) {
                        $options = [];
                }
-
-               if ( !isset( $options['logger'] ) ) {
-                       $options['logger'] = LoggerFactory::getInstance( 'http' );
-               }
-
-               switch ( Http::$httpEngine ) {
-                       case 'curl':
-                               return new CurlHttpRequest( $url, $options, $caller, Profiler::instance() );
-                       case 'php':
-                               if ( !wfIniGetBool( 'allow_url_fopen' ) ) {
-                                       throw new MWException( __METHOD__ . ': allow_url_fopen ' .
-                                               'needs to be enabled for pure PHP http requests to ' .
-                                               'work. If possible, curl should be used instead. See ' .
-                                               'http://php.net/curl.'
-                                       );
-                               }
-                               return new PhpHttpRequest( $url, $options, $caller, Profiler::instance() );
-                       default:
-                               throw new MWException( __METHOD__ . ': The setting of Http::$httpEngine is not valid.' );
-               }
+               return \MediaWiki\MediaWikiServices::getInstance()
+                       ->getHttpRequestFactory()
+                       ->create( $url, $options, $caller );
        }
 
        /**
@@ -222,7 +214,7 @@ class MWHttpRequest implements LoggerAwareInterface {
         *
         * @return void
         */
-       public function proxySetup() {
+       protected function proxySetup() {
                // If there is an explicit proxy set and proxies are not disabled, then use it
                if ( $this->proxy && !$this->noProxy ) {
                        return;
@@ -300,7 +292,7 @@ class MWHttpRequest implements LoggerAwareInterface {
         * Get an array of the headers
         * @return array
         */
-       public function getHeaderList() {
+       protected function getHeaderList() {
                $list = [];
 
                if ( $this->cookieJar ) {
@@ -333,12 +325,14 @@ class MWHttpRequest implements LoggerAwareInterface {
         * bytes are reported handled than were passed to you, the HTTP fetch
         * will be aborted.
         *
-        * @param callable $callback
-        * @throws MWException
+        * @param callable|null $callback
+        * @throws InvalidArgumentException
         */
        public function setCallback( $callback ) {
-               if ( !is_callable( $callback ) ) {
-                       throw new MWException( 'Invalid MwHttpRequest callback' );
+               if ( is_null( $callback ) ) {
+                       $callback = [ $this, 'read' ];
+               } elseif ( !is_callable( $callback ) ) {
+                       throw new InvalidArgumentException( __METHOD__ . ': invalid callback' );
                }
                $this->callback = $callback;
        }
@@ -350,6 +344,7 @@ class MWHttpRequest implements LoggerAwareInterface {
         * @param resource $fh
         * @param string $content
         * @return int
+        * @internal
         */
        public function read( $fh, $content ) {
                $this->content .= $content;
@@ -359,9 +354,14 @@ class MWHttpRequest implements LoggerAwareInterface {
        /**
         * Take care of whatever is necessary to perform the URI request.
         *
-        * @return Status
+        * @return StatusValue
+        * @note currently returns Status for B/C
         */
        public function execute() {
+               throw new LogicException( 'children must override this' );
+       }
+
+       protected function prepare() {
                $this->content = "";
 
                if ( strtoupper( $this->method ) == "HEAD" ) {
@@ -371,7 +371,7 @@ class MWHttpRequest implements LoggerAwareInterface {
                $this->proxySetup(); // set up any proxy as needed
 
                if ( !$this->callback ) {
-                       $this->setCallback( [ $this, 'read' ] );
+                       $this->setCallback( null );
                }
 
                if ( !isset( $this->reqHeaders['User-Agent'] ) ) {
@@ -494,6 +494,8 @@ class MWHttpRequest implements LoggerAwareInterface {
        /**
         * Tells the MWHttpRequest object to use this pre-loaded CookieJar.
         *
+        * To read response cookies from the jar, getCookieJar must be called first.
+        *
         * @param CookieJar $jar
         */
        public function setCookieJar( $jar ) {
@@ -519,14 +521,18 @@ class MWHttpRequest implements LoggerAwareInterface {
         * Set-Cookie headers.
         * @see Cookie::set
         * @param string $name
-        * @param mixed $value
+        * @param string $value
         * @param array $attr
         */
-       public function setCookie( $name, $value = null, $attr = null ) {
+       public function setCookie( $name, $value, $attr = [] ) {
                if ( !$this->cookieJar ) {
                        $this->cookieJar = new CookieJar;
                }
 
+               if ( $this->parsedUrl && !isset( $attr['domain'] ) ) {
+                       $attr['domain'] = $this->parsedUrl['host'];
+               }
+
                $this->cookieJar->setCookie( $name, $value, $attr );
        }
 
@@ -557,7 +563,7 @@ class MWHttpRequest implements LoggerAwareInterface {
         *
         * Note that the multiple Location: headers are an artifact of
         * CURL -- they shouldn't actually get returned this way. Rewrite
-        * this when bug 29232 is taken care of (high-level redirect
+        * this when T31232 is taken care of (high-level redirect
         * handling rewrite).
         *
         * @return string
@@ -583,19 +589,17 @@ class MWHttpRequest implements LoggerAwareInterface {
                                }
                        }
 
-                       if ( $foundRelativeURI ) {
-                               if ( $domain ) {
-                                       return $domain . $locations[$countLocations - 1];
-                               } else {
-                                       $url = parse_url( $this->url );
-                                       if ( isset( $url['host'] ) ) {
-                                               return $url['scheme'] . '://' . $url['host'] .
-                                                       $locations[$countLocations - 1];
-                                       }
-                               }
-                       } else {
+                       if ( !$foundRelativeURI ) {
                                return $locations[$countLocations - 1];
                        }
+                       if ( $domain ) {
+                               return $domain . $locations[$countLocations - 1];
+                       }
+                       $url = parse_url( $this->url );
+                       if ( isset( $url['host'] ) ) {
+                               return $url['scheme'] . '://' . $url['host'] .
+                                       $locations[$countLocations - 1];
+                       }
                }
 
                return $this->url;
@@ -609,4 +613,34 @@ class MWHttpRequest implements LoggerAwareInterface {
        public function canFollowRedirects() {
                return true;
        }
+
+       /**
+        * Set information about the original request. This can be useful for
+        * endpoints/API modules which act as a proxy for some service, and
+        * throttling etc. needs to happen in that service.
+        * Calling this will result in the X-Forwarded-For and X-Original-User-Agent
+        * headers being set.
+        * @param WebRequest|array $originalRequest When in array form, it's
+        *   expected to have the keys 'ip' and 'userAgent'.
+        * @note IP/user agent is personally identifiable information, and should
+        *   only be set when the privacy policy of the request target is
+        *   compatible with that of the MediaWiki installation.
+        */
+       public function setOriginalRequest( $originalRequest ) {
+               if ( $originalRequest instanceof WebRequest ) {
+                       $originalRequest = [
+                               'ip' => $originalRequest->getIP(),
+                               'userAgent' => $originalRequest->getHeader( 'User-Agent' ),
+                       ];
+               } elseif (
+                       !is_array( $originalRequest )
+                       || array_diff( [ 'ip', 'userAgent' ], array_keys( $originalRequest ) )
+               ) {
+                       throw new InvalidArgumentException( __METHOD__ . ': $originalRequest must be a '
+                               . "WebRequest or an array with 'ip' and 'userAgent' keys" );
+               }
+
+               $this->reqHeaders['X-Forwarded-For'] = $originalRequest['ip'];
+               $this->reqHeaders['X-Original-User-Agent'] = $originalRequest['userAgent'];
+       }
 }