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