blob: f168e6b7bf70c85a6a5b060747d3d7ed20ea28cb [file] [log] [blame]
/*
* Copyright (C) 2011, Google Inc. All rights reserved.
* Copyright (C) 2014, Samsung Electronics. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
#include "third_party/blink/renderer/modules/navigatorcontentutils/navigator_content_utils.h"
#include "base/stl_util.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/web_local_frame_impl.h"
#include "third_party/blink/renderer/modules/navigatorcontentutils/navigator_content_utils_client.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
namespace blink {
const char NavigatorContentUtils::kSupplementName[] = "NavigatorContentUtils";
namespace {
const char kToken[] = "%s";
// Changes to this list must be kept in sync with the browser-side checks in
// /chrome/common/custom_handlers/protocol_handler.cc.
static const HashSet<String>& SupportedSchemes() {
DEFINE_STATIC_LOCAL(
HashSet<String>, supported_schemes,
({
"bitcoin", "cabal", "dat", "did", "dweb", "ethereum",
"geo", "hyper", "im", "ipfs", "ipns", "irc",
"ircs", "magnet", "mailto", "mms", "news", "nntp",
"openpgp4fpr", "sip", "sms", "smsto", "ssb", "ssh",
"tel", "urn", "webcal", "wtai", "xmpp",
}));
return supported_schemes;
}
static bool VerifyCustomHandlerURLSecurity(const LocalDOMWindow& window,
const KURL& full_url,
String& error_message) {
// Although not required by the spec, the spec allows additional security
// checks. Bugs have arisen from allowing non-http/https URLs, e.g.
// https://crbug.com/971917 and it doesn't make a lot of sense to support
// them. We do need to allow extensions to continue using the API.
if (!full_url.ProtocolIsInHTTPFamily() &&
!full_url.ProtocolIs("chrome-extension")) {
error_message =
"The scheme of the url provided must be 'https' or "
"'chrome-extension'.";
return false;
}
// The specification says that the API throws SecurityError exception if the
// URL's origin differs from the window's origin.
if (!window.GetSecurityOrigin()->CanRequest(full_url)) {
error_message =
"Can only register custom handler in the document's origin.";
return false;
}
return true;
}
static bool VerifyCustomHandlerURL(const LocalDOMWindow& window,
const String& user_url,
ExceptionState& exception_state) {
KURL full_url = window.CompleteURL(user_url);
KURL base_url = window.BaseURL();
String error_message;
if (!VerifyCustomHandlerURLSyntax(full_url, base_url, user_url,
error_message)) {
exception_state.ThrowDOMException(DOMExceptionCode::kSyntaxError,
error_message);
return false;
}
if (!VerifyCustomHandlerURLSecurity(window, full_url, error_message)) {
exception_state.ThrowSecurityError(error_message);
return false;
}
return true;
}
// HTML5 requires that schemes with the |web+| prefix contain one or more
// ASCII alphas after that prefix.
static bool IsValidWebSchemeName(const String& protocol) {
if (protocol.length() < 5)
return false;
unsigned protocol_length = protocol.length();
for (unsigned i = 4; i < protocol_length; i++) {
char c = protocol[i];
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))) {
return false;
}
}
return true;
}
} // namespace
bool VerifyCustomHandlerScheme(const String& scheme, String& error_string) {
if (!IsValidProtocol(scheme)) {
error_string = "The scheme name '" + scheme +
"' is not allowed by URI syntax (RFC3986).";
return false;
}
if (scheme.StartsWithIgnoringASCIICase("web+")) {
if (IsValidWebSchemeName(scheme))
return true;
error_string =
"The scheme name '" + scheme +
"' is not allowed. Schemes starting with 'web+' must be followed by "
"one or more ASCII letters.";
return false;
}
if (SupportedSchemes().Contains(scheme.LowerASCII()))
return true;
error_string = "The scheme '" + scheme +
"' doesn't belong to the scheme allowlist. "
"Please prefix non-allowlisted schemes "
"with the string 'web+'.";
return false;
}
bool VerifyCustomHandlerURLSyntax(const KURL& full_url,
const KURL& base_url,
const String& user_url,
String& error_message) {
// The specification requires that it is a SyntaxError if the "%s" token is
// not present.
int index = user_url.Find(kToken);
if (-1 == index) {
error_message =
"The url provided ('" + user_url + "') does not contain '%s'.";
return false;
}
// It is also a SyntaxError if the custom handler URL, as created by removing
// the "%s" token and prepending the base url, does not resolve.
if (full_url.IsEmpty() || !full_url.IsValid()) {
error_message =
"The custom handler URL created by removing '%s' and prepending '" +
base_url.GetString() + "' is invalid.";
return false;
}
return true;
}
NavigatorContentUtils& NavigatorContentUtils::From(Navigator& navigator,
LocalFrame& frame) {
NavigatorContentUtils* navigator_content_utils =
Supplement<Navigator>::From<NavigatorContentUtils>(navigator);
if (!navigator_content_utils) {
navigator_content_utils = MakeGarbageCollected<NavigatorContentUtils>(
navigator, MakeGarbageCollected<NavigatorContentUtilsClient>(&frame));
ProvideTo(navigator, navigator_content_utils);
}
return *navigator_content_utils;
}
NavigatorContentUtils::~NavigatorContentUtils() = default;
void NavigatorContentUtils::registerProtocolHandler(
Navigator& navigator,
const String& scheme,
const String& url,
const String& title,
ExceptionState& exception_state) {
LocalDOMWindow* window = navigator.DomWindow();
if (!window)
return;
// Per the HTML specification, exceptions for arguments must be surfaced in
// the order of the arguments.
String error_message;
if (!VerifyCustomHandlerScheme(scheme, error_message)) {
exception_state.ThrowSecurityError(error_message);
return;
}
if (!VerifyCustomHandlerURL(*window, url, exception_state))
return;
// Count usage; perhaps we can forbid this from cross-origin subframes as
// proposed in https://crbug.com/977083.
UseCounter::Count(
window, window->GetFrame()->IsCrossOriginToMainFrame()
? WebFeature::kRegisterProtocolHandlerCrossOriginSubframe
: WebFeature::kRegisterProtocolHandlerSameOriginAsTop);
// Count usage. Context should now always be secure due to the same-origin
// check and the requirement that the calling context be secure.
UseCounter::Count(window,
window->IsSecureContext()
? WebFeature::kRegisterProtocolHandlerSecureOrigin
: WebFeature::kRegisterProtocolHandlerInsecureOrigin);
NavigatorContentUtils::From(navigator, *window->GetFrame())
.Client()
->RegisterProtocolHandler(scheme, window->CompleteURL(url), title);
}
void NavigatorContentUtils::unregisterProtocolHandler(
Navigator& navigator,
const String& scheme,
const String& url,
ExceptionState& exception_state) {
LocalDOMWindow* window = navigator.DomWindow();
if (!window)
return;
String error_message;
if (!VerifyCustomHandlerScheme(scheme, error_message)) {
exception_state.ThrowSecurityError(error_message);
return;
}
if (!VerifyCustomHandlerURL(*window, url, exception_state))
return;
NavigatorContentUtils::From(navigator, *window->GetFrame())
.Client()
->UnregisterProtocolHandler(scheme, window->CompleteURL(url));
}
void NavigatorContentUtils::Trace(Visitor* visitor) const {
visitor->Trace(client_);
Supplement<Navigator>::Trace(visitor);
}
} // namespace blink