Added Sec-Purpose header to prefetch requests.
This CL adds the "Sec-Purpose" header which is added to any prefetch
request. Direct prefetch requests that do not use the prefetch proxy
will have a value of "prefetch", and prefetch requests that do use the
prefetch proxy will have a value of "prefetch;anonymous-client-ip". This
will allow servers to differentiate between the two types of prefetch
requests.
Bug: 1290561
Change-Id: I24928fd75b6a55f92109022e94beeca54b53e2b4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3421706
Reviewed-by: Robert Ogden <[email protected]>
Commit-Queue: Max Curran <[email protected]>
Cr-Commit-Position: refs/heads/main@{#965381}
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 6cc814c..3911d9c7 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1258,6 +1258,8 @@
"prefetch/no_state_prefetch/no_state_prefetch_tab_helper.h",
"prefetch/pref_names.cc",
"prefetch/pref_names.h",
+ "prefetch/prefetch_headers.cc",
+ "prefetch/prefetch_headers.h",
"prefetch/prefetch_prefs.cc",
"prefetch/prefetch_prefs.h",
"prefetch/prefetch_proxy/chrome_speculation_host_delegate.cc",
diff --git a/chrome/browser/prefetch/prefetch_headers.cc b/chrome/browser/prefetch/prefetch_headers.cc
new file mode 100644
index 0000000..554daf6
--- /dev/null
+++ b/chrome/browser/prefetch/prefetch_headers.cc
@@ -0,0 +1,22 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/prefetch/prefetch_headers.h"
+
+namespace prefetch::headers {
+
+// The header used to indicate the purpose of a request. It should have one of
+// the two values below.
+const char kSecPurposeHeaderName[] = "Sec-Purpose";
+
+// This value indicates that the request is a prefetch request made directly to
+// the server.
+const char kSecPurposePrefetchHeaderValue[] = "prefetch";
+
+// This value indicates that the request is a prefetch request made via an
+// anonymous client IP proxy.
+const char kSecPurposePrefetchAnonymousClientIpHeaderValue[] =
+ "prefetch;anonymous-client-ip";
+
+} // namespace prefetch::headers
diff --git a/chrome/browser/prefetch/prefetch_headers.h b/chrome/browser/prefetch/prefetch_headers.h
new file mode 100644
index 0000000..591fed7
--- /dev/null
+++ b/chrome/browser/prefetch/prefetch_headers.h
@@ -0,0 +1,18 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_PREFETCH_PREFETCH_HEADERS_H_
+#define CHROME_BROWSER_PREFETCH_PREFETCH_HEADERS_H_
+
+namespace prefetch::headers {
+
+extern const char kSecPurposeHeaderName[];
+
+extern const char kSecPurposePrefetchHeaderValue[];
+
+extern const char kSecPurposePrefetchAnonymousClientIpHeaderValue[];
+
+} // namespace prefetch::headers
+
+#endif // CHROME_BROWSER_PREFETCH_PREFETCH_HEADERS_H_
diff --git a/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_browsertest.cc b/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_browsertest.cc
index 5a53dd66..02c3652 100644
--- a/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_browsertest.cc
+++ b/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_browsertest.cc
@@ -769,6 +769,31 @@
EXPECT_EQ(paths.size(), verified_url_count);
}
+ // Verifies that the "Sec-Purpose" header with the expected value
+ // ("prefetch;anonymous-client-ip" for requests that go through the proxy, and
+ // "prefetch" for non-private prefetches) was included on all requests for
+ // main resources. Subresources are fetched using NSP which does not add the
+ // "Sec-Purpose" header.
+ void VerifyPrefetchRequestsSecPurposeHeader(
+ const std::set<std::string>& main_resource_paths,
+ bool are_requests_anonymous_client_ip) {
+ size_t verified_header_count = 0;
+ for (const auto& request : origin_server_requests()) {
+ const GURL& url = request.GetURL();
+ if (main_resource_paths.find(url.path()) == main_resource_paths.end()) {
+ continue;
+ }
+
+ SCOPED_TRACE(request.GetURL().spec());
+ EXPECT_EQ(request.headers.find("Sec-Purpose")->second,
+ are_requests_anonymous_client_ip
+ ? "prefetch;anonymous-client-ip"
+ : "prefetch");
+ verified_header_count++;
+ }
+ EXPECT_EQ(main_resource_paths.size(), verified_header_count);
+ }
+
size_t OriginServerRequestCount() const {
base::RunLoop().RunUntilIdle();
return origin_server_request_count_;
@@ -823,7 +848,11 @@
bool is_prefetch =
request.headers.find("Purpose") != request.headers.end() &&
- request.headers.find("Purpose")->second == "prefetch";
+ request.headers.find("Purpose")->second == "prefetch" &&
+ request.headers.find("Sec-Purpose") != request.headers.end() &&
+ (request.headers.find("Sec-Purpose")->second == "prefetch" ||
+ request.headers.find("Sec-Purpose")->second ==
+ "prefetch;anonymous-client-ip");
if (request.relative_url == "/404_on_prefetch") {
std::unique_ptr<net::test_server::BasicHttpResponse> resp =
@@ -1362,6 +1391,9 @@
EXPECT_EQ(u"Title Of Awesomeness", GetWebContents()->GetTitle());
VerifyOriginRequestsAreIsolated({prefetch_url.path()});
+ VerifyPrefetchRequestsSecPurposeHeader(
+ {prefetch_url.path()},
+ /*are_requests_anonymous_client_ip=*/true);
// The origin server should not have served this request.
EXPECT_EQ(starting_origin_request_count, OriginServerRequestCount());
@@ -1429,6 +1461,14 @@
eligible_link_3.path(),
});
+ VerifyPrefetchRequestsSecPurposeHeader(
+ {
+ eligible_link_1.path(),
+ eligible_link_2.path(),
+ eligible_link_3.path(),
+ },
+ /*are_requests_anonymous_client_ip=*/true);
+
using UkmEntry = ukm::TestUkmRecorder::HumanReadableUkmEntry;
auto expected_entries = std::vector<UkmEntry>{
// eligible_link_1
@@ -2997,6 +3037,9 @@
"/prefetch/prefetch_proxy/prefetch.js",
eligible_link.path(),
});
+ VerifyPrefetchRequestsSecPurposeHeader(
+ {eligible_link.path()},
+ /*are_requests_anonymous_client_ip=*/true);
// Verify the resource load was reported to the subresource manager.
PrefetchProxyService* service =
@@ -4149,6 +4192,9 @@
"/prefetch/prefetch_proxy/prefetch.js",
eligible_link.path(),
});
+ VerifyPrefetchRequestsSecPurposeHeader(
+ {eligible_link.path()},
+ /*are_requests_anonymous_client_ip=*/true);
// Verify the resource load was reported to the subresource manager.
PrefetchProxyService* service =
@@ -4270,6 +4316,9 @@
EXPECT_EQ(u"Title Of Awesomeness", GetWebContents()->GetTitle());
VerifyOriginRequestsAreIsolated({prefetch_url.path()});
+ VerifyPrefetchRequestsSecPurposeHeader(
+ {prefetch_url.path()},
+ /*are_requests_anonymous_client_ip=*/true);
// The origin server should not have served this request.
EXPECT_EQ(starting_origin_request_count, OriginServerRequestCount());
@@ -4702,6 +4751,10 @@
content::GetCookies(
browser()->profile(), eligible_link,
net::CookieOptions::SameSiteCookieContext::MakeInclusive()));
+
+ VerifyPrefetchRequestsSecPurposeHeader(
+ {eligible_link.path()},
+ /*are_requests_anonymous_client_ip=*/false);
}
IN_PROC_BROWSER_TEST_F(
@@ -4742,7 +4795,7 @@
// The prefetch requests shouldn't use the proxy and should go directly to
// the origin server.
- EXPECT_EQ(proxy_server_requests().size(), (unsigned int)0);
+ EXPECT_EQ(proxy_server_requests().size(), 0U);
// The prefetch should be made using the default network context, so the
// cookie should be present once the prefetch is complete.
@@ -4765,4 +4818,8 @@
"PrefetchProxy.AfterClick.Mainframe.CookieWaitTime", 0, 1);
histogram_tester.ExpectUniqueSample(
"PrefetchProxy.Prefetch.Mainframe.CookiesToCopy", 0, 1);
+
+ VerifyPrefetchRequestsSecPurposeHeader(
+ {eligible_link.path()},
+ /*are_requests_anonymous_client_ip=*/false);
}
diff --git a/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_tab_helper.cc b/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_tab_helper.cc
index 9b33a80d..c0f65a3 100644
--- a/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_tab_helper.cc
+++ b/chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_tab_helper.cc
@@ -20,6 +20,7 @@
#include "chrome/browser/chrome_content_browser_client.h"
#include "chrome/browser/navigation_predictor/navigation_predictor_keyed_service_factory.h"
#include "chrome/browser/prefetch/no_state_prefetch/no_state_prefetch_manager_factory.h"
+#include "chrome/browser/prefetch/prefetch_headers.h"
#include "chrome/browser/prefetch/prefetch_prefs.h"
#include "chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_features.h"
#include "chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_network_context_client.h"
@@ -836,6 +837,11 @@
request->load_flags = net::LOAD_DISABLE_CACHE | net::LOAD_PREFETCH;
request->credentials_mode = network::mojom::CredentialsMode::kInclude;
request->headers.SetHeader(content::kCorsExemptPurposeHeaderName, "prefetch");
+ request->headers.SetHeader(
+ prefetch::headers::kSecPurposeHeaderName,
+ prefetch_container->GetPrefetchType().IsProxyRequired()
+ ? prefetch::headers::kSecPurposePrefetchAnonymousClientIpHeaderValue
+ : prefetch::headers::kSecPurposePrefetchHeaderValue);
// Remove the user agent header if it was set so that the network context's
// default is used.
request->headers.RemoveHeader("User-Agent");
diff --git a/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.cc b/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.cc
index 8fa921d..76e16d45 100644
--- a/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.cc
+++ b/chrome/browser/prefetch/search_prefetch/base_search_prefetch_request.cc
@@ -7,6 +7,7 @@
#include <vector>
#include "build/build_config.h"
+#include "chrome/browser/prefetch/prefetch_headers.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/common/pref_names.h"
@@ -189,6 +190,9 @@
resource_request->headers.SetHeader(content::kCorsExemptPurposeHeaderName,
"prefetch");
resource_request->headers.SetHeader(
+ prefetch::headers::kSecPurposeHeaderName,
+ prefetch::headers::kSecPurposePrefetchHeaderValue);
+ resource_request->headers.SetHeader(
net::HttpRequestHeaders::kAccept,
content::FrameAcceptHeaderValue(/*allow_sxg_responses=*/true, profile));
diff --git a/chrome/browser/prefetch/search_prefetch/search_prefetch_service_browsertest.cc b/chrome/browser/prefetch/search_prefetch/search_prefetch_service_browsertest.cc
index 67633e0..17123c988 100644
--- a/chrome/browser/prefetch/search_prefetch/search_prefetch_service_browsertest.cc
+++ b/chrome/browser/prefetch/search_prefetch/search_prefetch_service_browsertest.cc
@@ -455,7 +455,9 @@
bool is_prefetch =
request.headers.find("Purpose") != request.headers.end() &&
- request.headers.find("Purpose")->second == "prefetch";
+ request.headers.find("Purpose")->second == "prefetch" &&
+ request.headers.find("Sec-Purpose") != request.headers.end() &&
+ request.headers.find("Sec-Purpose")->second == "prefetch";
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,