Passed
Push — master ( 16e851...25995c )
by Daniel
09:34
created

ChangeDetectionMiddleware::process()   B

Complexity

Conditions 9
Paths 7

Size

Total Lines 34
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 17
nc 7
nop 2
dl 0
loc 34
rs 8.0555
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Control\Middleware;
4
5
use SilverStripe\Control\HTTPRequest;
6
use SilverStripe\Control\HTTPResponse;
7
use SilverStripe\Core\Injector\Injectable;
8
9
/**
10
 * Handles internal change detection via etag / ifmodifiedsince headers,
11
 * conditonally sending a 304 not modified if possible.
12
 */
13
class ChangeDetectionMiddleware implements HTTPMiddleware
14
{
15
    use Injectable;
16
17
    /**
18
     * Generate response for the given request
19
     *
20
     * @param HTTPRequest $request
21
     * @param callable $delegate
22
     * @return HTTPResponse
23
     */
24
    public function process(HTTPRequest $request, callable $delegate)
25
    {
26
        /** @var HTTPResponse $response */
27
        $response = $delegate($request);
28
        if (!$response) {
0 ignored issues
show
introduced by
$response is of type SilverStripe\Control\HTTPResponse, thus it always evaluated to true. If $response can have other possible types, add them to src/Control/Middleware/C...DetectionMiddleware.php:26
Loading history...
29
            return null;
30
        }
31
32
        // Ignore etag for no-store
33
        $cacheControl = $response->getHeader('Cache-Control');
34
        if ($cacheControl && strstr($cacheControl, 'no-store')) {
35
            return $response;
36
        }
37
38
        // Generate, assign, and conditionally check etag
39
        $etag = $this->generateETag($response);
40
        if ($etag) {
41
            $response->addHeader('ETag', $etag);
42
43
            // Check if we have a match
44
            $ifNoneMatch = $request->getHeader('If-None-Match');
45
            if ($ifNoneMatch === $etag) {
46
                return $this->sendNotModified($request, $response);
47
            }
48
        }
49
50
        // Check If-Modified-Since
51
        $ifModifiedSince = $request->getHeader('If-Modified-Since');
52
        $lastModified = $response->getHeader('Last-Modified');
53
        if ($ifModifiedSince && $lastModified && strtotime($ifModifiedSince) >= strtotime($lastModified)) {
54
            return $this->sendNotModified($request, $response);
55
        }
56
57
        return $response;
58
    }
59
60
    /**
61
     * @param HTTPResponse|string $response
62
     * @return string|false
63
     */
64
    protected function generateETag(HTTPResponse $response)
65
    {
66
        // Existing e-tag
67
        $etag = $response->getHeader('ETag');
68
        if ($etag) {
69
            return $etag;
70
        }
71
72
        // Generate etag from body
73
        return sprintf('"%s"', md5($response->getBody()));
74
    }
75
76
    /**
77
     * Sent not-modified response
78
     *
79
     * @param HTTPRequest $request
80
     * @param HTTPResponse $response
81
     * @return mixed
82
     */
83
    protected function sendNotModified(HTTPRequest $request, HTTPResponse $response)
84
    {
85
        // 304 is invalid for destructive requests
86
        if (in_array($request->httpMethod(), ['POST', 'DELETE', 'PUT'])) {
87
            $response->setStatusCode(412);
88
        } else {
89
            $response->setStatusCode(304);
90
        }
91
        $response->setBody('');
92
        return $response;
93
    }
94
}
95