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 Jan 6, 2020
1 parent 236f01d commit 0975ccd
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 33 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 \Throwable Exception during on_headers (if any) */
public $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(): void

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});
}
}
81 changes: 71 additions & 10 deletions tests/Handler/EasyHandleTest.php
Expand Up @@ -9,17 +9,78 @@
*/
class EasyHandleTest extends TestCase
{
/**
* @expectedException \BadMethodCallException
* @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(): void
{
$easy = new EasyHandle;
$easy->handle = curl_init();
curl_close($easy->handle);
unset($easy->handle);

$this->expectException(\BadMethodCallException::class);
$this->expectExceptionMessage('The EasyHandle has been released');
$easy->handle;
}

public function testSettingProperties()
public function testGetPropertyAfterHandleRelease(): void
{
$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(): void
{
$easy = new EasyHandle;
$easy->handle = null;
}

/**
* @expectedException \UnexpectedValueException
* @expectedExceptionMessage The EasyHandle has been released, please use a new EasyHandle instead
*/
public function testSettingPropertyOnReleasedHandle(): void
{
$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(): void
{
$easy = new EasyHandle;
$easy->handle = curl_init();
curl_close($easy->handle);
$easy->handle = curl_init();
}

public function testSettingProperties(): void
{
$handle = curl_init();
$stream = new Psr7\Stream(fopen('php://temp', 'r'));
Expand All @@ -43,16 +104,16 @@ 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()
public function testSettingHeadersWithoutHandle(): void
{
$easy = new EasyHandle;
$easy->headers = [];
}

public function testSettingErrnoWithHandle()
public function testSettingErrnoWithHandle(): void
{
$easy = new EasyHandle;
$easy->handle = curl_init();
Expand All @@ -65,10 +126,10 @@ 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()
public function testChangingHandleErrno(): void
{
$easy = new EasyHandle;
$easy->handle = curl_init();
Expand All @@ -85,14 +146,14 @@ public function testChangingHandleErrno()
* @expectedException \LogicException
* @expectedExceptionMessage Cannot set private property GuzzleHttp\Handler\EasyHandle::$response
*/
public function testSettingResponse()
public function testSettingResponse(): void
{
$easy = new EasyHandle;
$easy->response = new Psr7\Response();
}

/**
* @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 All @@ -105,13 +166,13 @@ public function testSettingErrnoWithoutHandle()
* @expectedException \LogicException
* @expectedExceptionMessage Undefined property: GuzzleHttp\Handler\EasyHandle::$nonexistent
*/
public function testGetInvalidProperty()
public function testGetInvalidProperty(): void
{
$easy = new EasyHandle;
$easy->nonexistent;
}

public function testPropertyOverload()
public function testPropertyOverload(): void
{
$overloadedValue = 42;

Expand Down

0 comments on commit 0975ccd

Please sign in to comment.