Repository: cfug/dio

Issue:
 The request headers key changes to lowercase
### Request Statement

When I made a network request, Authorization was added to the network request headers, but the result was changed to lowercase.
example：
var dio = Dio(); Options options = Options(headers: {"Authorization": "Basic xxx"}); Response response = await dio.get(“xxx” options: options, );

![276073621-af600c08-56c8-47ab-9610-e58857286808](https://github.com/cfug/dio/assets/22562281/0c4814b4-d4bf-4267-89cf-7aa9a2e49fae)


### Solution Brainstorm

_No response_


Discussion: HTTP headers are defined as being case insensitive, which means `Authorization` and `authorization` are identical.

What exactly, is your problem @hnsycsxhzcsh ?
The way I write it is：

dio.options.headers["Authorization"] =  "Basic <token&gt;";



But the reality is：





I want the argument in the request header to be Authorization instead of authorization. What should I do?



陈少华
***@***.***



&nbsp;




------------------ 原始邮件 ------------------
发件人:                                                                                                                        "cfug/dio"                                                                                    ***@***.***&gt;;
发送时间:&nbsp;2023年10月21日(星期六) 晚上9:56
***@***.***&gt;;
***@***.******@***.***&gt;;
主题:&nbsp;Re: [cfug/dio]  The request headers key changes to lowercase (Issue #2002)





 
HTTP headers are defined as being case insensitive, which means Authorization and authorization are identical.
 
What exactly, is your problem @hnsycsxhzcsh ?
 
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you were mentioned.Message ID: ***@***.***&gt;
The way I write it is：

dio.options.headers["Authorization"] =  "Basic <token&gt;";



But the reality is：





I want the argument in the request header to be&nbsp;Authorization instead of authorization. What should I do?



陈少华
***@***.***



&nbsp;




------------------&nbsp;原始邮件&nbsp;------------------
发件人:                                                                                                                        "cfug/dio"                                                                                    ***@***.***&gt;;
发送时间:&nbsp;2023年10月21日(星期六) 晚上9:56
***@***.***&gt;;
***@***.******@***.***&gt;;
主题:&nbsp;Re: [cfug/dio]  The request headers key changes to lowercase (Issue #2002)





 
HTTP headers are defined as being case insensitive, which means Authorization and authorization are identical.
 
What exactly, is your problem @hnsycsxhzcsh ?
 
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you were mentioned.Message ID: ***@***.***&gt;
I understand that, but why does it need to start with an upper case A?
The server interface is not compatible with lowercase, the server cannot change, the client needs to pass uppercase


陈少华
***@***.***



&nbsp;




------------------&nbsp;原始邮件&nbsp;------------------
发件人:                                                                                                                        "cfug/dio"                                                                                    ***@***.***&gt;;
发送时间:&nbsp;2023年10月23日(星期一) 下午3:01
***@***.***&gt;;
***@***.******@***.***&gt;;
主题:&nbsp;Re: [cfug/dio]  The request headers key changes to lowercase (Issue #2002)





 
I understand that, but why does it need to start with an upper case A?
 
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you were mentioned.Message ID: ***@***.***&gt;
I've tried to solve this multiple times and ended up with a complicated but different solution every time. I'll try again to see if I can make any difference this time.

P.S. We've been involved with customized headers and headers from the io package, which causes a significant amount of transformation code has be written during the implementation.

Fix patch:
diff --git a/dio/CHANGELOG.md b/dio/CHANGELOG.md
index 306a2b79f..786414884 100644
--- a/dio/CHANGELOG.md
+++ b/dio/CHANGELOG.md
@@ -6,6 +6,7 @@ See the [Migration Guide][] for the complete breaking changes list.**
 ## Unreleased
 
 - Improve `SyncTransformer`'s stream transform.
+- Allow case-sensitive header keys with the `preserveHeaderCase` flag through options.
 
 ## 5.3.4
 
diff --git a/dio/README-ZH.md b/dio/README-ZH.md
index 6ccca968a..a3819f000 100644
--- a/dio/README-ZH.md
+++ b/dio/README-ZH.md
@@ -291,8 +291,20 @@ String method;
 String? baseUrl;
 
 /// HTTP 请求头。
+///
+/// 请求头的键是否相等的判断大小写不敏感的。
+/// 例如：`content-type` 和 `Content-Type` 会视为同样的请求头键。
 Map<String, dynamic>? headers;
 
+/// 是否保留请求头的大小写。
+///
+/// 默认值为 false。
+///
+/// 该选项在以下场景无效：
+///  - XHR 不支持直接处理。
+///  - 按照 HTTP/2 的标准，只支持小写请求头键。
+bool? preserveHeaderCase;
+
 /// 连接服务器超时时间.
 Duration? connectTimeout;
 
diff --git a/dio/README.md b/dio/README.md
index c415d8f72..4a268d1c2 100644
--- a/dio/README.md
+++ b/dio/README.md
@@ -275,8 +275,20 @@ String method;
 String? baseUrl;
 
 /// Http request headers.
+///
+/// The equality of the header keys is case-insensitive,
+/// e.g.: `content-type` and `Content-Type` will be treated as the same key.
 Map<String, dynamic>? headers;
 
+/// Whether the case of header keys should be preserved.
+///
+/// Defaults to false.
+///
+/// This option WILL NOT take effect on these circumstances:
+/// - XHR ([HttpRequest]) does not support handling this explicitly.
+/// - The HTTP/2 standard only supports lowercase header keys.
+bool? preserveHeaderCase;
+
 /// Timeout for opening url.
 Duration? connectTimeout;
 
diff --git a/dio/lib/src/adapters/io_adapter.dart b/dio/lib/src/adapters/io_adapter.dart
index 35a1b781b..5c7d94ece 100644
--- a/dio/lib/src/adapters/io_adapter.dart
+++ b/dio/lib/src/adapters/io_adapter.dart
@@ -111,8 +111,14 @@ class IOHttpClientAdapter implements HttpClientAdapter {
       }
 
       // Set Headers
-      options.headers.forEach((k, v) {
-        if (v != null) request.headers.set(k, v);
+      options.headers.forEach((key, value) {
+        if (value != null) {
+          request.headers.set(
+            key,
+            value,
+            preserveHeaderCase: options.preserveHeaderCase,
+          );
+        }
       });
     } on SocketException catch (e) {
       if (e.message.contains('timed out')) {
diff --git a/dio/lib/src/dio_mixin.dart b/dio/lib/src/dio_mixin.dart
index 0387540a4..66973d5ba 100644
--- a/dio/lib/src/dio_mixin.dart
+++ b/dio/lib/src/dio_mixin.dart
@@ -517,7 +517,10 @@ abstract class DioMixin implements Dio {
         stream,
         cancelToken?.whenCancel,
       );
-      final headers = Headers.fromMap(responseBody.headers);
+      final headers = Headers.fromMap(
+        responseBody.headers,
+        preserveHeaderCase: reqOpt.preserveHeaderCase,
+      );
       // Make sure headers and [ResponseBody.headers] are the same instance.
       responseBody.headers = headers.map;
       final ret = Response<dynamic>(
@@ -713,7 +716,10 @@ abstract class DioMixin implements Dio {
       final T? data = response.data as T?;
       final Headers headers;
       if (data is ResponseBody) {
-        headers = Headers.fromMap(data.headers);
+        headers = Headers.fromMap(
+          data.headers,
+          preserveHeaderCase: requestOptions.preserveHeaderCase,
+        );
       } else {
         headers = response.headers;
       }
diff --git a/dio/lib/src/headers.dart b/dio/lib/src/headers.dart
index 42854c3e2..d0d9524a8 100644
--- a/dio/lib/src/headers.dart
+++ b/dio/lib/src/headers.dart
@@ -7,12 +7,16 @@ typedef HeaderForEachCallback = void Function(String name, List<String> values);
 
 /// The headers class for requests and responses.
 class Headers {
-  Headers() : _map = caseInsensitiveKeyMap<List<String>>();
+  Headers({
+    this.preserveHeaderCase = false,
+  }) : _map = caseInsensitiveKeyMap<List<String>>();
 
   /// Create the [Headers] from a [Map] instance.
-  Headers.fromMap(Map<String, List<String>> map)
-      : _map = caseInsensitiveKeyMap<List<String>>(
-          map.map((k, v) => MapEntry(k.trim().toLowerCase(), v)),
+  Headers.fromMap(
+    Map<String, List<String>> map, {
+    this.preserveHeaderCase = false,
+  }) : _map = caseInsensitiveKeyMap<List<String>>(
+          map.map((k, v) => MapEntry(k.trim(), v)),
         );
 
   static const acceptHeader = 'accept';
@@ -28,6 +32,11 @@ class Headers {
 
   static final jsonMimeType = MediaType.parse(jsonContentType);
 
+  /// Whether the header key should be case-sensitive.
+  ///
+  /// Defaults to false.
+  final bool preserveHeaderCase;
+
   final Map<String, List<String>> _map;
 
   Map<String, List<String>> get map => _map;
@@ -35,7 +44,7 @@ class Headers {
   /// Returns the list of values for the header named [name]. If there
   /// is no header with the provided name, [:null:] will be returned.
   List<String>? operator [](String name) {
-    return _map[name.trim().toLowerCase()];
+    return _map[name.trim()];
   }
 
   /// Convenience method for the value for a single valued header. If
@@ -63,9 +72,9 @@ class Headers {
   /// cleared before the value [value] is added as its value.
   void set(String name, dynamic value) {
     if (value == null) return;
-    name = name.trim().toLowerCase();
+    name = name.trim();
     if (value is List) {
-      _map[name] = value.map<String>((e) => e.toString()).toList();
+      _map[name] = value.map<String>((e) => '$e').toList();
     } else {
       _map[name] = ['$value'.trim()];
     }
diff --git a/dio/lib/src/options.dart b/dio/lib/src/options.dart
index 44038d380..b95994f7c 100644
--- a/dio/lib/src/options.dart
+++ b/dio/lib/src/options.dart
@@ -130,6 +130,7 @@ class BaseOptions extends _RequestConfig with OptionsMixin {
     Map<String, dynamic>? queryParameters,
     Map<String, dynamic>? extra,
     Map<String, dynamic>? headers,
+    bool preserveHeaderCase = false,
     ResponseType? responseType = ResponseType.json,
     String? contentType,
     ValidateStatus? validateStatus,
@@ -148,6 +149,7 @@ class BaseOptions extends _RequestConfig with OptionsMixin {
           sendTimeout: sendTimeout,
           extra: extra,
           headers: headers,
+          preserveHeaderCase: preserveHeaderCase,
           responseType: responseType,
           contentType: contentType,
           validateStatus: validateStatus,
@@ -175,6 +177,7 @@ class BaseOptions extends _RequestConfig with OptionsMixin {
     Duration? sendTimeout,
     Map<String, Object?>? extra,
     Map<String, Object?>? headers,
+    bool? preserveHeaderCase,
     ResponseType? responseType,
     String? contentType,
     ValidateStatus? validateStatus,
@@ -195,6 +198,7 @@ class BaseOptions extends _RequestConfig with OptionsMixin {
       sendTimeout: sendTimeout ?? this.sendTimeout,
       extra: extra ?? Map.from(this.extra),
       headers: headers ?? Map.from(this.headers),
+      preserveHeaderCase: preserveHeaderCase ?? this.preserveHeaderCase,
       responseType: responseType ?? this.responseType,
       contentType: contentType ?? this.contentType,
       validateStatus: validateStatus ?? this.validateStatus,
@@ -218,6 +222,7 @@ class Options {
     Duration? receiveTimeout,
     this.extra,
     this.headers,
+    this.preserveHeaderCase,
     this.responseType,
     this.contentType,
     this.validateStatus,
@@ -240,6 +245,7 @@ class Options {
     Duration? receiveTimeout,
     Map<String, Object?>? extra,
     Map<String, Object?>? headers,
+    bool? preserveHeaderCase,
     ResponseType? responseType,
     String? contentType,
     ValidateStatus? validateStatus,
@@ -276,6 +282,7 @@ class Options {
       receiveTimeout: receiveTimeout ?? this.receiveTimeout,
       extra: extra ?? effectiveExtra,
       headers: headers ?? effectiveHeaders,
+      preserveHeaderCase: preserveHeaderCase ?? this.preserveHeaderCase,
       responseType: responseType ?? this.responseType,
       contentType: contentType ?? this.contentType,
       validateStatus: validateStatus ?? this.validateStatus,
@@ -324,6 +331,7 @@ class Options {
       baseUrl: baseOpt.baseUrl,
       path: path,
       data: data,
+      preserveHeaderCase: preserveHeaderCase ?? baseOpt.preserveHeaderCase,
       sourceStackTrace: sourceStackTrace ?? StackTrace.current,
       connectTimeout: baseOpt.connectTimeout,
       sendTimeout: sendTimeout ?? baseOpt.sendTimeout,
@@ -354,10 +362,19 @@ class Options {
 
   /// HTTP request headers.
   ///
-  /// The keys of the header are case-insensitive,
-  /// e.g.: content-type and Content-Type will be treated as the same key.
+  /// The equality of the header keys is case-insensitive,
+  /// e.g.: `content-type` and `Content-Type` will be treated as the same key.
   Map<String, dynamic>? headers;
 
+  /// Whether the case of header keys should be preserved.
+  ///
+  /// Defaults to false.
+  ///
+  /// This option WILL NOT take effect on these circumstances:
+  /// - XHR ([HttpRequest]) does not support handling this explicitly.
+  /// - The HTTP/2 standard only supports lowercase header keys.
+  bool? preserveHeaderCase;
+
   /// Timeout when sending data.
   ///
   /// [Dio] will throw the [DioException] with
@@ -474,6 +491,7 @@ class RequestOptions extends _RequestConfig with OptionsMixin {
     String? baseUrl,
     Map<String, dynamic>? extra,
     Map<String, dynamic>? headers,
+    bool? preserveHeaderCase,
     ResponseType? responseType,
     String? contentType,
     ValidateStatus? validateStatus,
@@ -493,6 +511,7 @@ class RequestOptions extends _RequestConfig with OptionsMixin {
           receiveTimeout: receiveTimeout,
           extra: extra,
           headers: headers,
+          preserveHeaderCase: preserveHeaderCase,
           responseType: responseType,
           contentType: contentType,
           validateStatus: validateStatus,
@@ -525,6 +544,7 @@ class RequestOptions extends _RequestConfig with OptionsMixin {
     CancelToken? cancelToken,
     Map<String, dynamic>? extra,
     Map<String, dynamic>? headers,
+    bool? preserveHeaderCase,
     ResponseType? responseType,
     String? contentType,
     ValidateStatus? validateStatus,
@@ -561,6 +581,7 @@ class RequestOptions extends _RequestConfig with OptionsMixin {
       cancelToken: cancelToken ?? this.cancelToken,
       extra: extra ?? Map.from(this.extra),
       headers: headers ?? Map.from(this.headers),
+      preserveHeaderCase: preserveHeaderCase ?? this.preserveHeaderCase,
       responseType: responseType ?? this.responseType,
       validateStatus: validateStatus ?? this.validateStatus,
       receiveDataWhenStatusError:
@@ -638,6 +659,7 @@ class _RequestConfig {
     String? method,
     Map<String, dynamic>? extra,
     Map<String, dynamic>? headers,
+    bool? preserveHeaderCase,
     String? contentType,
     ListFormat? listFormat,
     bool? followRedirects,
@@ -653,6 +675,7 @@ class _RequestConfig {
         assert(sendTimeout == null || !sendTimeout.isNegative),
         _sendTimeout = sendTimeout,
         method = method ?? 'GET',
+        preserveHeaderCase = preserveHeaderCase ?? false,
         listFormat = listFormat ?? ListFormat.multi,
         extra = extra ?? {},
         followRedirects = followRedirects ?? true,
@@ -692,6 +715,8 @@ class _RequestConfig {
     }
   }
 
+  late bool preserveHeaderCase;
+
   Duration? get sendTimeout => _sendTimeout;
   Duration? _sendTimeout;
 
diff --git a/dio/lib/src/response.dart b/dio/lib/src/response.dart
index b6ff998de..4713330f2 100644
--- a/dio/lib/src/response.dart
+++ b/dio/lib/src/response.dart
@@ -19,7 +19,8 @@ class Response<T> {
     this.redirects = const [],
     Map<String, dynamic>? extra,
     Headers? headers,
-  })  : headers = headers ?? Headers(),
+  })  : headers = headers ??
+            Headers(preserveHeaderCase: requestOptions.preserveHeaderCase),
         extra = extra ?? <String, dynamic>{};
 
   /// The response payload in specific type.
diff --git a/plugins/native_dio_adapter/CHANGELOG.md b/plugins/native_dio_adapter/CHANGELOG.md
index c68a5d717..92f8f73ae 100644
--- a/plugins/native_dio_adapter/CHANGELOG.md
+++ b/plugins/native_dio_adapter/CHANGELOG.md
@@ -13,6 +13,7 @@
 - Bump `cronet_http` version.
 - Minimal required Dart version is now 3.1.
 - Minimal required Flutter version is now 3.13.0.
+- Allow case-sensitive header keys with the `preserveHeaderCase` flag through options.
 
 ## 1.0.0+2
 
diff --git a/plugins/native_dio_adapter/lib/src/conversion_layer_adapter.dart b/plugins/native_dio_adapter/lib/src/conversion_layer_adapter.dart
index a035f2c45..0e1904f0b 100644
--- a/plugins/native_dio_adapter/lib/src/conversion_layer_adapter.dart
+++ b/plugins/native_dio_adapter/lib/src/conversion_layer_adapter.dart
@@ -24,7 +24,7 @@ class ConversionLayerAdapter implements HttpClientAdapter {
   ) async {
     final request = await _fromOptionsAndStream(options, requestStream);
     final response = await client.send(request);
-    return response.toDioResponseBody();
+    return response.toDioResponseBody(options);
   }
 
   @override
@@ -41,7 +41,12 @@ class ConversionLayerAdapter implements HttpClientAdapter {
 
     request.headers.addAll(
       Map.fromEntries(
-        options.headers.entries.map((e) => MapEntry(e.key, e.value.toString())),
+        options.headers.entries.map(
+          (e) => MapEntry(
+            options.preserveHeaderCase ? e.key : e.key.toLowerCase(),
+            e.value.toString(),
+          ),
+        ),
       ),
     );
 
@@ -67,8 +72,13 @@ class ConversionLayerAdapter implements HttpClientAdapter {
 }
 
 extension on StreamedResponse {
-  ResponseBody toDioResponseBody() {
-    final dioHeaders = headers.entries.map((e) => MapEntry(e.key, [e.value]));
+  ResponseBody toDioResponseBody(RequestOptions options) {
+    final dioHeaders = headers.entries.map(
+      (e) => MapEntry(
+        options.preserveHeaderCase ? e.key : e.key.toLowerCase(),
+        [e.value],
+      ),
+    );
     return ResponseBody(
       stream.cast<Uint8List>(),
       statusCode,


Test patch:
diff --git a/dio/test/basic_test.dart b/dio/test/basic_test.dart
index e52faeae6..272d4f997 100644
--- a/dio/test/basic_test.dart
+++ b/dio/test/basic_test.dart
@@ -8,36 +8,6 @@ import 'mock/adapters.dart';
 import 'utils.dart';
 
 void main() {
-  test('test headers', () {
-    final headers = Headers.fromMap({
-      'set-cookie': ['k=v', 'k1=v1'],
-      'content-length': ['200'],
-      'test': ['1', '2'],
-    });
-    headers.add('SET-COOKIE', 'k2=v2');
-    expect(headers.value('content-length'), '200');
-    expect(Future(() => headers.value('test')), throwsException);
-    expect(headers['set-cookie']?.length, 3);
-    headers.remove('set-cookie', 'k=v');
-    expect(headers['set-cookie']?.length, 2);
-    headers.removeAll('set-cookie');
-    expect(headers['set-cookie'], isNull);
-    final ls = [];
-    headers.forEach((k, list) => ls.addAll(list));
-    expect(ls.length, 3);
-    expect(headers.toString(), 'content-length: 200\ntest: 1\ntest: 2\n');
-    headers.set('content-length', '300');
-    expect(headers.value('content-length'), '300');
-    headers.set('content-length', ['400']);
-    expect(headers.value('content-length'), '400');
-
-    final headers1 = Headers();
-    headers1.set('xx', 'v');
-    expect(headers1.value('xx'), 'v');
-    headers1.clear();
-    expect(headers1.map.isEmpty, isTrue);
-  });
-
   test('send with an invalid URL', () async {
     await expectLater(
       Dio().get('http://http.invalid'),
diff --git a/dio/test/headers_test.dart b/dio/test/headers_test.dart
new file mode 100644
index 000000000..4d71553fe
--- /dev/null
+++ b/dio/test/headers_test.dart
@@ -0,0 +1,56 @@
+import 'dart:async';
+
+import 'package:dio/dio.dart';
+import 'package:test/test.dart';
+
+void main() {
+  group(Headers, () {
+    test('set', () {
+      final headers = Headers.fromMap({
+        'set-cookie': ['k=v', 'k1=v1'],
+        'content-length': ['200'],
+        'test': ['1', '2'],
+      });
+      headers.add('SET-COOKIE', 'k2=v2');
+      expect(headers.value('content-length'), '200');
+      expect(Future(() => headers.value('test')), throwsException);
+      expect(headers['set-cookie']?.length, 3);
+      headers.remove('set-cookie', 'k=v');
+      expect(headers['set-cookie']?.length, 2);
+      headers.removeAll('set-cookie');
+      expect(headers['set-cookie'], isNull);
+      final ls = [];
+      headers.forEach((k, list) => ls.addAll(list));
+      expect(ls.length, 3);
+      expect(headers.toString(), 'content-length: 200\ntest: 1\ntest: 2\n');
+      headers.set('content-length', '300');
+      expect(headers.value('content-length'), '300');
+      headers.set('content-length', ['400']);
+      expect(headers.value('content-length'), '400');
+    });
+
+    test('clear', () {
+      final headers1 = Headers();
+      headers1.set('xx', 'v');
+      expect(headers1.value('xx'), 'v');
+      headers1.clear();
+      expect(headers1.map.isEmpty, isTrue);
+    });
+
+    test('case-sensitive', () {
+      final headers = Headers.fromMap(
+        {
+          'SET-COOKIE': ['k=v', 'k1=v1'],
+          'content-length': ['200'],
+          'Test': ['1', '2'],
+        },
+        preserveHeaderCase: true,
+      );
+      expect(headers['SET-COOKIE']?.length, 2);
+      // Although it's case-sensitive, we still use case-insensitive map.
+      expect(headers['set-cookie']?.length, 2);
+      expect(headers['content-length']?.length, 1);
+      expect(headers['Test']?.length, 2);
+    });
+  });
+}
diff --git a/dio/test/options_test.dart b/dio/test/options_test.dart
index c31229bf9..086fe8b34 100644
--- a/dio/test/options_test.dart
+++ b/dio/test/options_test.dart
@@ -1,10 +1,15 @@
 @TestOn('vm')
 import 'dart:convert';
+import 'dart:typed_data';
 
 import 'package:dio/dio.dart';
+import 'package:dio/io.dart';
+import 'package:dio/src/utils.dart';
+import 'package:mockito/mockito.dart';
 import 'package:test/test.dart';
 
 import 'mock/adapters.dart';
+import 'mock/http_mock.mocks.dart';
 import 'utils.dart';
 
 void main() {
@@ -523,4 +528,62 @@ void main() {
       expect(response.data, 'test');
     }
   });
+
+  test('Headers can be case-sensitive', () async {
+    final dio = Dio();
+    final client = MockHttpClient();
+    dio.httpClientAdapter = IOHttpClientAdapter(createHttpClient: () => client);
+
+    final headerMap = caseInsensitiveKeyMap();
+    late MockHttpHeaders mockRequestHeaders;
+    when(client.openUrl(any, any)).thenAnswer((_) async {
+      final request = MockHttpClientRequest();
+      final response = MockHttpClientResponse();
+      mockRequestHeaders = MockHttpHeaders();
+      when(
+        mockRequestHeaders.set(
+          any,
+          any,
+          preserveHeaderCase: anyNamed('preserveHeaderCase'),
+        ),
+      ).thenAnswer((invocation) {
+        final args = invocation.positionalArguments.cast<String>();
+        final preserveHeaderCase =
+            invocation.namedArguments[#preserveHeaderCase] as bool;
+        headerMap[preserveHeaderCase ? args[0] : args[0].toLowerCase()] =
+            args[1];
+      });
+      when(request.headers).thenAnswer((_) => mockRequestHeaders);
+      when(request.close()).thenAnswer((_) => Future.value(response));
+      when(request.addStream(any)).thenAnswer((_) async => null);
+      when(response.headers).thenReturn(MockHttpHeaders());
+      when(response.statusCode).thenReturn(200);
+      when(response.reasonPhrase).thenReturn('OK');
+      when(response.isRedirect).thenReturn(false);
+      when(response.redirects).thenReturn([]);
+      when(response.transform(any))
+          .thenAnswer((_) => Stream<Uint8List>.empty());
+      return Future.value(request);
+    });
+
+    await dio.get(
+      '',
+      options: Options(
+        preserveHeaderCase: true,
+        headers: {'Sensitive': 'test', 'insensitive': 'test'},
+      ),
+    );
+    expect(headerMap['Sensitive'], 'test');
+    expect(headerMap['insensitive'], 'test');
+    headerMap.clear();
+
+    await dio.get(
+      '',
+      options: Options(
+        headers: {'Sensitive': 'test', 'insensitive': 'test'},
+      ),
+    );
+    expect(headerMap['sensitive'], 'test');
+    expect(headerMap['insensitive'], 'test');
+  });
 }