[Private Network Access] Store redirect info when redirect mode is manual

When a redirected navigation request needs a preflight because of
Private Network Access, the redirect_info is not properly stored in
CorsURLLoader.  In FollowRedirect, the request URL and methods, etc will
be overwritten with the stored redirect_info which is currently empty,
causing the ERR_INVALID_URL in the associated bug.

This CL stores the redirect_info properly for this case.

The "missing cors headers" test cases are expected to fail because
kPrivateNetworkAccessForNavigationsWarningOnly is by default enabled so
requests actually succeeds
.
When kPrivateNetworkAccessForNavigations is disabled
(virtual/pna-navigations-disabled), the preflight aren't sent so we
expect failures too.

Bug: 327017904,327022946
Change-Id: I5631a2ba865c9a39dde7095888d7df24539194f2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5331430
Reviewed-by: Yifan Luo <lyf@chromium.org>
Reviewed-by: Adam Rice <ricea@chromium.org>
Commit-Queue: Jonathan Hao <phao@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1266894}
diff --git a/fetch/private-network-access/anchor.tentative.https.window.js b/fetch/private-network-access/anchor.tentative.https.window.js
index 4e860ad..f547386 100644
--- a/fetch/private-network-access/anchor.tentative.https.window.js
+++ b/fetch/private-network-access/anchor.tentative.https.window.js
@@ -149,6 +149,44 @@
   expected: NavigationTestResult.SUCCESS,
 }), "public to public: no preflight required.");
 
+subsetTestByKey(
+    'from-public', promise_test_parallel,
+    t => anchorTest(t, {
+      source: {server: Server.HTTPS_PUBLIC},
+      target: {
+        server: Server.HTTPS_PUBLIC,
+        behavior: {
+          redirect: preflightUrl({
+            server: Server.HTTPS_PRIVATE,
+            behavior: {
+              preflight: PreflightBehavior.noCorsHeader(token()),
+            }
+          }),
+        }
+      },
+      expected: NavigationTestResult.FAILURE,
+    }),
+    'public to public redirected to private: missing CORS headers.');
+
+subsetTestByKey(
+    'from-public', promise_test_parallel,
+    t => anchorTest(t, {
+      source: {server: Server.HTTPS_PUBLIC},
+      target: {
+        server: Server.HTTPS_PUBLIC,
+        behavior: {
+          redirect: preflightUrl({
+            server: Server.HTTPS_PRIVATE,
+            behavior: {
+              preflight: PreflightBehavior.navigation(token()),
+            }
+          }),
+        }
+      },
+      expected: NavigationTestResult.SUCCESS,
+    }),
+    'public to public to private: success.');
+
 // The following tests verify that `CSP: treat-as-public-address` makes
 // documents behave as if they had been served from a public IP address.
 
diff --git a/fetch/private-network-access/resources/support.sub.js b/fetch/private-network-access/resources/support.sub.js
index 46a9d9e..35f39b1 100644
--- a/fetch/private-network-access/resources/support.sub.js
+++ b/fetch/private-network-access/resources/support.sub.js
@@ -480,6 +480,13 @@
 };
 
 async function windowOpenTest(t, { source, target, expected }) {
+  if (target.behavior && target.behavior.redirect) {
+    target.behavior.redirect.searchParams.set('file', 'openee.html');
+    target.behavior.redirect.searchParams.set(
+        'file-if-no-preflight-received',
+        'no-preflight-received.html',
+    );
+  }
   const targetUrl = preflightUrl(target);
   targetUrl.searchParams.set("file", "openee.html");
   targetUrl.searchParams.set(
@@ -507,6 +514,13 @@
 }
 
 async function windowOpenExistingTest(t, { source, target, expected }) {
+  if (target.behavior && target.behavior.redirect) {
+    target.behavior.redirect.searchParams.set('file', 'openee.html');
+    target.behavior.redirect.searchParams.set(
+        'file-if-no-preflight-received',
+        'no-preflight-received.html',
+    );
+  }
   const targetUrl = preflightUrl(target);
   targetUrl.searchParams.set("file", "openee.html");
   targetUrl.searchParams.set(
@@ -535,6 +549,13 @@
 }
 
 async function anchorTest(t, { source, target, expected }) {
+  if (target.behavior && target.behavior.redirect) {
+    target.behavior.redirect.searchParams.set('file', 'openee.html');
+    target.behavior.redirect.searchParams.set(
+        'file-if-no-preflight-received',
+        'no-preflight-received.html',
+    );
+  }
   const targetUrl = preflightUrl(target);
   targetUrl.searchParams.set("file", "openee.html");
   targetUrl.searchParams.set(
diff --git a/fetch/private-network-access/window-open-existing.tentative.https.window.js b/fetch/private-network-access/window-open-existing.tentative.https.window.js
index 6a2a624..565a211 100644
--- a/fetch/private-network-access/window-open-existing.tentative.https.window.js
+++ b/fetch/private-network-access/window-open-existing.tentative.https.window.js
@@ -167,6 +167,44 @@
     }),
     'public to public: no preflight required.');
 
+subsetTestByKey(
+    'from-public', promise_test_parallel,
+    t => windowOpenExistingTest(t, {
+      source: {server: Server.HTTPS_PUBLIC},
+      target: {
+        server: Server.HTTPS_PUBLIC,
+        behavior: {
+          redirect: preflightUrl({
+            server: Server.HTTPS_PRIVATE,
+            behavior: {
+              preflight: PreflightBehavior.noCorsHeader(token()),
+            }
+          }),
+        }
+      },
+      expected: NavigationTestResult.FAILURE,
+    }),
+    'public to public redirected to private: missing CORS headers.');
+
+subsetTestByKey(
+    'from-public', promise_test_parallel,
+    t => windowOpenExistingTest(t, {
+      source: {server: Server.HTTPS_PUBLIC},
+      target: {
+        server: Server.HTTPS_PUBLIC,
+        behavior: {
+          redirect: preflightUrl({
+            server: Server.HTTPS_PRIVATE,
+            behavior: {
+              preflight: PreflightBehavior.navigation(token()),
+            }
+          }),
+        }
+      },
+      expected: NavigationTestResult.SUCCESS,
+    }),
+    'public to public to private: success.');
+
 // The following tests verify that `CSP: treat-as-public-address` makes
 // documents behave as if they had been served from a public IP address.
 
diff --git a/fetch/private-network-access/window-open.tentative.https.window.js b/fetch/private-network-access/window-open.tentative.https.window.js
index 6793d1f..42d70af 100644
--- a/fetch/private-network-access/window-open.tentative.https.window.js
+++ b/fetch/private-network-access/window-open.tentative.https.window.js
@@ -149,6 +149,44 @@
   expected: NavigationTestResult.SUCCESS,
 }), "public to public: no preflight required.");
 
+subsetTestByKey(
+    'from-public', promise_test_parallel,
+    t => windowOpenTest(t, {
+      source: {server: Server.HTTPS_PUBLIC},
+      target: {
+        server: Server.HTTPS_PUBLIC,
+        behavior: {
+          redirect: preflightUrl({
+            server: Server.HTTPS_PRIVATE,
+            behavior: {
+              preflight: PreflightBehavior.noCorsHeader(token()),
+            }
+          }),
+        }
+      },
+      expected: NavigationTestResult.FAILURE,
+    }),
+    'public to public redirected to private: missing CORS headers.');
+
+subsetTestByKey(
+    'from-public', promise_test_parallel,
+    t => windowOpenTest(t, {
+      source: {server: Server.HTTPS_PUBLIC},
+      target: {
+        server: Server.HTTPS_PUBLIC,
+        behavior: {
+          redirect: preflightUrl({
+            server: Server.HTTPS_PRIVATE,
+            behavior: {
+              preflight: PreflightBehavior.navigation(token()),
+            }
+          }),
+        }
+      },
+      expected: NavigationTestResult.SUCCESS,
+    }),
+    'public to public to private: success.');
+
 // The following tests verify that `CSP: treat-as-public-address` makes
 // documents behave as if they had been served from a public IP address.