[!!!][TASK] Extract testing framework for TYPO3
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Tests / Unit / Http / MessageTest.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\Message;
18 use TYPO3\CMS\Core\Http\Stream;
19
20 /**
21 * Testcase for \TYPO3\CMS\Core\Http\Message
22 *
23 * Adapted from https://github.com/phly/http/
24 */
25 class MessageTest extends \TYPO3\CMS\Components\TestingFramework\Core\UnitTestCase
26 {
27 /**
28 * @var Stream
29 */
30 protected $stream;
31
32 /**
33 * @var Message
34 */
35 protected $message;
36
37 protected function setUp()
38 {
39 $this->stream = new Stream('php://memory', 'wb+');
40 $this->message = (new Message())->withBody($this->stream);
41 }
42
43 /**
44 * @test
45 */
46 public function protocolHasAcceptableDefault()
47 {
48 $this->assertEquals('1.1', $this->message->getProtocolVersion());
49 }
50
51 /**
52 * @test
53 */
54 public function protocolMutatorReturnsCloneWithChanges()
55 {
56 $message = $this->message->withProtocolVersion('1.0');
57 $this->assertNotSame($this->message, $message);
58 $this->assertEquals('1.0', $message->getProtocolVersion());
59 }
60
61 /**
62 * @test
63 */
64 public function usesStreamProvidedInConstructorAsBody()
65 {
66 $this->assertSame($this->stream, $this->message->getBody());
67 }
68
69 /**
70 * @test
71 */
72 public function bodyMutatorReturnsCloneWithChanges()
73 {
74 $stream = new Stream('php://memory', 'wb+');
75 $message = $this->message->withBody($stream);
76 $this->assertNotSame($this->message, $message);
77 $this->assertSame($stream, $message->getBody());
78 }
79
80 /**
81 * @test
82 */
83 public function getHeaderReturnsHeaderValueAsArray()
84 {
85 $message = $this->message->withHeader('X-Foo', ['Foo', 'Bar']);
86 $this->assertNotSame($this->message, $message);
87 $this->assertEquals(['Foo', 'Bar'], $message->getHeader('X-Foo'));
88 }
89
90 /**
91 * @test
92 */
93 public function getHeaderLineReturnsHeaderValueAsCommaConcatenatedString()
94 {
95 $message = $this->message->withHeader('X-Foo', ['Foo', 'Bar']);
96 $this->assertNotSame($this->message, $message);
97 $this->assertEquals('Foo,Bar', $message->getHeaderLine('X-Foo'));
98 }
99
100 /**
101 * @test
102 */
103 public function getHeadersKeepsHeaderCaseSensitivity()
104 {
105 $message = $this->message->withHeader('X-Foo', ['Foo', 'Bar']);
106 $this->assertNotSame($this->message, $message);
107 $this->assertEquals(['X-Foo' => ['Foo', 'Bar']], $message->getHeaders());
108 }
109
110 /**
111 * @test
112 */
113 public function getHeadersReturnsCaseWithWhichHeaderFirstRegistered()
114 {
115 $message = $this->message
116 ->withHeader('X-Foo', 'Foo')
117 ->withAddedHeader('x-foo', 'Bar');
118 $this->assertNotSame($this->message, $message);
119 $this->assertEquals(['X-Foo' => ['Foo', 'Bar']], $message->getHeaders());
120 }
121
122 /**
123 * @test
124 */
125 public function hasHeaderReturnsFalseIfHeaderIsNotPresent()
126 {
127 $this->assertFalse($this->message->hasHeader('X-Foo'));
128 }
129
130 /**
131 * @test
132 */
133 public function hasHeaderReturnsTrueIfHeaderIsPresent()
134 {
135 $message = $this->message->withHeader('X-Foo', 'Foo');
136 $this->assertNotSame($this->message, $message);
137 $this->assertTrue($message->hasHeader('X-Foo'));
138 }
139
140 /**
141 * @test
142 */
143 public function addHeaderAppendsToExistingHeader()
144 {
145 $message = $this->message->withHeader('X-Foo', 'Foo');
146 $this->assertNotSame($this->message, $message);
147 $message2 = $message->withAddedHeader('X-Foo', 'Bar');
148 $this->assertNotSame($message, $message2);
149 $this->assertEquals('Foo,Bar', $message2->getHeaderLine('X-Foo'));
150 }
151
152 /**
153 * @test
154 */
155 public function canRemoveHeaders()
156 {
157 $message = $this->message->withHeader('X-Foo', 'Foo');
158 $this->assertNotSame($this->message, $message);
159 $this->assertTrue($message->hasHeader('x-foo'));
160 $message2 = $message->withoutHeader('x-foo');
161 $this->assertNotSame($this->message, $message2);
162 $this->assertNotSame($message, $message2);
163 $this->assertFalse($message2->hasHeader('X-Foo'));
164 }
165
166 /**
167 * @test
168 */
169 public function headerRemovalIsCaseInsensitive()
170 {
171 $message = $this->message
172 ->withHeader('X-Foo', 'Foo')
173 ->withAddedHeader('x-foo', 'Bar')
174 ->withAddedHeader('X-FOO', 'Baz');
175 $this->assertNotSame($this->message, $message);
176 $this->assertTrue($message->hasHeader('x-foo'));
177 $message2 = $message->withoutHeader('x-foo');
178 $this->assertNotSame($this->message, $message2);
179 $this->assertNotSame($message, $message2);
180 $this->assertFalse($message2->hasHeader('X-Foo'));
181 $headers = $message2->getHeaders();
182 $this->assertEquals(0, count($headers));
183 }
184
185 /**
186 * @return array
187 */
188 public function invalidGeneralHeaderValuesDataProvider()
189 {
190 return [
191 'null' => [null],
192 'true' => [true],
193 'false' => [false],
194 'int' => [1],
195 'float' => [1.1],
196 'array' => [['foo' => ['bar']]],
197 'object' => [(object) ['foo' => 'bar']],
198 ];
199 }
200
201 /**
202 * @dataProvider invalidGeneralHeaderValuesDataProvider
203 */
204 public function testWithHeaderRaisesExceptionForInvalidNestedHeaderValue($value)
205 {
206 $this->expectException(\InvalidArgumentException::class);
207 $this->expectExceptionCode(1436717266);
208 $this->message->withHeader('X-Foo', [$value]);
209 }
210
211 /**
212 * @return array
213 */
214 public function invalidHeaderValuesDataProvider()
215 {
216 return [
217 'null' => [null],
218 'true' => [true],
219 'false' => [false],
220 'int' => [1],
221 'float' => [1.1],
222 'object' => [(object) ['foo' => 'bar']],
223 ];
224 }
225
226 /**
227 * @dataProvider invalidHeaderValuesDataProvider
228 */
229 public function withHeaderRaisesExceptionForInvalidValueType($value)
230 {
231 $this->expectException(\InvalidArgumentException::class);
232 $this->expectExceptionCode(1436717266);
233 $this->message->withHeader('X-Foo', $value);
234 }
235
236 /**
237 * @dataProvider invalidHeaderValuesDataProvider
238 */
239 public function withAddedHeaderRaisesExceptionForNonStringNonArrayValue($value)
240 {
241 $this->expectException(\InvalidArgumentException::class);
242 $this->expectExceptionCode(1436717267);
243 $this->message->withAddedHeader('X-Foo', $value);
244 }
245
246 /**
247 * @test
248 */
249 public function withoutHeaderDoesNothingIfHeaderDoesNotExist()
250 {
251 $this->assertFalse($this->message->hasHeader('X-Foo'));
252 $message = $this->message->withoutHeader('X-Foo');
253 $this->assertNotSame($this->message, $message);
254 $this->assertFalse($message->hasHeader('X-Foo'));
255 }
256
257 /**
258 * @test
259 */
260 public function getHeaderReturnsAnEmptyArrayWhenHeaderDoesNotExist()
261 {
262 $this->assertSame([], $this->message->getHeader('X-Foo-Bar'));
263 }
264
265 /**
266 * @test
267 */
268 public function getHeaderLineReturnsEmptyStringWhenHeaderDoesNotExist()
269 {
270 $this->assertSame('', $this->message->getHeaderLine('X-Foo-Bar'));
271 }
272
273 /**
274 * @return array
275 */
276 public function headersWithInjectionVectorsDataProvider()
277 {
278 return [
279 'name-with-cr' => ["X-Foo\r-Bar", 'value'],
280 'name-with-lf' => ["X-Foo\n-Bar", 'value'],
281 'name-with-crlf' => ["X-Foo\r\n-Bar", 'value'],
282 'name-with-2crlf' => ["X-Foo\r\n\r\n-Bar", 'value'],
283 'value-with-cr' => ['X-Foo-Bar', "value\rinjection"],
284 'value-with-lf' => ['X-Foo-Bar', "value\ninjection"],
285 'value-with-crlf' => ['X-Foo-Bar', "value\r\ninjection"],
286 'value-with-2crlf' => ['X-Foo-Bar', "value\r\n\r\ninjection"],
287 'array-value-with-cr' => ['X-Foo-Bar', ["value\rinjection"]],
288 'array-value-with-lf' => ['X-Foo-Bar', ["value\ninjection"]],
289 'array-value-with-crlf' => ['X-Foo-Bar', ["value\r\ninjection"]],
290 'array-value-with-2crlf' => ['X-Foo-Bar', ["value\r\n\r\ninjection"]],
291 'multi-line-header-space' => ['X-Foo-Bar', "value\r\n injection"],
292 'multi-line-header-tab' => ['X-Foo-Bar', "value\r\n\tinjection"],
293 ];
294 }
295
296 /**
297 * @dataProvider headersWithInjectionVectorsDataProvider
298 * @test
299 */
300 public function doesNotAllowCRLFInjectionWhenCallingWithHeader($name, $value)
301 {
302 $this->expectException(\InvalidArgumentException::class);
303 $this->message->withHeader($name, $value);
304 }
305
306 /**
307 * @dataProvider headersWithInjectionVectorsDataProvider
308 * @test
309 */
310 public function doesNotAllowCRLFInjectionWhenCallingWithAddedHeader($name, $value)
311 {
312 $this->expectException(\InvalidArgumentException::class);
313 $this->message->withAddedHeader($name, $value);
314 }
315 }