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 39 namespace blink { 40 41 static HashSet<String>* schemeWhitelist; 42 43 static 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 71 static 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 104 static 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 117 static 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 140 NavigatorContentUtils* NavigatorContentUtils::from(Page& page) 141 { 142 return static_cast<NavigatorContentUtils*>(WillBeHeapSupplement<Page>::from(page, supplementName())); 143 } 144 145 NavigatorContentUtils::~NavigatorContentUtils() 146 { 147 } 148 149 PassOwnPtrWillBeRawPtr<NavigatorContentUtils> NavigatorContentUtils::create(PassOwnPtr<NavigatorContentUtilsClient> client) 150 { 151 return adoptPtrWillBeNoop(new NavigatorContentUtils(client)); 152 } 153 154 void 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 172 static 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 191 String 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 213 void 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 231 const char* NavigatorContentUtils::supplementName() 232 { 233 return "NavigatorContentUtils"; 234 } 235 236 void provideNavigatorContentUtilsTo(Page& page, PassOwnPtr<NavigatorContentUtilsClient> client) 237 { 238 NavigatorContentUtils::provideTo(page, NavigatorContentUtils::supplementName(), NavigatorContentUtils::create(client)); 239 } 240 241 } // namespace blink 242