Skip to content
This repository was archived by the owner on Jan 29, 2020. It is now read-only.

Commit bd33eb6

Browse files
committed
Intercept exceptions from the ServerRequest factory
As noted in zendframework/zend-diactoros#171, the `ServerRequestFactory::fromGlobals()` method raises exceptions on malformed information, such as: - malformed file upload data - invalid HTTP protocol versions At the application level, we should catch these, and invoke the final handler with a 400 response when caught.
1 parent 29a6578 commit bd33eb6

5 files changed

+149
-4
lines changed

composer.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131
"zendframework/zend-expressive-aurarouter": "^1.0",
3232
"zendframework/zend-expressive-fastroute": "^1.0",
3333
"zendframework/zend-expressive-zendrouter": "^1.0",
34-
"zendframework/zend-servicemanager": "^2.6"
34+
"zendframework/zend-servicemanager": "^2.6",
35+
"mockery/mockery": "^0.9.5"
3536
},
3637
"autoload": {
3738
"psr-4": {

src/Application.php

+38-3
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,15 @@
1111

1212
use Interop\Container\ContainerInterface;
1313
use Interop\Container\Exception\ContainerException;
14+
use InvalidArgumentException;
1415
use Psr\Http\Message\ResponseInterface;
1516
use Psr\Http\Message\ServerRequestInterface;
17+
use UnexpectedValueException;
1618
use Zend\Diactoros\Request;
1719
use Zend\Diactoros\Response;
1820
use Zend\Diactoros\Response\EmitterInterface;
1921
use Zend\Diactoros\Response\SapiEmitter;
22+
use Zend\Diactoros\ServerRequest;
2023
use Zend\Diactoros\ServerRequestFactory;
2124
use Zend\Stratigility\MiddlewarePipe;
2225

@@ -99,6 +102,13 @@ class Application extends MiddlewarePipe implements Router\RouteResultSubjectInt
99102
*/
100103
private $routes = [];
101104

105+
/**
106+
* Internal; factory class for server request (for mocking)
107+
*
108+
* @var string
109+
*/
110+
private $serverRequestFactory = ServerRequestFactory::class;
111+
102112
/**
103113
* Constructor
104114
*
@@ -552,10 +562,21 @@ public function route($path, $middleware = null, array $methods = null, $name =
552562
*/
553563
public function run(ServerRequestInterface $request = null, ResponseInterface $response = null)
554564
{
555-
$request = $request ?: ServerRequestFactory::fromGlobals();
556-
$response = $response ?: new Response();
565+
$factory = $this->serverRequestFactory;
566+
567+
try {
568+
$request = $request ?: $factory::fromGlobals();
569+
} catch (InvalidArgumentException $e) {
570+
// Unable to parse uploaded files
571+
$this->emitMarshalServerRequestException($e);
572+
return;
573+
} catch (UnexpectedValueException $e) {
574+
// Invalid request method
575+
$this->emitMarshalServerRequestException($e);
576+
return;
577+
}
557578

558-
$response = $this($request, $response);
579+
$response = $this($request, $response ?: new Response());
559580

560581
$emitter = $this->getEmitter();
561582
$emitter->emit($response);
@@ -654,4 +675,18 @@ private function checkForDuplicateRoute($path, $methods = null)
654675
);
655676
}
656677
}
678+
679+
/**
680+
* @var \Exception|\Throwable $exception
681+
* @return void
682+
*/
683+
private function emitMarshalServerRequestException($exception)
684+
{
685+
$response = (new Response())
686+
->withStatus(400);
687+
$finalHandler = $this->getFinalHandler();
688+
$response = $finalHandler(new ServerRequest(), $response, $exception);
689+
$emitter = $this->getEmitter();
690+
$emitter->emit($response);
691+
}
657692
}

test/ApplicationTest.php

+57
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@
1212
use BadMethodCallException;
1313
use DomainException;
1414
use Interop\Container\ContainerInterface;
15+
use InvalidArgumentException;
16+
use Mockery;
1517
use PHPUnit_Framework_TestCase as TestCase;
1618
use Prophecy\Argument;
1719
use Psr\Http\Message\ResponseInterface;
1820
use ReflectionProperty;
1921
use RuntimeException;
2022
use SplQueue;
23+
use UnexpectedValueException;
2124
use Zend\Diactoros\Response;
2225
use Zend\Diactoros\Response\EmitterInterface;
2326
use Zend\Diactoros\Response\SapiEmitter;
@@ -622,4 +625,58 @@ public function testPipingNotInvokableErrorMiddlewareRisesException()
622625
$this->setExpectedException(InvalidMiddlewareException::class);
623626
$handler('foo', 'bar', 'baz', 'bat');
624627
}
628+
629+
public function invalidRequestExceptions()
630+
{
631+
return [
632+
'invalid file' => [
633+
TestAsset\ServerRequestFactoryProducingInvalidFiles::class,
634+
InvalidArgumentException::class,
635+
'Invalid value in files specification',
636+
],
637+
'invalid protocol version' => [
638+
TestAsset\ServerRequestFactoryProducingUnknownProtocolVersion::class,
639+
UnexpectedValueException::class,
640+
'Unrecognized protocol version (foo-bar)',
641+
],
642+
];
643+
}
644+
645+
/**
646+
* @dataProvider invalidRequestExceptions
647+
*/
648+
public function testRunInvokesFinalHandlerWhenServerRequestFactoryRaisesException(
649+
$serverRequestFactory,
650+
$expectedException,
651+
$message
652+
) {
653+
$requestFactory = Mockery::mock($serverRequestFactory)
654+
->shouldReceive('fromGlobals')
655+
->withNoArgs()
656+
->andThrow($expectedException, $message)
657+
->once()
658+
->getMock();
659+
660+
$finalHandler = function ($request, $response, $err = null) use ($expectedException, $message) {
661+
$this->assertEquals(400, $response->getStatusCode());
662+
$this->assertInstanceOf($expectedException, $err);
663+
$this->assertEquals($message, $err->getMessage());
664+
return $response;
665+
};
666+
667+
$emitter = $this->prophesize(EmitterInterface::class);
668+
$emitter->emit(Argument::that(function ($response) {
669+
$this->assertInstanceOf(ResponseInterface::class, $response, 'Emitter did not receive a response');
670+
$this->assertEquals(400, $response->getStatusCode());
671+
return true;
672+
}))->shouldBeCalled();
673+
674+
$app = new Application($this->router->reveal(), null, $finalHandler, $emitter->reveal());
675+
676+
$r = new ReflectionProperty($app, 'serverRequestFactory');
677+
$r->setAccessible(true);
678+
$r->setValue($app, $requestFactory);
679+
680+
$app->run();
681+
}
625682
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
/**
3+
* @link http://github.com/zendframework/zend-expresive for the canonical source repository
4+
* @copyright Copyright (c) 2016 Zend Technologies USA Inc. (http://www.zend.com)
5+
* @license http://framework.zend.com/license/new-bsd New BSD License
6+
*/
7+
8+
namespace ZendTest\Expressive\TestAsset;
9+
10+
/**
11+
* Required due to limitations in Mockery when mocking static methods.
12+
*
13+
* When mocking static methods, the first mock created "wins", meaning
14+
* its behavior is used for all subsequent calls. In order to test
15+
* multiple different behaviors, we need multiple classes that mimic
16+
* the method.
17+
*/
18+
class ServerRequestFactoryProducingInvalidFiles
19+
{
20+
/**
21+
* Method to mock as ServerRequest factory.
22+
*/
23+
public static function fromGlobals()
24+
{
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
/**
3+
* @link http://github.com/zendframework/zend-expresive for the canonical source repository
4+
* @copyright Copyright (c) 2016 Zend Technologies USA Inc. (http://www.zend.com)
5+
* @license http://framework.zend.com/license/new-bsd New BSD License
6+
*/
7+
8+
namespace ZendTest\Expressive\TestAsset;
9+
10+
/**
11+
* Required due to limitations in Mockery when mocking static methods.
12+
*
13+
* When mocking static methods, the first mock created "wins", meaning
14+
* its behavior is used for all subsequent calls. In order to test
15+
* multiple different behaviors, we need multiple classes that mimic
16+
* the method.
17+
*/
18+
class ServerRequestFactoryProducingUnknownProtocolVersion
19+
{
20+
/**
21+
* Method to mock as ServerRequest factory.
22+
*/
23+
public static function fromGlobals()
24+
{
25+
}
26+
}

0 commit comments

Comments
 (0)