Skip to content

Commit f0bcf02

Browse files
authored
Add tests to verify NUL, CR & LF header value behavior (#1440)
1 parent 6ecd13a commit f0bcf02

File tree

4 files changed

+90
-2
lines changed

4 files changed

+90
-2
lines changed

pkgs/cupertino_http/example/integration_test/client_conformance_test.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ void main() {
2020
CupertinoClient.defaultSessionConfiguration,
2121
canReceiveSetCookieHeaders: true,
2222
canSendCookieHeaders: true,
23+
correctlyHandlesNullHeaderValues: false,
2324
);
2425
} finally {
2526
HttpClientRequestProfile.profilingEnabled = profile;
@@ -33,6 +34,7 @@ void main() {
3334
CupertinoClient.defaultSessionConfiguration,
3435
canReceiveSetCookieHeaders: true,
3536
canSendCookieHeaders: true,
37+
correctlyHandlesNullHeaderValues: false,
3638
);
3739
} finally {
3840
HttpClientRequestProfile.profilingEnabled = profile;
@@ -46,6 +48,7 @@ void main() {
4648
canWorkInIsolates: false,
4749
canReceiveSetCookieHeaders: true,
4850
canSendCookieHeaders: true,
51+
correctlyHandlesNullHeaderValues: false,
4952
);
5053
});
5154
}

pkgs/http/test/io/client_conformance_test.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,7 @@ void main() {
1414
IOClient.new, preservesMethodCase: false, // https://dartbug.com/54187
1515
canReceiveSetCookieHeaders: true,
1616
canSendCookieHeaders: true,
17+
correctlyHandlesNullHeaderValues:
18+
false, // https://github.com/dart-lang/sdk/issues/56636
1719
);
1820
}

pkgs/http_client_conformance_tests/lib/http_client_conformance_tests.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ export 'src/server_errors_test.dart' show testServerErrors;
6969
/// If [supportsFoldedHeaders] is `false` then the tests that assume that the
7070
/// [Client] can parse folded headers will be skipped.
7171
///
72+
/// If [correctlyHandlesNullHeaderValues] is `false` then the tests that assume
73+
/// that the [Client] correctly deals with NUL in header values are skipped.
74+
///
7275
/// If [supportsMultipartRequest] is `false` then tests that assume that
7376
/// multipart requests can be sent will be skipped.
7477
///
@@ -83,6 +86,7 @@ void testAll(
8386
bool canWorkInIsolates = true,
8487
bool preservesMethodCase = false,
8588
bool supportsFoldedHeaders = true,
89+
bool correctlyHandlesNullHeaderValues = true,
8690
bool canSendCookieHeaders = false,
8791
bool canReceiveSetCookieHeaders = false,
8892
bool supportsMultipartRequest = true,
@@ -97,7 +101,8 @@ void testAll(
97101
testRequestHeaders(clientFactory());
98102
testRequestMethods(clientFactory(), preservesMethodCase: preservesMethodCase);
99103
testResponseHeaders(clientFactory(),
100-
supportsFoldedHeaders: supportsFoldedHeaders);
104+
supportsFoldedHeaders: supportsFoldedHeaders,
105+
correctlyHandlesNullHeaderValues: correctlyHandlesNullHeaderValues);
101106
testResponseStatusLine(clientFactory());
102107
testRedirect(clientFactory(), redirectAlwaysAllowed: redirectAlwaysAllowed);
103108
testServerErrors(clientFactory());

pkgs/http_client_conformance_tests/lib/src/response_headers_tests.dart

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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.
1420
void 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\x002\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\n2\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\r2\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

Comments
 (0)