Skip to content

Commit 37dd5a8

Browse files
committed
Merge branch 'feature/allow-404-redirect-to-https'
2 parents f5f7f11 + 5c40f1a commit 37dd5a8

File tree

12 files changed

+160
-14
lines changed

12 files changed

+160
-14
lines changed

.travis.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ before_script:
99
- mkdir -p build/logs
1010
- composer self-update
1111
- travis_retry composer install --prefer-source --no-interaction
12-
- composer require --dev phpstan/phpstan
1312
- composer dump-autoload -o
1413

1514
script:

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Features
2626
- [x] Enable/disable HTTP Strict Transport Security Header and set its value.
2727
- [x] Allow add `www.` prefix during redirection from http or already https.
2828
- [x] Allow remove `www.` prefix during redirection from http or already https.
29+
- [x] Force Https for 404 pages
2930

3031
Installation
3132
------------
@@ -87,6 +88,8 @@ return [
8788
// remove existing "www." prefix during redirection from http or already https
8889
// only works if previous's config 'add_www_prefix' => false
8990
'remove_www_prefix' => false,
91+
// Force Https for 404 pages
92+
'allow_404' => true,
9093
],
9194
// ...
9295
];

composer.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
],
2727
"require": {
2828
"php": "^7.1",
29+
"webmozart/assert": "^1.4",
2930
"zendframework/zend-console": "^2.5"
3031
},
3132
"conflict": {
@@ -35,11 +36,14 @@
3536
"require-dev": {
3637
"kahlan/kahlan": "^4.0",
3738
"php-coveralls/php-coveralls": "^2.1",
39+
"phpstan/phpstan": "^0.10.8",
40+
"phpstan/phpstan-webmozart-assert": "^0.10.0",
3841
"zendframework/zend-expressive": "^3.0",
3942
"zendframework/zend-mvc": "^3.0"
4043
},
4144
"config": {
42-
"bin-dir": "bin"
45+
"bin-dir": "bin",
46+
"sort-packages": true
4347
},
4448
"extra": {
4549
"zf": {

config/expressive-force-https-module.local.php.dist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ return [
1717
],
1818
'add_www_prefix' => false,
1919
'remove_www_prefix' => false,
20+
'allow_404' => true,
2021
],
2122

2223
'dependencies' => [

config/force-https-module.local.php.dist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ return [
1515
],
1616
'add_www_prefix' => false,
1717
'remove_www_prefix' => false,
18+
'allow_404' => true,
1819
],
1920
];

config/module.config.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
return [
66
'service_manager' => [
77
'factories' => [
8-
Listener\ForceHttps::class => Listener\ForceHttpsFactory::class,
8+
Listener\ForceHttps::class => Listener\ForceHttpsFactory::class,
9+
Listener\NotFoundLoggingListenerOnSharedEventManager::class => Listener\NotFoundLoggingListenerOnSharedEventManagerFactory::class,
910
],
1011
],
1112
'listeners' => [

phpstan.neon

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
includes:
2+
- vendor/phpstan/phpstan-webmozart-assert/extension.neon
3+

spec/Listener/ForceHttpsSpec.php

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
use Zend\EventManager\EventManagerInterface;
1111
use Zend\Http\PhpEnvironment\Request;
1212
use Zend\Http\PhpEnvironment\Response;
13-
use Zend\Mvc\Application;
1413
use Zend\Mvc\MvcEvent;
1514
use Zend\Router\RouteMatch;
1615
use Zend\Uri\Uri;
@@ -35,6 +34,7 @@
3534
$listener->attach($this->eventManager);
3635

3736
expect($this->eventManager)->not->toReceive('attach')->with(MvcEvent::EVENT_ROUTE, [$listener, 'forceHttpsScheme']);
37+
expect($this->eventManager)->not->toReceive('attach')->with(MvcEvent::EVENT_DISPATCH_ERROR, [$listener, 'forceHttpsScheme'], 1000);
3838

3939
});
4040

@@ -50,6 +50,7 @@
5050
$listener->attach($this->eventManager);
5151

5252
expect($this->eventManager)->not->toReceive('attach')->with(MvcEvent::EVENT_ROUTE, [$listener, 'forceHttpsScheme']);
53+
expect($this->eventManager)->not->toReceive('attach')->with(MvcEvent::EVENT_DISPATCH_ERROR, [$listener, 'forceHttpsScheme'], 1000);
5354

5455
});
5556

@@ -63,9 +64,11 @@
6364
]);
6465

6566
allow($this->eventManager)->toReceive('attach')->with(MvcEvent::EVENT_ROUTE, [$listener, 'forceHttpsScheme']);
67+
allow($this->eventManager)->toReceive('attach')->with(MvcEvent::EVENT_DISPATCH_ERROR, [$listener, 'forceHttpsScheme'], 1000);
6668
$listener->attach($this->eventManager);
6769

6870
expect($this->eventManager)->toReceive('attach')->with(MvcEvent::EVENT_ROUTE, [$listener, 'forceHttpsScheme']);
71+
expect($this->eventManager)->toReceive('attach')->with(MvcEvent::EVENT_DISPATCH_ERROR, [$listener, 'forceHttpsScheme'], 1000);
6972

7073
});
7174

@@ -106,6 +109,27 @@
106109

107110
});
108111

112+
it('not redirect if router not match', function () {
113+
114+
$listener = new ForceHttps([
115+
'enable' => true,
116+
'force_all_routes' => true,
117+
'force_specific_routes' => [],
118+
]);
119+
120+
allow($this->mvcEvent)->toReceive('getRouteMatch')->andReturn(null);
121+
allow($this->routeMatch)->toReceive('getMatchedRouteName')->andReturn('about');
122+
123+
allow($this->mvcEvent)->toReceive('getRequest', 'getUri', 'getScheme')->andReturn('https');
124+
allow($this->mvcEvent)->toReceive('getRequest', 'getUri', 'toString')->andReturn('https://www.example.com/about');
125+
allow($this->mvcEvent)->toReceive('getResponse')->andReturn($this->response);
126+
expect($this->mvcEvent)->toReceive('getResponse');
127+
128+
$listener->forceHttpsScheme($this->mvcEvent);
129+
expect($this->response)->not->toReceive('getHeaders');
130+
131+
});
132+
109133
});
110134

111135
context('on current scheme is http', function () {
@@ -129,6 +153,24 @@
129153
$listener->forceHttpsScheme($this->mvcEvent);
130154

131155
expect($this->mvcEvent)->toReceive('getResponse');
156+
expect($this->response)->not->toReceive('send');
157+
158+
});
159+
160+
it('not redirect on router not match', function () {
161+
162+
$listener = new ForceHttps([
163+
'enable' => true,
164+
]);
165+
166+
allow($this->mvcEvent)->toReceive('getRequest', 'getUri', 'getScheme')->andReturn('http');
167+
allow($this->mvcEvent)->toReceive('getRouteMatch')->andReturn(null);
168+
allow($this->mvcEvent)->toReceive('getResponse')->andReturn($this->response);
169+
170+
$listener->forceHttpsScheme($this->mvcEvent);
171+
172+
expect($this->mvcEvent)->toReceive('getResponse');
173+
expect($this->response)->not->toReceive('send');
132174

133175
});
134176

@@ -232,6 +274,35 @@
232274

233275
});
234276

277+
278+
it('redirect no router not match, but allow_404 is true', function () {
279+
280+
$listener = new ForceHttps([
281+
'enable' => true,
282+
'allow_404' => true,
283+
]);
284+
285+
allow($this->mvcEvent)->toReceive('getRequest')->andReturn($this->request);
286+
allow($this->request)->toReceive('getUri')->andReturn($this->uri);
287+
allow($this->uri)->toReceive('getScheme')->andReturn('http');
288+
allow($this->mvcEvent)->toReceive('getRouteMatch')->andReturn(null);
289+
allow($this->uri)->toReceive('setScheme')->with('https')->andReturn($this->uri);
290+
allow($this->uri)->toReceive('toString')->andReturn('https://example.com/404');
291+
allow($this->mvcEvent)->toReceive('getResponse')->andReturn($this->response);
292+
allow($this->response)->toReceive('setStatusCode')->with(308)->andReturn($this->response);
293+
allow($this->response)->toReceive('getHeaders', 'addHeaderLine')->with('Location', 'https://example.com/404');
294+
allow($this->response)->toReceive('send');
295+
296+
$closure = function () use ($listener) {
297+
$listener->forceHttpsScheme($this->mvcEvent);
298+
};
299+
expect($closure)->toThrow(new QuitException('Exit statement occurred', 0));
300+
301+
expect($this->mvcEvent)->toReceive('getResponse');
302+
expect($this->response)->toReceive('getHeaders', 'addHeaderLine')->with('Location', 'https://example.com/404');
303+
304+
});
305+
235306
it('redirect with www prefix with configurable "add_www_prefix" on force_all_routes', function () {
236307

237308
$listener = new ForceHttps([

spec/Middleware/ForceHttpsSpec.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949

5050
$match = RouteResult::fromRouteFailure(null);
5151
allow($this->router)->toReceive('match')->andReturn($match);
52+
allow($this->request)->toReceive('getUri', 'getScheme')->andReturn('http');
5253

5354
$listener = new ForceHttps(['enable' => true], $this->router);
5455

@@ -61,6 +62,29 @@
6162

6263
});
6364

65+
it('not redirect on router not match and config allow_404 is false', function () {
66+
67+
$match = RouteResult::fromRouteFailure(null);
68+
allow($this->router)->toReceive('match')->andReturn($match);
69+
allow($this->request)->toReceive('getUri', 'getScheme')->andReturn('http');
70+
71+
$listener = new ForceHttps(
72+
[
73+
'enable' => true,
74+
'allow_404' => false,
75+
],
76+
$this->router
77+
);
78+
79+
$handler = Double::instance(['implements' => RequestHandlerInterface::class]);
80+
allow($handler)->toReceive('handle')->with($this->request)->andReturn($this->response);
81+
82+
$listener->process($this->request, $handler);
83+
84+
expect($this->response)->not->toReceive('withStatus');
85+
86+
});
87+
6488
it('not redirect on https and match but no strict_transport_security config', function () {
6589

6690
$match = RouteResult::fromRoute(new Route('/about', Double::instance(['implements' => MiddlewareInterface::class])));
@@ -199,6 +223,33 @@
199223

200224
});
201225

226+
it('return Response with 308 status on http and not match, but allow_404 is true', function () {
227+
228+
$match = RouteResult::fromRouteFailure(null);
229+
230+
allow($this->router)->toReceive('match')->andReturn($match);
231+
allow($this->request)->toReceive('getUri', 'getScheme')->andReturn('http');
232+
allow($this->request)->toReceive('getUri', 'withScheme', '__toString')->andReturn('https://example.com/404');
233+
234+
$handler = Double::instance(['implements' => RequestHandlerInterface::class]);
235+
allow($handler)->toReceive('handle')->with($this->request)->andReturn($this->response);
236+
allow($this->response)->toReceive('withStatus')->with(308)->andReturn($this->response);
237+
allow($this->response)->toReceive('withHeader')->with('Location', 'https://example.com/404')->andReturn($this->response);
238+
239+
$listener = new ForceHttps(
240+
[
241+
'enable' => true,
242+
'allow_404' => true,
243+
],
244+
$this->router
245+
);
246+
$listener->process($this->request, $handler);
247+
248+
expect($this->response)->toReceive('withStatus')->with(308);
249+
expect($this->response)->toReceive('withHeader')->with('Location', 'https://example.com/404');
250+
251+
});
252+
202253
it('return Response with 308 status with include www prefix on http and match with configurable "add_www_prefix"', function () {
203254

204255
$match = RouteResult::fromRoute(new Route('/about', Double::instance(['implements' => MiddlewareInterface::class])));

src/HttpsTrait.php

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace ForceHttpsModule;
66

77
use Psr\Http\Message\ResponseInterface;
8+
use Webmozart\Assert\Assert;
89
use Zend\Expressive\Router\RouteResult;
910
use Zend\Http\PhpEnvironment\Response;
1011
use Zend\Router\RouteMatch;
@@ -25,10 +26,23 @@ private function isSchemeHttps(string $uriScheme) : bool
2526
/**
2627
* Check Config if is going to be forced to https.
2728
*
28-
* @param RouteMatch|RouteResult $match
29+
* @param RouteMatch|RouteResult|null $match
2930
*/
30-
private function isGoingToBeForcedToHttps($match) : bool
31+
private function isGoingToBeForcedToHttps($match = null) : bool
3132
{
33+
$is404 = $match === null || ($match instanceof RouteResult && $match->isFailure());
34+
if (isset($this->config['allow_404']) &&
35+
$this->config['allow_404'] === true &&
36+
$is404
37+
) {
38+
return true;
39+
}
40+
41+
if ($is404) {
42+
return false;
43+
}
44+
45+
Assert::notNull($match);
3246
if (! $this->config['force_all_routes'] &&
3347
! \in_array(
3448
$match->getMatchedRouteName(),
@@ -44,11 +58,11 @@ private function isGoingToBeForcedToHttps($match) : bool
4458
/**
4559
* Check if Setup Strict-Transport-Security need to be skipped.
4660
*
47-
* @param RouteMatch|RouteResult $match
48-
* @param Response|ResponseInterface $response
61+
* @param RouteMatch|RouteResult|null $match
62+
* @param Response|ResponseInterface $response
4963
*
5064
*/
51-
private function isSkippedHttpStrictTransportSecurity(string $uriScheme, $match, $response) : bool
65+
private function isSkippedHttpStrictTransportSecurity(string $uriScheme, $match = null, $response) : bool
5266
{
5367
return ! $this->isSchemeHttps($uriScheme) ||
5468
! $this->isGoingToBeForcedToHttps($match) ||

0 commit comments

Comments
 (0)