* @file
*/
-use MediaWiki\Logger\LoggerFactory;
use Psr\Log\LoggerInterface;
use Psr\Log\LoggerAwareInterface;
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;
protected $reqHeaders = [];
protected $url;
protected $parsedUrl;
+ /** @var callable */
protected $callback;
protected $maxRedirects = 5;
protected $followRedirects = false;
+ protected $connectTimeout;
/**
* @var CookieJar
protected $respStatus = "200 Ok";
protected $respHeaders = [];
- public $status;
+ /** @var StatusValue */
+ protected $status;
/**
* @var Profiler
protected $profileName;
/**
- * @var LoggerInterface;
+ * @var LoggerInterface
*/
protected $logger;
* @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;
}
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' ) {
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" ];
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] );
}
/**
* 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 );
}
/**
*
* @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;
* Get an array of the headers
* @return array
*/
- public function getHeaderList() {
+ protected function getHeaderList() {
$list = [];
if ( $this->cookieJar ) {
* 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;
}
* @param resource $fh
* @param string $content
* @return int
+ * @internal
*/
public function read( $fh, $content ) {
$this->content .= $content;
/**
* 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" ) {
$this->proxySetup(); // set up any proxy as needed
if ( !$this->callback ) {
- $this->setCallback( [ $this, 'read' ] );
+ $this->setCallback( null );
}
if ( !isset( $this->reqHeaders['User-Agent'] ) ) {
/**
* 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 ) {
* 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 );
}
*
* 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
}
}
- 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;
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'];
+ }
}