blob: 34edc03c5bea6dfbea20843d2cbcbcdcb74860f7 [file] [log] [blame]
// Copyright 2015 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 "core/origin_trials/OriginTrialContext.h"
#include <memory>
#include "core/HTMLNames.h"
#include "core/dom/DOMException.h"
#include "core/frame/LocalFrameView.h"
#include "core/html/HTMLHeadElement.h"
#include "core/html/HTMLMetaElement.h"
#include "core/testing/DummyPageHolder.h"
#include "core/testing/NullExecutionContext.h"
#include "platform/testing/HistogramTester.h"
#include "platform/weborigin/KURL.h"
#include "platform/weborigin/SecurityOrigin.h"
#include "platform/wtf/PtrUtil.h"
#include "platform/wtf/Vector.h"
#include "public/platform/WebOriginTrialTokenStatus.h"
#include "public/platform/WebTrialTokenValidator.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace blink {
namespace {
const char kNonExistingTrialName[] = "This trial does not exist";
const char kFrobulateTrialName[] = "Frobulate";
const char kFrobulateEnabledOrigin[] = "https://www.example.com";
const char kFrobulateEnabledOriginUnsecure[] = "http://www.example.com";
// Names of UMA histograms
const char kResultHistogram[] = "OriginTrials.ValidationResult";
// Trial token placeholder for mocked calls to validator
const char kTokenPlaceholder[] = "The token contents are not used";
class MockTokenValidator : public WebTrialTokenValidator {
public:
MockTokenValidator()
: response_(WebOriginTrialTokenStatus::kNotSupported), call_count_(0) {}
~MockTokenValidator() override {}
// blink::WebTrialTokenValidator implementation
WebOriginTrialTokenStatus ValidateToken(const WebString& token,
const WebSecurityOrigin& origin,
WebString* feature_name) override {
call_count_++;
*feature_name = feature_;
return response_;
}
// Useful methods for controlling the validator
void SetResponse(WebOriginTrialTokenStatus response,
const WebString& feature) {
response_ = response;
feature_ = feature;
}
int CallCount() { return call_count_; }
private:
WebOriginTrialTokenStatus response_;
WebString feature_;
int call_count_;
DISALLOW_COPY_AND_ASSIGN(MockTokenValidator);
};
} // namespace
class OriginTrialContextTest : public ::testing::Test {
protected:
OriginTrialContextTest()
: framework_was_enabled_(RuntimeEnabledFeatures::OriginTrialsEnabled()),
execution_context_(new NullExecutionContext()),
token_validator_(WTF::MakeUnique<MockTokenValidator>()),
origin_trial_context_(new OriginTrialContext(*execution_context_,
token_validator_.get())),
histogram_tester_(new HistogramTester()) {
RuntimeEnabledFeatures::SetOriginTrialsEnabled(true);
}
~OriginTrialContextTest() {
RuntimeEnabledFeatures::SetOriginTrialsEnabled(framework_was_enabled_);
}
MockTokenValidator* TokenValidator() { return token_validator_.get(); }
void UpdateSecurityOrigin(const String& origin) {
KURL page_url(kParsedURLString, origin);
RefPtr<SecurityOrigin> page_origin = SecurityOrigin::Create(page_url);
execution_context_->SetSecurityOrigin(page_origin);
execution_context_->SetIsSecureContext(SecurityOrigin::IsSecure(page_url));
}
bool IsTrialEnabled(const String& origin, const String& feature_name) {
UpdateSecurityOrigin(origin);
// Need at least one token to ensure the token validator is called.
origin_trial_context_->AddToken(kTokenPlaceholder);
return origin_trial_context_->IsTrialEnabled(feature_name);
}
void ExpectStatusUniqueMetric(WebOriginTrialTokenStatus status, int count) {
histogram_tester_->ExpectUniqueSample(kResultHistogram,
static_cast<int>(status), count);
}
void ExpecStatusTotalMetric(int total) {
histogram_tester_->ExpectTotalCount(kResultHistogram, total);
}
private:
const bool framework_was_enabled_;
Persistent<NullExecutionContext> execution_context_;
std::unique_ptr<MockTokenValidator> token_validator_;
Persistent<OriginTrialContext> origin_trial_context_;
std::unique_ptr<HistogramTester> histogram_tester_;
};
TEST_F(OriginTrialContextTest, EnabledNonExistingTrial) {
TokenValidator()->SetResponse(WebOriginTrialTokenStatus::kSuccess,
kFrobulateTrialName);
bool is_non_existing_trial_enabled =
IsTrialEnabled(kFrobulateEnabledOrigin, kNonExistingTrialName);
EXPECT_FALSE(is_non_existing_trial_enabled);
// Status metric should be updated.
ExpectStatusUniqueMetric(WebOriginTrialTokenStatus::kSuccess, 1);
}
// The feature should be enabled if a valid token for the origin is provided
TEST_F(OriginTrialContextTest, EnabledSecureRegisteredOrigin) {
TokenValidator()->SetResponse(WebOriginTrialTokenStatus::kSuccess,
kFrobulateTrialName);
bool is_origin_enabled =
IsTrialEnabled(kFrobulateEnabledOrigin, kFrobulateTrialName);
EXPECT_TRUE(is_origin_enabled);
EXPECT_EQ(1, TokenValidator()->CallCount());
// Status metric should be updated.
ExpectStatusUniqueMetric(WebOriginTrialTokenStatus::kSuccess, 1);
}
// ... but if the browser says it's invalid for any reason, that's enough to
// reject.
TEST_F(OriginTrialContextTest, InvalidTokenResponseFromPlatform) {
TokenValidator()->SetResponse(WebOriginTrialTokenStatus::kMalformed,
kFrobulateTrialName);
bool is_origin_enabled =
IsTrialEnabled(kFrobulateEnabledOrigin, kFrobulateTrialName);
EXPECT_FALSE(is_origin_enabled);
EXPECT_EQ(1, TokenValidator()->CallCount());
// Status metric should be updated.
ExpectStatusUniqueMetric(WebOriginTrialTokenStatus::kMalformed, 1);
}
// The feature should not be enabled if the origin is insecure, even if a valid
// token for the origin is provided
TEST_F(OriginTrialContextTest, EnabledNonSecureRegisteredOrigin) {
TokenValidator()->SetResponse(WebOriginTrialTokenStatus::kSuccess,
kFrobulateTrialName);
bool is_origin_enabled =
IsTrialEnabled(kFrobulateEnabledOriginUnsecure, kFrobulateTrialName);
EXPECT_FALSE(is_origin_enabled);
EXPECT_EQ(0, TokenValidator()->CallCount());
ExpectStatusUniqueMetric(WebOriginTrialTokenStatus::kInsecure, 1);
}
TEST_F(OriginTrialContextTest, ParseHeaderValue) {
std::unique_ptr<Vector<String>> tokens;
ASSERT_TRUE(tokens = OriginTrialContext::ParseHeaderValue(" foo\t "));
ASSERT_EQ(1u, tokens->size());
EXPECT_EQ("foo", (*tokens)[0]);
ASSERT_TRUE(tokens = OriginTrialContext::ParseHeaderValue(" \" bar \" "));
ASSERT_EQ(1u, tokens->size());
EXPECT_EQ(" bar ", (*tokens)[0]);
ASSERT_TRUE(tokens = OriginTrialContext::ParseHeaderValue(" foo, bar"));
ASSERT_EQ(2u, tokens->size());
EXPECT_EQ("foo", (*tokens)[0]);
EXPECT_EQ("bar", (*tokens)[1]);
ASSERT_TRUE(tokens =
OriginTrialContext::ParseHeaderValue(",foo, ,bar,,' ', ''"));
ASSERT_EQ(3u, tokens->size());
EXPECT_EQ("foo", (*tokens)[0]);
EXPECT_EQ("bar", (*tokens)[1]);
EXPECT_EQ(" ", (*tokens)[2]);
ASSERT_TRUE(tokens =
OriginTrialContext::ParseHeaderValue(" \"abc\" , 'def',g"));
ASSERT_EQ(3u, tokens->size());
EXPECT_EQ("abc", (*tokens)[0]);
EXPECT_EQ("def", (*tokens)[1]);
EXPECT_EQ("g", (*tokens)[2]);
ASSERT_TRUE(tokens = OriginTrialContext::ParseHeaderValue(
" \"a\\b\\\"c'd\", 'e\\f\\'g' "));
ASSERT_EQ(2u, tokens->size());
EXPECT_EQ("ab\"c'd", (*tokens)[0]);
EXPECT_EQ("ef'g", (*tokens)[1]);
ASSERT_TRUE(tokens =
OriginTrialContext::ParseHeaderValue("\"ab,c\" , 'd,e'"));
ASSERT_EQ(2u, tokens->size());
EXPECT_EQ("ab,c", (*tokens)[0]);
EXPECT_EQ("d,e", (*tokens)[1]);
ASSERT_TRUE(tokens = OriginTrialContext::ParseHeaderValue(" "));
EXPECT_EQ(0u, tokens->size());
ASSERT_TRUE(tokens = OriginTrialContext::ParseHeaderValue(""));
EXPECT_EQ(0u, tokens->size());
ASSERT_TRUE(tokens = OriginTrialContext::ParseHeaderValue(" ,, \"\" "));
EXPECT_EQ(0u, tokens->size());
}
TEST_F(OriginTrialContextTest, ParseHeaderValue_NotCommaSeparated) {
EXPECT_FALSE(OriginTrialContext::ParseHeaderValue("foo bar"));
EXPECT_FALSE(OriginTrialContext::ParseHeaderValue("\"foo\" 'bar'"));
EXPECT_FALSE(OriginTrialContext::ParseHeaderValue("foo 'bar'"));
EXPECT_FALSE(OriginTrialContext::ParseHeaderValue("\"foo\" bar"));
}
} // namespace blink