@@ -11,8 +11,15 @@ import 'response_headers_server_vm.dart'
1111 if (dart.library.js_interop) 'response_headers_server_web.dart' ;
1212
1313/// Tests that the [Client] correctly processes response headers.
14+ ///
15+ /// If [supportsFoldedHeaders] is `false` then the tests that assume that the
16+ /// [Client] can parse folded headers will be skipped.
17+ ///
18+ /// If [correctlyHandlesNullHeaderValues] is `false` then the tests that assume
19+ /// that the [Client] correctly deals with NUL in header values are skipped.
1420void testResponseHeaders (Client client,
15- {bool supportsFoldedHeaders = true }) async {
21+ {bool supportsFoldedHeaders = true ,
22+ bool correctlyHandlesNullHeaderValues = true }) async {
1623 group ('server headers' , () {
1724 late String host;
1825 late StreamChannel <Object ?> httpServerChannel;
@@ -123,6 +130,77 @@ void testResponseHeaders(Client client,
123130 matches (r'apple[ \t]*,[ \t]*orange[ \t]*,[ \t]*banana' ));
124131 });
125132
133+ group ('invalid headers values' , () {
134+ // From RFC-9110:
135+ // Field values containing CR, LF, or NUL characters are invalid and
136+ // dangerous, due to the varying ways that implementations might parse and
137+ // interpret those characters; a recipient of CR, LF, or NUL within a
138+ // field value MUST either reject the message or replace each of those
139+ // characters with SP before further processing or forwarding of that
140+ // message.
141+ test ('NUL' , () async {
142+ httpServerChannel.sink.add ('invalid: 1\x 002\r\n ' );
143+
144+ try {
145+ final response = await client.get (Uri .http (host, '' ));
146+ expect (response.headers['invalid' ], '1 2' );
147+ } on ClientException {
148+ // The client rejected the response, which is allowed per RFC-9110.
149+ }
150+ },
151+ skip: ! correctlyHandlesNullHeaderValues
152+ ? 'does not correctly handle NUL in header values'
153+ : false );
154+
155+ // Bare CR/LF seem to be interpreted the same as CR + LF by most clients
156+ // so allow that behavior.
157+ test ('LF' , () async {
158+ httpServerChannel.sink.add ('foo: 1\n 2\r\n ' );
159+
160+ try {
161+ final response = await client.get (Uri .http (host, '' ));
162+ expect (
163+ response.headers['foo' ],
164+ anyOf (
165+ '1 2' , // RFC-specified behavior
166+ '1' // Common client behavior.
167+ ));
168+ } on ClientException {
169+ // The client rejected the response, which is allowed per RFC-9110.
170+ }
171+ });
172+
173+ test ('CR' , () async {
174+ httpServerChannel.sink.add ('foo: 1\r 2\r\n ' );
175+
176+ try {
177+ final response = await client.get (Uri .http (host, '' ));
178+ expect (
179+ response.headers['foo' ],
180+ anyOf (
181+ '1 2' , // RFC-specified behavior
182+ '1' // Common client behavior.
183+ ));
184+ } on ClientException {
185+ // The client rejected the response, which is allowed per RFC-9110.
186+ }
187+ });
188+ });
189+
190+ test ('quotes' , () async {
191+ httpServerChannel.sink.add ('FOO: "1, 2, 3"\r\n ' );
192+
193+ final response = await client.get (Uri .http (host, '' ));
194+ expect (response.headers['foo' ], '"1, 2, 3"' );
195+ });
196+
197+ test ('nested quotes' , () async {
198+ httpServerChannel.sink.add ('FOO: "\\ "1, 2, 3\\ ""\r\n ' );
199+
200+ final response = await client.get (Uri .http (host, '' ));
201+ expect (response.headers['foo' ], '"\\ "1, 2, 3\\ ""' );
202+ });
203+
126204 group ('content length' , () {
127205 test ('surrounded in spaces' , () async {
128206 // RFC-2616 4.2 says:
0 commit comments