From 362ffe902a834f01f302e115b4c9cf81f8aedb6f Mon Sep 17 00:00:00 2001 From: Alex Li Date: Tue, 25 Jun 2024 15:31:08 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=F0=9F=A5=85=20Catch=20`MediaType`=20parse?= =?UTF-8?q?=20exception?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dio/lib/src/transformer.dart | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/dio/lib/src/transformer.dart b/dio/lib/src/transformer.dart index 0750146c0..b167c12d0 100644 --- a/dio/lib/src/transformer.dart +++ b/dio/lib/src/transformer.dart @@ -78,10 +78,19 @@ abstract class Transformer { if (contentType == null) { return false; } - final mediaType = MediaType.parse(contentType); - return mediaType.mimeType == 'application/json' || - mediaType.mimeType == 'text/json' || - mediaType.subtype.endsWith('+json'); + try { + final mediaType = MediaType.parse(contentType); + return mediaType.mimeType == 'application/json' || + mediaType.mimeType == 'text/json' || + mediaType.subtype.endsWith('+json'); + } catch (e, s) { + debugLog( + 'Failed to parse the media type: $contentType, ' + 'thus it is not a JSON MIME type.', + s, + ); + return false; + } } static FutureOr defaultTransformRequest( From 9630c0aae163922d9ca53cacf94356b739b1aff6 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Tue, 25 Jun 2024 15:45:29 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=E2=9C=85=20Add=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dio/test/transformer_test.dart | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/dio/test/transformer_test.dart b/dio/test/transformer_test.dart index bafb7ef5c..3f31508f5 100644 --- a/dio/test/transformer_test.dart +++ b/dio/test/transformer_test.dart @@ -6,6 +6,17 @@ import 'package:dio/src/transformers/util/consolidate_bytes.dart'; import 'package:test/test.dart'; void main() { + // Regression: + test('Transformer.isJsonMimeType', () { + expect(Transformer.isJsonMimeType('application/json'), isTrue); + expect(Transformer.isJsonMimeType('application/json;charset=utf8'), isTrue); + expect(Transformer.isJsonMimeType('text/json'), isTrue); + expect(Transformer.isJsonMimeType('image/jpg'), isFalse); + expect(Transformer.isJsonMimeType('image/png'), isFalse); + expect(Transformer.isJsonMimeType('.png'), isFalse); + expect(Transformer.isJsonMimeType('.png;charset=utf-8'), isFalse); + }); + group(BackgroundTransformer(), () { test('transformResponse transforms the request', () async { final transformer = BackgroundTransformer(); From 9619900824d4f04001d690541bc482d99ef750c5 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Tue, 25 Jun 2024 15:45:45 +0800 Subject: [PATCH 3/5] =?UTF-8?q?=F0=9F=8E=A8=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dio/test/transformer_test.dart | 430 ++++++++++++++++----------------- 1 file changed, 213 insertions(+), 217 deletions(-) diff --git a/dio/test/transformer_test.dart b/dio/test/transformer_test.dart index 3f31508f5..cf49e30a0 100644 --- a/dio/test/transformer_test.dart +++ b/dio/test/transformer_test.dart @@ -70,240 +70,236 @@ void main() { expect(jsonResponse, null); }); - group( - FusedTransformer(), - () { - test( - 'transformResponse transforms json without content-length set in response', - () async { - final transformer = FusedTransformer(); - final response = await transformer.transformResponse( - RequestOptions(responseType: ResponseType.json), - ResponseBody.fromString( - '{"foo": "bar"}', - 200, - headers: { - Headers.contentTypeHeader: ['application/json'], - }, - ), - ); - expect(response, {'foo': 'bar'}); - }); + group(FusedTransformer, () { + test( + 'transformResponse transforms json without content-length set in response', + () async { + final transformer = FusedTransformer(); + final response = await transformer.transformResponse( + RequestOptions(responseType: ResponseType.json), + ResponseBody.fromString( + '{"foo": "bar"}', + 200, + headers: { + Headers.contentTypeHeader: ['application/json'], + }, + ), + ); + expect(response, {'foo': 'bar'}); + }); - test('transformResponse transforms json with content-length', () async { - final transformer = FusedTransformer(); - const jsonString = '{"foo": "bar"}'; - final response = await transformer.transformResponse( - RequestOptions(responseType: ResponseType.json), - ResponseBody.fromString( - jsonString, - 200, - headers: { - Headers.contentTypeHeader: ['application/json'], - Headers.contentLengthHeader: [ - utf8.encode(jsonString).length.toString(), - ], - }, - ), - ); - expect(response, {'foo': 'bar'}); - }); + test('transformResponse transforms json with content-length', () async { + final transformer = FusedTransformer(); + const jsonString = '{"foo": "bar"}'; + final response = await transformer.transformResponse( + RequestOptions(responseType: ResponseType.json), + ResponseBody.fromString( + jsonString, + 200, + headers: { + Headers.contentTypeHeader: ['application/json'], + Headers.contentLengthHeader: [ + utf8.encode(jsonString).length.toString(), + ], + }, + ), + ); + expect(response, {'foo': 'bar'}); + }); - test('transforms json in background isolate', () async { - final transformer = FusedTransformer(contentLengthIsolateThreshold: 0); - final jsonString = '{"foo": "bar"}'; - final response = await transformer.transformResponse( - RequestOptions(responseType: ResponseType.json), - ResponseBody.fromString( - jsonString, - 200, - headers: { - Headers.contentTypeHeader: ['application/json'], - Headers.contentLengthHeader: [ - utf8.encode(jsonString).length.toString(), - ], - }, - ), - ); - expect(response, {'foo': 'bar'}); - }); + test('transforms json in background isolate', () async { + final transformer = FusedTransformer(contentLengthIsolateThreshold: 0); + final jsonString = '{"foo": "bar"}'; + final response = await transformer.transformResponse( + RequestOptions(responseType: ResponseType.json), + ResponseBody.fromString( + jsonString, + 200, + headers: { + Headers.contentTypeHeader: ['application/json'], + Headers.contentLengthHeader: [ + utf8.encode(jsonString).length.toString(), + ], + }, + ), + ); + expect(response, {'foo': 'bar'}); + }); - test('transformResponse transforms that arrives in many chunks', - () async { - final transformer = FusedTransformer(); - final response = await transformer.transformResponse( - RequestOptions(responseType: ResponseType.json), - ResponseBody( - Stream.fromIterable( - /* utf-8 encoding of {"foo": "bar"} */ - [ - Uint8List.fromList([123]), - Uint8List.fromList([34]), - Uint8List.fromList([102, 111, 111]), - Uint8List.fromList([34]), - Uint8List.fromList([58]), - Uint8List.fromList([34]), - Uint8List.fromList([98, 97, 114]), - Uint8List.fromList([34]), - Uint8List.fromList([125]), - ]), - 200, - headers: { - Headers.contentTypeHeader: ['application/json'], - }, - ), - ); - expect(response, {'foo': 'bar'}); - }); + test('transformResponse transforms that arrives in many chunks', () async { + final transformer = FusedTransformer(); + final response = await transformer.transformResponse( + RequestOptions(responseType: ResponseType.json), + ResponseBody( + Stream.fromIterable( + /* utf-8 encoding of {"foo": "bar"} */ + [ + Uint8List.fromList([123]), + Uint8List.fromList([34]), + Uint8List.fromList([102, 111, 111]), + Uint8List.fromList([34]), + Uint8List.fromList([58]), + Uint8List.fromList([34]), + Uint8List.fromList([98, 97, 114]), + Uint8List.fromList([34]), + Uint8List.fromList([125]), + ]), + 200, + headers: { + Headers.contentTypeHeader: ['application/json'], + }, + ), + ); + expect(response, {'foo': 'bar'}); + }); - test('transformResponse handles bytes', () async { - final transformer = FusedTransformer(); - final response = await transformer.transformResponse( - RequestOptions(responseType: ResponseType.bytes), - ResponseBody.fromBytes( - [1, 2, 3], - 200, - ), - ); - expect(response, [1, 2, 3]); - }); + test('transformResponse handles bytes', () async { + final transformer = FusedTransformer(); + final response = await transformer.transformResponse( + RequestOptions(responseType: ResponseType.bytes), + ResponseBody.fromBytes( + [1, 2, 3], + 200, + ), + ); + expect(response, [1, 2, 3]); + }); - test('transformResponse handles when response stream has multiple chunks', - () async { - final transformer = FusedTransformer(); - final response = await transformer.transformResponse( - RequestOptions(responseType: ResponseType.bytes), - ResponseBody( - Stream.fromIterable([ - Uint8List.fromList([1, 2, 3]), - Uint8List.fromList([4, 5, 6]), - Uint8List.fromList([7, 8, 9]), - ]), - 200, - ), - ); - expect(response, [1, 2, 3, 4, 5, 6, 7, 8, 9]); - }); + test('transformResponse handles when response stream has multiple chunks', + () async { + final transformer = FusedTransformer(); + final response = await transformer.transformResponse( + RequestOptions(responseType: ResponseType.bytes), + ResponseBody( + Stream.fromIterable([ + Uint8List.fromList([1, 2, 3]), + Uint8List.fromList([4, 5, 6]), + Uint8List.fromList([7, 8, 9]), + ]), + 200, + ), + ); + expect(response, [1, 2, 3, 4, 5, 6, 7, 8, 9]); + }); - test('transformResponse handles plain text', () async { - final transformer = FusedTransformer(); - final response = await transformer.transformResponse( - RequestOptions(responseType: ResponseType.plain), - ResponseBody.fromString( - 'plain text', - 200, - headers: { - Headers.contentTypeHeader: ['text/plain'], - }, - ), - ); - expect(response, 'plain text'); - }); + test('transformResponse handles plain text', () async { + final transformer = FusedTransformer(); + final response = await transformer.transformResponse( + RequestOptions(responseType: ResponseType.plain), + ResponseBody.fromString( + 'plain text', + 200, + headers: { + Headers.contentTypeHeader: ['text/plain'], + }, + ), + ); + expect(response, 'plain text'); + }); - test('ResponseType.plain takes precedence over content-type', () async { - final transformer = FusedTransformer(); - final response = await transformer.transformResponse( - RequestOptions(responseType: ResponseType.plain), - ResponseBody.fromString( - '{"text": "plain text"}', - 200, - headers: { - Headers.contentTypeHeader: ['application/json'], - }, - ), - ); - expect(response, '{"text": "plain text"}'); - }); + test('ResponseType.plain takes precedence over content-type', () async { + final transformer = FusedTransformer(); + final response = await transformer.transformResponse( + RequestOptions(responseType: ResponseType.plain), + ResponseBody.fromString( + '{"text": "plain text"}', + 200, + headers: { + Headers.contentTypeHeader: ['application/json'], + }, + ), + ); + expect(response, '{"text": "plain text"}'); + }); - test('transformResponse handles streams', () async { - final transformer = FusedTransformer(); + test('transformResponse handles streams', () async { + final transformer = FusedTransformer(); + final response = await transformer.transformResponse( + RequestOptions(responseType: ResponseType.stream), + ResponseBody.fromBytes( + [1, 2, 3], + 200, + ), + ); + expect(response, isA()); + }); + + test('null response body only when the response is JSON', () async { + final transformer = FusedTransformer(); + for (final responseType in ResponseType.values) { final response = await transformer.transformResponse( - RequestOptions(responseType: ResponseType.stream), - ResponseBody.fromBytes( - [1, 2, 3], - 200, - ), + RequestOptions(responseType: responseType), + ResponseBody.fromBytes([], 200), ); - expect(response, isA()); - }); - - test('null response body only when the response is JSON', () async { - final transformer = FusedTransformer(); - for (final responseType in ResponseType.values) { - final response = await transformer.transformResponse( - RequestOptions(responseType: responseType), - ResponseBody.fromBytes([], 200), - ); - switch (responseType) { - case ResponseType.json: - case ResponseType.plain: - expect(response, ''); - break; - case ResponseType.stream: - expect(response, isA()); - break; - case ResponseType.bytes: - expect(response, []); - break; - default: - throw AssertionError('Unknown response type: $responseType'); - } + switch (responseType) { + case ResponseType.json: + case ResponseType.plain: + expect(response, ''); + break; + case ResponseType.stream: + expect(response, isA()); + break; + case ResponseType.bytes: + expect(response, []); + break; + default: + throw AssertionError('Unknown response type: $responseType'); } - final jsonResponse = await transformer.transformResponse( - RequestOptions(responseType: ResponseType.json), - ResponseBody.fromBytes( - [], - 200, - headers: { - Headers.contentTypeHeader: [Headers.jsonContentType], - }, - ), - ); - expect(jsonResponse, null); - }); + } + final jsonResponse = await transformer.transformResponse( + RequestOptions(responseType: ResponseType.json), + ResponseBody.fromBytes( + [], + 200, + headers: { + Headers.contentTypeHeader: [Headers.jsonContentType], + }, + ), + ); + expect(jsonResponse, null); + }); - test('transform the request using urlencode', () async { - final transformer = FusedTransformer(); + test('transform the request using urlencode', () async { + final transformer = FusedTransformer(); - final request = await transformer.transformRequest( - RequestOptions(responseType: ResponseType.json, data: {'foo': 'bar'}), - ); - expect(request, 'foo=bar'); - }); + final request = await transformer.transformRequest( + RequestOptions(responseType: ResponseType.json, data: {'foo': 'bar'}), + ); + expect(request, 'foo=bar'); + }); - test('transform the request using json', () async { - final transformer = FusedTransformer(); + test('transform the request using json', () async { + final transformer = FusedTransformer(); - final request = await transformer.transformRequest( - RequestOptions( - responseType: ResponseType.json, - data: {'foo': 'bar'}, - headers: {'Content-Type': 'application/json'}, + final request = await transformer.transformRequest( + RequestOptions( + responseType: ResponseType.json, + data: {'foo': 'bar'}, + headers: {'Content-Type': 'application/json'}, + ), + ); + expect(request, '{"foo":"bar"}'); + }); + + test( + 'HEAD request with content-length but empty body should not return null', + () async { + final transformer = FusedTransformer(); + final response = await transformer.transformResponse( + RequestOptions(responseType: ResponseType.json, method: 'HEAD'), + ResponseBody( + Stream.value(Uint8List(0)), + 200, + headers: { + Headers.contentTypeHeader: ['application/json'], + Headers.contentLengthHeader: ['123'], + }, ), ); - expect(request, '{"foo":"bar"}'); - }); - - test( - 'HEAD request with content-length but empty body should not return null', - () async { - final transformer = FusedTransformer(); - final response = await transformer.transformResponse( - RequestOptions(responseType: ResponseType.json, method: 'HEAD'), - ResponseBody( - Stream.value(Uint8List(0)), - 200, - headers: { - Headers.contentTypeHeader: ['application/json'], - Headers.contentLengthHeader: ['123'], - }, - ), - ); - expect(response, null); - }, - ); - }, - ); + expect(response, null); + }, + ); + }); group('consolidate bytes', () { test('consolidates bytes from a stream', () async { From db52baf6c1efca2aed9bd4605b644d868a7e477b Mon Sep 17 00:00:00 2001 From: Alex Li Date: Tue, 25 Jun 2024 15:54:33 +0800 Subject: [PATCH 4/5] =?UTF-8?q?=F0=9F=92=A1=20Link=20the=20regression?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dio/test/transformer_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dio/test/transformer_test.dart b/dio/test/transformer_test.dart index cf49e30a0..0175531f7 100644 --- a/dio/test/transformer_test.dart +++ b/dio/test/transformer_test.dart @@ -6,7 +6,7 @@ import 'package:dio/src/transformers/util/consolidate_bytes.dart'; import 'package:test/test.dart'; void main() { - // Regression: + // Regression: https://github.com/cfug/dio/issues/2256 test('Transformer.isJsonMimeType', () { expect(Transformer.isJsonMimeType('application/json'), isTrue); expect(Transformer.isJsonMimeType('application/json;charset=utf8'), isTrue); From e99c5ed46080206f724ff24ba7f252c1e60b9646 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Tue, 25 Jun 2024 15:57:44 +0800 Subject: [PATCH 5/5] =?UTF-8?q?=F0=9F=93=9D=20CHANGELOG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dio/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/dio/CHANGELOG.md b/dio/CHANGELOG.md index ec6f7da73..4243e91a3 100644 --- a/dio/CHANGELOG.md +++ b/dio/CHANGELOG.md @@ -15,6 +15,7 @@ See the [Migration Guide][] for the complete breaking changes list.** - Improves `InterceptorState.toString()`. - If the `CancelToken` got canceled before making requests, throws the exception directly rather than cut actual HTTP requests afterward. +- Catch `MediaType` parse exception in `Transformer.isJsonMimeType`. ## 5.4.3+1