Skip to content

Commit 25ec5c4

Browse files
authored
fix(data_connect): fix UTF 8 characters decoding in data connect (#18120)
* fix(data_connect): fix UTF 8 characters decoding in data connect * format
1 parent 9919bf0 commit 25ec5c4

File tree

2 files changed

+42
-3
lines changed

2 files changed

+42
-3
lines changed

packages/firebase_data_connect/firebase_data_connect/lib/src/network/rest_transport.dart

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,11 +116,12 @@ class RestTransport implements DataConnectTransport {
116116
headers: headers,
117117
);
118118
Map<String, dynamic> bodyJson =
119-
jsonDecode(r.body) as Map<String, dynamic>;
119+
jsonDecode(utf8.decode(r.bodyBytes)) as Map<String, dynamic>;
120120

121121
if (r.statusCode != 200) {
122-
String message =
123-
bodyJson.containsKey('message') ? bodyJson['message']! : r.body;
122+
String message = bodyJson.containsKey('message')
123+
? bodyJson['message']!
124+
: utf8.decode(r.bodyBytes);
124125
throw DataConnectError(
125126
r.statusCode == 401
126127
? DataConnectErrorCode.unauthorized

packages/firebase_data_connect/firebase_data_connect/test/src/network/rest_transport_test.dart

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,44 @@ void main() {
274274
).called(1);
275275
});
276276

277+
test(
278+
'regression #17290 - invokeOperation should correctly decode UTF-8 response with international characters',
279+
() async {
280+
// Simulate a server response with Korean characters, where the
281+
// Content-Type header does NOT include charset=utf-8 (which is
282+
// what the Firebase emulator sends). Without explicit UTF-8
283+
// decoding, the http package defaults to latin1, corrupting
284+
// multi-byte characters.
285+
const koreanJson =
286+
'{"data": {"name": "\ud55c\uad6d\uc5b4 \ud14c\uc2a4\ud2b8"}}';
287+
final mockResponse = http.Response.bytes(
288+
utf8.encode(koreanJson),
289+
200,
290+
headers: {'content-type': 'application/json'},
291+
);
292+
when(
293+
mockHttpClient.post(
294+
any,
295+
headers: anyNamed('headers'),
296+
body: anyNamed('body'),
297+
),
298+
).thenAnswer((_) async => mockResponse);
299+
300+
final deserializer = (String data) => 'Deserialized Data';
301+
302+
final result = await transport.invokeOperation(
303+
'testQuery',
304+
'executeQuery',
305+
deserializer,
306+
null,
307+
null,
308+
null,
309+
);
310+
311+
expect(result.data['data']['name'],
312+
equals('\ud55c\uad6d\uc5b4 \ud14c\uc2a4\ud2b8'));
313+
});
314+
277315
test(
278316
'invokeOperation should handle missing auth and appCheck tokens gracefully',
279317
() async {

0 commit comments

Comments
 (0)