Completed
Push — master ( 00ed35...63fbb3 )
by Oscar
10:20
created

ReadResponse::parseRangeHeader()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 1 Features 1
Metric Value
c 4
b 1
f 1
dl 0
loc 11
rs 9.4286
cc 3
eloc 6
nc 3
nop 1
1
<?php
2
3
namespace Psr7Middlewares\Middleware;
4
5
use Psr7Middlewares\Utils;
6
use Psr7Middlewares\Middleware;
7
use Psr\Http\Message\ServerRequestInterface;
8
use Psr\Http\Message\ResponseInterface;
9
use Psr\Http\Message\StreamInterface;
10
11
/**
12
 * Middleware to read the response.
13
 */
14
class ReadResponse
15
{
16
    use Utils\FileTrait;
17
18
    /**
19
     * Execute the middleware.
20
     *
21
     * @param ServerRequestInterface $request
22
     * @param ResponseInterface      $response
23
     * @param callable               $next
24
     *
25
     * @return ResponseInterface
26
     */
27
    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next)
28
    {
29
        //If basePath does not match
30
        if (!$this->testBasePath($request->getUri()->getPath())) {
31
            return $next($request, $response);
32
        }
33
34
        //If the method is not allowed
35
        if ($request->getMethod() !== 'GET') {
36
            return $response->withStatus(405);
37
        }
38
39
        $body = Middleware::createStream();
40
41
        $file = $this->getFilename($request);
42
43
        //If the file does not exists, check if is gzipped
44
        if (!is_file($file)) {
45
            $file .= '.gz';
46
47
            if (EncodingNegotiator::getEncoding($request) !== 'gzip' || !is_file($file)) {
48
                return $response->withStatus(404);
49
            }
50
51
            $response = $response->withHeader('Content-Encoding', 'gzip');
52
        }
53
54
        self::readFile($file, $body);
55
56
        $response = $response->withBody($body);
57
58
        //Handle range header
59
        $response = $this->range($request, $response);
60
61
        return $next($request, $response);
62
    }
63
64
    /**
65
     * Reads a file and write in the body.
66
     * 
67
     * @param string          $file
68
     * @param StreamInterface $body
69
     */
70
    private static function readFile($file, StreamInterface $body)
71
    {
72
        if (filesize($file) <= 4096) {
73
            $body->write(file_get_contents($file));
74
75
            return;
76
        }
77
78
        $stream = fopen($file, 'r');
79
80
        while (!feof($stream)) {
81
            $body->write(fread($stream, 4096));
82
        }
83
84
        fclose($stream);
85
    }
86
87
    /**
88
     * Handle range requests.
89
     * 
90
     * @param ServerRequestInterface $request
91
     * @param ResponseInterface      $response
92
     * 
93
     * @return ResponseInterface
94
     */
95
    private static function range(ServerRequestInterface $request, ResponseInterface $response)
96
    {
97
        $response = $response->withHeader('Accept-Ranges', 'bytes');
98
99
        $range = $request->getHeaderLine('Range');
100
101
        if (empty($range) || !($range = self::parseRangeHeader($range))) {
102
            return $response;
103
        }
104
105
        list($first, $last) = $range;
106
        $size = $response->getBody()->getSize();
107
108
        if ($last === null) {
109
            $last = $size - 1;
110
        }
111
112
        return $response
113
            ->withStatus(206)
114
            ->withHeader('Content-Length', (string) ($last - $first + 1))
115
            ->withHeader('Content-Range', sprintf('bytes %d-%d/%d', $first, $last, $size));
116
    }
117
118
    /**
119
     * Parses a range header, for example: bytes=500-999.
120
     *
121
     * @param string $header
122
     *
123
     * @return false|array [first, last]
124
     */
125
    private static function parseRangeHeader($header)
126
    {
127
        if (preg_match('/bytes=(?P<first>\d+)-(?P<last>\d+)?/', $header, $matches)) {
128
            return [
129
                (int) $matches['first'],
130
                isset($matches['last']) ? (int) $matches['last'] : null,
131
            ];
132
        }
133
134
        return false;
135
    }
136
}
137