blob: a36a76e12fc02783fca2bed07f64b2e3fbac1723 [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/loader/http_equiv.h"
#include "third_party/blink/public/platform/web_content_settings_client.h"
#include "third_party/blink/renderer/core/css/style_engine.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/scriptable_document_parser.h"
#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
#include "third_party/blink/renderer/core/frame/deprecation.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/loader/document_loader.h"
#include "third_party/blink/renderer/core/loader/frame_client_hints_preferences_context.h"
#include "third_party/blink/renderer/core/origin_trials/origin_trial_context.h"
#include "third_party/blink/renderer/platform/bindings/v8_binding.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
#include "third_party/blink/renderer/platform/loader/fetch/client_hints_preferences.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/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/weborigin/reporting_disposition.h"
namespace blink {
namespace {
// Returns true if execution of scripts from the url are allowed. Compared to
// AllowScriptFromSource(), this method does not generate any
// notification to the |ContentSettingsClient| that the execution of the
// script was blocked. This method should be called only when there is a need
// to check the settings, and where blocked setting doesn't really imply that
// JavaScript was blocked from being executed.
bool AllowScriptFromSourceWithoutNotifying(
const KURL& url,
WebContentSettingsClient* settings_client,
Settings* settings) {
bool allow_script = !settings || settings->GetScriptEnabled();
if (settings_client)
allow_script = settings_client->AllowScriptFromSource(allow_script, url);
return allow_script;
}
} // namespace
void HttpEquiv::Process(Document& document,
const AtomicString& equiv,
const AtomicString& content,
bool in_document_head_element,
Element* element) {
DCHECK(!equiv.IsNull());
DCHECK(!content.IsNull());
if (EqualIgnoringASCIICase(equiv, "default-style")) {
ProcessHttpEquivDefaultStyle(document, content);
} else if (EqualIgnoringASCIICase(equiv, "refresh")) {
ProcessHttpEquivRefresh(document.domWindow(), content, element);
} else if (EqualIgnoringASCIICase(equiv, "set-cookie")) {
ProcessHttpEquivSetCookie(document, content, element);
} else if (EqualIgnoringASCIICase(equiv, "content-language")) {
document.SetContentLanguage(content);
} else if (EqualIgnoringASCIICase(equiv, "x-dns-prefetch-control")) {
document.ParseDNSPrefetchControlHeader(content);
} else if (EqualIgnoringASCIICase(equiv, "x-frame-options")) {
document.AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::ConsoleMessageSource::kSecurity,
mojom::ConsoleMessageLevel::kError,
"X-Frame-Options may only be set via an HTTP header sent along with a "
"document. It may not be set inside <meta>."));
} else if (EqualIgnoringASCIICase(equiv, http_names::kAcceptCH)) {
ProcessHttpEquivAcceptCH(document, content);
} else if (EqualIgnoringASCIICase(equiv, "content-security-policy") ||
EqualIgnoringASCIICase(equiv,
"content-security-policy-report-only")) {
if (in_document_head_element) {
ProcessHttpEquivContentSecurityPolicy(document.domWindow(), equiv,
content);
} else if (auto* window = document.domWindow()) {
window->GetContentSecurityPolicy()->ReportMetaOutsideHead(content);
}
} else if (EqualIgnoringASCIICase(equiv, http_names::kOriginTrial)) {
if (in_document_head_element) {
ProcessHttpEquivOriginTrial(document.domWindow(), content);
}
}
}
void HttpEquiv::ProcessHttpEquivContentSecurityPolicy(
LocalDOMWindow* window,
const AtomicString& equiv,
const AtomicString& content) {
if (!window || !window->GetFrame())
return;
if (window->GetFrame()->GetSettings()->GetBypassCSP())
return;
if (EqualIgnoringASCIICase(equiv, "content-security-policy")) {
window->GetContentSecurityPolicy()->DidReceiveHeader(
content, *(window->GetSecurityOrigin()),
network::mojom::ContentSecurityPolicyType::kEnforce,
network::mojom::ContentSecurityPolicySource::kMeta);
} else if (EqualIgnoringASCIICase(equiv,
"content-security-policy-report-only")) {
window->GetContentSecurityPolicy()->DidReceiveHeader(
content, *(window->GetSecurityOrigin()),
network::mojom::ContentSecurityPolicyType::kReport,
network::mojom::ContentSecurityPolicySource::kMeta);
} else {
NOTREACHED();
}
}
void HttpEquiv::ProcessHttpEquivAcceptCH(Document& document,
const AtomicString& content) {
LocalFrame* frame = document.GetFrame();
if (!frame)
return;
if (!document.GetFrame()->IsMainFrame()) {
return;
}
if (!AllowScriptFromSourceWithoutNotifying(
document.Url(), document.GetFrame()->GetContentSettingsClient(),
document.GetFrame()->GetSettings())) {
// Do not allow configuring client hints if JavaScript is disabled.
return;
}
UseCounter::Count(document, WebFeature::kClientHintsMetaAcceptCH);
FrameClientHintsPreferencesContext hints_context(frame);
frame->GetClientHintsPreferences().UpdateFromHttpEquivAcceptCH(
content, document.Url(), &hints_context);
}
void HttpEquiv::ProcessHttpEquivDefaultStyle(Document& document,
const AtomicString& content) {
document.GetStyleEngine().SetHttpDefaultStyle(content);
}
void HttpEquiv::ProcessHttpEquivOriginTrial(LocalDOMWindow* window,
const AtomicString& content) {
if (!window)
return;
// For meta tags injected by script, process the token with the origin of the
// external script, if available.
// NOTE: The external script origin is not considered security-critical. See
// the comment thread in the design doc for details:
// https://docs.google.com/document/d/1xALH9W7rWmX0FpjudhDeS2TNTEOXuPn4Tlc9VmuPdHA/edit?disco=AAAAJyG8StI
if (RuntimeEnabledFeatures::ThirdPartyOriginTrialsEnabled()) {
KURL external_script_url(GetCurrentScriptUrl(/*max_stack_depth=*/1));
if (external_script_url.IsValid()) {
scoped_refptr<SecurityOrigin> external_origin =
SecurityOrigin::Create(external_script_url);
window->GetOriginTrialContext()->AddTokenFromExternalScript(
content, external_origin.get());
return;
}
}
// Process token as usual, without an external script origin.
window->GetOriginTrialContext()->AddToken(content);
}
void HttpEquiv::ProcessHttpEquivRefresh(LocalDOMWindow* window,
const AtomicString& content,
Element* element) {
if (!window)
return;
UseCounter::Count(window, WebFeature::kMetaRefresh);
if (!window->GetContentSecurityPolicy()->AllowInline(
ContentSecurityPolicy::InlineType::kScript, element, "" /* content */,
"" /* nonce */, NullURL(), OrdinalNumber(),
ReportingDisposition::kSuppressReporting)) {
UseCounter::Count(window,
WebFeature::kMetaRefreshWhenCSPBlocksInlineScript);
}
window->document()->MaybeHandleHttpRefresh(content,
Document::kHttpRefreshFromMetaTag);
}
void HttpEquiv::ProcessHttpEquivSetCookie(Document& document,
const AtomicString& content,
Element* element) {
document.AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::ConsoleMessageSource::kSecurity,
mojom::ConsoleMessageLevel::kError,
String::Format("Blocked setting the `%s` cookie from a `<meta>` tag.",
content.Utf8().c_str())));
}
} // namespace blink