[FEATURE] Introduce Request/Response based on PSR-7
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Tests / Unit / Http / RequestTest.php
1 <?php
2 namespace TYPO3\CMS\Core\Tests\Unit\Http;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Http\Uri;
18 use TYPO3\CMS\Core\Http\Request;
19 use TYPO3\CMS\Core\Http\Stream;
20
21 /**
22 * Testcase for \TYPO3\CMS\Core\Http\Request
23 *
24 * Adapted from https://github.com/phly/http/
25 */
26 class RequestTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
27
28 /**
29 * @var Request
30 */
31 protected $request;
32
33 public function setUp() {
34 $this->request = new Request();
35 }
36
37 /**
38 * @test
39 */
40 public function getMethodIsGetByDefault() {
41 $this->assertEquals('GET', $this->request->getMethod());
42 }
43
44 /**
45 * @test
46 */
47 public function getMethodMutatorReturnsCloneWithChangedMethod() {
48 $request = $this->request->withMethod('GET');
49 $this->assertNotSame($this->request, $request);
50 $this->assertEquals('GET', $request->getMethod());
51 }
52
53 /**
54 * @test
55 */
56 public function getUriIsNullByDefault() {
57 $this->assertNull($this->request->getUri());
58 }
59
60 /**
61 * @test
62 */
63 public function constructorRaisesExceptionForInvalidStream() {
64 $this->setExpectedException('InvalidArgumentException');
65 new Request(['TOTALLY INVALID']);
66 }
67
68 /**
69 * @test
70 */
71 public function withUriReturnsNewInstanceWithNewUri() {
72 $request = $this->request->withUri(new Uri('https://example.com:10082/foo/bar?baz=bat'));
73 $this->assertNotSame($this->request, $request);
74 $request2 = $request->withUri(new Uri('/baz/bat?foo=bar'));
75 $this->assertNotSame($this->request, $request2);
76 $this->assertNotSame($request, $request2);
77 $this->assertEquals('/baz/bat?foo=bar', (string) $request2->getUri());
78 }
79
80 /**
81 * @test
82 */
83 public function constructorCanAcceptAllMessageParts() {
84 $uri = new Uri('http://example.com/');
85 $body = new Stream('php://memory');
86 $headers = [
87 'x-foo' => ['bar'],
88 ];
89 $request = new Request(
90 $uri,
91 'POST',
92 $body,
93 $headers
94 );
95
96 $this->assertSame($uri, $request->getUri());
97 $this->assertEquals('POST', $request->getMethod());
98 $this->assertSame($body, $request->getBody());
99 $testHeaders = $request->getHeaders();
100 foreach ($headers as $key => $value) {
101 $this->assertArrayHasKey($key, $testHeaders);
102 $this->assertEquals($value, $testHeaders[$key]);
103 }
104 }
105
106 /**
107 * @return array
108 */
109 public function invalidRequestUriDataProvider() {
110 return [
111 'true' => [TRUE],
112 'false' => [FALSE],
113 'int' => [1],
114 'float' => [1.1],
115 'array' => [['http://example.com']],
116 'stdClass' => [(object) ['href' => 'http://example.com']],
117 ];
118 }
119
120 /**
121 * @dataProvider invalidRequestUriDataProvider
122 * @test
123 */
124 public function constructorRaisesExceptionForInvalidUri($uri) {
125 $this->setExpectedException('InvalidArgumentException', 'Invalid URI');
126 new Request($uri);
127 }
128
129 /**
130 * @return array
131 */
132 public function invalidRequestMethodDataProvider() {
133 return [
134 'true' => [TRUE],
135 'false' => [FALSE],
136 'int' => [1],
137 'float' => [1.1],
138 'bad-string' => ['BOGUS-METHOD'],
139 'array' => [['POST']],
140 'stdClass' => [(object) ['method' => 'POST']],
141 ];
142 }
143
144 /**
145 * @dataProvider invalidRequestMethodDataProvider
146 * @test
147 */
148 public function constructorRaisesExceptionForInvalidMethod($method) {
149 $this->setExpectedException('InvalidArgumentException', 'Unsupported HTTP method');
150 new Request(NULL, $method);
151 }
152
153 /**
154 * @return array
155 */
156 public function invalidRequestBodyDataProvider() {
157 return [
158 'true' => [TRUE],
159 'false' => [FALSE],
160 'int' => [1],
161 'float' => [1.1],
162 'array' => [['BODY']],
163 'stdClass' => [(object) ['body' => 'BODY']],
164 ];
165 }
166
167 /**
168 * @dataProvider invalidRequestBodyDataProvider
169 * @test
170 */
171 public function constructorRaisesExceptionForInvalidBody($body) {
172 $this->setExpectedException('InvalidArgumentException', 'stream');
173 new Request(NULL, NULL, $body);
174 }
175
176 /**
177 * @test
178 */
179 public function constructorIgnoresInvalidHeaders() {
180 $headers = [
181 ['INVALID'],
182 'x-invalid-null' => NULL,
183 'x-invalid-true' => TRUE,
184 'x-invalid-false' => FALSE,
185 'x-invalid-int' => 1,
186 'x-invalid-object' => (object) ['INVALID'],
187 'x-valid-string' => 'VALID',
188 'x-valid-array' => ['VALID'],
189 ];
190 $expected = [
191 'x-valid-string' => ['VALID'],
192 'x-valid-array' => ['VALID'],
193 ];
194 $request = new Request(NULL, NULL, 'php://memory', $headers);
195 $this->assertEquals($expected, $request->getHeaders());
196 }
197
198 /**
199 * @test
200 */
201 public function getRequestTargetIsSlashWhenNoUriPresent() {
202 $request = new Request();
203 $this->assertEquals('/', $request->getRequestTarget());
204 }
205
206 /**
207 * @test
208 */
209 public function getRequestTargetIsSlashWhenUriHasNoPathOrQuery() {
210 $request = (new Request())
211 ->withUri(new Uri('http://example.com'));
212 $this->assertEquals('/', $request->getRequestTarget());
213 }
214
215 /**
216 * @return array
217 */
218 public function requestsWithUriDataProvider() {
219 return [
220 'absolute-uri' => [
221 (new Request())
222 ->withUri(new Uri('https://api.example.com/user'))
223 ->withMethod('POST'),
224 '/user'
225 ],
226 'absolute-uri-with-query' => [
227 (new Request())
228 ->withUri(new Uri('https://api.example.com/user?foo=bar'))
229 ->withMethod('POST'),
230 '/user?foo=bar'
231 ],
232 'relative-uri' => [
233 (new Request())
234 ->withUri(new Uri('/user'))
235 ->withMethod('GET'),
236 '/user'
237 ],
238 'relative-uri-with-query' => [
239 (new Request())
240 ->withUri(new Uri('/user?foo=bar'))
241 ->withMethod('GET'),
242 '/user?foo=bar'
243 ],
244 ];
245 }
246
247 /**
248 * @dataProvider requestsWithUriDataProvider
249 * @test
250 */
251 public function getRequestTargetWhenUriIsPresent($request, $expected) {
252 $this->assertEquals($expected, $request->getRequestTarget());
253 }
254
255 /**
256 * @return array
257 */
258 public function validRequestTargetsDataProvider() {
259 return [
260 'asterisk-form' => ['*'],
261 'authority-form' => ['api.example.com'],
262 'absolute-form' => ['https://api.example.com/users'],
263 'absolute-form-query' => ['https://api.example.com/users?foo=bar'],
264 'origin-form-path-only' => ['/users'],
265 'origin-form' => ['/users?id=foo'],
266 ];
267 }
268
269 /**
270 * @dataProvider validRequestTargetsDataProvider
271 * @test
272 */
273 public function getRequestTargetCanProvideARequestTarget($requestTarget) {
274 $request = (new Request())->withRequestTarget($requestTarget);
275 $this->assertEquals($requestTarget, $request->getRequestTarget());
276 }
277
278 /**
279 * @test
280 */
281 public function withRequestTargetCannotContainWhitespace() {
282 $request = new Request();
283 $this->setExpectedException('InvalidArgumentException', 'Invalid request target');
284 $request->withRequestTarget('foo bar baz');
285 }
286
287 /**
288 * @test
289 */
290 public function getRequestTargetDoesNotCacheBetweenInstances() {
291 $request = (new Request())->withUri(new Uri('https://example.com/foo/bar'));
292 $original = $request->getRequestTarget();
293 $newRequest = $request->withUri(new Uri('http://mwop.net/bar/baz'));
294 $this->assertNotEquals($original, $newRequest->getRequestTarget());
295 }
296
297 /**
298 * @test
299 */
300 public function getRequestTargetIsResetWithNewUri() {
301 $request = (new Request())->withUri(new Uri('https://example.com/foo/bar'));
302 $original = $request->getRequestTarget();
303 $newRequest = $request->withUri(new Uri('http://mwop.net/bar/baz'));
304 }
305
306 /**
307 * @test
308 */
309 public function getHeadersContainsHostHeaderIfUriWithHostIsPresent() {
310 $request = new Request('http://example.com');
311 $headers = $request->getHeaders();
312 $this->assertArrayHasKey('host', $headers);
313 $this->assertContains('example.com', $headers['host']);
314 }
315
316 /**
317 * @test
318 */
319 public function getHeadersContainsNoHostHeaderIfNoUriPresent() {
320 $request = new Request();
321 $headers = $request->getHeaders();
322 $this->assertArrayNotHasKey('host', $headers);
323 }
324
325 /**
326 * @test
327 */
328 public function getHeadersContainsNoHostHeaderIfUriDoesNotContainHost() {
329 $request = new Request(new Uri());
330 $headers = $request->getHeaders();
331 $this->assertArrayNotHasKey('host', $headers);
332 }
333
334 /**
335 * @test
336 */
337 public function getHeaderWithHostReturnsUriHostWhenPresent() {
338 $request = new Request('http://example.com');
339 $header = $request->getHeader('host');
340 $this->assertEquals(array('example.com'), $header);
341 }
342
343 /**
344 * @test
345 */
346 public function getHeaderWithHostReturnsEmptyArrayIfNoUriPresent() {
347 $request = new Request();
348 $this->assertSame([], $request->getHeader('host'));
349 }
350
351 /**
352 * @test
353 */
354 public function getHeaderWithHostReturnsEmptyArrayIfUriDoesNotContainHost() {
355 $request = new Request(new Uri());
356 $this->assertSame([], $request->getHeader('host'));
357 }
358
359 /**
360 * @test
361 */
362 public function getHeaderLineWithHostReturnsUriHostWhenPresent() {
363 $request = new Request('http://example.com');
364 $header = $request->getHeaderLine('host');
365 $this->assertContains('example.com', $header);
366 }
367
368 /**
369 * @test
370 */
371 public function getHeaderLineWithHostReturnsEmptyStringIfNoUriPresent() {
372 $request = new Request();
373 $this->assertSame('', $request->getHeaderLine('host'));
374 }
375
376 /**
377 * @test
378 */
379 public function getHeaderLineWithHostReturnsEmptyStringIfUriDoesNotContainHost() {
380 $request = new Request(new Uri());
381 $this->assertSame('', $request->getHeaderLine('host'));
382 }
383
384 /**
385 * @test
386 */
387 public function getHeaderLineWithHostTakesPrecedenceOverModifiedUri() {
388 $request = (new Request())
389 ->withAddedHeader('Host', 'example.com');
390
391 $uri = (new Uri())->withHost('www.example.com');
392 $new = $request->withUri($uri, TRUE);
393
394 $this->assertEquals('example.com', $new->getHeaderLine('Host'));
395 }
396
397 /**
398 * @test
399 */
400 public function getHeaderLineWithHostTakesPrecedenceOverEmptyUri() {
401 $request = (new Request())
402 ->withAddedHeader('Host', 'example.com');
403
404 $uri = new Uri();
405 $new = $request->withUri($uri);
406
407 $this->assertEquals('example.com', $new->getHeaderLine('Host'));
408 }
409
410 /**
411 * @test
412 */
413 public function getHeaderLineWithHostDoesNotTakePrecedenceOverHostWithPortFromUri() {
414 $request = (new Request())
415 ->withAddedHeader('Host', 'example.com');
416
417 $uri = (new Uri())
418 ->withHost('www.example.com')
419 ->withPort(10081);
420 $new = $request->withUri($uri);
421
422 $this->assertEquals('www.example.com:10081', $new->getHeaderLine('Host'));
423 }
424
425 /**
426 * @return array
427 */
428 public function headersWithInjectionVectorsDataProvider() {
429 return [
430 'name-with-cr' => ["X-Foo\r-Bar", 'value'],
431 'name-with-lf' => ["X-Foo\n-Bar", 'value'],
432 'name-with-crlf' => ["X-Foo\r\n-Bar", 'value'],
433 'name-with-2crlf' => ["X-Foo\r\n\r\n-Bar", 'value'],
434 'value-with-cr' => ['X-Foo-Bar', "value\rinjection"],
435 'value-with-lf' => ['X-Foo-Bar', "value\ninjection"],
436 'value-with-crlf' => ['X-Foo-Bar', "value\r\ninjection"],
437 'value-with-2crlf' => ['X-Foo-Bar', "value\r\n\r\ninjection"],
438 'array-value-with-cr' => ['X-Foo-Bar', ["value\rinjection"]],
439 'array-value-with-lf' => ['X-Foo-Bar', ["value\ninjection"]],
440 'array-value-with-crlf' => ['X-Foo-Bar', ["value\r\ninjection"]],
441 'array-value-with-2crlf' => ['X-Foo-Bar', ["value\r\n\r\ninjection"]],
442 ];
443 }
444
445 /**
446 * @test
447 * @dataProvider headersWithInjectionVectorsDataProvider
448 */
449 public function constructorRaisesExceptionForHeadersWithCRLFVectors($name, $value) {
450 $this->setExpectedException('InvalidArgumentException');
451 $request = new Request(NULL, NULL, 'php://memory', [$name => $value]);
452 }
453 }