1/* 2 * Copyright (C) 2011, Google Inc. All rights reserved. 3 * Copyright (C) 2014, Samsung Electronics. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 24 * DAMAGE. 25 */ 26 27#include "config.h" 28#include "modules/navigatorcontentutils/NavigatorContentUtils.h" 29 30#include "bindings/core/v8/ExceptionState.h" 31#include "core/dom/Document.h" 32#include "core/dom/ExceptionCode.h" 33#include "core/frame/LocalFrame.h" 34#include "core/frame/Navigator.h" 35#include "core/page/Page.h" 36#include "wtf/HashSet.h" 37#include "wtf/text/StringBuilder.h" 38 39namespace blink { 40 41static HashSet<String>* schemeWhitelist; 42 43static void initCustomSchemeHandlerWhitelist() 44{ 45 schemeWhitelist = new HashSet<String>; 46 static const char* const schemes[] = { 47 "bitcoin", 48 "geo", 49 "im", 50 "irc", 51 "ircs", 52 "magnet", 53 "mailto", 54 "mms", 55 "news", 56 "nntp", 57 "sip", 58 "sms", 59 "smsto", 60 "ssh", 61 "tel", 62 "urn", 63 "webcal", 64 "wtai", 65 "xmpp", 66 }; 67 for (size_t i = 0; i < WTF_ARRAY_LENGTH(schemes); ++i) 68 schemeWhitelist->add(schemes[i]); 69} 70 71static bool verifyCustomHandlerURL(const Document& document, const String& url, ExceptionState& exceptionState) 72{ 73 // The specification requires that it is a SyntaxError if the "%s" token is 74 // not present. 75 static const char token[] = "%s"; 76 int index = url.find(token); 77 if (-1 == index) { 78 exceptionState.throwDOMException(SyntaxError, "The url provided ('" + url + "') does not contain '%s'."); 79 return false; 80 } 81 82 // It is also a SyntaxError if the custom handler URL, as created by removing 83 // the "%s" token and prepending the base url, does not resolve. 84 String newURL = url; 85 newURL.remove(index, WTF_ARRAY_LENGTH(token) - 1); 86 87 KURL kurl = document.completeURL(url); 88 89 if (kurl.isEmpty() || !kurl.isValid()) { 90 exceptionState.throwDOMException(SyntaxError, "The custom handler URL created by removing '%s' and prepending '" + document.baseURL().string() + "' is invalid."); 91 return false; 92 } 93 94 // The specification says that the API throws SecurityError exception if the 95 // URL's origin differs from the document's origin. 96 if (!document.securityOrigin()->canRequest(kurl)) { 97 exceptionState.throwSecurityError("Can only register custom handler in the document's origin."); 98 return false; 99 } 100 101 return true; 102} 103 104static bool isSchemeWhitelisted(const String& scheme) 105{ 106 if (!schemeWhitelist) 107 initCustomSchemeHandlerWhitelist(); 108 109 StringBuilder builder; 110 unsigned length = scheme.length(); 111 for (unsigned i = 0; i < length; ++i) 112 builder.append(toASCIILower(scheme[i])); 113 114 return schemeWhitelist->contains(builder.toString()); 115} 116 117static bool verifyCustomHandlerScheme(const String& scheme, ExceptionState& exceptionState) 118{ 119 if (!isValidProtocol(scheme)) { 120 exceptionState.throwSecurityError("The scheme '" + scheme + "' is not valid protocol"); 121 return false; 122 } 123 124 if (scheme.startsWith("web+")) { 125 // The specification requires that the length of scheme is at least five characteres (including 'web+' prefix). 126 if (scheme.length() >= 5) 127 return true; 128 129 exceptionState.throwSecurityError("The scheme '" + scheme + "' is less than five characters long."); 130 return false; 131 } 132 133 if (isSchemeWhitelisted(scheme)) 134 return true; 135 136 exceptionState.throwSecurityError("The scheme '" + scheme + "' doesn't belong to the scheme whitelist. Please prefix non-whitelisted schemes with the string 'web+'."); 137 return false; 138} 139 140NavigatorContentUtils* NavigatorContentUtils::from(Page& page) 141{ 142 return static_cast<NavigatorContentUtils*>(WillBeHeapSupplement<Page>::from(page, supplementName())); 143} 144 145NavigatorContentUtils::~NavigatorContentUtils() 146{ 147} 148 149PassOwnPtrWillBeRawPtr<NavigatorContentUtils> NavigatorContentUtils::create(PassOwnPtr<NavigatorContentUtilsClient> client) 150{ 151 return adoptPtrWillBeNoop(new NavigatorContentUtils(client)); 152} 153 154void NavigatorContentUtils::registerProtocolHandler(Navigator& navigator, const String& scheme, const String& url, const String& title, ExceptionState& exceptionState) 155{ 156 if (!navigator.frame()) 157 return; 158 159 Document* document = navigator.frame()->document(); 160 ASSERT(document); 161 162 if (!verifyCustomHandlerURL(*document, url, exceptionState)) 163 return; 164 165 if (!verifyCustomHandlerScheme(scheme, exceptionState)) 166 return; 167 168 ASSERT(navigator.frame()->page()); 169 NavigatorContentUtils::from(*navigator.frame()->page())->client()->registerProtocolHandler(scheme, document->completeURL(url), title); 170} 171 172static String customHandlersStateString(const NavigatorContentUtilsClient::CustomHandlersState state) 173{ 174 DEFINE_STATIC_LOCAL(const String, newHandler, ("new")); 175 DEFINE_STATIC_LOCAL(const String, registeredHandler, ("registered")); 176 DEFINE_STATIC_LOCAL(const String, declinedHandler, ("declined")); 177 178 switch (state) { 179 case NavigatorContentUtilsClient::CustomHandlersNew: 180 return newHandler; 181 case NavigatorContentUtilsClient::CustomHandlersRegistered: 182 return registeredHandler; 183 case NavigatorContentUtilsClient::CustomHandlersDeclined: 184 return declinedHandler; 185 } 186 187 ASSERT_NOT_REACHED(); 188 return String(); 189} 190 191String NavigatorContentUtils::isProtocolHandlerRegistered(Navigator& navigator, const String& scheme, const String& url, ExceptionState& exceptionState) 192{ 193 DEFINE_STATIC_LOCAL(const String, declined, ("declined")); 194 195 if (!navigator.frame()) 196 return declined; 197 198 Document* document = navigator.frame()->document(); 199 ASSERT(document); 200 if (document->activeDOMObjectsAreStopped()) 201 return declined; 202 203 if (!verifyCustomHandlerURL(*document, url, exceptionState)) 204 return declined; 205 206 if (!verifyCustomHandlerScheme(scheme, exceptionState)) 207 return declined; 208 209 ASSERT(navigator.frame()->page()); 210 return customHandlersStateString(NavigatorContentUtils::from(*navigator.frame()->page())->client()->isProtocolHandlerRegistered(scheme, document->completeURL(url))); 211} 212 213void NavigatorContentUtils::unregisterProtocolHandler(Navigator& navigator, const String& scheme, const String& url, ExceptionState& exceptionState) 214{ 215 if (!navigator.frame()) 216 return; 217 218 Document* document = navigator.frame()->document(); 219 ASSERT(document); 220 221 if (!verifyCustomHandlerURL(*document, url, exceptionState)) 222 return; 223 224 if (!verifyCustomHandlerScheme(scheme, exceptionState)) 225 return; 226 227 ASSERT(navigator.frame()->page()); 228 NavigatorContentUtils::from(*navigator.frame()->page())->client()->unregisterProtocolHandler(scheme, document->completeURL(url)); 229} 230 231const char* NavigatorContentUtils::supplementName() 232{ 233 return "NavigatorContentUtils"; 234} 235 236void provideNavigatorContentUtilsTo(Page& page, PassOwnPtr<NavigatorContentUtilsClient> client) 237{ 238 NavigatorContentUtils::provideTo(page, NavigatorContentUtils::supplementName(), NavigatorContentUtils::create(client)); 239} 240 241} // namespace blink 242