Completed
Push — master ( 89aec4...213e17 )
by
unknown
16s queued 14s
created

PageViewProxy::handleHead()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 25
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 15
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 25
rs 9.7666
1
<?php
2
3
/**
4
 * (c) Kitodo. Key to digital objects e.V. <[email protected]>
5
 *
6
 * This file is part of the Kitodo and TYPO3 projects.
7
 *
8
 * @license GNU General Public License version 3 or later.
9
 * For the full copyright and license information, please read the
10
 * LICENSE.txt file that was distributed with this source code.
11
 */
12
13
namespace Kitodo\Dlf\Eid;
14
15
use Kitodo\Dlf\Common\Helper;
16
use Kitodo\Dlf\Common\StdOutStream;
17
use Psr\Http\Message\ResponseInterface;
18
use Psr\Http\Message\ServerRequestInterface;
19
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
20
use TYPO3\CMS\Core\Http\JsonResponse;
21
use TYPO3\CMS\Core\Http\RequestFactory;
22
use TYPO3\CMS\Core\Http\Response;
23
use TYPO3\CMS\Core\Utility\GeneralUtility;
24
25
/**
26
 * eID image proxy for plugin 'Page View' of the 'dlf' extension
27
 *
28
 * Supported query parameters:
29
 * - `url` (mandatory): The URL to be proxied
30
 * - `uHash` (mandatory): HMAC of the URL
31
 *
32
 * @author Alexander Bigga <[email protected]>
33
 * @package TYPO3
34
 * @subpackage dlf
35
 * @access public
36
 */
37
class PageViewProxy
38
{
39
    /**
40
     * @var RequestFactory
41
     */
42
    protected $requestFactory;
43
44
    /**
45
     * @var mixed
46
     */
47
    protected $extConf;
48
49
    public function __construct()
50
    {
51
        $this->requestFactory = GeneralUtility::makeInstance(RequestFactory::class);
52
        $this->extConf = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('dlf');
53
    }
54
55
    /**
56
     * Return a response that is derived from $response and contains CORS
57
     * headers to be sent to the client.
58
     *
59
     * @return ResponseInterface $response
60
     * @return ServerRequestInterface $request The incoming request.
61
     * @return ResponseInterface
62
     */
63
    protected function withCorsResponseHeaders(
64
        ResponseInterface $response,
65
        ServerRequestInterface $request
66
    ): ResponseInterface {
67
        $origin = (string) ($request->getHeaderLine('Origin') ? : '*');
68
69
        return $response
70
            ->withHeader('Access-Control-Allow-Methods', 'GET, OPTIONS, HEAD')
71
            ->withHeader('Access-Control-Allow-Origin', $origin)
72
            ->withHeader('Access-Control-Max-Age', '86400');
73
    }
74
75
    /**
76
     * Takes headers listed in $headerNames from $fromResponse, adds them to
77
     * $toResponse and returns the result.
78
     *
79
     * @param ResponseInterface $fromResponse
80
     * @param ResponseInterface $toResponse
81
     * @param array $headerNames
82
     * @return ResponseInterface
83
     */
84
    protected function copyHeaders(
85
        ResponseInterface $fromResponse,
86
        ResponseInterface $toResponse,
87
        array $headerNames
88
    ) {
89
        $result = $toResponse;
90
91
        foreach ($headerNames as $headerName) {
92
            $headerValues = $fromResponse->getHeader($headerName);
93
            // Don't include empty header field when not present
94
            if (!empty($headerValues)) {
95
                $result = $result->withAddedHeader($headerName, $headerValues);
96
            }
97
        }
98
99
        return $result;
100
    }
101
102
    /**
103
     * Handle an OPTIONS request.
104
     *
105
     * @param ServerRequestInterface $request
106
     * @return ResponseInterface
107
     */
108
    protected function handleOptions(ServerRequestInterface $request): ResponseInterface
109
    {
110
        // 204 No Content
111
        $response = GeneralUtility::makeInstance(Response::class)
112
            ->withStatus(204);
113
        return $this->withCorsResponseHeaders($response, $request);
114
    }
115
116
    /**
117
     * Handle an HEAD request.
118
     *
119
     * @param ServerRequestInterface $request
120
     * @return ResponseInterface
121
     */
122
    protected function handleHead(ServerRequestInterface $request): ResponseInterface
123
    {
124
        $queryParams = $request->getQueryParams();
125
126
        $url = (string) ($queryParams['url'] ?? '');
127
        try {
128
            $targetResponse = $this->requestFactory->request($url, 'HEAD', [
129
                'headers' => [
130
                    'User-Agent' => $this->extConf['useragent'] ?? 'Kitodo.Presentation Proxy',
131
                ]
132
            ]);
133
        } catch (\Exception $e) {
134
            return new JsonResponse(['message' => 'Could not fetch resource of given URL.'], 500);
135
        }
136
137
        $clientResponse = GeneralUtility::makeInstance(Response::class)
138
            ->withStatus($targetResponse->getStatusCode());
139
140
        $clientResponse = $this->copyHeaders($targetResponse, $clientResponse, [
141
            'Content-Length',
142
            'Content-Type',
143
            'Last-Modified',
144
        ]);
145
146
        return $this->withCorsResponseHeaders($clientResponse, $request);
147
    }
148
149
    /**
150
     * Handle a GET request.
151
     *
152
     * @param ServerRequestInterface $request
153
     * @return ResponseInterface
154
     */
155
    protected function handleGet(ServerRequestInterface $request): ResponseInterface
156
    {
157
        $queryParams = $request->getQueryParams();
158
159
        $url = (string) ($queryParams['url'] ?? '');
160
        if (!Helper::isValidHttpUrl($url)) {
161
            return new JsonResponse(['message' => 'Did not receive a valid URL.'], 400);
162
        }
163
164
        // get and verify the uHash
165
        $uHash = (string) ($queryParams['uHash'] ?? '');
166
        if (!hash_equals(GeneralUtility::hmac($url, 'PageViewProxy'), $uHash)) {
167
            return new JsonResponse(['message' => 'No valid uHash passed!'], 401);
168
        }
169
        try {
170
            $targetResponse = $this->requestFactory->request($url, 'GET', [
171
                'headers' => [
172
                    'User-Agent' => $this->extConf['useragent'] ?? 'Kitodo.Presentation Proxy',
173
                ],
174
175
                // For performance, don't download content up-front. Rather, we'll
176
                // download and upload simultaneously.
177
                // https://docs.guzzlephp.org/en/6.5/request-options.html#stream
178
                'stream' => true,
179
180
                // Don't throw exceptions when a non-success status code is
181
                // received. We handle these manually.
182
                'http_errors' => false,
183
            ]);
184
        } catch (\Exception $e) {
185
            return new JsonResponse(['message' => 'Could not fetch resource of given URL.'], 500);
186
        }
187
188
        $body = new StdOutStream($targetResponse->getBody());
189
190
        $clientResponse = GeneralUtility::makeInstance(Response::class)
191
            ->withStatus($targetResponse->getStatusCode())
192
            ->withBody($body);
193
194
        $clientResponse = $this->copyHeaders($targetResponse, $clientResponse, [
195
            'Content-Length',
196
            'Content-Type',
197
            'Last-Modified',
198
        ]);
199
200
        return $this->withCorsResponseHeaders($clientResponse, $request);
201
    }
202
203
    /**
204
     * The main method of the eID script
205
     *
206
     * @access public
207
     *
208
     * @param ServerRequestInterface $request
209
     * @return ResponseInterface
210
     */
211
    public function main(ServerRequestInterface $request)
212
    {
213
        switch ($request->getMethod()) {
214
            case 'OPTIONS':
215
                return $this->handleOptions($request);
216
217
            case 'GET':
218
                return $this->handleGet($request);
219
220
            case 'HEAD':
221
                return $this->handleHead($request);
222
223
            default:
224
                // 405 Method Not Allowed
225
                return GeneralUtility::makeInstance(Response::class)
226
                    ->withStatus(405);
227
        }
228
    }
229
}
230