blob: eedc5e1663cc4438cc1de17119cf3d7dbd9a6f0b [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 "third_party/blink/renderer/core/origin_trials/origin_trial_context.h"
#include "base/time/time.h"
#include "third_party/blink/public/common/origin_trials/trial_token.h"
#include "third_party/blink/public/common/origin_trials/trial_token_validator.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/web_security_origin.h"
#include "third_party/blink/renderer/bindings/core/v8/script_controller.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/window_proxy.h"
#include "third_party/blink/renderer/bindings/core/v8/worker_or_worklet_script_controller.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/origin_trials/origin_trials.h"
#include "third_party/blink/renderer/core/workers/worklet_global_scope.h"
#include "third_party/blink/renderer/platform/bindings/origin_trial_features.h"
#include "third_party/blink/renderer/platform/histogram.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
#include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
#include "v8/include/v8.h"
namespace blink {
namespace {
static EnumerationHistogram& TokenValidationResultHistogram() {
DEFINE_THREAD_SAFE_STATIC_LOCAL(
EnumerationHistogram, histogram,
("OriginTrials.ValidationResult",
static_cast<int>(OriginTrialTokenStatus::kLast)));
return histogram;
}
bool IsWhitespace(UChar chr) {
return (chr == ' ') || (chr == '\t');
}
bool SkipWhiteSpace(const String& str, unsigned& pos) {
unsigned len = str.length();
while (pos < len && IsWhitespace(str[pos]))
++pos;
return pos < len;
}
// Extracts a quoted or unquoted token from an HTTP header. If the token was a
// quoted string, this also removes the quotes and unescapes any escaped
// characters. Also skips all whitespace before and after the token.
String ExtractTokenOrQuotedString(const String& header_value, unsigned& pos) {
unsigned len = header_value.length();
String result;
if (!SkipWhiteSpace(header_value, pos))
return String();
if (header_value[pos] == '\'' || header_value[pos] == '"') {
StringBuilder out;
// Quoted string, append characters until matching quote is found,
// unescaping as we go.
UChar quote = header_value[pos++];
while (pos < len && header_value[pos] != quote) {
if (header_value[pos] == '\\')
pos++;
if (pos < len)
out.Append(header_value[pos++]);
}
if (pos < len)
pos++;
result = out.ToString();
} else {
// Unquoted token. Consume all characters until whitespace or comma.
int start_pos = pos;
while (pos < len && !IsWhitespace(header_value[pos]) &&
header_value[pos] != ',')
pos++;
result = header_value.Substring(start_pos, pos - start_pos);
}
SkipWhiteSpace(header_value, pos);
return result;
}
} // namespace
OriginTrialContext::OriginTrialContext(
ExecutionContext& context,
std::unique_ptr<TrialTokenValidator> validator)
: Supplement<ExecutionContext>(context),
trial_token_validator_(std::move(validator)) {}
// static
const char OriginTrialContext::kSupplementName[] = "OriginTrialContext";
// static
const OriginTrialContext* OriginTrialContext::From(
const ExecutionContext* context) {
return Supplement<ExecutionContext>::From<OriginTrialContext>(context);
}
// static
OriginTrialContext* OriginTrialContext::FromOrCreate(
ExecutionContext* context) {
OriginTrialContext* origin_trials =
Supplement<ExecutionContext>::From<OriginTrialContext>(context);
if (!origin_trials) {
origin_trials = MakeGarbageCollected<OriginTrialContext>(
*context, TrialTokenValidator::Policy()
? std::make_unique<TrialTokenValidator>()
: nullptr);
Supplement<ExecutionContext>::ProvideTo(*context, origin_trials);
}
return origin_trials;
}
// static
std::unique_ptr<Vector<String>> OriginTrialContext::ParseHeaderValue(
const String& header_value) {
std::unique_ptr<Vector<String>> tokens(new Vector<String>);
unsigned pos = 0;
unsigned len = header_value.length();
while (pos < len) {
String token = ExtractTokenOrQuotedString(header_value, pos);
if (!token.IsEmpty())
tokens->push_back(token);
// Make sure tokens are comma-separated.
if (pos < len && header_value[pos++] != ',')
return nullptr;
}
return tokens;
}
// static
void OriginTrialContext::AddTokensFromHeader(ExecutionContext* context,
const String& header_value) {
if (header_value.IsEmpty())
return;
std::unique_ptr<Vector<String>> tokens(ParseHeaderValue(header_value));
if (!tokens)
return;
AddTokens(context, tokens.get());
}
// static
void OriginTrialContext::AddTokens(ExecutionContext* context,
const Vector<String>* tokens) {
if (!tokens || tokens->IsEmpty())
return;
FromOrCreate(context)->AddTokens(*tokens);
}
// static
std::unique_ptr<Vector<String>> OriginTrialContext::GetTokens(
ExecutionContext* execution_context) {
const OriginTrialContext* context = From(execution_context);
if (!context || context->tokens_.IsEmpty())
return nullptr;
return std::make_unique<Vector<String>>(context->tokens_);
}
void OriginTrialContext::AddToken(const String& token) {
if (token.IsEmpty())
return;
tokens_.push_back(token);
if (EnableTrialFromToken(token)) {
// Only install pending features if the provided token is valid. Otherwise,
// there was no change to the list of enabled features.
InitializePendingFeatures();
}
}
void OriginTrialContext::AddTokens(const Vector<String>& tokens) {
if (tokens.IsEmpty())
return;
bool found_valid = false;
for (const String& token : tokens) {
if (!token.IsEmpty()) {
tokens_.push_back(token);
if (EnableTrialFromToken(token))
found_valid = true;
}
}
if (found_valid) {
// Only install pending features if at least one of the provided tokens are
// valid. Otherwise, there was no change to the list of enabled features.
InitializePendingFeatures();
}
}
void OriginTrialContext::InitializePendingFeatures() {
if (!enabled_trials_.size())
return;
auto* document = DynamicTo<Document>(GetSupplementable());
if (!document)
return;
LocalFrame* frame = document->GetFrame();
if (!frame)
return;
ScriptState* script_state = ToScriptStateForMainWorld(frame);
if (!script_state)
return;
if (!script_state->ContextIsValid())
return;
ScriptState::Scope scope(script_state);
for (auto enabled_trial : enabled_trials_) {
if (installed_trials_.Contains(enabled_trial))
continue;
InstallPendingOriginTrialFeature(enabled_trial, script_state);
installed_trials_.insert(enabled_trial);
}
}
void OriginTrialContext::AddFeature(const String& feature) {
enabled_trials_.insert(feature);
InitializePendingFeatures();
}
bool OriginTrialContext::IsTrialEnabled(const String& trial_name) const {
if (!RuntimeEnabledFeatures::OriginTrialsEnabled())
return false;
return enabled_trials_.Contains(trial_name);
}
bool OriginTrialContext::EnableTrialFromToken(const String& token) {
DCHECK(!token.IsEmpty());
// Origin trials are only enabled for secure origins
// - For worklets, they are currently spec'd to not be secure, given their
// scope has unique origin:
// https://drafts.css-houdini.org/worklets/#script-settings-for-worklets
// - For the purpose of origin trials, we consider worklets as running in the
// same context as the originating document. Thus, the special logic here
// to validate the token against the document context.
bool is_secure = false;
ExecutionContext* context = GetSupplementable();
if (auto* scope = DynamicTo<WorkletGlobalScope>(context)) {
is_secure = scope->DocumentSecureContext();
} else {
is_secure = context->IsSecureContext();
}
if (!is_secure) {
TokenValidationResultHistogram().Count(
static_cast<int>(OriginTrialTokenStatus::kInsecure));
return false;
}
if (!trial_token_validator_) {
TokenValidationResultHistogram().Count(
static_cast<int>(OriginTrialTokenStatus::kNotSupported));
return false;
}
const SecurityOrigin* origin;
if (auto* scope = DynamicTo<WorkletGlobalScope>(context))
origin = scope->DocumentSecurityOrigin();
else
origin = context->GetSecurityOrigin();
bool valid = false;
StringUTF8Adaptor token_string(token);
std::string trial_name_str;
OriginTrialTokenStatus token_result = trial_token_validator_->ValidateToken(
token_string.AsStringPiece(), origin->ToUrlOrigin(), &trial_name_str,
base::Time::Now());
if (token_result == OriginTrialTokenStatus::kSuccess) {
valid = true;
String trial_name =
String::FromUTF8(trial_name_str.data(), trial_name_str.size());
enabled_trials_.insert(trial_name);
// Also enable any trials implied by this trial
Vector<AtomicString> implied_trials =
origin_trials::GetImpliedTrials(trial_name);
for (const AtomicString& implied_trial_name : implied_trials) {
enabled_trials_.insert(implied_trial_name);
}
}
TokenValidationResultHistogram().Count(static_cast<int>(token_result));
return valid;
}
void OriginTrialContext::Trace(blink::Visitor* visitor) {
Supplement<ExecutionContext>::Trace(visitor);
}
} // namespace blink