| /* |
| * 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/public/common/custom_handlers/protocol_handler_utils.h" |
| #include "third_party/blink/public/common/security/protocol_handler_security_level.h" |
| #include "third_party/blink/public/platform/platform.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" |
| #include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h" |
| |
| namespace blink { |
| |
| const char NavigatorContentUtils::kSupplementName[] = "NavigatorContentUtils"; |
| |
| namespace { |
| |
| const char kToken[] = "%s"; |
| |
| static bool VerifyCustomHandlerURLSecurity( |
| const LocalDOMWindow& window, |
| const KURL& full_url, |
| String& error_message, |
| ProtocolHandlerSecurityLevel security_level) { |
| // 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() && |
| (security_level < ProtocolHandlerSecurityLevel::kExtensionFeatures || |
| !full_url.ProtocolIs("chrome-extension"))) { |
| error_message = "The scheme of the url provided must be 'https'."; |
| return false; |
| } |
| |
| // The specification says that the API throws SecurityError exception if the |
| // URL's origin differs from the window's origin. |
| if (security_level < ProtocolHandlerSecurityLevel::kUntrustedOrigins && |
| !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, |
| ProtocolHandlerSecurityLevel security_level) { |
| 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, |
| security_level)) { |
| exception_state.ThrowSecurityError(error_message); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| } // namespace |
| |
| bool VerifyCustomHandlerScheme(const String& scheme, |
| String& error_string, |
| ProtocolHandlerSecurityLevel security_level) { |
| if (!IsValidProtocol(scheme)) { |
| error_string = "The scheme name '" + scheme + |
| "' is not allowed by URI syntax (RFC3986)."; |
| return false; |
| } |
| |
| bool allow_ext_plus_prefix = |
| security_level >= ProtocolHandlerSecurityLevel::kExtensionFeatures; |
| bool has_custom_scheme_prefix; |
| StringUTF8Adaptor scheme_adaptor(scheme); |
| if (!IsValidCustomHandlerScheme(scheme_adaptor.AsStringPiece(), |
| allow_ext_plus_prefix, |
| has_custom_scheme_prefix)) { |
| if (has_custom_scheme_prefix) { |
| error_string = "The scheme name '" + scheme + |
| "' is not allowed. Schemes starting with '" + scheme + |
| "' must be followed by one or more ASCII letters."; |
| } else { |
| error_string = "The scheme '" + scheme + |
| "' doesn't belong to the scheme allowlist. " |
| "Please prefix non-allowlisted schemes " |
| "with the string 'web+'."; |
| } |
| return false; |
| } |
| |
| return true; |
| } |
| |
| 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, |
| ExceptionState& exception_state) { |
| LocalDOMWindow* window = navigator.DomWindow(); |
| if (!window) |
| return; |
| |
| ProtocolHandlerSecurityLevel security_level = |
| Platform::Current()->GetProtocolHandlerSecurityLevel(); |
| |
| // Per the HTML specification, exceptions for arguments must be surfaced in |
| // the order of the arguments. |
| String error_message; |
| if (!VerifyCustomHandlerScheme(scheme, error_message, security_level)) { |
| exception_state.ThrowSecurityError(error_message); |
| return; |
| } |
| |
| if (!VerifyCustomHandlerURL(*window, url, exception_state, security_level)) |
| 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)); |
| } |
| |
| void NavigatorContentUtils::unregisterProtocolHandler( |
| Navigator& navigator, |
| const String& scheme, |
| const String& url, |
| ExceptionState& exception_state) { |
| LocalDOMWindow* window = navigator.DomWindow(); |
| if (!window) |
| return; |
| |
| ProtocolHandlerSecurityLevel security_level = |
| Platform::Current()->GetProtocolHandlerSecurityLevel(); |
| |
| String error_message; |
| if (!VerifyCustomHandlerScheme(scheme, error_message, security_level)) { |
| exception_state.ThrowSecurityError(error_message); |
| return; |
| } |
| |
| if (!VerifyCustomHandlerURL(*window, url, exception_state, security_level)) |
| 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 |