ef9fdb26c6c6a66b8e9b045e6ede4071c87e43fe
[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\Request;
18 use TYPO3\CMS\Core\Http\Stream;
19 use TYPO3\CMS\Core\Http\Uri;
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\Components\TestingFramework\Core\UnitTestCase
27 {
28 /**
29 * @var Request
30 */
31 protected $request;
32
33 protected function setUp()
34 {
35 $this->request = new Request();
36 }
37
38 /**
39 * @test
40 */
41 public function getMethodIsGetByDefault()
42 {
43 $this->assertEquals('GET', $this->request->getMethod());
44 }
45
46 /**
47 * @test
48 */
49 public function getMethodMutatorReturnsCloneWithChangedMethod()
50 {
51 $request = $this->request->withMethod('GET');
52 $this->assertNotSame($this->request, $request);
53 $this->assertEquals('GET', $request->getMethod());
54 }
55
56 /**
57 * @test
58 */
59 public function getUriIsNullByDefault()
60 {
61 $this->assertNull($this->request->getUri());
62 }
63
64 /**
65 * @test
66 */
67 public function constructorRaisesExceptionForInvalidStream()
68 {
69 $this->expectException(\InvalidArgumentException::class);
70 new Request(['TOTALLY INVALID']);
71 }
72
73 /**
74 * @test
75 */
76 public function withUriReturnsNewInstanceWithNewUri()
77 {
78 $request = $this->request->withUri(new Uri('https://example.com:10082/foo/bar?baz=bat'));
79 $this->assertNotSame($this->request, $request);
80 $request2 = $request->withUri(new Uri('/baz/bat?foo=bar'));
81 $this->assertNotSame($this->request, $request2);
82 $this->assertNotSame($request, $request2);
83 $this->assertEquals('/baz/bat?foo=bar', (string) $request2->getUri());
84 }
85
86 /**
87 * @test
88 */
89 public function constructorCanAcceptAllMessageParts()
90 {
91 $uri = new Uri('http://example.com/');
92 $body = new Stream('php://memory');
93 $headers = [
94 'x-foo' => ['bar'],
95 ];
96 $request = new Request(
97 $uri,
98 'POST',
99 $body,
100 $headers
101 );
102
103 $this->assertSame($uri, $request->getUri());
104 $this->assertEquals('POST', $request->getMethod());
105 $this->assertSame($body, $request->getBody());
106 $testHeaders = $request->getHeaders();
107 foreach ($headers as $key => $value) {
108 $this->assertArrayHasKey($key, $testHeaders);
109 $this->assertEquals($value, $testHeaders[$key]);
110 }
111 }
112
113 /**
114 * @return array
115 */
116 public function invalidRequestUriDataProvider()
117 {
118 return [
119 'true' => [true],
120 'false' => [false],
121 'int' => [1],
122 'float' => [1.1],
123 'array' => [['http://example.com']],
124 'stdClass' => [(object) ['href' => 'http://example.com']],
125 ];
126 }
127
128 /**
129 * @dataProvider invalidRequestUriDataProvider
130 * @test
131 */
132 public function constructorRaisesExceptionForInvalidUri($uri)
133 {
134 $this->expectException(\InvalidArgumentException::class);
135 $this->expectExceptionCode(1436717272);
136 new Request($uri);
137 }
138
139 /**
140 * @return array
141 */
142 public function invalidRequestMethodDataProvider()
143 {
144 return [
145 'true' => [true],
146 'false' => [false],
147 'int' => [1],
148 'float' => [1.1],
149 'array' => [['POST']],
150 'stdClass' => [(object) ['method' => 'POST']],
151 ];
152 }
153
154 /**
155 * @dataProvider invalidRequestMethodDataProvider
156 * @test
157 */
158 public function constructorRaisesExceptionForInvalidMethodByType($method)
159 {
160 $this->expectException(\InvalidArgumentException::class);
161 $this->expectExceptionCode(1436717274);
162 new Request(null, $method);
163 }
164
165 /**
166 * @test
167 */
168 public function constructorRaisesExceptionForInvalidMethodByString()
169 {
170 $this->expectException(\InvalidArgumentException::class);
171 $this->expectExceptionCode(1436717275);
172 new Request(null, 'BOGUS-METHOD');
173 }
174
175 /**
176 * @return array
177 */
178 public function invalidRequestBodyDataProvider()
179 {
180 return [
181 'true' => [true],
182 'false' => [false],
183 'int' => [1],
184 'float' => [1.1],
185 'array' => [['BODY']],
186 'stdClass' => [(object) ['body' => 'BODY']],
187 ];
188 }
189
190 /**
191 * @dataProvider invalidRequestBodyDataProvider
192 * @test
193 */
194 public function constructorRaisesExceptionForInvalidBody($body)
195 {
196 $this->expectException(\InvalidArgumentException::class);
197 $this->expectExceptionCode(1436717271);
198 new Request(null, null, $body);
199 }
200
201 /**
202 * @test
203 */
204 public function constructorIgnoresInvalidHeaders()
205 {
206 $headers = [
207 ['INVALID'],
208 'x-invalid-null' => null,
209 'x-invalid-true' => true,
210 'x-invalid-false' => false,
211 'x-invalid-int' => 1,
212 'x-invalid-object' => (object) ['INVALID'],
213 'x-valid-string' => 'VALID',
214 'x-valid-array' => ['VALID'],
215 ];
216 $expected = [
217 'x-valid-string' => ['VALID'],
218 'x-valid-array' => ['VALID'],
219 ];
220 $request = new Request(null, null, 'php://memory', $headers);
221 $this->assertEquals($expected, $request->getHeaders());
222 }
223
224 /**
225 * @test
226 */
227 public function getRequestTargetIsSlashWhenNoUriPresent()
228 {
229 $request = new Request();
230 $this->assertEquals('/', $request->getRequestTarget());
231 }
232
233 /**
234 * @test
235 */
236 public function getRequestTargetIsSlashWhenUriHasNoPathOrQuery()
237 {
238 $request = (new Request())
239 ->withUri(new Uri('http://example.com'));
240 $this->assertEquals('/', $request->getRequestTarget());
241 }
242
243 /**
244 * @return array
245 */
246 public function requestsWithUriDataProvider()
247 {
248 return [
249 'absolute-uri' => [
250 (new Request())
251 ->withUri(new Uri('https://api.example.com/user'))
252 ->withMethod('POST'),
253 '/user'
254 ],
255 'absolute-uri-with-query' => [
256 (new Request())
257 ->withUri(new Uri('https://api.example.com/user?foo=bar'))
258 ->withMethod('POST'),
259 '/user?foo=bar'
260 ],
261 'relative-uri' => [
262 (new Request())
263 ->withUri(new Uri('/user'))
264 ->withMethod('GET'),
265 '/user'
266 ],
267 'relative-uri-with-query' => [
268 (new Request())
269 ->withUri(new Uri('/user?foo=bar'))
270 ->withMethod('GET'),
271 '/user?foo=bar'
272 ],
273 ];
274 }
275
276 /**
277 * @dataProvider requestsWithUriDataProvider
278 * @test
279 */
280 public function getRequestTargetWhenUriIsPresent($request, $expected)
281 {
282 $this->assertEquals($expected, $request->getRequestTarget());
283 }
284
285 /**
286 * @return array
287 */
288 public function validRequestTargetsDataProvider()
289 {
290 return [
291 'asterisk-form' => ['*'],
292 'authority-form' => ['api.example.com'],
293 'absolute-form' => ['https://api.example.com/users'],
294 'absolute-form-query' => ['https://api.example.com/users?foo=bar'],
295 'origin-form-path-only' => ['/users'],
296 'origin-form' => ['/users?id=foo'],
297 ];
298 }
299
300 /**
301 * @dataProvider validRequestTargetsDataProvider
302 * @test
303 */
304 public function getRequestTargetCanProvideARequestTarget($requestTarget)
305 {
306 $request = (new Request())->withRequestTarget($requestTarget);
307 $this->assertEquals($requestTarget, $request->getRequestTarget());
308 }
309
310 /**
311 * @test
312 */
313 public function withRequestTargetCannotContainWhitespace()
314 {
315 $request = new Request();
316 $this->expectException(\InvalidArgumentException::class);
317 $this->expectExceptionCode(1436717273);
318 $request->withRequestTarget('foo bar baz');
319 }
320
321 /**
322 * @test
323 */
324 public function getRequestTargetDoesNotCacheBetweenInstances()
325 {
326 $request = (new Request())->withUri(new Uri('https://example.com/foo/bar'));
327 $original = $request->getRequestTarget();
328 $newRequest = $request->withUri(new Uri('http://mwop.net/bar/baz'));
329 $this->assertNotEquals($original, $newRequest->getRequestTarget());
330 }
331
332 /**
333 * @test
334 */
335 public function getRequestTargetIsResetWithNewUri()
336 {
337 $request = (new Request())->withUri(new Uri('https://example.com/foo/bar'));
338 $request->getRequestTarget();
339 $request->withUri(new Uri('http://mwop.net/bar/baz'));
340 }
341
342 /**
343 * @test
344 */
345 public function getHeadersContainsHostHeaderIfUriWithHostIsPresent()
346 {
347 $request = new Request('http://example.com');
348 $headers = $request->getHeaders();
349 $this->assertArrayHasKey('host', $headers);
350 $this->assertContains('example.com', $headers['host']);
351 }
352
353 /**
354 * @test
355 */
356 public function getHeadersContainsNoHostHeaderIfNoUriPresent()
357 {
358 $request = new Request();
359 $headers = $request->getHeaders();
360 $this->assertArrayNotHasKey('host', $headers);
361 }
362
363 /**
364 * @test
365 */
366 public function getHeadersContainsNoHostHeaderIfUriDoesNotContainHost()
367 {
368 $request = new Request(new Uri());
369 $headers = $request->getHeaders();
370 $this->assertArrayNotHasKey('host', $headers);
371 }
372
373 /**
374 * @test
375 */
376 public function getHeaderWithHostReturnsUriHostWhenPresent()
377 {
378 $request = new Request('http://example.com');
379 $header = $request->getHeader('host');
380 $this->assertEquals(['example.com'], $header);
381 }
382
383 /**
384 * @test
385 */
386 public function getHeaderWithHostReturnsEmptyArrayIfNoUriPresent()
387 {
388 $request = new Request();
389 $this->assertSame([], $request->getHeader('host'));
390 }
391
392 /**
393 * @test
394 */
395 public function getHeaderWithHostReturnsEmptyArrayIfUriDoesNotContainHost()
396 {
397 $request = new Request(new Uri());
398 $this->assertSame([], $request->getHeader('host'));
399 }
400
401 /**
402 * @test
403 */
404 public function getHeaderLineWithHostReturnsUriHostWhenPresent()
405 {
406 $request = new Request('http://example.com');
407 $header = $request->getHeaderLine('host');
408 $this->assertContains('example.com', $header);
409 }
410
411 /**
412 * @test
413 */
414 public function getHeaderLineWithHostReturnsEmptyStringIfNoUriPresent()
415 {
416 $request = new Request();
417 $this->assertSame('', $request->getHeaderLine('host'));
418 }
419
420 /**
421 * @test
422 */
423 public function getHeaderLineWithHostReturnsEmptyStringIfUriDoesNotContainHost()
424 {
425 $request = new Request(new Uri());
426 $this->assertSame('', $request->getHeaderLine('host'));
427 }
428
429 /**
430 * @test
431 */
432 public function getHeaderLineWithHostTakesPrecedenceOverModifiedUri()
433 {
434 $request = (new Request())
435 ->withAddedHeader('Host', 'example.com');
436
437 $uri = (new Uri())->withHost('www.example.com');
438 $new = $request->withUri($uri, true);
439
440 $this->assertEquals('example.com', $new->getHeaderLine('Host'));
441 }
442
443 /**
444 * @test
445 */
446 public function getHeaderLineWithHostTakesPrecedenceOverEmptyUri()
447 {
448 $request = (new Request())
449 ->withAddedHeader('Host', 'example.com');
450
451 $uri = new Uri();
452 $new = $request->withUri($uri);
453
454 $this->assertEquals('example.com', $new->getHeaderLine('Host'));
455 }
456
457 /**
458 * @test
459 */
460 public function getHeaderLineWithHostDoesNotTakePrecedenceOverHostWithPortFromUri()
461 {
462 $request = (new Request())
463 ->withAddedHeader('Host', 'example.com');
464
465 $uri = (new Uri())
466 ->withHost('www.example.com')
467 ->withPort(10081);
468 $new = $request->withUri($uri);
469
470 $this->assertEquals('www.example.com:10081', $new->getHeaderLine('Host'));
471 }
472
473 /**
474 * @return array
475 */
476 public function headersWithUpperAndLowerCaseValuesDataProvider()
477 {
478 // 'name' => [$headerName, $headerValue, $expectedValue]
479 return [
480 'Foo' => ['Foo', 'bar', 'bar'],
481 'foo' => ['foo', 'bar', 'bar'],
482 'Foo-with-array' => ['Foo', ['bar'], 'bar'],
483 'foo-with-array' => ['foo', ['bar'], 'bar'],
484 ];
485 }
486
487 /**
488 * @test
489 * @dataProvider headersWithUpperAndLowerCaseValuesDataProvider
490 */
491 public function headerCanBeRetrieved($header, $value, $expected)
492 {
493 $request = new Request(null, null, 'php://memory', [$header => $value]);
494 $this->assertEquals([$expected], $request->getHeader(strtolower($header)));
495 $this->assertEquals([$expected], $request->getHeader(strtoupper($header)));
496 }
497
498 /**
499 * @return array
500 */
501 public function headersWithInjectionVectorsDataProvider()
502 {
503 return [
504 'name-with-cr' => ["X-Foo\r-Bar", 'value'],
505 'name-with-lf' => ["X-Foo\n-Bar", 'value'],
506 'name-with-crlf' => ["X-Foo\r\n-Bar", 'value'],
507 'name-with-2crlf' => ["X-Foo\r\n\r\n-Bar", 'value'],
508 'value-with-cr' => ['X-Foo-Bar', "value\rinjection"],
509 'value-with-lf' => ['X-Foo-Bar', "value\ninjection"],
510 'value-with-crlf' => ['X-Foo-Bar', "value\r\ninjection"],
511 'value-with-2crlf' => ['X-Foo-Bar', "value\r\n\r\ninjection"],
512 'array-value-with-cr' => ['X-Foo-Bar', ["value\rinjection"]],
513 'array-value-with-lf' => ['X-Foo-Bar', ["value\ninjection"]],
514 'array-value-with-crlf' => ['X-Foo-Bar', ["value\r\ninjection"]],
515 'array-value-with-2crlf' => ['X-Foo-Bar', ["value\r\n\r\ninjection"]],
516 ];
517 }
518
519 /**
520 * @test
521 * @dataProvider headersWithInjectionVectorsDataProvider
522 */
523 public function constructorRaisesExceptionForHeadersWithCRLFVectors($name, $value)
524 {
525 $this->expectException(\InvalidArgumentException::class);
526 new Request(null, null, 'php://memory', [$name => $value]);
527 }
528 }