Completed
Push — master ( 035a24...9f3c5b )
by
unknown
26s queued 21s
created

PageViewProxy::copyHeaders()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 5
c 1
b 0
f 0
dl 0
loc 13
rs 10
cc 2
nc 2
nop 3
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\Plugin\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')
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
            $result = $result->withAddedHeader($headerName, $headerValues);
94
        }
95
96
        return $result;
97
    }
98
99
    /**
100
     * Handle an OPTIONS request.
101
     *
102
     * @param ServerRequestInterface $request
103
     * @return ResponseInterface
104
     */
105
    protected function handleOptions(ServerRequestInterface $request): ResponseInterface
106
    {
107
        // 204 No Content
108
        $response = GeneralUtility::makeInstance(Response::class)
109
            ->withStatus(204);
110
        return $this->withCorsResponseHeaders($response, $request);
111
    }
112
113
    /**
114
     * Handle a GET request.
115
     *
116
     * @param ServerRequestInterface $request
117
     * @return ResponseInterface
118
     */
119
    protected function handleGet(ServerRequestInterface $request): ResponseInterface
120
    {
121
        $queryParams = $request->getQueryParams();
122
123
        $url = (string) ($queryParams['url'] ?? '');
124
        if (!Helper::isValidHttpUrl($url)) {
125
            return new JsonResponse(['message' => 'Did not receive a valid URL.'], 400);
126
        }
127
128
        // get and verify the uHash
129
        $uHash = (string) ($queryParams['uHash'] ?? '');
130
        if (!hash_equals(GeneralUtility::hmac($url, 'PageViewProxy'), $uHash)) {
131
            return new JsonResponse(['message' => 'No valid uHash passed!'], 401);
132
        }
133
134
        try {
135
            $targetResponse = $this->requestFactory->request($url, 'GET', [
136
                'headers' => [
137
                    'User-Agent' => $this->extConf['useragent'] ?? 'Kitodo.Presentation Proxy',
138
                ],
139
140
                // For performance, don't download content up-front. Rather, we'll
141
                // download and upload simultaneously.
142
                // https://docs.guzzlephp.org/en/6.5/request-options.html#stream
143
                'stream' => true,
144
145
                // Don't throw exceptions when a non-success status code is
146
                // received. We handle these manually.
147
                'http_errors' => false,
148
            ]);
149
        } catch (\Exception $e) {
150
            return new JsonResponse(['message' => 'Could not fetch resource of given URL.'], 500);
151
        }
152
153
        $body = new StdOutStream($targetResponse->getBody());
154
155
        $clientResponse = GeneralUtility::makeInstance(Response::class)
156
            ->withStatus($targetResponse->getStatusCode())
157
            ->withBody($body);
158
159
        $clientResponse = $this->copyHeaders($targetResponse, $clientResponse, [
160
            'Content-Length',
161
            'Content-Type',
162
            'Last-Modified',
163
        ]);
164
165
        return $this->withCorsResponseHeaders($clientResponse, $request);
166
    }
167
168
    /**
169
     * The main method of the eID script
170
     *
171
     * @access public
172
     *
173
     * @param ServerRequestInterface $request
174
     * @return ResponseInterface
175
     */
176
    public function main(ServerRequestInterface $request)
177
    {
178
        switch ($request->getMethod()) {
179
            case 'OPTIONS':
180
                return $this->handleOptions($request);
181
182
            case 'GET':
183
                return $this->handleGet($request);
184
185
            default:
186
                // 405 Method Not Allowed
187
                return GeneralUtility::makeInstance(Response::class)
188
                    ->withStatus(405);
189
        }
190
    }
191
}
192