Skip to content

Commit 1adce3e

Browse files
committed
Updated the branch fork
2 parents 53dc82f + 900da9f commit 1adce3e

File tree

16 files changed

+172
-66
lines changed

16 files changed

+172
-66
lines changed

.github/workflows/okhttp.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
- uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b
3333
with:
3434
distribution: 'zulu'
35-
java-version: '21'
35+
java-version: '17'
3636
- uses: subosito/flutter-action@44ac965b96f18d999802d4b807e3256d5a3f9fa1
3737
with:
3838
channel: 'stable'

pkgs/cronet_http/android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ buildscript {
99
}
1010

1111
dependencies {
12-
classpath 'com.android.tools.build:gradle:8.9.0'
12+
classpath 'com.android.tools.build:gradle:8.1.0'
1313
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
1414
}
1515
}

pkgs/cupertino_http/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ void main() async {
3939
httpClient = IOClient(HttpClient()..userAgent = 'Book Agent');
4040
}
4141
42-
final response = await client.get(Uri.https(
42+
final response = await httpClient.get(Uri.https(
4343
'www.googleapis.com',
4444
'/books/v1/volumes',
4545
{'q': 'HTTP', 'maxResults': '40', 'printType': 'books'}));

pkgs/http/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## 1.4.0-wip
2+
3+
* Fixed default encoding for application/json without a charset
4+
to use utf8 instead of latin1, ensuring proper JSON decoding.
5+
* Avoid references to `window` in `BrowserClient`, restoring support for web
6+
workers and NodeJS.
7+
18
## 1.3.0
29

310
* Fixed unintended HTML tags in doc comments.

pkgs/http/lib/src/browser_client.dart

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import 'package:web/web.dart'
99
AbortController,
1010
HeadersInit,
1111
ReadableStreamDefaultReader,
12+
RequestInfo,
1213
RequestInit,
13-
Response,
14-
window;
14+
Response;
1515

1616
import 'base_client.dart';
1717
import 'base_request.dart';
@@ -46,6 +46,12 @@ enum CacheMode {
4646
const CacheMode(this.cacheType);
4747
}
4848

49+
@JS('fetch')
50+
external JSPromise<Response> _fetch(
51+
RequestInfo input, [
52+
RequestInit init,
53+
]);
54+
4955
/// A `package:web`-based HTTP client that runs in the browser and is backed by
5056
/// [`window.fetch`](https://fetch.spec.whatwg.org/).
5157
///
@@ -93,25 +99,24 @@ class BrowserClient extends BaseClient {
9399

94100
final bodyBytes = await request.finalize().toBytes();
95101
try {
96-
final response = await window
97-
.fetch(
98-
'${request.url}'.toJS,
99-
RequestInit(
100-
method: request.method,
101-
body: bodyBytes.isNotEmpty ? bodyBytes.toJS : null,
102-
cache: _cacheMode.cacheType,
103-
credentials: withCredentials ? 'include' : 'same-origin',
104-
headers: {
105-
if (request.contentLength case final contentLength?)
106-
'content-length': contentLength,
107-
for (var header in request.headers.entries)
108-
header.key: header.value,
109-
}.jsify()! as HeadersInit,
110-
signal: _abortController.signal,
111-
redirect: request.followRedirects ? 'follow' : 'error',
112-
),
113-
)
114-
.toDart;
102+
103+
final response = await _fetch(
104+
'${request.url}'.toJS,
105+
RequestInit(
106+
method: request.method,
107+
body: bodyBytes.isNotEmpty ? bodyBytes.toJS : null,
108+
cache: _cacheMode.cacheType,
109+
credentials: withCredentials ? 'include' : 'same-origin',
110+
headers: {
111+
if (request.contentLength case final contentLength?)
112+
'content-length': contentLength,
113+
for (var header in request.headers.entries)
114+
header.key: header.value,
115+
}.jsify()! as HeadersInit,
116+
signal: _abortController.signal,
117+
redirect: request.followRedirects ? 'follow' : 'error',
118+
),
119+
).toDart;
115120

116121
final contentLengthHeader = response.headers.get('content-length');
117122

pkgs/http/lib/src/multipart_file.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ class MultipartFile {
7373
factory MultipartFile.fromString(String field, String value,
7474
{String? filename, MediaType? contentType}) {
7575
contentType ??= MediaType('text', 'plain');
76-
var encoding = encodingForCharset(contentType.parameters['charset'], utf8);
76+
var encoding = encodingForContentTypeHeader(contentType, utf8);
7777
contentType = contentType.change(parameters: {'charset': encoding.name});
7878

7979
return MultipartFile.fromBytes(field, encoding.encode(value),

pkgs/http/lib/src/response.dart

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ class Response extends BaseResponse {
2121
///
2222
/// This is converted from [bodyBytes] using the `charset` parameter of the
2323
/// `Content-Type` header field, if available. If it's unavailable or if the
24-
/// encoding name is unknown, [latin1] is used by default, as per
25-
/// [RFC 2616][].
24+
/// encoding name is unknown:
25+
/// - [utf8] is used when the content-type is 'application/json' (see [RFC 3629][]).
26+
/// - [latin1] is used in all other cases (see [RFC 2616][])
2627
///
28+
/// [RFC 3629]: https://www.rfc-editor.org/rfc/rfc3629.
2729
/// [RFC 2616]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html
2830
String get body => _encodingForHeaders(headers).decode(bodyBytes);
2931

@@ -66,10 +68,13 @@ class Response extends BaseResponse {
6668

6769
/// Returns the encoding to use for a response with the given headers.
6870
///
69-
/// Defaults to [latin1] if the headers don't specify a charset or if that
70-
/// charset is unknown.
71+
/// If the `Content-Type` header specifies a charset, it will use that charset.
72+
/// If no charset is provided or the charset is unknown:
73+
/// - Defaults to [utf8] if the `Content-Type` is `application/json`
74+
/// (since JSON is defined to use UTF-8 by default).
75+
/// - Otherwise, defaults to [latin1] for compatibility.
7176
Encoding _encodingForHeaders(Map<String, String> headers) =>
72-
encodingForCharset(_contentTypeForHeaders(headers).parameters['charset']);
77+
encodingForContentTypeHeader(_contentTypeForHeaders(headers));
7378

7479
/// Returns the [MediaType] object for the given headers' content-type.
7580
///

pkgs/http/lib/src/utils.dart

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import 'dart:async';
66
import 'dart:convert';
77
import 'dart:typed_data';
88

9+
import 'package:http_parser/http_parser.dart';
10+
911
import 'byte_stream.dart';
1012

1113
/// Converts a [Map] from parameter names to values to a URL query string.
@@ -18,13 +20,27 @@ String mapToQuery(Map<String, String> map, {required Encoding encoding}) =>
1820
'=${Uri.encodeQueryComponent(e.value, encoding: encoding)}')
1921
.join('&');
2022

21-
/// Returns the [Encoding] that corresponds to [charset].
23+
/// Determines the appropriate [Encoding] based on the given [contentTypeHeader]
2224
///
23-
/// Returns [fallback] if [charset] is null or if no [Encoding] was found that
24-
/// corresponds to [charset].
25-
Encoding encodingForCharset(String? charset, [Encoding fallback = latin1]) {
26-
if (charset == null) return fallback;
27-
return Encoding.getByName(charset) ?? fallback;
25+
/// - If the `Content-Type` is `application/json` and no charset is specified,
26+
/// it defaults to [utf8].
27+
/// - If a charset is specified in the parameters,
28+
/// it attempts to find a matching [Encoding].
29+
/// - If no charset is specified or the charset is unknown,
30+
/// it falls back to the provided [fallback], which defaults to [latin1].
31+
Encoding encodingForContentTypeHeader(MediaType contentTypeHeader,
32+
[Encoding fallback = latin1]) {
33+
final charset = contentTypeHeader.parameters['charset'];
34+
35+
// Default to utf8 for application/json when charset is unspecified.
36+
if (contentTypeHeader.type == 'application' &&
37+
contentTypeHeader.subtype == 'json' &&
38+
charset == null) {
39+
return utf8;
40+
}
41+
42+
// Attempt to find the encoding or fall back to the default.
43+
return charset != null ? Encoding.getByName(charset) ?? fallback : fallback;
2844
}
2945

3046
/// Returns the [Encoding] that corresponds to [charset].

pkgs/http/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: http
2-
version: 1.3.0
2+
version: 1.4.0-wip
33
description: A composable, multi-platform, Future-based API for HTTP requests.
44
repository: https://github.com/dart-lang/http/tree/master/pkgs/http
55

pkgs/http/test/response_test.dart

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'dart:async';
6+
import 'dart:convert';
67

78
import 'package:http/http.dart' as http;
89
import 'package:test/test.dart';
@@ -45,6 +46,49 @@ void main() {
4546
headers: {'content-type': 'text/plain; charset=iso-8859-1'});
4647
expect(response.body, equals('föøbãr'));
4748
});
49+
50+
test(
51+
'decoding with empty charset if content type is application/json, Russian text',
52+
() {
53+
final utf8Bytes = utf8.encode('{"foo":"Привет, мир!"}');
54+
var response = http.Response.bytes(utf8Bytes, 200,
55+
headers: {'content-type': 'application/json'});
56+
expect(response.body, equals('{"foo":"Привет, мир!"}'));
57+
});
58+
59+
test(
60+
'test decoding with empty charset if content type is application/json, Chinese text',
61+
() {
62+
final chineseUtf8Bytes = utf8.encode('{"foo":"你好,世界!"}');
63+
var responseChinese = http.Response.bytes(chineseUtf8Bytes, 200,
64+
headers: {'content-type': 'application/json'});
65+
66+
expect(responseChinese.body, equals('{"foo":"你好,世界!"}'));
67+
});
68+
69+
test(
70+
'test decoding with empty charset if content type is application/json, Korean text',
71+
() {
72+
final koreanUtf8Bytes = utf8.encode('{"foo":"안녕하세요, 세계!"}');
73+
var responseKorean = http.Response.bytes(koreanUtf8Bytes, 200,
74+
headers: {'content-type': 'application/json'});
75+
expect(responseKorean.body, equals('{"foo":"안녕하세요, 세계!"}'));
76+
});
77+
78+
test('respects the inferred encoding for application/json content-type',
79+
() {
80+
final latin1Bytes = latin1.encode('{"foo":"Olá, mundo!"}');
81+
var response = http.Response.bytes(latin1Bytes, 200,
82+
headers: {'content-type': 'application/json; charset=iso-8859-1'});
83+
expect(response.body, equals('{"foo":"Olá, mundo!"}'));
84+
});
85+
86+
test('latin1 decode on empty charset if content type is text/plain', () {
87+
final latin1Bytes = latin1.encode('Hello, wold!');
88+
var response = http.Response.bytes(latin1Bytes, 200,
89+
headers: {'content-type': 'text/plain'});
90+
expect(response.body, equals('Hello, wold!'));
91+
});
4892
});
4993

5094
group('.fromStream()', () {

0 commit comments

Comments
 (0)