Skip to content

Commit 40a46d8

Browse files
authored
Fix a bug where cronet_http sends incorrect HTTP request methods (#1058)
1 parent c125ed5 commit 40a46d8

File tree

8 files changed

+168
-9
lines changed

8 files changed

+168
-9
lines changed

pkgs/cronet_http/CHANGELOG.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
## 0.4.3-wip
2-
31
## 0.4.2
42

53
* Require `package:jni >= 0.7.2` to remove a potential buffer overflow.
4+
* Fix a bug where incorrect HTTP request methods were sent.
65

76
## 0.4.1
87

pkgs/cronet_http/lib/src/cronet_client.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ class CronetClient extends BaseClient {
317317
jb.UrlRequestCallbackProxy.new1(
318318
_urlRequestCallbacks(request, responseCompleter)),
319319
_executor,
320-
);
320+
)..setHttpMethod(request.method.toJString());
321321

322322
var headers = request.headers;
323323
if (body.isNotEmpty &&

pkgs/http/test/io/client_conformance_test.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ import 'package:http_client_conformance_tests/http_client_conformance_tests.dart
1010
import 'package:test/test.dart';
1111

1212
void main() {
13-
testAll(IOClient.new);
13+
testAll(IOClient.new, preservesMethodCase: false // https://dartbug.com/54187
14+
);
1415
}

pkgs/http_client_conformance_tests/lib/http_client_conformance_tests.dart

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import 'src/redirect_tests.dart';
1212
import 'src/request_body_streamed_tests.dart';
1313
import 'src/request_body_tests.dart';
1414
import 'src/request_headers_tests.dart';
15+
import 'src/request_methods_tests.dart';
1516
import 'src/response_body_streamed_test.dart';
1617
import 'src/response_body_tests.dart';
1718
import 'src/response_headers_tests.dart';
@@ -27,6 +28,7 @@ export 'src/redirect_tests.dart' show testRedirect;
2728
export 'src/request_body_streamed_tests.dart' show testRequestBodyStreamed;
2829
export 'src/request_body_tests.dart' show testRequestBody;
2930
export 'src/request_headers_tests.dart' show testRequestHeaders;
31+
export 'src/request_methods_tests.dart' show testRequestMethods;
3032
export 'src/response_body_streamed_test.dart' show testResponseBodyStreamed;
3133
export 'src/response_body_tests.dart' show testResponseBody;
3234
export 'src/response_headers_tests.dart' show testResponseHeaders;
@@ -49,14 +51,20 @@ export 'src/server_errors_test.dart' show testServerErrors;
4951
/// If [canWorkInIsolates] is `false` then tests that require that the [Client]
5052
/// work in Isolates other than the main isolate will be skipped.
5153
///
54+
/// If [preservesMethodCase] is `false` then tests that assume that the
55+
/// [Client] preserves custom request method casing will be skipped.
56+
///
5257
/// The tests are run against a series of HTTP servers that are started by the
5358
/// tests. If the tests are run in the browser, then the test servers are
5459
/// started in another process. Otherwise, the test servers are run in-process.
55-
void testAll(Client Function() clientFactory,
56-
{bool canStreamRequestBody = true,
57-
bool canStreamResponseBody = true,
58-
bool redirectAlwaysAllowed = false,
59-
bool canWorkInIsolates = true}) {
60+
void testAll(
61+
Client Function() clientFactory, {
62+
bool canStreamRequestBody = true,
63+
bool canStreamResponseBody = true,
64+
bool redirectAlwaysAllowed = false,
65+
bool canWorkInIsolates = true,
66+
bool preservesMethodCase = false,
67+
}) {
6068
testRequestBody(clientFactory());
6169
testRequestBodyStreamed(clientFactory(),
6270
canStreamRequestBody: canStreamRequestBody);
@@ -65,6 +73,7 @@ void testAll(Client Function() clientFactory,
6573
testResponseBodyStreamed(clientFactory(),
6674
canStreamResponseBody: canStreamResponseBody);
6775
testRequestHeaders(clientFactory());
76+
testRequestMethods(clientFactory(), preservesMethodCase: preservesMethodCase);
6877
testResponseHeaders(clientFactory());
6978
testResponseStatusLine(clientFactory());
7079
testRedirect(clientFactory(), redirectAlwaysAllowed: redirectAlwaysAllowed);
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:async';
6+
import 'dart:io';
7+
8+
import 'package:stream_channel/stream_channel.dart';
9+
10+
/// Starts an HTTP server that captures the request headers.
11+
///
12+
/// Channel protocol:
13+
/// On Startup:
14+
/// - send port
15+
/// On Request Received:
16+
/// - send the received request method (e.g. GET) as a String
17+
/// When Receive Anything:
18+
/// - exit
19+
void hybridMain(StreamChannel<Object?> channel) async {
20+
late HttpServer server;
21+
22+
server = (await HttpServer.bind('localhost', 0))
23+
..listen((request) async {
24+
request.response.headers.set('Access-Control-Allow-Origin', '*');
25+
if (request.method == 'OPTIONS') {
26+
// Handle a CORS preflight request:
27+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#preflighted_requests
28+
request.response.headers
29+
..set('Access-Control-Allow-Methods', '*')
30+
..set('Access-Control-Allow-Headers', '*');
31+
} else {
32+
channel.sink.add(request.method);
33+
}
34+
unawaited(request.response.close());
35+
});
36+
37+
channel.sink.add(server.port);
38+
await channel
39+
.stream.first; // Any writes indicates that the server should exit.
40+
unawaited(server.close());
41+
}

pkgs/http_client_conformance_tests/lib/src/request_methods_server_vm.dart

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkgs/http_client_conformance_tests/lib/src/request_methods_server_web.dart

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:async/async.dart';
6+
import 'package:http/http.dart';
7+
import 'package:stream_channel/stream_channel.dart';
8+
import 'package:test/test.dart';
9+
10+
import 'request_methods_server_vm.dart'
11+
if (dart.library.html) 'request_methods_server_web.dart';
12+
13+
/// Tests that the [Client] correctly sends HTTP request methods
14+
/// (e.g. GET, HEAD).
15+
///
16+
/// If [preservesMethodCase] is `false` then tests that assume that the
17+
/// [Client] preserves custom request method casing will be skipped.
18+
void testRequestMethods(Client client,
19+
{bool preservesMethodCase = true}) async {
20+
group('request methods', () {
21+
late final String host;
22+
late final StreamChannel<Object?> httpServerChannel;
23+
late final StreamQueue<Object?> httpServerQueue;
24+
25+
setUpAll(() async {
26+
httpServerChannel = await startServer();
27+
httpServerQueue = StreamQueue(httpServerChannel.stream);
28+
host = 'localhost:${await httpServerQueue.next}';
29+
});
30+
tearDownAll(() => httpServerChannel.sink.add(null));
31+
32+
test('custom method - not case preserving', () async {
33+
await client.send(Request(
34+
'CuStOm',
35+
Uri.http(host, ''),
36+
));
37+
final method = await httpServerQueue.next as String;
38+
expect('CUSTOM', method.toUpperCase());
39+
});
40+
41+
test('custom method case preserving', () async {
42+
await client.send(Request(
43+
'CuStOm',
44+
Uri.http(host, ''),
45+
));
46+
final method = await httpServerQueue.next as String;
47+
expect('CuStOm', method);
48+
},
49+
skip: preservesMethodCase
50+
? false
51+
: 'does not preserve HTTP request method case');
52+
53+
test('delete', () async {
54+
await client.delete(Uri.http(host, ''));
55+
final method = await httpServerQueue.next as String;
56+
expect('DELETE', method);
57+
});
58+
59+
test('get', () async {
60+
await client.get(Uri.http(host, ''));
61+
final method = await httpServerQueue.next as String;
62+
expect('GET', method);
63+
});
64+
test('head', () async {
65+
await client.head(Uri.http(host, ''));
66+
final method = await httpServerQueue.next as String;
67+
expect('HEAD', method);
68+
});
69+
70+
test('patch', () async {
71+
await client.patch(Uri.http(host, ''));
72+
final method = await httpServerQueue.next as String;
73+
expect('PATCH', method);
74+
});
75+
76+
test('post', () async {
77+
await client.post(Uri.http(host, ''));
78+
final method = await httpServerQueue.next as String;
79+
expect('POST', method);
80+
});
81+
82+
test('put', () async {
83+
await client.put(Uri.http(host, ''));
84+
final method = await httpServerQueue.next as String;
85+
expect('PUT', method);
86+
});
87+
});
88+
}

0 commit comments

Comments
 (0)