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