diff options
Diffstat (limited to 'includes/libs/MultiHttpClient.php')
-rw-r--r-- | includes/libs/MultiHttpClient.php | 68 |
1 files changed, 49 insertions, 19 deletions
diff --git a/includes/libs/MultiHttpClient.php b/includes/libs/MultiHttpClient.php index fb2daa69..5555cbcb 100644 --- a/includes/libs/MultiHttpClient.php +++ b/includes/libs/MultiHttpClient.php @@ -58,8 +58,8 @@ class MultiHttpClient { /** * @param array $options - * - connTimeout : default connection timeout - * - reqTimeout : default request timeout + * - connTimeout : default connection timeout (seconds) + * - reqTimeout : default request timeout (seconds) * - proxy : HTTP proxy to use * - usePipelining : whether to use HTTP pipelining if possible (for all hosts) * - maxConnsPerHost : maximum number of concurrent connections (per host) @@ -72,7 +72,9 @@ class MultiHttpClient { throw new Exception( "Cannot find CA bundle: " . $this->caBundlePath ); } } - static $opts = array( 'connTimeout', 'reqTimeout', 'usePipelining', 'maxConnsPerHost', 'proxy' ); + static $opts = array( + 'connTimeout', 'reqTimeout', 'usePipelining', 'maxConnsPerHost', 'proxy' + ); foreach ( $opts as $key ) { if ( isset( $options[$key] ) ) { $this->$key = $options[$key]; @@ -84,19 +86,19 @@ class MultiHttpClient { * Execute an HTTP(S) request * * This method returns a response map of: - * - code : HTTP response code or 0 if there was a serious cURL error - * - reason : HTTP response reason (empty if there was a serious cURL error) - * - headers : <header name/value associative array> - * - body : HTTP response body or resource (if "stream" was set) + * - code : HTTP response code or 0 if there was a serious cURL error + * - reason : HTTP response reason (empty if there was a serious cURL error) + * - headers : <header name/value associative array> + * - body : HTTP response body or resource (if "stream" was set) * - error : Any cURL error string - * The map also stores integer-indexed copies of these values. This lets callers do: - * <code> + * The map also stores integer-indexed copies of these values. This lets callers do: + * @code * list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $http->run( $req ); - * </code> + * @endcode * @param array $req HTTP request array * @param array $opts - * - connTimeout : connection timeout per request - * - reqTimeout : post-connection timeout per request + * - connTimeout : connection timeout per request (seconds) + * - reqTimeout : post-connection timeout per request (seconds) * @return array Response array for request */ final public function run( array $req, array $opts = array() ) { @@ -114,17 +116,17 @@ class MultiHttpClient { * - body : HTTP response body or resource (if "stream" was set) * - error : Any cURL error string * The map also stores integer-indexed copies of these values. This lets callers do: - * <code> + * @code * list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $req['response']; - * </code> + * @endcode * All headers in the 'headers' field are normalized to use lower case names. * This is true for the request headers and the response headers. Integer-indexed * method/URL entries will also be changed to use the corresponding string keys. * * @param array $reqs Map of HTTP request arrays * @param array $opts - * - connTimeout : connection timeout per request - * - reqTimeout : post-connection timeout per request + * - connTimeout : connection timeout per request (seconds) + * - reqTimeout : post-connection timeout per request (seconds) * - usePipelining : whether to use HTTP pipelining if possible * - maxConnsPerHost : maximum number of concurrent connections (per host) * @return array $reqs With response array populated for each @@ -189,6 +191,7 @@ class MultiHttpClient { // @TODO: use a per-host rolling handle window (e.g. CURLMOPT_MAX_HOST_CONNECTIONS) $batches = array_chunk( $indexes, $this->maxConnsPerHost ); + $infos = array(); foreach ( $batches as $batch ) { // Attach all cURL handles for this batch @@ -201,6 +204,10 @@ class MultiHttpClient { // Do any available work... do { $mrc = curl_multi_exec( $chm, $active ); + $info = curl_multi_info_read( $chm ); + if ( $info !== false ) { + $infos[(int)$info['handle']] = $info; + } } while ( $mrc == CURLM_CALL_MULTI_PERFORM ); // Wait (if possible) for available work... if ( $active > 0 && $mrc == CURLM_OK ) { @@ -216,10 +223,20 @@ class MultiHttpClient { foreach ( $reqs as $index => &$req ) { $ch = $handles[$index]; curl_multi_remove_handle( $chm, $ch ); - if ( curl_errno( $ch ) !== 0 ) { - $req['response']['error'] = "(curl error: " . - curl_errno( $ch ) . ") " . curl_error( $ch ); + + if ( isset( $infos[(int)$ch] ) ) { + $info = $infos[(int)$ch]; + $errno = $info['result']; + if ( $errno !== 0 ) { + $req['response']['error'] = "(curl error: $errno)"; + if ( function_exists( 'curl_strerror' ) ) { + $req['response']['error'] .= " " . curl_strerror( $errno ); + } + } + } else { + $req['response']['error'] = "(curl error: no status set)"; } + // For convenience with the list() operator $req['response'][0] = $req['response']['code']; $req['response'][1] = $req['response']['reason']; @@ -318,6 +335,19 @@ class MultiHttpClient { ); } elseif ( $req['method'] === 'POST' ) { curl_setopt( $ch, CURLOPT_POST, 1 ); + // Don't interpret POST parameters starting with '@' as file uploads, because this + // makes it impossible to POST plain values starting with '@' (and causes security + // issues potentially exposing the contents of local files). + // The PHP manual says this option was introduced in PHP 5.5 defaults to true in PHP 5.6, + // but we support lower versions, and the option doesn't exist in HHVM 5.6.99. + if ( defined( 'CURLOPT_SAFE_UPLOAD' ) ) { + curl_setopt( $ch, CURLOPT_SAFE_UPLOAD, true ); + } else if ( is_array( $req['body'] ) ) { + // In PHP 5.2 and later, '@' is interpreted as a file upload if POSTFIELDS + // is an array, but not if it's a string. So convert $req['body'] to a string + // for safety. + $req['body'] = wfArrayToCgi( $req['body'] ); + } curl_setopt( $ch, CURLOPT_POSTFIELDS, $req['body'] ); } else { if ( is_resource( $req['body'] ) || $req['body'] !== '' ) { |