Support `BaseResponseWithUrl` in `package:cupertino_http` and `package:cronet_http` (#1110)

diff --git a/.github/workflows/cupertino.yml b/.github/workflows/cupertino.yml
index 6617635..7194f9f 100644
--- a/.github/workflows/cupertino.yml
+++ b/.github/workflows/cupertino.yml
@@ -57,7 +57,7 @@
       matrix:
         # Test on the minimum supported flutter version and the latest
         # version.
-        flutter-version: ["3.10.0", "any"]
+        flutter-version: ["3.16.0", "any"]
     runs-on: macos-latest
     defaults:
       run:
diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml
index 3cb8d19..18ba166 100644
--- a/.github/workflows/dart.yml
+++ b/.github/workflows/dart.yml
@@ -40,16 +40,16 @@
       - name: mono_repo self validate
         run: dart pub global run mono_repo generate --validate
   job_002:
-    name: "analyze_and_format; linux; Dart 3.0.0; PKGS: pkgs/http_client_conformance_tests, pkgs/http_profile; `dart analyze --fatal-infos`"
+    name: "analyze_and_format; linux; Dart 3.0.0; PKG: pkgs/http_profile; `dart analyze --fatal-infos`"
     runs-on: ubuntu-latest
     steps:
       - name: Cache Pub hosted dependencies
         uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84
         with:
           path: "~/.pub-cache/hosted"
-          key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.0.0;packages:pkgs/http_client_conformance_tests-pkgs/http_profile;commands:analyze_1"
+          key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.0.0;packages:pkgs/http_profile;commands:analyze_1"
           restore-keys: |
-            os:ubuntu-latest;pub-cache-hosted;sdk:3.0.0;packages:pkgs/http_client_conformance_tests-pkgs/http_profile
+            os:ubuntu-latest;pub-cache-hosted;sdk:3.0.0;packages:pkgs/http_profile
             os:ubuntu-latest;pub-cache-hosted;sdk:3.0.0
             os:ubuntu-latest;pub-cache-hosted
             os:ubuntu-latest
@@ -60,15 +60,6 @@
       - id: checkout
         name: Checkout repository
         uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
-      - id: pkgs_http_client_conformance_tests_pub_upgrade
-        name: pkgs/http_client_conformance_tests; dart pub upgrade
-        run: dart pub upgrade
-        if: "always() && steps.checkout.conclusion == 'success'"
-        working-directory: pkgs/http_client_conformance_tests
-      - name: "pkgs/http_client_conformance_tests; dart analyze --fatal-infos"
-        run: dart analyze --fatal-infos
-        if: "always() && steps.pkgs_http_client_conformance_tests_pub_upgrade.conclusion == 'success'"
-        working-directory: pkgs/http_client_conformance_tests
       - id: pkgs_http_profile_pub_upgrade
         name: pkgs/http_profile; dart pub upgrade
         run: dart pub upgrade
@@ -79,16 +70,16 @@
         if: "always() && steps.pkgs_http_profile_pub_upgrade.conclusion == 'success'"
         working-directory: pkgs/http_profile
   job_003:
-    name: "analyze_and_format; linux; Dart 3.2.0; PKG: pkgs/http; `dart analyze --fatal-infos`"
+    name: "analyze_and_format; linux; Dart 3.2.0; PKGS: pkgs/http, pkgs/http_client_conformance_tests; `dart analyze --fatal-infos`"
     runs-on: ubuntu-latest
     steps:
       - name: Cache Pub hosted dependencies
         uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84
         with:
           path: "~/.pub-cache/hosted"
-          key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.2.0;packages:pkgs/http;commands:analyze_1"
+          key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.2.0;packages:pkgs/http-pkgs/http_client_conformance_tests;commands:analyze_1"
           restore-keys: |
-            os:ubuntu-latest;pub-cache-hosted;sdk:3.2.0;packages:pkgs/http
+            os:ubuntu-latest;pub-cache-hosted;sdk:3.2.0;packages:pkgs/http-pkgs/http_client_conformance_tests
             os:ubuntu-latest;pub-cache-hosted;sdk:3.2.0
             os:ubuntu-latest;pub-cache-hosted
             os:ubuntu-latest
@@ -108,6 +99,15 @@
         run: dart analyze --fatal-infos
         if: "always() && steps.pkgs_http_pub_upgrade.conclusion == 'success'"
         working-directory: pkgs/http
+      - id: pkgs_http_client_conformance_tests_pub_upgrade
+        name: pkgs/http_client_conformance_tests; dart pub upgrade
+        run: dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/http_client_conformance_tests
+      - name: "pkgs/http_client_conformance_tests; dart analyze --fatal-infos"
+        run: dart analyze --fatal-infos
+        if: "always() && steps.pkgs_http_client_conformance_tests_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/http_client_conformance_tests
   job_004:
     name: "analyze_and_format; linux; Dart dev; PKGS: pkgs/http, pkgs/http_client_conformance_tests, pkgs/http_profile; `dart analyze --fatal-infos`"
     runs-on: ubuntu-latest
diff --git a/pkgs/cronet_http/CHANGELOG.md b/pkgs/cronet_http/CHANGELOG.md
index fa461d5..bc92551 100644
--- a/pkgs/cronet_http/CHANGELOG.md
+++ b/pkgs/cronet_http/CHANGELOG.md
@@ -1,7 +1,8 @@
-## 1.0.1-wip
+## 1.1.0
 
 * Use `package:http_image_provider` in the example application.
 * Support Android API 21+.
+* Support `BaseResponseWithUrl`.
 
 ## 1.0.0
 
diff --git a/pkgs/cronet_http/example/pubspec.yaml b/pkgs/cronet_http/example/pubspec.yaml
index 904b1e5..41c6611 100644
--- a/pkgs/cronet_http/example/pubspec.yaml
+++ b/pkgs/cronet_http/example/pubspec.yaml
@@ -4,7 +4,7 @@
 publish_to: 'none'
 
 environment:
-  sdk: ^3.0.0
+  sdk: ^3.2.0
 
 dependencies:
   cronet_http:
diff --git a/pkgs/cronet_http/lib/src/cronet_client.dart b/pkgs/cronet_http/lib/src/cronet_client.dart
index 272b602..3c2a426 100644
--- a/pkgs/cronet_http/lib/src/cronet_client.dart
+++ b/pkgs/cronet_http/lib/src/cronet_client.dart
@@ -23,6 +23,21 @@
 final _digitRegex = RegExp(r'^\d+$');
 const _bufferSize = 10 * 1024; // The size of the Cronet read buffer.
 
+/// This class can be removed when `package:http` v2 is released.
+class _StreamedResponseWithUrl extends StreamedResponse
+    implements BaseResponseWithUrl {
+  @override
+  final Uri url;
+
+  _StreamedResponseWithUrl(super.stream, super.statusCode,
+      {required this.url,
+      super.contentLength,
+      super.request,
+      super.headers,
+      super.isRedirect,
+      super.reasonPhrase});
+}
+
 /// The type of caching to use when making HTTP requests.
 enum CacheMode {
   disabled,
@@ -163,9 +178,11 @@
         case final contentLengthHeader?:
           contentLength = int.parse(contentLengthHeader);
       }
-      responseCompleter.complete(StreamedResponse(
+      responseCompleter.complete(_StreamedResponseWithUrl(
         responseStream!.stream,
         responseInfo.getHttpStatusCode(),
+        url: Uri.parse(
+            responseInfo.getUrl().toDartString(releaseOriginal: true)),
         contentLength: contentLength,
         reasonPhrase: responseInfo
             .getHttpStatusText()
diff --git a/pkgs/cronet_http/pubspec.yaml b/pkgs/cronet_http/pubspec.yaml
index c9e0e4d..f1a642b 100644
--- a/pkgs/cronet_http/pubspec.yaml
+++ b/pkgs/cronet_http/pubspec.yaml
@@ -1,5 +1,5 @@
 name: cronet_http
-version: 1.0.1-wip
+version: 1.1.0
 description: >-
   An Android Flutter plugin that provides access to the Cronet HTTP client.
 repository: https://github.com/dart-lang/http/tree/master/pkgs/cronet_http
@@ -11,7 +11,7 @@
 dependencies:
   flutter:
     sdk: flutter
-  http: '>=0.13.4 <2.0.0'
+  http: ^1.2.0
   jni: ^0.7.2
 
 dev_dependencies:
diff --git a/pkgs/cupertino_http/CHANGELOG.md b/pkgs/cupertino_http/CHANGELOG.md
index 395bb8b..d077593 100644
--- a/pkgs/cupertino_http/CHANGELOG.md
+++ b/pkgs/cupertino_http/CHANGELOG.md
@@ -1,6 +1,7 @@
-## 1.2.1-wip
+## 1.3.0
 
 * Use `package:http_image_provider` in the example application.
+* Support `BaseResponseWithUrl`.
 
 ## 1.2.0
 
diff --git a/pkgs/cupertino_http/example/pubspec.yaml b/pkgs/cupertino_http/example/pubspec.yaml
index 0804857..026cb8a 100644
--- a/pkgs/cupertino_http/example/pubspec.yaml
+++ b/pkgs/cupertino_http/example/pubspec.yaml
@@ -6,8 +6,8 @@
 version: 1.0.0+1
 
 environment:
-  sdk: ^3.0.0
-  flutter: '>=3.10.0'
+  sdk: ^3.2.0
+  flutter: ^3.16.0
 
 dependencies:
   cupertino_http:
diff --git a/pkgs/cupertino_http/lib/src/cupertino_client.dart b/pkgs/cupertino_http/lib/src/cupertino_client.dart
index a4c7715..b93dd5c 100644
--- a/pkgs/cupertino_http/lib/src/cupertino_client.dart
+++ b/pkgs/cupertino_http/lib/src/cupertino_client.dart
@@ -17,11 +17,27 @@
 
 final _digitRegex = RegExp(r'^\d+$');
 
+/// This class can be removed when `package:http` v2 is released.
+class _StreamedResponseWithUrl extends StreamedResponse
+    implements BaseResponseWithUrl {
+  @override
+  final Uri url;
+
+  _StreamedResponseWithUrl(super.stream, super.statusCode,
+      {required this.url,
+      super.contentLength,
+      super.request,
+      super.headers,
+      super.isRedirect,
+      super.reasonPhrase});
+}
+
 class _TaskTracker {
   final responseCompleter = Completer<URLResponse>();
   final BaseRequest request;
   final responseController = StreamController<Uint8List>();
   int numRedirects = 0;
+  Uri? lastUrl; // The last URL redirected to.
 
   _TaskTracker(this.request);
 
@@ -180,6 +196,7 @@
     ++taskTracker.numRedirects;
     if (taskTracker.request.followRedirects &&
         taskTracker.numRedirects <= taskTracker.request.maxRedirects) {
+      taskTracker.lastUrl = request.url;
       return request;
     }
     return null;
@@ -292,9 +309,10 @@
       );
     }
 
-    return StreamedResponse(
+    return _StreamedResponseWithUrl(
       taskTracker.responseController.stream,
       response.statusCode,
+      url: taskTracker.lastUrl ?? request.url,
       contentLength: response.expectedContentLength == -1
           ? null
           : response.expectedContentLength,
diff --git a/pkgs/cupertino_http/pubspec.yaml b/pkgs/cupertino_http/pubspec.yaml
index e9a2bbf..644bf1b 100644
--- a/pkgs/cupertino_http/pubspec.yaml
+++ b/pkgs/cupertino_http/pubspec.yaml
@@ -1,20 +1,20 @@
 name: cupertino_http
-version: 1.2.1-wip
+version: 1.3.0
 description: >-
   A macOS/iOS Flutter plugin that provides access to the Foundation URL
   Loading System.
 repository: https://github.com/dart-lang/http/tree/master/pkgs/cupertino_http
 
 environment:
-  sdk: ^3.0.0
-  flutter: '>=3.10.0'  # If changed, update test matrix.
+  sdk: ^3.2.0
+  flutter: ^3.16.0  # If changed, update test matrix.
 
 dependencies:
   async: ^2.5.0
   ffi: ^2.1.0
   flutter:
     sdk: flutter
-  http: '>=0.13.4 <2.0.0'
+  http: ^1.2.0
 
 dev_dependencies:
   dart_flutter_team_lints: ^2.0.0
diff --git a/pkgs/http_client_conformance_tests/lib/src/redirect_tests.dart b/pkgs/http_client_conformance_tests/lib/src/redirect_tests.dart
index 47a77a7..a33d077 100644
--- a/pkgs/http_client_conformance_tests/lib/src/redirect_tests.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/redirect_tests.dart
@@ -27,12 +27,26 @@
     });
     tearDownAll(() => httpServerChannel.sink.add(null));
 
+    test('no redirect', () async {
+      final request = Request('GET', Uri.http(host, '/'))
+        ..followRedirects = false;
+      final response = await client.send(request);
+      expect(response.statusCode, 200);
+      expect(response.isRedirect, false);
+      if (response case BaseResponseWithUrl(url: final url)) {
+        expect(url, Uri.http(host, '/'));
+      }
+    });
+
     test('disallow redirect', () async {
       final request = Request('GET', Uri.http(host, '/1'))
         ..followRedirects = false;
       final response = await client.send(request);
       expect(response.statusCode, 302);
       expect(response.isRedirect, true);
+      if (response case BaseResponseWithUrl(url: final url)) {
+        expect(url, Uri.http(host, '/1'));
+      }
     }, skip: redirectAlwaysAllowed ? 'redirects always allowed' : false);
 
     test('disallow redirect, 0 maxRedirects', () async {
@@ -42,6 +56,9 @@
       final response = await client.send(request);
       expect(response.statusCode, 302);
       expect(response.isRedirect, true);
+      if (response case BaseResponseWithUrl(url: final url)) {
+        expect(url, Uri.http(host, '/1'));
+      }
     }, skip: redirectAlwaysAllowed ? 'redirects always allowed' : false);
 
     test('allow redirect', () async {
@@ -50,6 +67,9 @@
       final response = await client.send(request);
       expect(response.statusCode, 200);
       expect(response.isRedirect, false);
+      if (response case BaseResponseWithUrl(url: final url)) {
+        expect(url, Uri.http(host, '/'));
+      }
     });
 
     test('allow redirect, 0 maxRedirects', () async {
@@ -69,6 +89,9 @@
       final response = await client.send(request);
       expect(response.statusCode, 200);
       expect(response.isRedirect, false);
+      if (response case BaseResponseWithUrl(url: final url)) {
+        expect(url, Uri.http(host, '/'));
+      }
     }, skip: redirectAlwaysAllowed ? 'redirects always allowed' : false);
 
     test('too many redirects', () async {
diff --git a/pkgs/http_client_conformance_tests/pubspec.yaml b/pkgs/http_client_conformance_tests/pubspec.yaml
index 520f131..f904b2d 100644
--- a/pkgs/http_client_conformance_tests/pubspec.yaml
+++ b/pkgs/http_client_conformance_tests/pubspec.yaml
@@ -7,12 +7,12 @@
 publish_to: none
 
 environment:
-  sdk: ^3.0.0
+  sdk: ^3.2.0
 
 dependencies:
   async: ^2.8.2
   dart_style: ^2.2.3
-  http: ^1.0.0
+  http: ^1.2.0
   stream_channel: ^2.1.1
   test: ^1.21.2