Update HTTP_Request (#399)

This commit is contained in:
onli 2016-04-27 18:52:12 +00:00
parent 51d88128f4
commit 4aefefc295
13 changed files with 5615 additions and 5191 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,137 +1,137 @@
<?php <?php
/** /**
* Base class for HTTP_Request2 adapters * Base class for HTTP_Request2 adapters
* *
* PHP version 5 * PHP version 5
* *
* LICENSE * LICENSE
* *
* This source file is subject to BSD 3-Clause License that is bundled * This source file is subject to BSD 3-Clause License that is bundled
* with this package in the file LICENSE and available at the URL * with this package in the file LICENSE and available at the URL
* https://raw.github.com/pear/HTTP_Request2/trunk/docs/LICENSE * https://raw.github.com/pear/HTTP_Request2/trunk/docs/LICENSE
* *
* @category HTTP * @category HTTP
* @package HTTP_Request2 * @package HTTP_Request2
* @author Alexey Borzov <avb@php.net> * @author Alexey Borzov <avb@php.net>
* @copyright 2008-2014 Alexey Borzov <avb@php.net> * @copyright 2008-2016 Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License * @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
* @link http://pear.php.net/package/HTTP_Request2 * @link http://pear.php.net/package/HTTP_Request2
*/ */
/** /**
* Class representing a HTTP response * Class representing a HTTP response
*/ */
require_once 'HTTP/Request2/Response.php'; require_once 'HTTP/Request2/Response.php';
/** /**
* Base class for HTTP_Request2 adapters * Base class for HTTP_Request2 adapters
* *
* HTTP_Request2 class itself only defines methods for aggregating the request * HTTP_Request2 class itself only defines methods for aggregating the request
* data, all actual work of sending the request to the remote server and * data, all actual work of sending the request to the remote server and
* receiving its response is performed by adapters. * receiving its response is performed by adapters.
* *
* @category HTTP * @category HTTP
* @package HTTP_Request2 * @package HTTP_Request2
* @author Alexey Borzov <avb@php.net> * @author Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License * @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
* @version Release: 2.2.1 * @version Release: 2.3.0
* @link http://pear.php.net/package/HTTP_Request2 * @link http://pear.php.net/package/HTTP_Request2
*/ */
abstract class HTTP_Request2_Adapter abstract class HTTP_Request2_Adapter
{ {
/** /**
* A list of methods that MUST NOT have a request body, per RFC 2616 * A list of methods that MUST NOT have a request body, per RFC 2616
* @var array * @var array
*/ */
protected static $bodyDisallowed = array('TRACE'); protected static $bodyDisallowed = array('TRACE');
/** /**
* Methods having defined semantics for request body * Methods having defined semantics for request body
* *
* Content-Length header (indicating that the body follows, section 4.3 of * Content-Length header (indicating that the body follows, section 4.3 of
* RFC 2616) will be sent for these methods even if no body was added * RFC 2616) will be sent for these methods even if no body was added
* *
* @var array * @var array
* @link http://pear.php.net/bugs/bug.php?id=12900 * @link http://pear.php.net/bugs/bug.php?id=12900
* @link http://pear.php.net/bugs/bug.php?id=14740 * @link http://pear.php.net/bugs/bug.php?id=14740
*/ */
protected static $bodyRequired = array('POST', 'PUT'); protected static $bodyRequired = array('POST', 'PUT');
/** /**
* Request being sent * Request being sent
* @var HTTP_Request2 * @var HTTP_Request2
*/ */
protected $request; protected $request;
/** /**
* Request body * Request body
* @var string|resource|HTTP_Request2_MultipartBody * @var string|resource|HTTP_Request2_MultipartBody
* @see HTTP_Request2::getBody() * @see HTTP_Request2::getBody()
*/ */
protected $requestBody; protected $requestBody;
/** /**
* Length of the request body * Length of the request body
* @var integer * @var integer
*/ */
protected $contentLength; protected $contentLength;
/** /**
* Sends request to the remote server and returns its response * Sends request to the remote server and returns its response
* *
* @param HTTP_Request2 $request HTTP request message * @param HTTP_Request2 $request HTTP request message
* *
* @return HTTP_Request2_Response * @return HTTP_Request2_Response
* @throws HTTP_Request2_Exception * @throws HTTP_Request2_Exception
*/ */
abstract public function sendRequest(HTTP_Request2 $request); abstract public function sendRequest(HTTP_Request2 $request);
/** /**
* Calculates length of the request body, adds proper headers * Calculates length of the request body, adds proper headers
* *
* @param array &$headers associative array of request headers, this method * @param array &$headers associative array of request headers, this method
* will add proper 'Content-Length' and 'Content-Type' * will add proper 'Content-Length' and 'Content-Type'
* headers to this array (or remove them if not needed) * headers to this array (or remove them if not needed)
*/ */
protected function calculateRequestLength(&$headers) protected function calculateRequestLength(&$headers)
{ {
$this->requestBody = $this->request->getBody(); $this->requestBody = $this->request->getBody();
if (is_string($this->requestBody)) { if (is_string($this->requestBody)) {
$this->contentLength = strlen($this->requestBody); $this->contentLength = strlen($this->requestBody);
} elseif (is_resource($this->requestBody)) { } elseif (is_resource($this->requestBody)) {
$stat = fstat($this->requestBody); $stat = fstat($this->requestBody);
$this->contentLength = $stat['size']; $this->contentLength = $stat['size'];
rewind($this->requestBody); rewind($this->requestBody);
} else { } else {
$this->contentLength = $this->requestBody->getLength(); $this->contentLength = $this->requestBody->getLength();
$headers['content-type'] = 'multipart/form-data; boundary=' . $headers['content-type'] = 'multipart/form-data; boundary=' .
$this->requestBody->getBoundary(); $this->requestBody->getBoundary();
$this->requestBody->rewind(); $this->requestBody->rewind();
} }
if (in_array($this->request->getMethod(), self::$bodyDisallowed) if (in_array($this->request->getMethod(), self::$bodyDisallowed)
|| 0 == $this->contentLength || 0 == $this->contentLength
) { ) {
// No body: send a Content-Length header nonetheless (request #12900), // No body: send a Content-Length header nonetheless (request #12900),
// but do that only for methods that require a body (bug #14740) // but do that only for methods that require a body (bug #14740)
if (in_array($this->request->getMethod(), self::$bodyRequired)) { if (in_array($this->request->getMethod(), self::$bodyRequired)) {
$headers['content-length'] = 0; $headers['content-length'] = 0;
} else { } else {
unset($headers['content-length']); unset($headers['content-length']);
// if the method doesn't require a body and doesn't have a // if the method doesn't require a body and doesn't have a
// body, don't send a Content-Type header. (request #16799) // body, don't send a Content-Type header. (request #16799)
unset($headers['content-type']); unset($headers['content-type']);
} }
} else { } else {
if (empty($headers['content-type'])) { if (empty($headers['content-type'])) {
$headers['content-type'] = 'application/x-www-form-urlencoded'; $headers['content-type'] = 'application/x-www-form-urlencoded';
} }
// Content-Length should not be sent for chunked Transfer-Encoding (bug #20125) // Content-Length should not be sent for chunked Transfer-Encoding (bug #20125)
if (!isset($headers['transfer-encoding'])) { if (!isset($headers['transfer-encoding'])) {
$headers['content-length'] = $this->contentLength; $headers['content-length'] = $this->contentLength;
} }
} }
} }
} }
?> ?>

File diff suppressed because it is too large Load Diff

View File

@ -1,166 +1,166 @@
<?php <?php
/** /**
* Mock adapter intended for testing * Mock adapter intended for testing
* *
* PHP version 5 * PHP version 5
* *
* LICENSE * LICENSE
* *
* This source file is subject to BSD 3-Clause License that is bundled * This source file is subject to BSD 3-Clause License that is bundled
* with this package in the file LICENSE and available at the URL * with this package in the file LICENSE and available at the URL
* https://raw.github.com/pear/HTTP_Request2/trunk/docs/LICENSE * https://raw.github.com/pear/HTTP_Request2/trunk/docs/LICENSE
* *
* @category HTTP * @category HTTP
* @package HTTP_Request2 * @package HTTP_Request2
* @author Alexey Borzov <avb@php.net> * @author Alexey Borzov <avb@php.net>
* @copyright 2008-2014 Alexey Borzov <avb@php.net> * @copyright 2008-2016 Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License * @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
* @link http://pear.php.net/package/HTTP_Request2 * @link http://pear.php.net/package/HTTP_Request2
*/ */
/** /**
* Base class for HTTP_Request2 adapters * Base class for HTTP_Request2 adapters
*/ */
require_once 'HTTP/Request2/Adapter.php'; require_once 'HTTP/Request2/Adapter.php';
/** /**
* Mock adapter intended for testing * Mock adapter intended for testing
* *
* Can be used to test applications depending on HTTP_Request2 package without * Can be used to test applications depending on HTTP_Request2 package without
* actually performing any HTTP requests. This adapter will return responses * actually performing any HTTP requests. This adapter will return responses
* previously added via addResponse() * previously added via addResponse()
* <code> * <code>
* $mock = new HTTP_Request2_Adapter_Mock(); * $mock = new HTTP_Request2_Adapter_Mock();
* $mock->addResponse("HTTP/1.1 ... "); * $mock->addResponse("HTTP/1.1 ... ");
* *
* $request = new HTTP_Request2(); * $request = new HTTP_Request2();
* $request->setAdapter($mock); * $request->setAdapter($mock);
* *
* // This will return the response set above * // This will return the response set above
* $response = $req->send(); * $response = $req->send();
* </code> * </code>
* *
* @category HTTP * @category HTTP
* @package HTTP_Request2 * @package HTTP_Request2
* @author Alexey Borzov <avb@php.net> * @author Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License * @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
* @version Release: 2.2.1 * @version Release: 2.3.0
* @link http://pear.php.net/package/HTTP_Request2 * @link http://pear.php.net/package/HTTP_Request2
*/ */
class HTTP_Request2_Adapter_Mock extends HTTP_Request2_Adapter class HTTP_Request2_Adapter_Mock extends HTTP_Request2_Adapter
{ {
/** /**
* A queue of responses to be returned by sendRequest() * A queue of responses to be returned by sendRequest()
* @var array * @var array
*/ */
protected $responses = array(); protected $responses = array();
/** /**
* Returns the next response from the queue built by addResponse() * Returns the next response from the queue built by addResponse()
* *
* Only responses without explicit URLs or with URLs equal to request URL * Only responses without explicit URLs or with URLs equal to request URL
* will be considered. If matching response is not found or the queue is * will be considered. If matching response is not found or the queue is
* empty then default empty response with status 400 will be returned, * empty then default empty response with status 400 will be returned,
* if an Exception object was added to the queue it will be thrown. * if an Exception object was added to the queue it will be thrown.
* *
* @param HTTP_Request2 $request HTTP request message * @param HTTP_Request2 $request HTTP request message
* *
* @return HTTP_Request2_Response * @return HTTP_Request2_Response
* @throws Exception * @throws Exception
*/ */
public function sendRequest(HTTP_Request2 $request) public function sendRequest(HTTP_Request2 $request)
{ {
$requestUrl = (string)$request->getUrl(); $requestUrl = (string)$request->getUrl();
$response = null; $response = null;
foreach ($this->responses as $k => $v) { foreach ($this->responses as $k => $v) {
if (!$v[1] || $requestUrl == $v[1]) { if (!$v[1] || $requestUrl == $v[1]) {
$response = $v[0]; $response = $v[0];
array_splice($this->responses, $k, 1); array_splice($this->responses, $k, 1);
break; break;
} }
} }
if (!$response) { if (!$response) {
return self::createResponseFromString("HTTP/1.1 400 Bad Request\r\n\r\n"); return self::createResponseFromString("HTTP/1.1 400 Bad Request\r\n\r\n");
} elseif ($response instanceof HTTP_Request2_Response) { } elseif ($response instanceof HTTP_Request2_Response) {
return $response; return $response;
} else { } else {
// rethrow the exception // rethrow the exception
$class = get_class($response); $class = get_class($response);
$message = $response->getMessage(); $message = $response->getMessage();
$code = $response->getCode(); $code = $response->getCode();
throw new $class($message, $code); throw new $class($message, $code);
} }
} }
/** /**
* Adds response to the queue * Adds response to the queue
* *
* @param mixed $response either a string, a pointer to an open file, * @param mixed $response either a string, a pointer to an open file,
* an instance of HTTP_Request2_Response or Exception * an instance of HTTP_Request2_Response or Exception
* @param string $url A request URL this response should be valid for * @param string $url A request URL this response should be valid for
* (see {@link http://pear.php.net/bugs/bug.php?id=19276}) * (see {@link http://pear.php.net/bugs/bug.php?id=19276})
* *
* @throws HTTP_Request2_Exception * @throws HTTP_Request2_Exception
*/ */
public function addResponse($response, $url = null) public function addResponse($response, $url = null)
{ {
if (is_string($response)) { if (is_string($response)) {
$response = self::createResponseFromString($response); $response = self::createResponseFromString($response);
} elseif (is_resource($response)) { } elseif (is_resource($response)) {
$response = self::createResponseFromFile($response); $response = self::createResponseFromFile($response);
} elseif (!$response instanceof HTTP_Request2_Response && } elseif (!$response instanceof HTTP_Request2_Response &&
!$response instanceof Exception !$response instanceof Exception
) { ) {
throw new HTTP_Request2_Exception('Parameter is not a valid response'); throw new HTTP_Request2_Exception('Parameter is not a valid response');
} }
$this->responses[] = array($response, $url); $this->responses[] = array($response, $url);
} }
/** /**
* Creates a new HTTP_Request2_Response object from a string * Creates a new HTTP_Request2_Response object from a string
* *
* @param string $str string containing HTTP response message * @param string $str string containing HTTP response message
* *
* @return HTTP_Request2_Response * @return HTTP_Request2_Response
* @throws HTTP_Request2_Exception * @throws HTTP_Request2_Exception
*/ */
public static function createResponseFromString($str) public static function createResponseFromString($str)
{ {
$parts = preg_split('!(\r?\n){2}!m', $str, 2); $parts = preg_split('!(\r?\n){2}!m', $str, 2);
$headerLines = explode("\n", $parts[0]); $headerLines = explode("\n", $parts[0]);
$response = new HTTP_Request2_Response(array_shift($headerLines)); $response = new HTTP_Request2_Response(array_shift($headerLines));
foreach ($headerLines as $headerLine) { foreach ($headerLines as $headerLine) {
$response->parseHeaderLine($headerLine); $response->parseHeaderLine($headerLine);
} }
$response->parseHeaderLine(''); $response->parseHeaderLine('');
if (isset($parts[1])) { if (isset($parts[1])) {
$response->appendBody($parts[1]); $response->appendBody($parts[1]);
} }
return $response; return $response;
} }
/** /**
* Creates a new HTTP_Request2_Response object from a file * Creates a new HTTP_Request2_Response object from a file
* *
* @param resource $fp file pointer returned by fopen() * @param resource $fp file pointer returned by fopen()
* *
* @return HTTP_Request2_Response * @return HTTP_Request2_Response
* @throws HTTP_Request2_Exception * @throws HTTP_Request2_Exception
*/ */
public static function createResponseFromFile($fp) public static function createResponseFromFile($fp)
{ {
$response = new HTTP_Request2_Response(fgets($fp)); $response = new HTTP_Request2_Response(fgets($fp));
do { do {
$headerLine = fgets($fp); $headerLine = fgets($fp);
$response->parseHeaderLine($headerLine); $response->parseHeaderLine($headerLine);
} while ('' != trim($headerLine)); } while ('' != trim($headerLine));
while (!feof($fp)) { while (!feof($fp)) {
$response->appendBody(fread($fp, 8192)); $response->appendBody(fread($fp, 8192));
} }
return $response; return $response;
} }
} }
?> ?>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,160 +1,160 @@
<?php <?php
/** /**
* Exception classes for HTTP_Request2 package * Exception classes for HTTP_Request2 package
* *
* PHP version 5 * PHP version 5
* *
* LICENSE * LICENSE
* *
* This source file is subject to BSD 3-Clause License that is bundled * This source file is subject to BSD 3-Clause License that is bundled
* with this package in the file LICENSE and available at the URL * with this package in the file LICENSE and available at the URL
* https://raw.github.com/pear/HTTP_Request2/trunk/docs/LICENSE * https://raw.github.com/pear/HTTP_Request2/trunk/docs/LICENSE
* *
* @category HTTP * @category HTTP
* @package HTTP_Request2 * @package HTTP_Request2
* @author Alexey Borzov <avb@php.net> * @author Alexey Borzov <avb@php.net>
* @copyright 2008-2014 Alexey Borzov <avb@php.net> * @copyright 2008-2016 Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License * @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
* @link http://pear.php.net/package/HTTP_Request2 * @link http://pear.php.net/package/HTTP_Request2
*/ */
/** /**
* Base class for exceptions in PEAR * Base class for exceptions in PEAR
*/ */
require_once 'PEAR/Exception.php'; require_once 'PEAR/Exception.php';
/** /**
* Base exception class for HTTP_Request2 package * Base exception class for HTTP_Request2 package
* *
* @category HTTP * @category HTTP
* @package HTTP_Request2 * @package HTTP_Request2
* @author Alexey Borzov <avb@php.net> * @author Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License * @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
* @version Release: 2.2.1 * @version Release: 2.3.0
* @link http://pear.php.net/package/HTTP_Request2 * @link http://pear.php.net/package/HTTP_Request2
* @link http://pear.php.net/pepr/pepr-proposal-show.php?id=132 * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=132
*/ */
class HTTP_Request2_Exception extends PEAR_Exception class HTTP_Request2_Exception extends PEAR_Exception
{ {
/** An invalid argument was passed to a method */ /** An invalid argument was passed to a method */
const INVALID_ARGUMENT = 1; const INVALID_ARGUMENT = 1;
/** Some required value was not available */ /** Some required value was not available */
const MISSING_VALUE = 2; const MISSING_VALUE = 2;
/** Request cannot be processed due to errors in PHP configuration */ /** Request cannot be processed due to errors in PHP configuration */
const MISCONFIGURATION = 3; const MISCONFIGURATION = 3;
/** Error reading the local file */ /** Error reading the local file */
const READ_ERROR = 4; const READ_ERROR = 4;
/** Server returned a response that does not conform to HTTP protocol */ /** Server returned a response that does not conform to HTTP protocol */
const MALFORMED_RESPONSE = 10; const MALFORMED_RESPONSE = 10;
/** Failure decoding Content-Encoding or Transfer-Encoding of response */ /** Failure decoding Content-Encoding or Transfer-Encoding of response */
const DECODE_ERROR = 20; const DECODE_ERROR = 20;
/** Operation timed out */ /** Operation timed out */
const TIMEOUT = 30; const TIMEOUT = 30;
/** Number of redirects exceeded 'max_redirects' configuration parameter */ /** Number of redirects exceeded 'max_redirects' configuration parameter */
const TOO_MANY_REDIRECTS = 40; const TOO_MANY_REDIRECTS = 40;
/** Redirect to a protocol other than http(s):// */ /** Redirect to a protocol other than http(s):// */
const NON_HTTP_REDIRECT = 50; const NON_HTTP_REDIRECT = 50;
/** /**
* Native error code * Native error code
* @var int * @var int
*/ */
private $_nativeCode; private $_nativeCode;
/** /**
* Constructor, can set package error code and native error code * Constructor, can set package error code and native error code
* *
* @param string $message exception message * @param string $message exception message
* @param int $code package error code, one of class constants * @param int $code package error code, one of class constants
* @param int $nativeCode error code from underlying PHP extension * @param int $nativeCode error code from underlying PHP extension
*/ */
public function __construct($message = null, $code = null, $nativeCode = null) public function __construct($message = null, $code = null, $nativeCode = null)
{ {
parent::__construct($message, $code); parent::__construct($message, $code);
$this->_nativeCode = $nativeCode; $this->_nativeCode = $nativeCode;
} }
/** /**
* Returns error code produced by underlying PHP extension * Returns error code produced by underlying PHP extension
* *
* For Socket Adapter this may contain error number returned by * For Socket Adapter this may contain error number returned by
* stream_socket_client(), for Curl Adapter this will contain error number * stream_socket_client(), for Curl Adapter this will contain error number
* returned by curl_errno() * returned by curl_errno()
* *
* @return integer * @return integer
*/ */
public function getNativeCode() public function getNativeCode()
{ {
return $this->_nativeCode; return $this->_nativeCode;
} }
} }
/** /**
* Exception thrown in case of missing features * Exception thrown in case of missing features
* *
* @category HTTP * @category HTTP
* @package HTTP_Request2 * @package HTTP_Request2
* @author Alexey Borzov <avb@php.net> * @author Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License * @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
* @version Release: 2.2.1 * @version Release: 2.3.0
* @link http://pear.php.net/package/HTTP_Request2 * @link http://pear.php.net/package/HTTP_Request2
*/ */
class HTTP_Request2_NotImplementedException extends HTTP_Request2_Exception class HTTP_Request2_NotImplementedException extends HTTP_Request2_Exception
{ {
} }
/** /**
* Exception that represents error in the program logic * Exception that represents error in the program logic
* *
* This exception usually implies a programmer's error, like passing invalid * This exception usually implies a programmer's error, like passing invalid
* data to methods or trying to use PHP extensions that weren't installed or * data to methods or trying to use PHP extensions that weren't installed or
* enabled. Usually exceptions of this kind will be thrown before request even * enabled. Usually exceptions of this kind will be thrown before request even
* starts. * starts.
* *
* The exception will usually contain a package error code. * The exception will usually contain a package error code.
* *
* @category HTTP * @category HTTP
* @package HTTP_Request2 * @package HTTP_Request2
* @author Alexey Borzov <avb@php.net> * @author Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License * @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
* @version Release: 2.2.1 * @version Release: 2.3.0
* @link http://pear.php.net/package/HTTP_Request2 * @link http://pear.php.net/package/HTTP_Request2
*/ */
class HTTP_Request2_LogicException extends HTTP_Request2_Exception class HTTP_Request2_LogicException extends HTTP_Request2_Exception
{ {
} }
/** /**
* Exception thrown when connection to a web or proxy server fails * Exception thrown when connection to a web or proxy server fails
* *
* The exception will not contain a package error code, but will contain * The exception will not contain a package error code, but will contain
* native error code, as returned by stream_socket_client() or curl_errno(). * native error code, as returned by stream_socket_client() or curl_errno().
* *
* @category HTTP * @category HTTP
* @package HTTP_Request2 * @package HTTP_Request2
* @author Alexey Borzov <avb@php.net> * @author Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License * @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
* @version Release: 2.2.1 * @version Release: 2.3.0
* @link http://pear.php.net/package/HTTP_Request2 * @link http://pear.php.net/package/HTTP_Request2
*/ */
class HTTP_Request2_ConnectionException extends HTTP_Request2_Exception class HTTP_Request2_ConnectionException extends HTTP_Request2_Exception
{ {
} }
/** /**
* Exception thrown when sending or receiving HTTP message fails * Exception thrown when sending or receiving HTTP message fails
* *
* The exception may contain both package error code and native error code. * The exception may contain both package error code and native error code.
* *
* @category HTTP * @category HTTP
* @package HTTP_Request2 * @package HTTP_Request2
* @author Alexey Borzov <avb@php.net> * @author Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License * @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
* @version Release: 2.2.1 * @version Release: 2.3.0
* @link http://pear.php.net/package/HTTP_Request2 * @link http://pear.php.net/package/HTTP_Request2
*/ */
class HTTP_Request2_MessageException extends HTTP_Request2_Exception class HTTP_Request2_MessageException extends HTTP_Request2_Exception
{ {
} }
?> ?>

View File

@ -1,268 +1,268 @@
<?php <?php
/** /**
* Helper class for building multipart/form-data request body * Helper class for building multipart/form-data request body
* *
* PHP version 5 * PHP version 5
* *
* LICENSE * LICENSE
* *
* This source file is subject to BSD 3-Clause License that is bundled * This source file is subject to BSD 3-Clause License that is bundled
* with this package in the file LICENSE and available at the URL * with this package in the file LICENSE and available at the URL
* https://raw.github.com/pear/HTTP_Request2/trunk/docs/LICENSE * https://raw.github.com/pear/HTTP_Request2/trunk/docs/LICENSE
* *
* @category HTTP * @category HTTP
* @package HTTP_Request2 * @package HTTP_Request2
* @author Alexey Borzov <avb@php.net> * @author Alexey Borzov <avb@php.net>
* @copyright 2008-2014 Alexey Borzov <avb@php.net> * @copyright 2008-2016 Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License * @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
* @link http://pear.php.net/package/HTTP_Request2 * @link http://pear.php.net/package/HTTP_Request2
*/ */
/** Exception class for HTTP_Request2 package */ /** Exception class for HTTP_Request2 package */
require_once 'HTTP/Request2/Exception.php'; require_once 'HTTP/Request2/Exception.php';
/** /**
* Class for building multipart/form-data request body * Class for building multipart/form-data request body
* *
* The class helps to reduce memory consumption by streaming large file uploads * The class helps to reduce memory consumption by streaming large file uploads
* from disk, it also allows monitoring of upload progress (see request #7630) * from disk, it also allows monitoring of upload progress (see request #7630)
* *
* @category HTTP * @category HTTP
* @package HTTP_Request2 * @package HTTP_Request2
* @author Alexey Borzov <avb@php.net> * @author Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License * @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
* @version Release: 2.2.1 * @version Release: 2.3.0
* @link http://pear.php.net/package/HTTP_Request2 * @link http://pear.php.net/package/HTTP_Request2
* @link http://tools.ietf.org/html/rfc1867 * @link http://tools.ietf.org/html/rfc1867
*/ */
class HTTP_Request2_MultipartBody class HTTP_Request2_MultipartBody
{ {
/** /**
* MIME boundary * MIME boundary
* @var string * @var string
*/ */
private $_boundary; private $_boundary;
/** /**
* Form parameters added via {@link HTTP_Request2::addPostParameter()} * Form parameters added via {@link HTTP_Request2::addPostParameter()}
* @var array * @var array
*/ */
private $_params = array(); private $_params = array();
/** /**
* File uploads added via {@link HTTP_Request2::addUpload()} * File uploads added via {@link HTTP_Request2::addUpload()}
* @var array * @var array
*/ */
private $_uploads = array(); private $_uploads = array();
/** /**
* Header for parts with parameters * Header for parts with parameters
* @var string * @var string
*/ */
private $_headerParam = "--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n"; private $_headerParam = "--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n";
/** /**
* Header for parts with uploads * Header for parts with uploads
* @var string * @var string
*/ */
private $_headerUpload = "--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\nContent-Type: %s\r\n\r\n"; private $_headerUpload = "--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\nContent-Type: %s\r\n\r\n";
/** /**
* Current position in parameter and upload arrays * Current position in parameter and upload arrays
* *
* First number is index of "current" part, second number is position within * First number is index of "current" part, second number is position within
* "current" part * "current" part
* *
* @var array * @var array
*/ */
private $_pos = array(0, 0); private $_pos = array(0, 0);
/** /**
* Constructor. Sets the arrays with POST data. * Constructor. Sets the arrays with POST data.
* *
* @param array $params values of form fields set via * @param array $params values of form fields set via
* {@link HTTP_Request2::addPostParameter()} * {@link HTTP_Request2::addPostParameter()}
* @param array $uploads file uploads set via * @param array $uploads file uploads set via
* {@link HTTP_Request2::addUpload()} * {@link HTTP_Request2::addUpload()}
* @param bool $useBrackets whether to append brackets to array variable names * @param bool $useBrackets whether to append brackets to array variable names
*/ */
public function __construct(array $params, array $uploads, $useBrackets = true) public function __construct(array $params, array $uploads, $useBrackets = true)
{ {
$this->_params = self::_flattenArray('', $params, $useBrackets); $this->_params = self::_flattenArray('', $params, $useBrackets);
foreach ($uploads as $fieldName => $f) { foreach ($uploads as $fieldName => $f) {
if (!is_array($f['fp'])) { if (!is_array($f['fp'])) {
$this->_uploads[] = $f + array('name' => $fieldName); $this->_uploads[] = $f + array('name' => $fieldName);
} else { } else {
for ($i = 0; $i < count($f['fp']); $i++) { for ($i = 0; $i < count($f['fp']); $i++) {
$upload = array( $upload = array(
'name' => ($useBrackets? $fieldName . '[' . $i . ']': $fieldName) 'name' => ($useBrackets? $fieldName . '[' . $i . ']': $fieldName)
); );
foreach (array('fp', 'filename', 'size', 'type') as $key) { foreach (array('fp', 'filename', 'size', 'type') as $key) {
$upload[$key] = $f[$key][$i]; $upload[$key] = $f[$key][$i];
} }
$this->_uploads[] = $upload; $this->_uploads[] = $upload;
} }
} }
} }
} }
/** /**
* Returns the length of the body to use in Content-Length header * Returns the length of the body to use in Content-Length header
* *
* @return integer * @return integer
*/ */
public function getLength() public function getLength()
{ {
$boundaryLength = strlen($this->getBoundary()); $boundaryLength = strlen($this->getBoundary());
$headerParamLength = strlen($this->_headerParam) - 4 + $boundaryLength; $headerParamLength = strlen($this->_headerParam) - 4 + $boundaryLength;
$headerUploadLength = strlen($this->_headerUpload) - 8 + $boundaryLength; $headerUploadLength = strlen($this->_headerUpload) - 8 + $boundaryLength;
$length = $boundaryLength + 6; $length = $boundaryLength + 6;
foreach ($this->_params as $p) { foreach ($this->_params as $p) {
$length += $headerParamLength + strlen($p[0]) + strlen($p[1]) + 2; $length += $headerParamLength + strlen($p[0]) + strlen($p[1]) + 2;
} }
foreach ($this->_uploads as $u) { foreach ($this->_uploads as $u) {
$length += $headerUploadLength + strlen($u['name']) + strlen($u['type']) + $length += $headerUploadLength + strlen($u['name']) + strlen($u['type']) +
strlen($u['filename']) + $u['size'] + 2; strlen($u['filename']) + $u['size'] + 2;
} }
return $length; return $length;
} }
/** /**
* Returns the boundary to use in Content-Type header * Returns the boundary to use in Content-Type header
* *
* @return string * @return string
*/ */
public function getBoundary() public function getBoundary()
{ {
if (empty($this->_boundary)) { if (empty($this->_boundary)) {
$this->_boundary = '--' . md5('PEAR-HTTP_Request2-' . microtime()); $this->_boundary = '--' . md5('PEAR-HTTP_Request2-' . microtime());
} }
return $this->_boundary; return $this->_boundary;
} }
/** /**
* Returns next chunk of request body * Returns next chunk of request body
* *
* @param integer $length Number of bytes to read * @param integer $length Number of bytes to read
* *
* @return string Up to $length bytes of data, empty string if at end * @return string Up to $length bytes of data, empty string if at end
* @throws HTTP_Request2_LogicException * @throws HTTP_Request2_LogicException
*/ */
public function read($length) public function read($length)
{ {
$ret = ''; $ret = '';
$boundary = $this->getBoundary(); $boundary = $this->getBoundary();
$paramCount = count($this->_params); $paramCount = count($this->_params);
$uploadCount = count($this->_uploads); $uploadCount = count($this->_uploads);
while ($length > 0 && $this->_pos[0] <= $paramCount + $uploadCount) { while ($length > 0 && $this->_pos[0] <= $paramCount + $uploadCount) {
$oldLength = $length; $oldLength = $length;
if ($this->_pos[0] < $paramCount) { if ($this->_pos[0] < $paramCount) {
$param = sprintf( $param = sprintf(
$this->_headerParam, $boundary, $this->_params[$this->_pos[0]][0] $this->_headerParam, $boundary, $this->_params[$this->_pos[0]][0]
) . $this->_params[$this->_pos[0]][1] . "\r\n"; ) . $this->_params[$this->_pos[0]][1] . "\r\n";
$ret .= substr($param, $this->_pos[1], $length); $ret .= substr($param, $this->_pos[1], $length);
$length -= min(strlen($param) - $this->_pos[1], $length); $length -= min(strlen($param) - $this->_pos[1], $length);
} elseif ($this->_pos[0] < $paramCount + $uploadCount) { } elseif ($this->_pos[0] < $paramCount + $uploadCount) {
$pos = $this->_pos[0] - $paramCount; $pos = $this->_pos[0] - $paramCount;
$header = sprintf( $header = sprintf(
$this->_headerUpload, $boundary, $this->_uploads[$pos]['name'], $this->_headerUpload, $boundary, $this->_uploads[$pos]['name'],
$this->_uploads[$pos]['filename'], $this->_uploads[$pos]['type'] $this->_uploads[$pos]['filename'], $this->_uploads[$pos]['type']
); );
if ($this->_pos[1] < strlen($header)) { if ($this->_pos[1] < strlen($header)) {
$ret .= substr($header, $this->_pos[1], $length); $ret .= substr($header, $this->_pos[1], $length);
$length -= min(strlen($header) - $this->_pos[1], $length); $length -= min(strlen($header) - $this->_pos[1], $length);
} }
$filePos = max(0, $this->_pos[1] - strlen($header)); $filePos = max(0, $this->_pos[1] - strlen($header));
if ($filePos < $this->_uploads[$pos]['size']) { if ($filePos < $this->_uploads[$pos]['size']) {
while ($length > 0 && !feof($this->_uploads[$pos]['fp'])) { while ($length > 0 && !feof($this->_uploads[$pos]['fp'])) {
if (false === ($chunk = fread($this->_uploads[$pos]['fp'], $length))) { if (false === ($chunk = fread($this->_uploads[$pos]['fp'], $length))) {
throw new HTTP_Request2_LogicException( throw new HTTP_Request2_LogicException(
'Failed reading file upload', HTTP_Request2_Exception::READ_ERROR 'Failed reading file upload', HTTP_Request2_Exception::READ_ERROR
); );
} }
$ret .= $chunk; $ret .= $chunk;
$length -= strlen($chunk); $length -= strlen($chunk);
} }
} }
if ($length > 0) { if ($length > 0) {
$start = $this->_pos[1] + ($oldLength - $length) - $start = $this->_pos[1] + ($oldLength - $length) -
strlen($header) - $this->_uploads[$pos]['size']; strlen($header) - $this->_uploads[$pos]['size'];
$ret .= substr("\r\n", $start, $length); $ret .= substr("\r\n", $start, $length);
$length -= min(2 - $start, $length); $length -= min(2 - $start, $length);
} }
} else { } else {
$closing = '--' . $boundary . "--\r\n"; $closing = '--' . $boundary . "--\r\n";
$ret .= substr($closing, $this->_pos[1], $length); $ret .= substr($closing, $this->_pos[1], $length);
$length -= min(strlen($closing) - $this->_pos[1], $length); $length -= min(strlen($closing) - $this->_pos[1], $length);
} }
if ($length > 0) { if ($length > 0) {
$this->_pos = array($this->_pos[0] + 1, 0); $this->_pos = array($this->_pos[0] + 1, 0);
} else { } else {
$this->_pos[1] += $oldLength; $this->_pos[1] += $oldLength;
} }
} }
return $ret; return $ret;
} }
/** /**
* Sets the current position to the start of the body * Sets the current position to the start of the body
* *
* This allows reusing the same body in another request * This allows reusing the same body in another request
*/ */
public function rewind() public function rewind()
{ {
$this->_pos = array(0, 0); $this->_pos = array(0, 0);
foreach ($this->_uploads as $u) { foreach ($this->_uploads as $u) {
rewind($u['fp']); rewind($u['fp']);
} }
} }
/** /**
* Returns the body as string * Returns the body as string
* *
* Note that it reads all file uploads into memory so it is a good idea not * Note that it reads all file uploads into memory so it is a good idea not
* to use this method with large file uploads and rely on read() instead. * to use this method with large file uploads and rely on read() instead.
* *
* @return string * @return string
*/ */
public function __toString() public function __toString()
{ {
$this->rewind(); $this->rewind();
return $this->read($this->getLength()); return $this->read($this->getLength());
} }
/** /**
* Helper function to change the (probably multidimensional) associative array * Helper function to change the (probably multidimensional) associative array
* into the simple one. * into the simple one.
* *
* @param string $name name for item * @param string $name name for item
* @param mixed $values item's values * @param mixed $values item's values
* @param bool $useBrackets whether to append [] to array variables' names * @param bool $useBrackets whether to append [] to array variables' names
* *
* @return array array with the following items: array('item name', 'item value'); * @return array array with the following items: array('item name', 'item value');
*/ */
private static function _flattenArray($name, $values, $useBrackets) private static function _flattenArray($name, $values, $useBrackets)
{ {
if (!is_array($values)) { if (!is_array($values)) {
return array(array($name, $values)); return array(array($name, $values));
} else { } else {
$ret = array(); $ret = array();
foreach ($values as $k => $v) { foreach ($values as $k => $v) {
if (empty($name)) { if (empty($name)) {
$newName = $k; $newName = $k;
} elseif ($useBrackets) { } elseif ($useBrackets) {
$newName = $name . '[' . $k . ']'; $newName = $name . '[' . $k . ']';
} else { } else {
$newName = $name; $newName = $name;
} }
$ret = array_merge($ret, self::_flattenArray($newName, $v, $useBrackets)); $ret = array_merge($ret, self::_flattenArray($newName, $v, $useBrackets));
} }
return $ret; return $ret;
} }
} }
} }
?> ?>

View File

@ -1,192 +1,192 @@
<?php <?php
/** /**
* An observer useful for debugging / testing. * An observer useful for debugging / testing.
* *
* PHP version 5 * PHP version 5
* *
* LICENSE * LICENSE
* *
* This source file is subject to BSD 3-Clause License that is bundled * This source file is subject to BSD 3-Clause License that is bundled
* with this package in the file LICENSE and available at the URL * with this package in the file LICENSE and available at the URL
* https://raw.github.com/pear/HTTP_Request2/trunk/docs/LICENSE * https://raw.github.com/pear/HTTP_Request2/trunk/docs/LICENSE
* *
* @category HTTP * @category HTTP
* @package HTTP_Request2 * @package HTTP_Request2
* @author David Jean Louis <izi@php.net> * @author David Jean Louis <izi@php.net>
* @author Alexey Borzov <avb@php.net> * @author Alexey Borzov <avb@php.net>
* @copyright 2008-2014 Alexey Borzov <avb@php.net> * @copyright 2008-2016 Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License * @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
* @link http://pear.php.net/package/HTTP_Request2 * @link http://pear.php.net/package/HTTP_Request2
*/ */
/** /**
* Exception class for HTTP_Request2 package * Exception class for HTTP_Request2 package
*/ */
require_once 'HTTP/Request2/Exception.php'; require_once 'HTTP/Request2/Exception.php';
/** /**
* A debug observer useful for debugging / testing. * A debug observer useful for debugging / testing.
* *
* This observer logs to a log target data corresponding to the various request * This observer logs to a log target data corresponding to the various request
* and response events, it logs by default to php://output but can be configured * and response events, it logs by default to php://output but can be configured
* to log to a file or via the PEAR Log package. * to log to a file or via the PEAR Log package.
* *
* A simple example: * A simple example:
* <code> * <code>
* require_once 'HTTP/Request2.php'; * require_once 'HTTP/Request2.php';
* require_once 'HTTP/Request2/Observer/Log.php'; * require_once 'HTTP/Request2/Observer/Log.php';
* *
* $request = new HTTP_Request2('http://www.example.com'); * $request = new HTTP_Request2('http://www.example.com');
* $observer = new HTTP_Request2_Observer_Log(); * $observer = new HTTP_Request2_Observer_Log();
* $request->attach($observer); * $request->attach($observer);
* $request->send(); * $request->send();
* </code> * </code>
* *
* A more complex example with PEAR Log: * A more complex example with PEAR Log:
* <code> * <code>
* require_once 'HTTP/Request2.php'; * require_once 'HTTP/Request2.php';
* require_once 'HTTP/Request2/Observer/Log.php'; * require_once 'HTTP/Request2/Observer/Log.php';
* require_once 'Log.php'; * require_once 'Log.php';
* *
* $request = new HTTP_Request2('http://www.example.com'); * $request = new HTTP_Request2('http://www.example.com');
* // we want to log with PEAR log * // we want to log with PEAR log
* $observer = new HTTP_Request2_Observer_Log(Log::factory('console')); * $observer = new HTTP_Request2_Observer_Log(Log::factory('console'));
* *
* // we only want to log received headers * // we only want to log received headers
* $observer->events = array('receivedHeaders'); * $observer->events = array('receivedHeaders');
* *
* $request->attach($observer); * $request->attach($observer);
* $request->send(); * $request->send();
* </code> * </code>
* *
* @category HTTP * @category HTTP
* @package HTTP_Request2 * @package HTTP_Request2
* @author David Jean Louis <izi@php.net> * @author David Jean Louis <izi@php.net>
* @author Alexey Borzov <avb@php.net> * @author Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License * @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
* @version Release: 2.2.1 * @version Release: 2.3.0
* @link http://pear.php.net/package/HTTP_Request2 * @link http://pear.php.net/package/HTTP_Request2
*/ */
class HTTP_Request2_Observer_Log implements SplObserver class HTTP_Request2_Observer_Log implements SplObserver
{ {
// properties {{{ // properties {{{
/** /**
* The log target, it can be a a resource or a PEAR Log instance. * The log target, it can be a a resource or a PEAR Log instance.
* *
* @var resource|Log $target * @var resource|Log $target
*/ */
protected $target = null; protected $target = null;
/** /**
* The events to log. * The events to log.
* *
* @var array $events * @var array $events
*/ */
public $events = array( public $events = array(
'connect', 'connect',
'sentHeaders', 'sentHeaders',
'sentBody', 'sentBody',
'receivedHeaders', 'receivedHeaders',
'receivedBody', 'receivedBody',
'disconnect', 'disconnect',
); );
// }}} // }}}
// __construct() {{{ // __construct() {{{
/** /**
* Constructor. * Constructor.
* *
* @param mixed $target Can be a file path (default: php://output), a resource, * @param mixed $target Can be a file path (default: php://output), a resource,
* or an instance of the PEAR Log class. * or an instance of the PEAR Log class.
* @param array $events Array of events to listen to (default: all events) * @param array $events Array of events to listen to (default: all events)
* *
* @return void * @return void
*/ */
public function __construct($target = 'php://output', array $events = array()) public function __construct($target = 'php://output', array $events = array())
{ {
if (!empty($events)) { if (!empty($events)) {
$this->events = $events; $this->events = $events;
} }
if (is_resource($target) || $target instanceof Log) { if (is_resource($target) || $target instanceof Log) {
$this->target = $target; $this->target = $target;
} elseif (false === ($this->target = @fopen($target, 'ab'))) { } elseif (false === ($this->target = @fopen($target, 'ab'))) {
throw new HTTP_Request2_Exception("Unable to open '{$target}'"); throw new HTTP_Request2_Exception("Unable to open '{$target}'");
} }
} }
// }}} // }}}
// update() {{{ // update() {{{
/** /**
* Called when the request notifies us of an event. * Called when the request notifies us of an event.
* *
* @param HTTP_Request2 $subject The HTTP_Request2 instance * @param HTTP_Request2 $subject The HTTP_Request2 instance
* *
* @return void * @return void
*/ */
public function update(SplSubject $subject) public function update(SplSubject $subject)
{ {
$event = $subject->getLastEvent(); $event = $subject->getLastEvent();
if (!in_array($event['name'], $this->events)) { if (!in_array($event['name'], $this->events)) {
return; return;
} }
switch ($event['name']) { switch ($event['name']) {
case 'connect': case 'connect':
$this->log('* Connected to ' . $event['data']); $this->log('* Connected to ' . $event['data']);
break; break;
case 'sentHeaders': case 'sentHeaders':
$headers = explode("\r\n", $event['data']); $headers = explode("\r\n", $event['data']);
array_pop($headers); array_pop($headers);
foreach ($headers as $header) { foreach ($headers as $header) {
$this->log('> ' . $header); $this->log('> ' . $header);
} }
break; break;
case 'sentBody': case 'sentBody':
$this->log('> ' . $event['data'] . ' byte(s) sent'); $this->log('> ' . $event['data'] . ' byte(s) sent');
break; break;
case 'receivedHeaders': case 'receivedHeaders':
$this->log(sprintf( $this->log(sprintf(
'< HTTP/%s %s %s', $event['data']->getVersion(), '< HTTP/%s %s %s', $event['data']->getVersion(),
$event['data']->getStatus(), $event['data']->getReasonPhrase() $event['data']->getStatus(), $event['data']->getReasonPhrase()
)); ));
$headers = $event['data']->getHeader(); $headers = $event['data']->getHeader();
foreach ($headers as $key => $val) { foreach ($headers as $key => $val) {
$this->log('< ' . $key . ': ' . $val); $this->log('< ' . $key . ': ' . $val);
} }
$this->log('< '); $this->log('< ');
break; break;
case 'receivedBody': case 'receivedBody':
$this->log($event['data']->getBody()); $this->log($event['data']->getBody());
break; break;
case 'disconnect': case 'disconnect':
$this->log('* Disconnected'); $this->log('* Disconnected');
break; break;
} }
} }
// }}} // }}}
// log() {{{ // log() {{{
/** /**
* Logs the given message to the configured target. * Logs the given message to the configured target.
* *
* @param string $message Message to display * @param string $message Message to display
* *
* @return void * @return void
*/ */
protected function log($message) protected function log($message)
{ {
if ($this->target instanceof Log) { if ($this->target instanceof Log) {
$this->target->debug($message); $this->target->debug($message);
} elseif (is_resource($this->target)) { } elseif (is_resource($this->target)) {
fwrite($this->target, $message . "\r\n"); fwrite($this->target, $message . "\r\n");
} }
} }
// }}} // }}}
} }
?> ?>

View File

@ -0,0 +1,265 @@
<?php
/**
* An observer that saves response body to stream, possibly uncompressing it
*
* PHP version 5
*
* LICENSE
*
* This source file is subject to BSD 3-Clause License that is bundled
* with this package in the file LICENSE and available at the URL
* https://raw.github.com/pear/HTTP_Request2/trunk/docs/LICENSE
*
* @category HTTP
* @package HTTP_Request2
* @author Delian Krustev <krustev@krustev.net>
* @author Alexey Borzov <avb@php.net>
* @copyright 2008-2016 Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
* @link http://pear.php.net/package/HTTP_Request2
*/
require_once 'HTTP/Request2/Response.php';
/**
* An observer that saves response body to stream, possibly uncompressing it
*
* This Observer is written in compliment to pear's HTTP_Request2 in order to
* avoid reading the whole response body in memory. Instead it writes the body
* to a stream. If the body is transferred with content-encoding set to
* "deflate" or "gzip" it is decoded on the fly.
*
* The constructor accepts an already opened (for write) stream (file_descriptor).
* If the response is deflate/gzip encoded a "zlib.inflate" filter is applied
* to the stream. When the body has been read from the request and written to
* the stream ("receivedBody" event) the filter is removed from the stream.
*
* The "zlib.inflate" filter works fine with pure "deflate" encoding. It does
* not understand the "deflate+zlib" and "gzip" headers though, so they have to
* be removed prior to being passed to the stream. This is done in the "update"
* method.
*
* It is also possible to limit the size of written extracted bytes by passing
* "max_bytes" to the constructor. This is important because e.g. 1GB of
* zeroes take about a MB when compressed.
*
* Exceptions are being thrown if data could not be written to the stream or
* the written bytes have already exceeded the requested maximum. If the "gzip"
* header is malformed or could not be parsed an exception will be thrown too.
*
* Example usage follows:
*
* <code>
* require_once 'HTTP/Request2.php';
* require_once 'HTTP/Request2/Observer/UncompressingDownload.php';
*
* #$inPath = 'http://carsten.codimi.de/gzip.yaws/daniels.html';
* #$inPath = 'http://carsten.codimi.de/gzip.yaws/daniels.html?deflate=on';
* $inPath = 'http://carsten.codimi.de/gzip.yaws/daniels.html?deflate=on&zlib=on';
* #$outPath = "/dev/null";
* $outPath = "delme";
*
* $stream = fopen($outPath, 'wb');
* if (!$stream) {
* throw new Exception('fopen failed');
* }
*
* $request = new HTTP_Request2(
* $inPath,
* HTTP_Request2::METHOD_GET,
* array(
* 'store_body' => false,
* 'connect_timeout' => 5,
* 'timeout' => 10,
* 'ssl_verify_peer' => true,
* 'ssl_verify_host' => true,
* 'ssl_cafile' => null,
* 'ssl_capath' => '/etc/ssl/certs',
* 'max_redirects' => 10,
* 'follow_redirects' => true,
* 'strict_redirects' => false
* )
* );
*
* $observer = new HTTP_Request2_Observer_UncompressingDownload($stream, 9999999);
* $request->attach($observer);
*
* $response = $request->send();
*
* fclose($stream);
* echo "OK\n";
* </code>
*
* @category HTTP
* @package HTTP_Request2
* @author Delian Krustev <krustev@krustev.net>
* @author Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
* @version Release: 2.3.0
* @link http://pear.php.net/package/HTTP_Request2
*/
class HTTP_Request2_Observer_UncompressingDownload implements SplObserver
{
/**
* The stream to write response body to
* @var resource
*/
private $_stream;
/**
* zlib.inflate filter possibly added to stream
* @var resource
*/
private $_streamFilter;
/**
* The value of response's Content-Encoding header
* @var string
*/
private $_encoding;
/**
* Whether the observer is still waiting for gzip/deflate header
* @var bool
*/
private $_processingHeader = true;
/**
* Starting position in the stream observer writes to
* @var int
*/
private $_startPosition = 0;
/**
* Maximum bytes to write
* @var int|null
*/
private $_maxDownloadSize;
/**
* Whether response being received is a redirect
* @var bool
*/
private $_redirect = false;
/**
* Accumulated body chunks that may contain (gzip) header
* @var string
*/
private $_possibleHeader = '';
/**
* Class constructor
*
* Note that there might be problems with max_bytes and files bigger
* than 2 GB on 32bit platforms
*
* @param resource $stream a stream (or file descriptor) opened for writing.
* @param int $maxDownloadSize maximum bytes to write
*/
public function __construct($stream, $maxDownloadSize = null)
{
$this->_stream = $stream;
if ($maxDownloadSize) {
$this->_maxDownloadSize = $maxDownloadSize;
$this->_startPosition = ftell($this->_stream);
}
}
/**
* Called when the request notifies us of an event.
*
* @param SplSubject $request The HTTP_Request2 instance
*
* @return void
* @throws HTTP_Request2_MessageException
*/
public function update(SplSubject $request)
{
/* @var $request HTTP_Request2 */
$event = $request->getLastEvent();
$encoded = false;
/* @var $event['data'] HTTP_Request2_Response */
switch ($event['name']) {
case 'receivedHeaders':
$this->_processingHeader = true;
$this->_redirect = $event['data']->isRedirect();
$this->_encoding = strtolower($event['data']->getHeader('content-encoding'));
$this->_possibleHeader = '';
break;
case 'receivedEncodedBodyPart':
if (!$this->_streamFilter
&& ($this->_encoding === 'deflate' || $this->_encoding === 'gzip')
) {
$this->_streamFilter = stream_filter_append(
$this->_stream, 'zlib.inflate', STREAM_FILTER_WRITE
);
}
$encoded = true;
// fall-through is intentional
case 'receivedBodyPart':
if ($this->_redirect) {
break;
}
if (!$encoded || !$this->_processingHeader) {
$bytes = fwrite($this->_stream, $event['data']);
} else {
$offset = 0;
$this->_possibleHeader .= $event['data'];
if ('deflate' === $this->_encoding) {
if (2 > strlen($this->_possibleHeader)) {
break;
}
$header = unpack('n', substr($this->_possibleHeader, 0, 2));
if (0 == $header[1] % 31) {
$offset = 2;
}
} elseif ('gzip' === $this->_encoding) {
if (10 > strlen($this->_possibleHeader)) {
break;
}
try {
$offset = HTTP_Request2_Response::parseGzipHeader($this->_possibleHeader, false);
} catch (HTTP_Request2_MessageException $e) {
// need more data?
if (false !== strpos($e->getMessage(), 'data too short')) {
break;
}
throw $e;
}
}
$this->_processingHeader = false;
$bytes = fwrite($this->_stream, substr($this->_possibleHeader, $offset));
}
if (false === $bytes) {
throw new HTTP_Request2_MessageException('fwrite failed.');
}
if ($this->_maxDownloadSize
&& ftell($this->_stream) - $this->_startPosition > $this->_maxDownloadSize
) {
throw new HTTP_Request2_MessageException(sprintf(
'Body length limit (%d bytes) reached',
$this->_maxDownloadSize
));
}
break;
case 'receivedBody':
if ($this->_streamFilter) {
stream_filter_remove($this->_streamFilter);
$this->_streamFilter = null;
}
break;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,135 +1,135 @@
<?php <?php
/** /**
* SOCKS5 proxy connection class * SOCKS5 proxy connection class
* *
* PHP version 5 * PHP version 5
* *
* LICENSE * LICENSE
* *
* This source file is subject to BSD 3-Clause License that is bundled * This source file is subject to BSD 3-Clause License that is bundled
* with this package in the file LICENSE and available at the URL * with this package in the file LICENSE and available at the URL
* https://raw.github.com/pear/HTTP_Request2/trunk/docs/LICENSE * https://raw.github.com/pear/HTTP_Request2/trunk/docs/LICENSE
* *
* @category HTTP * @category HTTP
* @package HTTP_Request2 * @package HTTP_Request2
* @author Alexey Borzov <avb@php.net> * @author Alexey Borzov <avb@php.net>
* @copyright 2008-2014 Alexey Borzov <avb@php.net> * @copyright 2008-2016 Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License * @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
* @link http://pear.php.net/package/HTTP_Request2 * @link http://pear.php.net/package/HTTP_Request2
*/ */
/** Socket wrapper class used by Socket Adapter */ /** Socket wrapper class used by Socket Adapter */
require_once 'HTTP/Request2/SocketWrapper.php'; require_once 'HTTP/Request2/SocketWrapper.php';
/** /**
* SOCKS5 proxy connection class (used by Socket Adapter) * SOCKS5 proxy connection class (used by Socket Adapter)
* *
* @category HTTP * @category HTTP
* @package HTTP_Request2 * @package HTTP_Request2
* @author Alexey Borzov <avb@php.net> * @author Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License * @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
* @version Release: 2.2.1 * @version Release: 2.3.0
* @link http://pear.php.net/package/HTTP_Request2 * @link http://pear.php.net/package/HTTP_Request2
* @link http://pear.php.net/bugs/bug.php?id=19332 * @link http://pear.php.net/bugs/bug.php?id=19332
* @link http://tools.ietf.org/html/rfc1928 * @link http://tools.ietf.org/html/rfc1928
*/ */
class HTTP_Request2_SOCKS5 extends HTTP_Request2_SocketWrapper class HTTP_Request2_SOCKS5 extends HTTP_Request2_SocketWrapper
{ {
/** /**
* Constructor, tries to connect and authenticate to a SOCKS5 proxy * Constructor, tries to connect and authenticate to a SOCKS5 proxy
* *
* @param string $address Proxy address, e.g. 'tcp://localhost:1080' * @param string $address Proxy address, e.g. 'tcp://localhost:1080'
* @param int $timeout Connection timeout (seconds) * @param int $timeout Connection timeout (seconds)
* @param array $contextOptions Stream context options * @param array $contextOptions Stream context options
* @param string $username Proxy user name * @param string $username Proxy user name
* @param string $password Proxy password * @param string $password Proxy password
* *
* @throws HTTP_Request2_LogicException * @throws HTTP_Request2_LogicException
* @throws HTTP_Request2_ConnectionException * @throws HTTP_Request2_ConnectionException
* @throws HTTP_Request2_MessageException * @throws HTTP_Request2_MessageException
*/ */
public function __construct( public function __construct(
$address, $timeout = 10, array $contextOptions = array(), $address, $timeout = 10, array $contextOptions = array(),
$username = null, $password = null $username = null, $password = null
) { ) {
parent::__construct($address, $timeout, $contextOptions); parent::__construct($address, $timeout, $contextOptions);
if (strlen($username)) { if (strlen($username)) {
$request = pack('C4', 5, 2, 0, 2); $request = pack('C4', 5, 2, 0, 2);
} else { } else {
$request = pack('C3', 5, 1, 0); $request = pack('C3', 5, 1, 0);
} }
$this->write($request); $this->write($request);
$response = unpack('Cversion/Cmethod', $this->read(3)); $response = unpack('Cversion/Cmethod', $this->read(3));
if (5 != $response['version']) { if (5 != $response['version']) {
throw new HTTP_Request2_MessageException( throw new HTTP_Request2_MessageException(
'Invalid version received from SOCKS5 proxy: ' . $response['version'], 'Invalid version received from SOCKS5 proxy: ' . $response['version'],
HTTP_Request2_Exception::MALFORMED_RESPONSE HTTP_Request2_Exception::MALFORMED_RESPONSE
); );
} }
switch ($response['method']) { switch ($response['method']) {
case 2: case 2:
$this->performAuthentication($username, $password); $this->performAuthentication($username, $password);
case 0: case 0:
break; break;
default: default:
throw new HTTP_Request2_ConnectionException( throw new HTTP_Request2_ConnectionException(
"Connection rejected by proxy due to unsupported auth method" "Connection rejected by proxy due to unsupported auth method"
); );
} }
} }
/** /**
* Performs username/password authentication for SOCKS5 * Performs username/password authentication for SOCKS5
* *
* @param string $username Proxy user name * @param string $username Proxy user name
* @param string $password Proxy password * @param string $password Proxy password
* *
* @throws HTTP_Request2_ConnectionException * @throws HTTP_Request2_ConnectionException
* @throws HTTP_Request2_MessageException * @throws HTTP_Request2_MessageException
* @link http://tools.ietf.org/html/rfc1929 * @link http://tools.ietf.org/html/rfc1929
*/ */
protected function performAuthentication($username, $password) protected function performAuthentication($username, $password)
{ {
$request = pack('C2', 1, strlen($username)) . $username $request = pack('C2', 1, strlen($username)) . $username
. pack('C', strlen($password)) . $password; . pack('C', strlen($password)) . $password;
$this->write($request); $this->write($request);
$response = unpack('Cvn/Cstatus', $this->read(3)); $response = unpack('Cvn/Cstatus', $this->read(3));
if (1 != $response['vn'] || 0 != $response['status']) { if (1 != $response['vn'] || 0 != $response['status']) {
throw new HTTP_Request2_ConnectionException( throw new HTTP_Request2_ConnectionException(
'Connection rejected by proxy due to invalid username and/or password' 'Connection rejected by proxy due to invalid username and/or password'
); );
} }
} }
/** /**
* Connects to a remote host via proxy * Connects to a remote host via proxy
* *
* @param string $remoteHost Remote host * @param string $remoteHost Remote host
* @param int $remotePort Remote port * @param int $remotePort Remote port
* *
* @throws HTTP_Request2_ConnectionException * @throws HTTP_Request2_ConnectionException
* @throws HTTP_Request2_MessageException * @throws HTTP_Request2_MessageException
*/ */
public function connect($remoteHost, $remotePort) public function connect($remoteHost, $remotePort)
{ {
$request = pack('C5', 0x05, 0x01, 0x00, 0x03, strlen($remoteHost)) $request = pack('C5', 0x05, 0x01, 0x00, 0x03, strlen($remoteHost))
. $remoteHost . pack('n', $remotePort); . $remoteHost . pack('n', $remotePort);
$this->write($request); $this->write($request);
$response = unpack('Cversion/Creply/Creserved', $this->read(1024)); $response = unpack('Cversion/Creply/Creserved', $this->read(1024));
if (5 != $response['version'] || 0 != $response['reserved']) { if (5 != $response['version'] || 0 != $response['reserved']) {
throw new HTTP_Request2_MessageException( throw new HTTP_Request2_MessageException(
'Invalid response received from SOCKS5 proxy', 'Invalid response received from SOCKS5 proxy',
HTTP_Request2_Exception::MALFORMED_RESPONSE HTTP_Request2_Exception::MALFORMED_RESPONSE
); );
} elseif (0 != $response['reply']) { } elseif (0 != $response['reply']) {
throw new HTTP_Request2_ConnectionException( throw new HTTP_Request2_ConnectionException(
"Unable to connect to {$remoteHost}:{$remotePort} through SOCKS5 proxy", "Unable to connect to {$remoteHost}:{$remotePort} through SOCKS5 proxy",
0, $response['reply'] 0, $response['reply']
); );
} }
} }
} }
?> ?>

View File

@ -1,297 +1,320 @@
<?php <?php
/** /**
* Socket wrapper class used by Socket Adapter * Socket wrapper class used by Socket Adapter
* *
* PHP version 5 * PHP version 5
* *
* LICENSE * LICENSE
* *
* This source file is subject to BSD 3-Clause License that is bundled * This source file is subject to BSD 3-Clause License that is bundled
* with this package in the file LICENSE and available at the URL * with this package in the file LICENSE and available at the URL
* https://raw.github.com/pear/HTTP_Request2/trunk/docs/LICENSE * https://raw.github.com/pear/HTTP_Request2/trunk/docs/LICENSE
* *
* @category HTTP * @category HTTP
* @package HTTP_Request2 * @package HTTP_Request2
* @author Alexey Borzov <avb@php.net> * @author Alexey Borzov <avb@php.net>
* @copyright 2008-2014 Alexey Borzov <avb@php.net> * @copyright 2008-2016 Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License * @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
* @link http://pear.php.net/package/HTTP_Request2 * @link http://pear.php.net/package/HTTP_Request2
*/ */
/** Exception classes for HTTP_Request2 package */ /** Exception classes for HTTP_Request2 package */
require_once 'HTTP/Request2/Exception.php'; require_once 'HTTP/Request2/Exception.php';
/** /**
* Socket wrapper class used by Socket Adapter * Socket wrapper class used by Socket Adapter
* *
* Needed to properly handle connection errors, global timeout support and * Needed to properly handle connection errors, global timeout support and
* similar things. Loosely based on Net_Socket used by older HTTP_Request. * similar things. Loosely based on Net_Socket used by older HTTP_Request.
* *
* @category HTTP * @category HTTP
* @package HTTP_Request2 * @package HTTP_Request2
* @author Alexey Borzov <avb@php.net> * @author Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License * @license http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
* @version Release: 2.2.1 * @version Release: 2.3.0
* @link http://pear.php.net/package/HTTP_Request2 * @link http://pear.php.net/package/HTTP_Request2
* @link http://pear.php.net/bugs/bug.php?id=19332 * @link http://pear.php.net/bugs/bug.php?id=19332
* @link http://tools.ietf.org/html/rfc1928 * @link http://tools.ietf.org/html/rfc1928
*/ */
class HTTP_Request2_SocketWrapper class HTTP_Request2_SocketWrapper
{ {
/** /**
* PHP warning messages raised during stream_socket_client() call * PHP warning messages raised during stream_socket_client() call
* @var array * @var array
*/ */
protected $connectionWarnings = array(); protected $connectionWarnings = array();
/** /**
* Connected socket * Connected socket
* @var resource * @var resource
*/ */
protected $socket; protected $socket;
/** /**
* Sum of start time and global timeout, exception will be thrown if request continues past this time * Sum of start time and global timeout, exception will be thrown if request continues past this time
* @var integer * @var integer
*/ */
protected $deadline; protected $deadline;
/** /**
* Global timeout value, mostly for exception messages * Global timeout value, mostly for exception messages
* @var integer * @var integer
*/ */
protected $timeout; protected $timeout;
/** /**
* Class constructor, tries to establish connection * Class constructor, tries to establish connection
* *
* @param string $address Address for stream_socket_client() call, * @param string $address Address for stream_socket_client() call,
* e.g. 'tcp://localhost:80' * e.g. 'tcp://localhost:80'
* @param int $timeout Connection timeout (seconds) * @param int $timeout Connection timeout (seconds)
* @param array $contextOptions Context options * @param array $contextOptions Context options
* *
* @throws HTTP_Request2_LogicException * @throws HTTP_Request2_LogicException
* @throws HTTP_Request2_ConnectionException * @throws HTTP_Request2_ConnectionException
*/ */
public function __construct($address, $timeout, array $contextOptions = array()) public function __construct($address, $timeout, array $contextOptions = array())
{ {
if (!empty($contextOptions) if (!empty($contextOptions)
&& !isset($contextOptions['socket']) && !isset($contextOptions['ssl']) && !isset($contextOptions['socket']) && !isset($contextOptions['ssl'])
) { ) {
// Backwards compatibility with 2.1.0 and 2.1.1 releases // Backwards compatibility with 2.1.0 and 2.1.1 releases
$contextOptions = array('ssl' => $contextOptions); $contextOptions = array('ssl' => $contextOptions);
} }
$context = stream_context_create(); if (isset($contextOptions['ssl'])) {
foreach ($contextOptions as $wrapper => $options) { $contextOptions['ssl'] += array(
foreach ($options as $name => $value) { // Using "Intermediate compatibility" cipher bundle from
if (!stream_context_set_option($context, $wrapper, $name, $value)) { // https://wiki.mozilla.org/Security/Server_Side_TLS
throw new HTTP_Request2_LogicException( 'ciphers' => 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:'
"Error setting '{$wrapper}' wrapper context option '{$name}'" . 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:'
); . 'DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:'
} . 'ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:'
} . 'ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:'
} . 'ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:'
set_error_handler(array($this, 'connectionWarningsHandler')); . 'ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:'
$this->socket = stream_socket_client( . 'DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:'
$address, $errno, $errstr, $timeout, STREAM_CLIENT_CONNECT, $context . 'DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:'
); . 'ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:'
restore_error_handler(); . 'AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:'
// if we fail to bind to a specified local address (see request #19515), . 'AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:'
// connection still succeeds, albeit with a warning. Throw an Exception . '!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA'
// with the warning text in this case as that connection is unlikely );
// to be what user wants and as Curl throws an error in similar case. if (version_compare(phpversion(), '5.4.13', '>=')) {
if ($this->connectionWarnings) { $contextOptions['ssl']['disable_compression'] = true;
if ($this->socket) { if (version_compare(phpversion(), '5.6', '>=')) {
fclose($this->socket); $contextOptions['ssl']['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT
} | STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
$error = $errstr ? $errstr : implode("\n", $this->connectionWarnings); }
throw new HTTP_Request2_ConnectionException( }
"Unable to connect to {$address}. Error: {$error}", 0, $errno }
); $context = stream_context_create();
} foreach ($contextOptions as $wrapper => $options) {
} foreach ($options as $name => $value) {
if (!stream_context_set_option($context, $wrapper, $name, $value)) {
/** throw new HTTP_Request2_LogicException(
* Destructor, disconnects socket "Error setting '{$wrapper}' wrapper context option '{$name}'"
*/ );
public function __destruct() }
{ }
fclose($this->socket); }
} set_error_handler(array($this, 'connectionWarningsHandler'));
$this->socket = stream_socket_client(
/** $address, $errno, $errstr, $timeout, STREAM_CLIENT_CONNECT, $context
* Wrapper around fread(), handles global request timeout );
* restore_error_handler();
* @param int $length Reads up to this number of bytes // if we fail to bind to a specified local address (see request #19515),
* // connection still succeeds, albeit with a warning. Throw an Exception
* @return string Data read from socket // with the warning text in this case as that connection is unlikely
* @throws HTTP_Request2_MessageException In case of timeout // to be what user wants and as Curl throws an error in similar case.
*/ if ($this->connectionWarnings) {
public function read($length) if ($this->socket) {
{ fclose($this->socket);
if ($this->deadline) { }
stream_set_timeout($this->socket, max($this->deadline - time(), 1)); $error = $errstr ? $errstr : implode("\n", $this->connectionWarnings);
} throw new HTTP_Request2_ConnectionException(
$data = fread($this->socket, $length); "Unable to connect to {$address}. Error: {$error}", 0, $errno
$this->checkTimeout(); );
return $data; }
} }
/** /**
* Reads until either the end of the socket or a newline, whichever comes first * Destructor, disconnects socket
* */
* Strips the trailing newline from the returned data, handles global public function __destruct()
* request timeout. Method idea borrowed from Net_Socket PEAR package. {
* fclose($this->socket);
* @param int $bufferSize buffer size to use for reading }
* @param int $localTimeout timeout value to use just for this call
* (used when waiting for "100 Continue" response) /**
* * Wrapper around fread(), handles global request timeout
* @return string Available data up to the newline (not including newline) *
* @throws HTTP_Request2_MessageException In case of timeout * @param int $length Reads up to this number of bytes
*/ *
public function readLine($bufferSize, $localTimeout = null) * @return string Data read from socket
{ * @throws HTTP_Request2_MessageException In case of timeout
$line = ''; */
while (!feof($this->socket)) { public function read($length)
if (null !== $localTimeout) { {
stream_set_timeout($this->socket, $localTimeout); if ($this->deadline) {
} elseif ($this->deadline) { stream_set_timeout($this->socket, max($this->deadline - time(), 1));
stream_set_timeout($this->socket, max($this->deadline - time(), 1)); }
} $data = fread($this->socket, $length);
$this->checkTimeout();
$line .= @fgets($this->socket, $bufferSize); return $data;
}
if (null === $localTimeout) {
$this->checkTimeout(); /**
* Reads until either the end of the socket or a newline, whichever comes first
} else { *
$info = stream_get_meta_data($this->socket); * Strips the trailing newline from the returned data, handles global
// reset socket timeout if we don't have request timeout specified, * request timeout. Method idea borrowed from Net_Socket PEAR package.
// prevents further calls failing with a bogus Exception *
if (!$this->deadline) { * @param int $bufferSize buffer size to use for reading
$default = (int)@ini_get('default_socket_timeout'); * @param int $localTimeout timeout value to use just for this call
stream_set_timeout($this->socket, $default > 0 ? $default : PHP_INT_MAX); * (used when waiting for "100 Continue" response)
} *
if ($info['timed_out']) { * @return string Available data up to the newline (not including newline)
throw new HTTP_Request2_MessageException( * @throws HTTP_Request2_MessageException In case of timeout
"readLine() call timed out", HTTP_Request2_Exception::TIMEOUT */
); public function readLine($bufferSize, $localTimeout = null)
} {
} $line = '';
if (substr($line, -1) == "\n") { while (!feof($this->socket)) {
return rtrim($line, "\r\n"); if (null !== $localTimeout) {
} stream_set_timeout($this->socket, $localTimeout);
} } elseif ($this->deadline) {
return $line; stream_set_timeout($this->socket, max($this->deadline - time(), 1));
} }
/** $line .= @fgets($this->socket, $bufferSize);
* Wrapper around fwrite(), handles global request timeout
* if (null === $localTimeout) {
* @param string $data String to be written $this->checkTimeout();
*
* @return int } else {
* @throws HTTP_Request2_MessageException $info = stream_get_meta_data($this->socket);
*/ // reset socket timeout if we don't have request timeout specified,
public function write($data) // prevents further calls failing with a bogus Exception
{ if (!$this->deadline) {
if ($this->deadline) { $default = (int)@ini_get('default_socket_timeout');
stream_set_timeout($this->socket, max($this->deadline - time(), 1)); stream_set_timeout($this->socket, $default > 0 ? $default : PHP_INT_MAX);
} }
$written = fwrite($this->socket, $data); if ($info['timed_out']) {
$this->checkTimeout(); throw new HTTP_Request2_MessageException(
// http://www.php.net/manual/en/function.fwrite.php#96951 "readLine() call timed out", HTTP_Request2_Exception::TIMEOUT
if ($written < strlen($data)) { );
throw new HTTP_Request2_MessageException('Error writing request'); }
} }
return $written; if (substr($line, -1) == "\n") {
} return rtrim($line, "\r\n");
}
/** }
* Tests for end-of-file on a socket return $line;
* }
* @return bool
*/ /**
public function eof() * Wrapper around fwrite(), handles global request timeout
{ *
return feof($this->socket); * @param string $data String to be written
} *
* @return int
/** * @throws HTTP_Request2_MessageException
* Sets request deadline */
* public function write($data)
* @param int $deadline Exception will be thrown if request continues {
* past this time if ($this->deadline) {
* @param int $timeout Original request timeout value, to use in stream_set_timeout($this->socket, max($this->deadline - time(), 1));
* Exception message }
*/ $written = fwrite($this->socket, $data);
public function setDeadline($deadline, $timeout) $this->checkTimeout();
{ // http://www.php.net/manual/en/function.fwrite.php#96951
$this->deadline = $deadline; if ($written < strlen($data)) {
$this->timeout = $timeout; throw new HTTP_Request2_MessageException('Error writing request');
} }
return $written;
/** }
* Turns on encryption on a socket
* /**
* @throws HTTP_Request2_ConnectionException * Tests for end-of-file on a socket
*/ *
public function enableCrypto() * @return bool
{ */
$modes = array( public function eof()
STREAM_CRYPTO_METHOD_TLS_CLIENT, {
STREAM_CRYPTO_METHOD_SSLv3_CLIENT, return feof($this->socket);
STREAM_CRYPTO_METHOD_SSLv23_CLIENT, }
STREAM_CRYPTO_METHOD_SSLv2_CLIENT
); /**
* Sets request deadline
foreach ($modes as $mode) { *
if (stream_socket_enable_crypto($this->socket, true, $mode)) { * @param int $deadline Exception will be thrown if request continues
return; * past this time
} * @param int $timeout Original request timeout value, to use in
} * Exception message
throw new HTTP_Request2_ConnectionException( */
'Failed to enable secure connection when connecting through proxy' public function setDeadline($deadline, $timeout)
); {
} $this->deadline = $deadline;
$this->timeout = $timeout;
/** }
* Throws an Exception if stream timed out
* /**
* @throws HTTP_Request2_MessageException * Turns on encryption on a socket
*/ *
protected function checkTimeout() * @throws HTTP_Request2_ConnectionException
{ */
$info = stream_get_meta_data($this->socket); public function enableCrypto()
if ($info['timed_out'] || $this->deadline && time() > $this->deadline) { {
$reason = $this->deadline if (version_compare(phpversion(), '5.6', '<')) {
? "after {$this->timeout} second(s)" $cryptoMethod = STREAM_CRYPTO_METHOD_TLS_CLIENT;
: 'due to default_socket_timeout php.ini setting'; } else {
throw new HTTP_Request2_MessageException( $cryptoMethod = STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT
"Request timed out {$reason}", HTTP_Request2_Exception::TIMEOUT | STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
); }
}
} if (!stream_socket_enable_crypto($this->socket, true, $cryptoMethod)) {
throw new HTTP_Request2_ConnectionException(
/** 'Failed to enable secure connection when connecting through proxy'
* Error handler to use during stream_socket_client() call );
* }
* One stream_socket_client() call may produce *multiple* PHP warnings }
* (especially OpenSSL-related), we keep them in an array to later use for
* the message of HTTP_Request2_ConnectionException /**
* * Throws an Exception if stream timed out
* @param int $errno error level *
* @param string $errstr error message * @throws HTTP_Request2_MessageException
* */
* @return bool protected function checkTimeout()
*/ {
protected function connectionWarningsHandler($errno, $errstr) $info = stream_get_meta_data($this->socket);
{ if ($info['timed_out'] || $this->deadline && time() > $this->deadline) {
if ($errno & E_WARNING) { $reason = $this->deadline
array_unshift($this->connectionWarnings, $errstr); ? "after {$this->timeout} second(s)"
} : 'due to default_socket_timeout php.ini setting';
return true; throw new HTTP_Request2_MessageException(
} "Request timed out {$reason}", HTTP_Request2_Exception::TIMEOUT
} );
?> }
}
/**
* Error handler to use during stream_socket_client() call
*
* One stream_socket_client() call may produce *multiple* PHP warnings
* (especially OpenSSL-related), we keep them in an array to later use for
* the message of HTTP_Request2_ConnectionException
*
* @param int $errno error level
* @param string $errstr error message
*
* @return bool
*/
protected function connectionWarningsHandler($errno, $errstr)
{
if ($errno & E_WARNING) {
array_unshift($this->connectionWarnings, $errstr);
}
return true;
}
}
?>