summaryrefslogtreecommitdiff
path: root/includes/HttpFunctions.php
diff options
context:
space:
mode:
Diffstat (limited to 'includes/HttpFunctions.php')
-rw-r--r--includes/HttpFunctions.php105
1 files changed, 92 insertions, 13 deletions
diff --git a/includes/HttpFunctions.php b/includes/HttpFunctions.php
index 8e05f597..fa54487a 100644
--- a/includes/HttpFunctions.php
+++ b/includes/HttpFunctions.php
@@ -25,6 +25,8 @@
* @defgroup HTTP HTTP
*/
+use MediaWiki\Logger\LoggerFactory;
+
/**
* Various HTTP related functions
* @ingroup HTTP
@@ -73,11 +75,14 @@ class Http {
$req = MWHttpRequest::factory( $url, $options, $caller );
$status = $req->execute();
- $content = false;
if ( $status->isOK() ) {
- $content = $req->getContent();
+ return $req->getContent();
+ } else {
+ $errors = $status->getErrorsByType( 'error' );
+ $logger = LoggerFactory::getInstance( 'http' );
+ $logger->warning( $status->getWikiText(), array( 'caller' => $caller ) );
+ return false;
}
- return $content;
}
/**
@@ -850,6 +855,8 @@ class CurlHttpRequest extends MWHttpRequest {
class PhpHttpRequest extends MWHttpRequest {
+ private $fopenErrors = array();
+
/**
* @param string $url
* @return string
@@ -860,6 +867,60 @@ class PhpHttpRequest extends MWHttpRequest {
return 'tcp://' . $parsedUrl['host'] . ':' . $parsedUrl['port'];
}
+ /**
+ * Returns an array with a 'capath' or 'cafile' key that is suitable to be merged into the 'ssl' sub-array of a
+ * stream context options array. Uses the 'caInfo' option of the class if it is provided, otherwise uses the system
+ * default CA bundle if PHP supports that, or searches a few standard locations.
+ * @return array
+ * @throws DomainException
+ */
+ protected function getCertOptions() {
+ $certOptions = array();
+ $certLocations = array();
+ if ( $this->caInfo ) {
+ $certLocations = array( 'manual' => $this->caInfo );
+ } elseif ( version_compare( PHP_VERSION, '5.6.0', '<' ) ) {
+ // Default locations, based on
+ // https://www.happyassassin.net/2015/01/12/a-note-about-ssltls-trusted-certificate-stores-and-platforms/
+ // PHP 5.5 and older doesn't have any defaults, so we try to guess ourselves. PHP 5.6+ gets the CA location
+ // from OpenSSL as long as it is not set manually, so we should leave capath/cafile empty there.
+ $certLocations = array_filter( array(
+ getenv( 'SSL_CERT_DIR' ),
+ getenv( 'SSL_CERT_PATH' ),
+ '/etc/pki/tls/certs/ca-bundle.crt', # Fedora et al
+ '/etc/ssl/certs', # Debian et al
+ '/etc/pki/tls/certs/ca-bundle.trust.crt',
+ '/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem',
+ '/System/Library/OpenSSL', # OSX
+ ) );
+ }
+
+ foreach( $certLocations as $key => $cert ) {
+ if ( is_dir( $cert ) ) {
+ $certOptions['capath'] = $cert;
+ break;
+ } elseif ( is_file( $cert ) ) {
+ $certOptions['cafile'] = $cert;
+ break;
+ } elseif ( $key === 'manual' ) {
+ // fail more loudly if a cert path was manually configured and it is not valid
+ throw new DomainException( "Invalid CA info passed: $cert" );
+ }
+ }
+
+ return $certOptions;
+ }
+
+ /**
+ * Custom error handler for dealing with fopen() errors. fopen() tends to fire multiple errors in succession, and the last one
+ * is completely useless (something like "fopen: failed to open stream") so normal methods of handling errors programmatically
+ * like get_last_error() don't work.
+ */
+ public function errorHandler( $errno, $errstr ) {
+ $n = count( $this->fopenErrors ) + 1;
+ $this->fopenErrors += array( "errno$n" => $errno, "errstr$n" => $errstr );
+ }
+
public function execute() {
parent::execute();
@@ -912,16 +973,16 @@ class PhpHttpRequest extends MWHttpRequest {
}
if ( $this->sslVerifyHost ) {
- $options['ssl']['CN_match'] = $this->parsedUrl['host'];
+ // PHP 5.6.0 deprecates CN_match, in favour of peer_name which
+ // actually checks SubjectAltName properly.
+ if ( version_compare( PHP_VERSION, '5.6.0', '>=' ) ) {
+ $options['ssl']['peer_name'] = $this->parsedUrl['host'];
+ } else {
+ $options['ssl']['CN_match'] = $this->parsedUrl['host'];
+ }
}
- if ( is_dir( $this->caInfo ) ) {
- $options['ssl']['capath'] = $this->caInfo;
- } elseif ( is_file( $this->caInfo ) ) {
- $options['ssl']['cafile'] = $this->caInfo;
- } elseif ( $this->caInfo ) {
- throw new MWException( "Invalid CA info passed: {$this->caInfo}" );
- }
+ $options['ssl'] += $this->getCertOptions();
$context = stream_context_create( $options );
@@ -938,11 +999,25 @@ class PhpHttpRequest extends MWHttpRequest {
}
do {
$reqCount++;
- wfSuppressWarnings();
+ $this->fopenErrors = array();
+ set_error_handler( array( $this, 'errorHandler' ) );
$fh = fopen( $url, "r", false, $context );
- wfRestoreWarnings();
+ restore_error_handler();
if ( !$fh ) {
+ // HACK for instant commons.
+ // If we are contacting (commons|upload).wikimedia.org
+ // try again with CN_match for en.wikipedia.org
+ // as php does not handle SubjectAltName properly
+ // prior to "peer_name" option in php 5.6
+ if ( isset( $options['ssl']['CN_match'] )
+ && ( $options['ssl']['CN_match'] === 'commons.wikimedia.org'
+ || $options['ssl']['CN_match'] === 'upload.wikimedia.org' )
+ ) {
+ $options['ssl']['CN_match'] = 'en.wikipedia.org';
+ $context = stream_context_create( $options );
+ continue;
+ }
break;
}
@@ -973,6 +1048,10 @@ class PhpHttpRequest extends MWHttpRequest {
$this->setStatus();
if ( $fh === false ) {
+ if ( $this->fopenErrors ) {
+ LoggerFactory::getInstance( 'http' )->warning( __CLASS__
+ . ': error opening connection: {errstr1}', $this->fopenErrors );
+ }
$this->status->fatal( 'http-request-error' );
return $this->status;
}