Skip to content

Commit

Permalink
Add some protection for EasyHandle
Browse files Browse the repository at this point in the history
  • Loading branch information
phansys committed Apr 29, 2019
1 parent 9441d79 commit a1d7847
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 27 deletions.
4 changes: 2 additions & 2 deletions src/Handler/CurlFactory.php
Expand Up @@ -101,7 +101,7 @@ public static function finish(
self::invokeStats($easy);
}

if (!$easy->response || $easy->errno) {
if (!$easy->response || CURLE_OK !== $easy->errno) {
return self::finishError($handler, $easy, $factory);
}

Expand Down Expand Up @@ -147,7 +147,7 @@ private static function finishError(

// Retry when nothing is present or when curl failed to rewind.
if (empty($easy->options['_err_message'])
&& (!$easy->errno || $easy->errno == 65)
&& (!$easy->errno || $easy->errno == /* CURLE_SEND_FAIL_REWIND */ 65)
) {
return self::retryFailedRewind($handler, $easy, $ctx);
}
Expand Down
79 changes: 58 additions & 21 deletions src/Handler/EasyHandle.php
Expand Up @@ -9,33 +9,45 @@
/**
* Represents a cURL easy handle and the data it populates.
*
* @property resource $handle resource cURL resource
* @property StreamInterface $sink Where data is being written
* @property array $headers Received HTTP headers so far
* @property ResponseInterface $response Received response (if any)
* @property RequestInterface $request Request being sent
* @property array $options Request options
* @property int $errno int cURL error number
* @property \Exception $onHeadersException Exception during on_headers (if any)
*
* @internal
*/
final class EasyHandle
{
/** @var resource cURL resource */
public $handle;
private $handle;

/** @var StreamInterface Where data is being written */
public $sink;
private $sink;

/** @var array Received HTTP headers so far */
private $headers = [];

/** @var ResponseInterface Received response (if any) */
private $response;

/** @var RequestInterface Request being sent */
public $request;
private $request;

/** @var array Request options */
public $options = [];
private $options = [];

/** @var int cURL error number (if any) */
private $errno = CURLE_OK;

/** @var array Received HTTP headers so far */
private $headers = [];

/** @var \Exception Exception during on_headers (if any) */
private $onHeadersException;

/** @var ResponseInterface Received response (if any) */
private $response;
/** @var bool Tells if the EasyHandle has been initialized */
private $initialized = false;

/**
* Attach a response to the easy handle based on the received headers.
Expand Down Expand Up @@ -84,33 +96,58 @@ public function createResponse()

public function &__get($name)
{
if (in_array($name, ['errno', 'headers', 'onHeadersException', 'response'], true)) {
if (('handle' !== $name && property_exists($this, $name)) || $this->initialized && isset($this->handle)) {
return $this->{$name};
}

$msg = $name === 'handle'
? 'The EasyHandle has been released'
? 'The EasyHandle '.($this->initialized ? 'has been released' : 'is not initialized')
: sprintf('Undefined property: %s::$%s', __CLASS__, $name);
throw new \BadMethodCallException($msg);
}

public function __set($name, $value)
{
if (in_array($name, ['errno', 'headers', 'onHeadersException', 'response'], true)) {
if ('response' === $name) {
// BC: Change to `\Error` when bumping PHP version to ^7.0
throw new \LogicException(sprintf('Cannot set private property %s::$%s', __CLASS__, $name));
}
if ($this->initialized && !isset($this->handle)) {
throw new \UnexpectedValueException('The EasyHandle has been released, please use a new EasyHandle instead');
}

if (!isset($this->handle) || !is_resource($this->handle) || 'curl' !== get_resource_type($this->handle)) {
throw new \LogicException(sprintf('Property %s::$%s could not be set when there isn\'t a valid handle', __CLASS__, $name));
}
if (in_array($name, ['response', 'initialized'], true)) {
throw new \LogicException(sprintf('Cannot set private property %s::$%s', __CLASS__, $name));
}

if (in_array($name, ['errno', 'handle', 'headers', 'onHeadersException'], true)) {
if ('handle' === $name) {
if (isset($this->handle)) {
throw new \UnexpectedValueException(sprintf('Property %s::$%s is already set, please use a new EasyHandle instead', __CLASS__, $name));
}

if (!is_resource($value) || 'curl' !== get_resource_type($value)) {
throw new \UnexpectedValueException(sprintf('Property %s::$%s can only accept a resource of type "curl"', __CLASS__, $name));
}

$this->initialized = true;
} else {
if (!isset($this->handle) || !is_resource($this->handle) || 'curl' !== get_resource_type($this->handle)) {
throw new \UnexpectedValueException(sprintf('Property %s::$%s could not be set when there isn\'t a valid handle', __CLASS__, $name));
}

if ('errno' === $name && CURLE_OK !== ($handleErrno = curl_errno($this->handle)) && $value !== $handleErrno) {
throw new \LogicException(sprintf('Property %s::$errno could not be set with %u since the handle is reporting error %u', __CLASS__, $value, $handleErrno));
if ('errno' === $name && CURLE_OK !== ($handleErrno = curl_errno($this->handle)) && $value !== $handleErrno) {
throw new \UnexpectedValueException(sprintf('Property %s::$errno could not be set with %u since the handle is reporting error %u', __CLASS__, $value, $handleErrno));
}
}
}

$this->{$name} = $value;
}

public function __unset($name)
{
if ('handle' !== $name) {
// BC: Change to `\Error` when bumping PHP version to ^7.0
throw new \LogicException(sprintf('Cannot unset private property %s::$%s', __CLASS__, $name));
}

unset($this->{$name});
}
}
65 changes: 61 additions & 4 deletions tests/Handler/EasyHandleTest.php
Expand Up @@ -12,15 +12,72 @@ class EasyHandleTest extends TestCase
{
/**
* @expectedException \BadMethodCallException
* @expectedExceptionMessage The EasyHandle has been released
* @expectedExceptionMessage The EasyHandle is not initialized
*/
public function testEnsuresHandleExists()
{
$easy = new EasyHandle;
$easy->handle;
}

/**
* @expectedException \BadMethodCallException
* @expectedExceptionMessage The EasyHandle has been released
*/
public function testGetReleasedHandle()
{
$easy = new EasyHandle;
$easy->handle = curl_init();
curl_close($easy->handle);
unset($easy->handle);
$easy->handle;
}

public function testGetPropertyAfterHandleRelease()
{
$easy = new EasyHandle;
$easy->handle = curl_init();
curl_close($easy->handle);
unset($easy->handle);

$this->assertSame([], $easy->options);
}

/**
* @expectedException \UnexpectedValueException
* @expectedExceptionMessage Property GuzzleHttp\Handler\EasyHandle::$handle can only accept a resource of type "curl"
*/
public function testSettingBadHandle()
{
$easy = new EasyHandle;
$easy->handle = null;
}

/**
* @expectedException \UnexpectedValueException
* @expectedExceptionMessage The EasyHandle has been released, please use a new EasyHandle instead
*/
public function testSettingPropertyOnReleasedHandle()
{
$easy = new EasyHandle;
$easy->handle = curl_init();
curl_close($easy->handle);
unset($easy->handle);
$easy->options = [];
}

/**
* @expectedException \UnexpectedValueException
* @expectedExceptionMessage Property GuzzleHttp\Handler\EasyHandle::$handle is already set, please use a new EasyHandle instead
*/
public function testSettingHandleTwice()
{
$easy = new EasyHandle;
$easy->handle = curl_init();
curl_close($easy->handle);
$easy->handle = curl_init();
}

public function testSettingProperties()
{
$handle = curl_init();
Expand All @@ -45,7 +102,7 @@ public function testSettingProperties()
}

/**
* @expectedException \LogicException
* @expectedException \UnexpectedValueException
* @expectedExceptionMessage Property GuzzleHttp\Handler\EasyHandle::$headers could not be set when there isn't a valid handle
*/
public function testSettingHeadersWithoutHandle()
Expand All @@ -67,7 +124,7 @@ public function testSettingErrnoWithHandle()
}

/**
* @expectedException \LogicException
* @expectedException \UnexpectedValueException
* @expectedExceptionMessage Property GuzzleHttp\Handler\EasyHandle::$errno could not be set with 0 since the handle is reporting error 3
*/
public function testChangingHandleErrno()
Expand All @@ -94,7 +151,7 @@ public function testSettingResponse()
}

/**
* @expectedException \LogicException
* @expectedException \UnexpectedValueException
* @expectedExceptionMessage Property GuzzleHttp\Handler\EasyHandle::$errno could not be set when there isn't a valid handle
*/
public function testSettingErrnoWithoutHandle()
Expand Down

0 comments on commit a1d7847

Please sign in to comment.