Commit d12baeba authored by Tarun Bansal's avatar Tarun Bansal Committed by Commit Bot

Refactor client hints preferences code.

This separates out the code for parsing
accept-ch and accept-cl-lifetime header.

This makes it easier to parse http-equiv
accept-cl-lifetime header which will be
added in the next CL.

Previously, when only accept-ch header was present, it
was handled using a different code path then when
both accept-ch and accept-cl-lifetime headers
are present.

This CL changes it to handle the two headers separately.
That also makes it possible to unify the two code paths.

Change-Id: I1013419a55360249718855677678356f8446ec11
Bug: 852484
Reviewed-on: https://chromium-review.googlesource.com/1125083
Commit-Queue: Tarun Bansal <tbansal@chromium.org>
Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Cr-Commit-Position: refs/heads/master@{#572495}
parent 98f5773e
...@@ -574,12 +574,6 @@ void FrameFetchContext::DispatchDidReceiveResponse( ...@@ -574,12 +574,6 @@ void FrameFetchContext::DispatchDidReceiveResponse(
document_loader_ == document_loader_->GetFrame() document_loader_ == document_loader_->GetFrame()
->Loader() ->Loader()
.GetProvisionalDocumentLoader()) { .GetProvisionalDocumentLoader()) {
FrameClientHintsPreferencesContext hints_context(GetFrame());
document_loader_->GetClientHintsPreferences()
.UpdateFromAcceptClientHintsHeader(
response.HttpHeaderField(HTTPNames::Accept_CH), response.Url(),
&hints_context);
// When response is received with a provisional docloader, the resource // When response is received with a provisional docloader, the resource
// haven't committed yet, and we cannot load resources, only preconnect. // haven't committed yet, and we cannot load resources, only preconnect.
resource_loading_policy = LinkLoader::kDoNotLoadResources; resource_loading_policy = LinkLoader::kDoNotLoadResources;
...@@ -1329,16 +1323,26 @@ bool FrameFetchContext::ShouldSendClientHint( ...@@ -1329,16 +1323,26 @@ bool FrameFetchContext::ShouldSendClientHint(
void FrameFetchContext::ParseAndPersistClientHints( void FrameFetchContext::ParseAndPersistClientHints(
const ResourceResponse& response) { const ResourceResponse& response) {
ClientHintsPreferences hints_preferences;
WebEnabledClientHints enabled_client_hints;
TimeDelta persist_duration;
FrameClientHintsPreferencesContext hints_context(GetFrame()); FrameClientHintsPreferencesContext hints_context(GetFrame());
hints_preferences.UpdatePersistentHintsFromHeaders(
response, &hints_context, enabled_client_hints, &persist_duration);
document_loader_->GetClientHintsPreferences()
.UpdateFromAcceptClientHintsLifetimeHeader(
response.HttpHeaderField(HTTPNames::Accept_CH_Lifetime),
response.Url(), &hints_context);
document_loader_->GetClientHintsPreferences()
.UpdateFromAcceptClientHintsHeader(
response.HttpHeaderField(HTTPNames::Accept_CH), response.Url(),
&hints_context);
// Notify content settings client of persistent client hints.
TimeDelta persist_duration =
document_loader_->GetClientHintsPreferences().GetPersistDuration();
if (persist_duration.InSeconds() <= 0) if (persist_duration.InSeconds() <= 0)
return; return;
WebEnabledClientHints enabled_client_hints =
document_loader_->GetClientHintsPreferences().GetWebEnabledClientHints();
if (!AllowScriptFromSourceWithoutNotifying(response.Url())) { if (!AllowScriptFromSourceWithoutNotifying(response.Url())) {
// Do not persist client hint preferences if the JavaScript is disabled. // Do not persist client hint preferences if the JavaScript is disabled.
return; return;
......
...@@ -47,7 +47,7 @@ void HttpEquiv::Process(Document& document, ...@@ -47,7 +47,7 @@ void HttpEquiv::Process(Document& document,
kSecurityMessageSource, kErrorMessageLevel, kSecurityMessageSource, kErrorMessageLevel,
"X-Frame-Options may only be set via an HTTP header sent along with a " "X-Frame-Options may only be set via an HTTP header sent along with a "
"document. It may not be set inside <meta>.")); "document. It may not be set inside <meta>."));
} else if (EqualIgnoringASCIICase(equiv, "accept-ch")) { } else if (EqualIgnoringASCIICase(equiv, HTTPNames::Accept_CH)) {
ProcessHttpEquivAcceptCH(document, content); ProcessHttpEquivAcceptCH(document, content);
} else if (EqualIgnoringASCIICase(equiv, "content-security-policy") || } else if (EqualIgnoringASCIICase(equiv, "content-security-policy") ||
EqualIgnoringASCIICase(equiv, EqualIgnoringASCIICase(equiv,
......
...@@ -6,11 +6,11 @@ ...@@ -6,11 +6,11 @@
#include "base/macros.h" #include "base/macros.h"
#include "third_party/blink/public/common/client_hints/client_hints.h" #include "third_party/blink/public/common/client_hints/client_hints.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_response.h"
#include "third_party/blink/renderer/platform/network/http_names.h" #include "third_party/blink/renderer/platform/network/http_names.h"
#include "third_party/blink/renderer/platform/network/http_parsers.h" #include "third_party/blink/renderer/platform/network/http_parsers.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h" #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h" #include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
namespace blink { namespace blink {
...@@ -98,42 +98,25 @@ void ClientHintsPreferences::UpdateFromAcceptClientHintsHeader( ...@@ -98,42 +98,25 @@ void ClientHintsPreferences::UpdateFromAcceptClientHintsHeader(
} }
} }
// static void ClientHintsPreferences::UpdateFromAcceptClientHintsLifetimeHeader(
void ClientHintsPreferences::UpdatePersistentHintsFromHeaders( const String& header_value,
const ResourceResponse& response, const KURL& url,
Context* context, Context* context) {
WebEnabledClientHints& enabled_hints, if (header_value.IsEmpty())
TimeDelta* persist_duration) {
*persist_duration = base::TimeDelta();
if (response.WasCached())
return; return;
String accept_ch_header_value = // Client hints should be allowed only on secure URLs.
response.HttpHeaderField(HTTPNames::Accept_CH);
String accept_ch_lifetime_header_value =
response.HttpHeaderField(HTTPNames::Accept_CH_Lifetime);
if (accept_ch_header_value.IsEmpty() ||
accept_ch_lifetime_header_value.IsEmpty()) {
return;
}
const KURL url = response.Url();
if (!IsClientHintsAllowed(url)) if (!IsClientHintsAllowed(url))
return; return;
bool conversion_ok = false; bool conversion_ok = false;
int64_t persist_duration_seconds = int64_t persist_duration_seconds = header_value.ToInt64Strict(&conversion_ok);
accept_ch_lifetime_header_value.ToInt64Strict(&conversion_ok);
if (!conversion_ok || persist_duration_seconds <= 0) if (!conversion_ok || persist_duration_seconds <= 0)
return; return;
*persist_duration = TimeDelta::FromSeconds(persist_duration_seconds); persist_duration_ = TimeDelta::FromSeconds(persist_duration_seconds);
if (context) if (context)
context->CountPersistentClientHintHeaders(); context->CountPersistentClientHintHeaders();
ParseAcceptChHeader(accept_ch_header_value, enabled_hints);
} }
// static // static
...@@ -143,4 +126,12 @@ bool ClientHintsPreferences::IsClientHintsAllowed(const KURL& url) { ...@@ -143,4 +126,12 @@ bool ClientHintsPreferences::IsClientHintsAllowed(const KURL& url) {
SecurityOrigin::Create(url)->IsLocalhost()); SecurityOrigin::Create(url)->IsLocalhost());
} }
WebEnabledClientHints ClientHintsPreferences::GetWebEnabledClientHints() const {
return enabled_hints_;
}
base::TimeDelta ClientHintsPreferences::GetPersistDuration() const {
return persist_duration_;
}
} // namespace blink } // namespace blink
...@@ -14,7 +14,6 @@ ...@@ -14,7 +14,6 @@
namespace blink { namespace blink {
class KURL; class KURL;
class ResourceResponse;
// TODO (tbansal): Remove PLATFORM_EXPORT, and pass WebClientHintsType // TODO (tbansal): Remove PLATFORM_EXPORT, and pass WebClientHintsType
// everywhere. // everywhere.
...@@ -50,26 +49,25 @@ class PLATFORM_EXPORT ClientHintsPreferences { ...@@ -50,26 +49,25 @@ class PLATFORM_EXPORT ClientHintsPreferences {
enabled_hints_.SetIsEnabled(type, true); enabled_hints_.SetIsEnabled(type, true);
} }
// Parses the client hints headers, and populates |enabled_hints| with the // Parses the accept-ch-lifetime header, and populates |this| with the client
// client hint preferences that should be persisted for |persist_duration|. // hints persistence duration. |url| is the URL of the resource whose response
// |persist_duration| should be non-null. // included the |header_value|. |context| may be null. If client hints are not
// If there are no client hints that need to be persisted, // allowed for |url|, then |this| would not be updated.
// |persist_duration| is not set, otherwise it is set to the duration for void UpdateFromAcceptClientHintsLifetimeHeader(const String& header_value,
// which the client hint preferences should be persisted. const KURL& url,
// UpdatePersistentHintsFromHeaders may be called for all responses Context* context);
// received (including subresources). |context| may be null.
static void UpdatePersistentHintsFromHeaders(
const ResourceResponse&,
Context*,
WebEnabledClientHints& enabled_hints,
TimeDelta* persist_duration);
// Returns true if client hints are allowed for the provided KURL. Client // Returns true if client hints are allowed for the provided KURL. Client
// hints are allowed only on HTTP URLs that belong to secure contexts. // hints are allowed only on HTTP URLs that belong to secure contexts.
static bool IsClientHintsAllowed(const KURL&); static bool IsClientHintsAllowed(const KURL&);
WebEnabledClientHints GetWebEnabledClientHints() const;
base::TimeDelta GetPersistDuration() const;
private: private:
WebEnabledClientHints enabled_hints_; WebEnabledClientHints enabled_hints_;
base::TimeDelta persist_duration_;
}; };
} // namespace blink } // namespace blink
......
...@@ -82,6 +82,58 @@ TEST_F(ClientHintsPreferencesTest, BasicSecure) { ...@@ -82,6 +82,58 @@ TEST_F(ClientHintsPreferencesTest, BasicSecure) {
} }
} }
// Verify that the set of enabled client hints is updated every time Update*()
// methods are called.
TEST_F(ClientHintsPreferencesTest, SecureEnabledTypesAreUpdated) {
ClientHintsPreferences preferences;
const KURL kurl(String::FromUTF8("https://www.google.com/"));
preferences.UpdateFromAcceptClientHintsHeader("rtt, downlink", kurl, nullptr);
EXPECT_EQ(base::TimeDelta(), preferences.GetPersistDuration());
EXPECT_FALSE(
preferences.ShouldSend(mojom::WebClientHintsType::kResourceWidth));
EXPECT_FALSE(preferences.ShouldSend(mojom::WebClientHintsType::kDpr));
EXPECT_FALSE(
preferences.ShouldSend(mojom::WebClientHintsType::kViewportWidth));
EXPECT_TRUE(preferences.ShouldSend(mojom::WebClientHintsType::kRtt));
EXPECT_TRUE(preferences.ShouldSend(mojom::WebClientHintsType::kDownlink));
EXPECT_FALSE(preferences.ShouldSend(mojom::WebClientHintsType::kEct));
// Calling UpdateFromAcceptClientHintsHeader with empty header should have
// no impact on client hint preferences.
preferences.UpdateFromAcceptClientHintsHeader("", kurl, nullptr);
EXPECT_EQ(base::TimeDelta(), preferences.GetPersistDuration());
EXPECT_FALSE(
preferences.ShouldSend(mojom::WebClientHintsType::kResourceWidth));
EXPECT_TRUE(preferences.ShouldSend(mojom::WebClientHintsType::kRtt));
EXPECT_TRUE(preferences.ShouldSend(mojom::WebClientHintsType::kDownlink));
EXPECT_FALSE(preferences.ShouldSend(mojom::WebClientHintsType::kEct));
// Calling UpdateFromAcceptClientHintsHeader with an invalid header should
// have no impact on client hint preferences.
preferences.UpdateFromAcceptClientHintsHeader("foobar", kurl, nullptr);
EXPECT_EQ(base::TimeDelta(), preferences.GetPersistDuration());
EXPECT_FALSE(
preferences.ShouldSend(mojom::WebClientHintsType::kResourceWidth));
EXPECT_TRUE(preferences.ShouldSend(mojom::WebClientHintsType::kRtt));
EXPECT_TRUE(preferences.ShouldSend(mojom::WebClientHintsType::kDownlink));
EXPECT_FALSE(preferences.ShouldSend(mojom::WebClientHintsType::kEct));
// Calling UpdateFromAcceptClientHintsHeader with "width" header should
// have no impact on already enabled client hint preferences.
preferences.UpdateFromAcceptClientHintsHeader("width", kurl, nullptr);
EXPECT_EQ(base::TimeDelta(), preferences.GetPersistDuration());
EXPECT_TRUE(
preferences.ShouldSend(mojom::WebClientHintsType::kResourceWidth));
EXPECT_TRUE(preferences.ShouldSend(mojom::WebClientHintsType::kRtt));
EXPECT_TRUE(preferences.ShouldSend(mojom::WebClientHintsType::kDownlink));
EXPECT_FALSE(preferences.ShouldSend(mojom::WebClientHintsType::kEct));
preferences.UpdateFromAcceptClientHintsLifetimeHeader("1000", kurl, nullptr);
EXPECT_EQ(base::TimeDelta::FromSeconds(1000),
preferences.GetPersistDuration());
}
TEST_F(ClientHintsPreferencesTest, Insecure) { TEST_F(ClientHintsPreferencesTest, Insecure) {
for (const auto& use_secure_url : {false, true}) { for (const auto& use_secure_url : {false, true}) {
ClientHintsPreferences preferences; ClientHintsPreferences preferences;
...@@ -94,60 +146,83 @@ TEST_F(ClientHintsPreferencesTest, Insecure) { ...@@ -94,60 +146,83 @@ TEST_F(ClientHintsPreferencesTest, Insecure) {
} }
} }
TEST_F(ClientHintsPreferencesTest, PersistentHints) { // Verify that the client hints header and the lifetime header is parsed
// correctly.
TEST_F(ClientHintsPreferencesTest, ParseHeaders) {
struct TestCase { struct TestCase {
const char* accept_ch_header_value; const char* accept_ch_header_value;
const char* accept_lifetime_header_value; const char* accept_lifetime_header_value;
int64_t expect_persist_duration_seconds; int64_t expect_persist_duration_seconds;
bool expect_device_memory;
bool expect_width;
bool expect_dpr;
bool expect_viewport_width;
bool expect_rtt;
bool expect_downlink;
bool expect_ect;
} test_cases[] = { } test_cases[] = {
{"width, dpr, viewportWidth", "", 0}, {"width, dpr, viewportWidth", "", 0, false, true, true, false, false,
{"width, dpr, viewportWidth", "-1000", 0}, false, false},
{"width, dpr, viewportWidth", "1000s", 0}, {"width, dpr, viewportWidth", "-1000", 0, false, true, true, false, false,
{"width, dpr, viewportWidth", "1000.5", 0}, false, false},
{"width, dpr, rtt, downlink, ect", "1000", 1000}, {"width, dpr, viewportWidth", "1000s", 0, false, true, true, false, false,
false, false},
{"width, dpr, viewportWidth", "1000.5", 0, false, true, true, false,
false, false, false},
{"width, dpr, rtt, downlink, ect", "1000", 1000, false, true, true, false,
true, true, true},
{"device-memory", "-1000", 0, true, false, false, false, false, false,
false},
{"dpr rtt", "1000", 1000, false, false, false, false, false, false,
false},
}; };
for (const auto& test : test_cases) { for (const auto& test : test_cases) {
WebEnabledClientHints enabled_types; ClientHintsPreferences preferences;
TimeDelta persist_duration; WebEnabledClientHints enabled_types =
preferences.GetWebEnabledClientHints();
EXPECT_FALSE(
enabled_types.IsEnabled(mojom::WebClientHintsType::kDeviceMemory));
EXPECT_FALSE(enabled_types.IsEnabled(mojom::WebClientHintsType::kDpr));
EXPECT_FALSE(
enabled_types.IsEnabled(mojom::WebClientHintsType::kResourceWidth));
EXPECT_FALSE(
enabled_types.IsEnabled(mojom::WebClientHintsType::kViewportWidth));
EXPECT_FALSE(enabled_types.IsEnabled(mojom::WebClientHintsType::kRtt));
EXPECT_FALSE(enabled_types.IsEnabled(mojom::WebClientHintsType::kDownlink));
EXPECT_FALSE(enabled_types.IsEnabled(mojom::WebClientHintsType::kEct));
TimeDelta persist_duration = preferences.GetPersistDuration();
EXPECT_EQ(base::TimeDelta(), persist_duration);
const KURL kurl(String::FromUTF8("https://www.google.com/")); const KURL kurl(String::FromUTF8("https://www.google.com/"));
preferences.UpdateFromAcceptClientHintsHeader(test.accept_ch_header_value,
kurl, nullptr);
preferences.UpdateFromAcceptClientHintsLifetimeHeader(
test.accept_lifetime_header_value, kurl, nullptr);
ResourceResponse response(kurl); enabled_types = preferences.GetWebEnabledClientHints();
response.SetHTTPHeaderField(HTTPNames::Accept_CH, persist_duration = preferences.GetPersistDuration();
test.accept_ch_header_value);
response.SetHTTPHeaderField(HTTPNames::Accept_CH_Lifetime,
test.accept_lifetime_header_value);
ClientHintsPreferences::UpdatePersistentHintsFromHeaders(
response, nullptr, enabled_types, &persist_duration);
EXPECT_EQ(test.expect_persist_duration_seconds, EXPECT_EQ(test.expect_persist_duration_seconds,
persist_duration.InSeconds()); persist_duration.InSeconds());
if (test.expect_persist_duration_seconds > 0) {
EXPECT_FALSE( EXPECT_EQ(
enabled_types.IsEnabled(mojom::WebClientHintsType::kDeviceMemory)); test.expect_device_memory,
EXPECT_TRUE(enabled_types.IsEnabled(mojom::WebClientHintsType::kDpr)); enabled_types.IsEnabled(mojom::WebClientHintsType::kDeviceMemory));
EXPECT_TRUE( EXPECT_EQ(test.expect_dpr,
enabled_types.IsEnabled(mojom::WebClientHintsType::kResourceWidth)); enabled_types.IsEnabled(mojom::WebClientHintsType::kDpr));
EXPECT_FALSE( EXPECT_EQ(
enabled_types.IsEnabled(mojom::WebClientHintsType::kViewportWidth)); test.expect_width,
EXPECT_TRUE(enabled_types.IsEnabled(mojom::WebClientHintsType::kRtt)); enabled_types.IsEnabled(mojom::WebClientHintsType::kResourceWidth));
EXPECT_TRUE( EXPECT_EQ(
enabled_types.IsEnabled(mojom::WebClientHintsType::kDownlink)); test.expect_viewport_width,
EXPECT_TRUE(enabled_types.IsEnabled(mojom::WebClientHintsType::kEct)); enabled_types.IsEnabled(mojom::WebClientHintsType::kViewportWidth));
} else { EXPECT_EQ(test.expect_rtt,
EXPECT_FALSE( enabled_types.IsEnabled(mojom::WebClientHintsType::kRtt));
enabled_types.IsEnabled(mojom::WebClientHintsType::kDeviceMemory)); EXPECT_EQ(test.expect_downlink,
EXPECT_FALSE(enabled_types.IsEnabled(mojom::WebClientHintsType::kDpr)); enabled_types.IsEnabled(mojom::WebClientHintsType::kDownlink));
EXPECT_FALSE( EXPECT_EQ(test.expect_ect,
enabled_types.IsEnabled(mojom::WebClientHintsType::kResourceWidth)); enabled_types.IsEnabled(mojom::WebClientHintsType::kEct));
EXPECT_FALSE(
enabled_types.IsEnabled(mojom::WebClientHintsType::kViewportWidth));
EXPECT_FALSE(enabled_types.IsEnabled(mojom::WebClientHintsType::kRtt));
EXPECT_FALSE(
enabled_types.IsEnabled(mojom::WebClientHintsType::kDownlink));
EXPECT_FALSE(enabled_types.IsEnabled(mojom::WebClientHintsType::kEct));
}
} }
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment