WP_HTTP: Enable developers to request the first x bytes of a document using the 'limit-response-size' parameter.

The connection to the remote server will be disconnected after x number of bytes has been received.
See #23472


git-svn-id: http://core.svn.wordpress.org/trunk@23605 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
Dion Hulse 2013-03-04 04:24:26 +00:00
parent 709dd51ae6
commit 324d2a57f0
1 changed files with 101 additions and 24 deletions

View File

@ -95,7 +95,8 @@ class WP_Http {
'decompress' => true, 'decompress' => true,
'sslverify' => true, 'sslverify' => true,
'stream' => false, 'stream' => false,
'filename' => null 'filename' => null,
'limit-response-size' => null,
); );
// Pre-parse for the HEAD checks. // Pre-parse for the HEAD checks.
@ -733,6 +734,10 @@ class WP_Http_Fsockopen {
$strResponse = ''; $strResponse = '';
$bodyStarted = false; $bodyStarted = false;
$keep_reading = true;
$block_size = 4096;
if ( isset( $r['limit-response-size'] ) )
$block_size = min( $block_size, $r['limit-response-size'] );
// If streaming to a file setup the file handle // If streaming to a file setup the file handle
if ( $r['stream'] ) { if ( $r['stream'] ) {
@ -743,30 +748,45 @@ class WP_Http_Fsockopen {
if ( ! $stream_handle ) if ( ! $stream_handle )
return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) ); return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) );
while ( ! feof($handle) ) { $bytes_written = 0;
$block = fread( $handle, 4096 ); while ( ! feof($handle) && $keep_reading ) {
if ( $bodyStarted ) { $block = fread( $handle, $block_size );
fwrite( $stream_handle, $block ); if ( ! $bodyStarted ) {
} else {
$strResponse .= $block; $strResponse .= $block;
if ( strpos( $strResponse, "\r\n\r\n" ) ) { if ( strpos( $strResponse, "\r\n\r\n" ) ) {
$process = WP_Http::processResponse( $strResponse ); $process = WP_Http::processResponse( $strResponse );
$bodyStarted = true; $bodyStarted = true;
fwrite( $stream_handle, $process['body'] ); $block = $process['body'];
unset( $strResponse ); unset( $strResponse );
$process['body'] = ''; $process['body'] = '';
} }
} }
if ( isset( $r['limit-response-size'] ) && ( $bytes_written + strlen( $block ) ) > $r['limit-response-size'] )
$block = substr( $block, 0, ( $r['limit-response-size'] - $bytes_written ) );
$bytes_written += fwrite( $stream_handle, $block );
$keep_reading = !isset( $r['limit-response-size'] ) || $bytes_written < $r['limit-response-size'];
} }
fclose( $stream_handle ); fclose( $stream_handle );
} else { } else {
while ( ! feof($handle) ) $header_length = 0;
$strResponse .= fread( $handle, 4096 ); while ( ! feof( $handle ) && $keep_reading ) {
$block = fread( $handle, $block_size );
$strResponse .= $block;
if ( ! $bodyStarted && strpos( $strResponse, "\r\n\r\n" ) ) {
$header_length = strpos( $strResponse, "\r\n\r\n" ) + 4;
$bodyStarted = true;
}
$keep_reading = ( ! $bodyStarted || !isset( $r['limit-response-size'] ) || strlen( $strResponse ) < ( $header_length + $r['limit-response-size'] ) );
}
$process = WP_Http::processResponse( $strResponse ); $process = WP_Http::processResponse( $strResponse );
unset( $strResponse ); unset( $strResponse );
} }
fclose( $handle ); fclose( $handle );
@ -792,6 +812,9 @@ class WP_Http_Fsockopen {
if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($arrHeaders['headers']) ) if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($arrHeaders['headers']) )
$process['body'] = WP_Http_Encoding::decompress( $process['body'] ); $process['body'] = WP_Http_Encoding::decompress( $process['body'] );
if ( isset( $r['limit-response-size'] ) && strlen( $process['body'] ) > $r['limit-response-size'] )
$process['body'] = substr( $process['body'], 0, $r['limit-response-size'] );
return array( 'headers' => $arrHeaders['headers'], 'body' => $process['body'], 'response' => $arrHeaders['response'], 'cookies' => $arrHeaders['cookies'], 'filename' => $r['filename'] ); return array( 'headers' => $arrHeaders['headers'], 'body' => $process['body'], 'response' => $arrHeaders['response'], 'cookies' => $arrHeaders['cookies'], 'filename' => $r['filename'] );
} }
@ -936,6 +959,7 @@ class WP_Http_Streams {
return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() ); return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() );
} }
$max_bytes = isset( $r['limit-response-size'] ) ? intval( $r['limit-response-size'] ) : -1;
if ( $r['stream'] ) { if ( $r['stream'] ) {
if ( ! WP_DEBUG ) if ( ! WP_DEBUG )
$stream_handle = @fopen( $r['filename'], 'w+' ); $stream_handle = @fopen( $r['filename'], 'w+' );
@ -945,12 +969,12 @@ class WP_Http_Streams {
if ( ! $stream_handle ) if ( ! $stream_handle )
return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) ); return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) );
stream_copy_to_stream( $handle, $stream_handle ); stream_copy_to_stream( $handle, $stream_handle, $max_bytes );
fclose( $stream_handle ); fclose( $stream_handle );
$strResponse = ''; $strResponse = '';
} else { } else {
$strResponse = stream_get_contents( $handle ); $strResponse = stream_get_contents( $handle, $max_bytes );
} }
$meta = stream_get_meta_data( $handle ); $meta = stream_get_meta_data( $handle );
@ -1017,7 +1041,7 @@ class WP_Http_Streams {
class WP_Http_Curl { class WP_Http_Curl {
/** /**
* Temporary header storage for use with streaming to a file. * Temporary header storage for during requests.
* *
* @since 3.2.0 * @since 3.2.0
* @access private * @access private
@ -1025,6 +1049,33 @@ class WP_Http_Curl {
*/ */
private $headers = ''; private $headers = '';
/**
* Temporary body storage for during requests.
*
* @since 3.6.0
* @access private
* @var string
*/
private $body = '';
/**
* The maximum amount of data to recieve from the remote server
*
* @since 3.6.0
* @access private
* @var int
*/
private $max_body_length = false;
/**
* The file resource used for streaming to file.
*
* @since 3.6.0
* @access private
* @var resource
*/
private $stream_handle = false;
/** /**
* Send a HTTP request to a URI using cURL extension. * Send a HTTP request to a URI using cURL extension.
* *
@ -1114,20 +1165,24 @@ class WP_Http_Curl {
break; break;
} }
if ( true === $r['blocking'] ) if ( true === $r['blocking'] ) {
curl_setopt( $handle, CURLOPT_HEADERFUNCTION, array( $this, 'stream_headers' ) ); curl_setopt( $handle, CURLOPT_HEADERFUNCTION, array( $this, 'stream_headers' ) );
curl_setopt( $handle, CURLOPT_WRITEFUNCTION, array( $this, 'stream_body' ) );
}
curl_setopt( $handle, CURLOPT_HEADER, false ); curl_setopt( $handle, CURLOPT_HEADER, false );
if ( isset( $r['limit-response-size'] ) )
$this->max_body_length = intval( $r['limit-response-size'] );
// If streaming to a file open a file handle, and setup our curl streaming handler // If streaming to a file open a file handle, and setup our curl streaming handler
if ( $r['stream'] ) { if ( $r['stream'] ) {
if ( ! WP_DEBUG ) if ( ! WP_DEBUG )
$stream_handle = @fopen( $r['filename'], 'w+' ); $this->stream_handle = @fopen( $r['filename'], 'w+' );
else else
$stream_handle = fopen( $r['filename'], 'w+' ); $this->stream_handle = fopen( $r['filename'], 'w+' );
if ( ! $stream_handle ) if ( ! $this->stream_handle )
return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) ); return new WP_Error( 'http_request_failed', sprintf( __( 'Could not open handle for fopen() to %s' ), $r['filename'] ) );
curl_setopt( $handle, CURLOPT_FILE, $stream_handle );
} }
if ( !empty( $r['headers'] ) ) { if ( !empty( $r['headers'] ) ) {
@ -1156,22 +1211,20 @@ class WP_Http_Curl {
} }
$theResponse = curl_exec( $handle ); $theResponse = curl_exec( $handle );
$theBody = '';
$theHeaders = WP_Http::processHeaders( $this->headers ); $theHeaders = WP_Http::processHeaders( $this->headers );
$theBody = $this->body;
if ( strlen($theResponse) > 0 && ! is_bool( $theResponse ) ) // is_bool: when using $args['stream'], curl_exec will return (bool)true $this->headers = '';
$theBody = $theResponse; $this->body = '';
// If no response // If no response
if ( 0 == strlen( $theResponse ) && empty( $theHeaders['headers'] ) ) { if ( 0 == strlen( $theBody ) && empty( $theHeaders['headers'] ) ) {
if ( $curl_error = curl_error( $handle ) ) if ( $curl_error = curl_error( $handle ) )
return new WP_Error( 'http_request_failed', $curl_error ); return new WP_Error( 'http_request_failed', $curl_error );
if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array( 301, 302 ) ) ) if ( in_array( curl_getinfo( $handle, CURLINFO_HTTP_CODE ), array( 301, 302 ) ) )
return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) ); return new WP_Error( 'http_request_failed', __( 'Too many redirects.' ) );
} }
$this->headers = '';
$response = array(); $response = array();
$response['code'] = curl_getinfo( $handle, CURLINFO_HTTP_CODE ); $response['code'] = curl_getinfo( $handle, CURLINFO_HTTP_CODE );
$response['message'] = get_status_header_desc($response['code']); $response['message'] = get_status_header_desc($response['code']);
@ -1179,7 +1232,7 @@ class WP_Http_Curl {
curl_close( $handle ); curl_close( $handle );
if ( $r['stream'] ) if ( $r['stream'] )
fclose( $stream_handle ); fclose( $this->stream_handle );
// See #11305 - When running under safe mode, redirection is disabled above. Handle it manually. // See #11305 - When running under safe mode, redirection is disabled above. Handle it manually.
if ( ! empty( $theHeaders['headers']['location'] ) && 0 !== $r['_redirection'] ) { // _redirection: The requested number of redirections if ( ! empty( $theHeaders['headers']['location'] ) && 0 !== $r['_redirection'] ) { // _redirection: The requested number of redirections
@ -1210,6 +1263,28 @@ class WP_Http_Curl {
return strlen( $headers ); return strlen( $headers );
} }
/**
* Grab the body of the cURL request
*
* The contents of the document are passed in chunks, so we append to the $body property for temporary storage.
* Returning a length shorter than the length of $data passed in will cause cURL to abort the request as "completed"
*
* @since 3.6.0
* @access private
* @return int
*/
private function stream_body( $handle, $data ) {
if ( $this->max_body_length && ( strlen( $this->body ) + strlen( $data ) ) > $this->max_body_length )
$data = substr( $data, 0, ( $this->max_body_length - strlen( $this->body ) ) );
if ( $this->stream_handle )
fwrite( $this->stream_handle, $data );
else
$this->body .= $data;
return strlen( $data );
}
/** /**
* Whether this class can be used for retrieving an URL. * Whether this class can be used for retrieving an URL.
* *
@ -1744,6 +1819,8 @@ class WP_Http_Encoding {
$compression_enabled = false; $compression_enabled = false;
elseif ( $args['stream'] ) // disable when streaming to file elseif ( $args['stream'] ) // disable when streaming to file
$compression_enabled = false; $compression_enabled = false;
elseif ( isset( $args['limit-response-size'] ) ) // If only partial content is being requested, we won't be able to decompress it
$compression_enabled = false;
if ( $compression_enabled ) { if ( $compression_enabled ) {
if ( function_exists( 'gzinflate' ) ) if ( function_exists( 'gzinflate' ) )