daa144e97926f92c93b770c9dfeede72e03b8da9
[WebKit-https.git] / Source / WebCore / loader / SubframeLoader.cpp
1 /*
2  * Copyright (C) 2006-2017 Apple Inc. All rights reserved.
3  * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
4  * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
5  * Copyright (C) 2008 Alp Toker <alp@atoker.com>
6  * Copyright (C) Research In Motion Limited 2009. All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * 1.  Redistributions of source code must retain the above copyright
13  *     notice, this list of conditions and the following disclaimer. 
14  * 2.  Redistributions in binary form must reproduce the above copyright
15  *     notice, this list of conditions and the following disclaimer in the
16  *     documentation and/or other materials provided with the distribution. 
17  * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
18  *     its contributors may be used to endorse or promote products derived
19  *     from this software without specific prior written permission. 
20  *
21  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
22  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
25  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
28  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 #include "config.h"
34 #include "SubframeLoader.h"
35
36 #include "ContentSecurityPolicy.h"
37 #include "DiagnosticLoggingClient.h"
38 #include "DiagnosticLoggingKeys.h"
39 #include "DocumentLoader.h"
40 #include "Frame.h"
41 #include "FrameLoader.h"
42 #include "FrameLoaderClient.h"
43 #include "HTMLAppletElement.h"
44 #include "HTMLFrameElement.h"
45 #include "HTMLIFrameElement.h"
46 #include "HTMLNames.h"
47 #include "HTMLObjectElement.h"
48 #include "MIMETypeRegistry.h"
49 #include "NavigationScheduler.h"
50 #include "Page.h"
51 #include "PluginData.h"
52 #include "PluginDocument.h"
53 #include "RenderEmbeddedObject.h"
54 #include "RenderView.h"
55 #include "ScriptController.h"
56 #include "SecurityOrigin.h"
57 #include "SecurityPolicy.h"
58 #include "Settings.h"
59 #include <wtf/CompletionHandler.h>
60
61 namespace WebCore {
62     
63 using namespace HTMLNames;
64
65 SubframeLoader::SubframeLoader(Frame& frame)
66     : m_containsPlugins(false)
67     , m_frame(frame)
68 {
69 }
70
71 void SubframeLoader::clear()
72 {
73     m_containsPlugins = false;
74 }
75
76 bool SubframeLoader::requestFrame(HTMLFrameOwnerElement& ownerElement, const String& urlString, const AtomString& frameName, LockHistory lockHistory, LockBackForwardList lockBackForwardList)
77 {
78     // Support for <frame src="javascript:string">
79     URL scriptURL;
80     URL url;
81     if (WTF::protocolIsJavaScript(urlString)) {
82         scriptURL = completeURL(urlString); // completeURL() encodes the URL.
83         url = WTF::blankURL();
84     } else
85         url = completeURL(urlString);
86
87     if (shouldConvertInvalidURLsToBlank() && !url.isValid())
88         url = WTF::blankURL();
89
90     // If we will schedule a JavaScript URL load, we need to delay the firing of the load event at least until we've run the JavaScript in the URL.
91     CompletionHandlerCallingScope stopDelayingLoadEvent;
92     if (!scriptURL.isEmpty()) {
93         ownerElement.document().incrementLoadEventDelayCount();
94         stopDelayingLoadEvent = CompletionHandlerCallingScope([ownerDocument = makeRef(ownerElement.document())] {
95             ownerDocument->decrementLoadEventDelayCount();
96         });
97     }
98
99     Frame* frame = loadOrRedirectSubframe(ownerElement, url, frameName, lockHistory, lockBackForwardList);
100     if (!frame)
101         return false;
102
103     if (!scriptURL.isEmpty() && ownerElement.canLoadScriptURL(scriptURL)) {
104         // FIXME: Some sites rely on the javascript:'' loading synchronously, which is why we have this special case.
105         // Blink has the same workaround (https://bugs.chromium.org/p/chromium/issues/detail?id=923585).
106         if (urlString == "javascript:''" || urlString == "javascript:\"\"")
107             frame->script().executeIfJavaScriptURL(scriptURL);
108         else
109             frame->navigationScheduler().scheduleLocationChange(ownerElement.document(), ownerElement.document().securityOrigin(), scriptURL, m_frame.loader().outgoingReferrer(), lockHistory, lockBackForwardList, stopDelayingLoadEvent.release());
110     }
111
112     return true;
113 }
114     
115 bool SubframeLoader::resourceWillUsePlugin(const String& url, const String& mimeType)
116 {
117     URL completedURL;
118     if (!url.isEmpty())
119         completedURL = completeURL(url);
120
121     bool useFallback;
122     return shouldUsePlugin(completedURL, mimeType, false, useFallback);
123 }
124
125 bool SubframeLoader::pluginIsLoadable(const URL& url, const String& mimeType)
126 {
127     auto* document = m_frame.document();
128
129     if (MIMETypeRegistry::isJavaAppletMIMEType(mimeType)) {
130         if (!m_frame.settings().isJavaEnabled())
131             return false;
132         if (document && document->securityOrigin().isLocal() && !m_frame.settings().isJavaEnabledForLocalFiles())
133             return false;
134     }
135
136     if (document) {
137         if (document->isSandboxed(SandboxPlugins))
138             return false;
139
140         if (!document->securityOrigin().canDisplay(url)) {
141             FrameLoader::reportLocalLoadFailed(&m_frame, url.string());
142             return false;
143         }
144
145         if (!m_frame.loader().mixedContentChecker().canRunInsecureContent(document->securityOrigin(), url))
146             return false;
147     }
148
149     return true;
150 }
151
152 bool SubframeLoader::requestPlugin(HTMLPlugInImageElement& ownerElement, const URL& url, const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues, bool useFallback)
153 {
154     // Application plug-ins are plug-ins implemented by the user agent, for example Qt plug-ins,
155     // as opposed to third-party code such as Flash. The user agent decides whether or not they are
156     // permitted, rather than WebKit.
157     if ((!allowPlugins() && !MIMETypeRegistry::isApplicationPluginMIMEType(mimeType)))
158         return false;
159
160     if (!pluginIsLoadable(url, mimeType))
161         return false;
162
163     ASSERT(ownerElement.hasTagName(objectTag) || ownerElement.hasTagName(embedTag));
164     return loadPlugin(ownerElement, url, mimeType, paramNames, paramValues, useFallback);
165 }
166
167 static String findPluginMIMETypeFromURL(Page* page, const String& url)
168 {
169     if (!url)
170         return String();
171
172     size_t dotIndex = url.reverseFind('.');
173     if (dotIndex == notFound)
174         return String();
175
176     String extension = url.substring(dotIndex + 1);
177
178     const PluginData& pluginData = page->pluginData();
179
180     Vector<MimeClassInfo> mimes;
181     Vector<size_t> mimePluginIndices;
182     pluginData.getWebVisibleMimesAndPluginIndices(mimes, mimePluginIndices);
183     for (auto& mime : mimes) {
184         for (auto& mimeExtension : mime.extensions) {
185             if (equalIgnoringASCIICase(extension, mimeExtension))
186                 return mime.type;
187         }
188     }
189
190     return String();
191 }
192
193 static void logPluginRequest(Page* page, const String& mimeType, const String& url, bool success)
194 {
195     if (!page)
196         return;
197
198     String newMIMEType = mimeType;
199     if (!newMIMEType) {
200         // Try to figure out the MIME type from the URL extension.
201         newMIMEType = findPluginMIMETypeFromURL(page, url);
202         if (!newMIMEType)
203             return;
204     }
205
206     String pluginFile = page->pluginData().pluginFileForWebVisibleMimeType(newMIMEType);
207     String description = !pluginFile ? newMIMEType : pluginFile;
208
209     DiagnosticLoggingClient& diagnosticLoggingClient = page->diagnosticLoggingClient();
210     diagnosticLoggingClient.logDiagnosticMessage(success ? DiagnosticLoggingKeys::pluginLoadedKey() : DiagnosticLoggingKeys::pluginLoadingFailedKey(), description, ShouldSample::No);
211
212     if (!page->hasSeenAnyPlugin())
213         diagnosticLoggingClient.logDiagnosticMessage(DiagnosticLoggingKeys::pageContainsAtLeastOnePluginKey(), emptyString(), ShouldSample::No);
214
215     if (!page->hasSeenPlugin(description))
216         diagnosticLoggingClient.logDiagnosticMessage(DiagnosticLoggingKeys::pageContainsPluginKey(), description, ShouldSample::No);
217
218     page->sawPlugin(description);
219 }
220
221 bool SubframeLoader::requestObject(HTMLPlugInImageElement& ownerElement, const String& url, const AtomString& frameName, const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues)
222 {
223     if (url.isEmpty() && mimeType.isEmpty())
224         return false;
225
226     auto& document = ownerElement.document();
227
228     URL completedURL;
229     if (!url.isEmpty())
230         completedURL = completeURL(url);
231
232     document.contentSecurityPolicy()->upgradeInsecureRequestIfNeeded(completedURL, ContentSecurityPolicy::InsecureRequestType::Load);
233
234     bool hasFallbackContent = is<HTMLObjectElement>(ownerElement) && downcast<HTMLObjectElement>(ownerElement).hasFallbackContent();
235
236     bool useFallback;
237     if (shouldUsePlugin(completedURL, mimeType, hasFallbackContent, useFallback)) {
238         bool success = requestPlugin(ownerElement, completedURL, mimeType, paramNames, paramValues, useFallback);
239         logPluginRequest(document.page(), mimeType, completedURL, success);
240         return success;
241     }
242
243     // If the plug-in element already contains a subframe, loadOrRedirectSubframe will re-use it. Otherwise,
244     // it will create a new frame and set it as the RenderWidget's Widget, causing what was previously 
245     // in the widget to be torn down.
246     return loadOrRedirectSubframe(ownerElement, completedURL, frameName, LockHistory::Yes, LockBackForwardList::Yes);
247 }
248
249 RefPtr<Widget> SubframeLoader::createJavaAppletWidget(const IntSize& size, HTMLAppletElement& element, const Vector<String>& paramNames, const Vector<String>& paramValues)
250 {
251     String baseURLString;
252     String codeBaseURLString;
253
254     for (size_t i = 0; i < paramNames.size(); ++i) {
255         if (equalLettersIgnoringASCIICase(paramNames[i], "baseurl"))
256             baseURLString = paramValues[i];
257         else if (equalLettersIgnoringASCIICase(paramNames[i], "codebase"))
258             codeBaseURLString = paramValues[i];
259     }
260
261     if (!codeBaseURLString.isEmpty()) {
262         URL codeBaseURL = completeURL(codeBaseURLString);
263         if (!element.document().securityOrigin().canDisplay(codeBaseURL)) {
264             FrameLoader::reportLocalLoadFailed(&m_frame, codeBaseURL.string());
265             return nullptr;
266         }
267
268         const char javaAppletMimeType[] = "application/x-java-applet";
269         ASSERT(element.document().contentSecurityPolicy());
270         auto& contentSecurityPolicy = *element.document().contentSecurityPolicy();
271         // Elements in user agent show tree should load whatever the embedding document policy is.
272         if (!element.isInUserAgentShadowTree()
273             && (!contentSecurityPolicy.allowObjectFromSource(codeBaseURL) || !contentSecurityPolicy.allowPluginType(javaAppletMimeType, javaAppletMimeType, codeBaseURL)))
274             return nullptr;
275     }
276
277     if (baseURLString.isEmpty())
278         baseURLString = element.document().baseURL().string();
279     URL baseURL = completeURL(baseURLString);
280
281     RefPtr<Widget> widget;
282     if (allowPlugins())
283         widget = m_frame.loader().client().createJavaAppletWidget(size, element, baseURL, paramNames, paramValues);
284
285     logPluginRequest(m_frame.page(), element.serviceType(), String(), widget);
286
287     if (!widget) {
288         RenderEmbeddedObject* renderer = element.renderEmbeddedObject();
289
290         if (!renderer->isPluginUnavailable())
291             renderer->setPluginUnavailabilityReason(RenderEmbeddedObject::PluginMissing);
292         return nullptr;
293     }
294
295     m_containsPlugins = true;
296     return widget;
297 }
298
299 Frame* SubframeLoader::loadOrRedirectSubframe(HTMLFrameOwnerElement& ownerElement, const URL& requestURL, const AtomString& frameName, LockHistory lockHistory, LockBackForwardList lockBackForwardList)
300 {
301     auto& initiatingDocument = ownerElement.document();
302
303     URL upgradedRequestURL = requestURL;
304     initiatingDocument.contentSecurityPolicy()->upgradeInsecureRequestIfNeeded(upgradedRequestURL, ContentSecurityPolicy::InsecureRequestType::Load);
305
306     auto* frame = ownerElement.contentFrame();
307     if (frame)
308         frame->navigationScheduler().scheduleLocationChange(initiatingDocument, initiatingDocument.securityOrigin(), upgradedRequestURL, m_frame.loader().outgoingReferrer(), lockHistory, lockBackForwardList);
309     else
310         frame = loadSubframe(ownerElement, upgradedRequestURL, frameName, m_frame.loader().outgoingReferrer());
311
312     if (!frame)
313         return nullptr;
314
315     ASSERT(ownerElement.contentFrame() == frame || !ownerElement.contentFrame());
316     return ownerElement.contentFrame();
317 }
318
319 Frame* SubframeLoader::loadSubframe(HTMLFrameOwnerElement& ownerElement, const URL& url, const String& name, const String& referrer)
320 {
321     Ref<Frame> protect(m_frame);
322     auto document = makeRef(ownerElement.document());
323
324     if (!document->securityOrigin().canDisplay(url)) {
325         FrameLoader::reportLocalLoadFailed(&m_frame, url.string());
326         return nullptr;
327     }
328
329     if (!SubframeLoadingDisabler::canLoadFrame(ownerElement))
330         return nullptr;
331
332     if (!m_frame.page() || m_frame.page()->subframeCount() >= Page::maxNumberOfFrames)
333         return nullptr;
334
335     ReferrerPolicy policy = ownerElement.referrerPolicy();
336     if (policy == ReferrerPolicy::EmptyString)
337         policy = document->referrerPolicy();
338     String referrerToUse = SecurityPolicy::generateReferrerHeader(policy, url, referrer);
339
340     // Prevent initial empty document load from triggering load events.
341     document->incrementLoadEventDelayCount();
342
343     auto frame = m_frame.loader().client().createFrame(url, name, ownerElement, referrerToUse);
344
345     document->decrementLoadEventDelayCount();
346
347     if (!frame)  {
348         m_frame.loader().checkCallImplicitClose();
349         return nullptr;
350     }
351
352     // All new frames will have m_isComplete set to true at this point due to synchronously loading
353     // an empty document in FrameLoader::init(). But many frames will now be starting an
354     // asynchronous load of url, so we set m_isComplete to false and then check if the load is
355     // actually completed below. (Note that we set m_isComplete to false even for synchronous
356     // loads, so that checkCompleted() below won't bail early.)
357     // FIXME: Can we remove this entirely? m_isComplete normally gets set to false when a load is committed.
358     frame->loader().started();
359    
360     auto* renderer = ownerElement.renderer();
361     auto* view = frame->view();
362     if (is<RenderWidget>(renderer) && view)
363         downcast<RenderWidget>(*renderer).setWidget(view);
364
365     m_frame.loader().checkCallImplicitClose();
366
367     // Some loads are performed synchronously (e.g., about:blank and loads
368     // cancelled by returning a null ResourceRequest from requestFromDelegate).
369     // In these cases, the synchronous load would have finished
370     // before we could connect the signals, so make sure to send the 
371     // completed() signal for the child by hand and mark the load as being
372     // complete.
373     // FIXME: In this case the Frame will have finished loading before 
374     // it's being added to the child list. It would be a good idea to
375     // create the child first, then invoke the loader separately.
376     if (frame->loader().state() == FrameStateComplete && !frame->loader().policyDocumentLoader())
377         frame->loader().checkCompleted();
378
379     return frame.get();
380 }
381
382 bool SubframeLoader::allowPlugins()
383 {
384     return m_frame.settings().arePluginsEnabled();
385 }
386
387 bool SubframeLoader::shouldUsePlugin(const URL& url, const String& mimeType, bool hasFallback, bool& useFallback)
388 {
389     if (m_frame.loader().client().shouldAlwaysUsePluginDocument(mimeType)) {
390         useFallback = false;
391         return true;
392     }
393
394     ObjectContentType objectType = m_frame.loader().client().objectContentType(url, mimeType);
395     // If an object's content can't be handled and it has no fallback, let
396     // it be handled as a plugin to show the broken plugin icon.
397     useFallback = objectType == ObjectContentType::None && hasFallback;
398
399     return objectType == ObjectContentType::None || objectType == ObjectContentType::PlugIn;
400 }
401
402 bool SubframeLoader::loadPlugin(HTMLPlugInImageElement& pluginElement, const URL& url, const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues, bool useFallback)
403 {
404     if (useFallback)
405         return false;
406
407     auto& document = pluginElement.document();
408     auto* renderer = pluginElement.renderEmbeddedObject();
409
410     // FIXME: This code should not depend on renderer!
411     if (!renderer)
412         return false;
413
414     pluginElement.subframeLoaderWillCreatePlugIn(url);
415
416     IntSize contentSize = roundedIntSize(LayoutSize(renderer->contentWidth(), renderer->contentHeight()));
417     bool loadManually = is<PluginDocument>(document) && !m_containsPlugins && downcast<PluginDocument>(document).shouldLoadPluginManually();
418
419 #if PLATFORM(IOS_FAMILY)
420     // On iOS, we only tell the plugin to be in full page mode if the containing plugin document is the top level document.
421     if (document.ownerElement())
422         loadManually = false;
423 #endif
424
425     auto weakRenderer = makeWeakPtr(*renderer);
426
427     auto widget = m_frame.loader().client().createPlugin(contentSize, pluginElement, url, paramNames, paramValues, mimeType, loadManually);
428
429     // The call to createPlugin *may* cause this renderer to disappear from underneath.
430     if (!weakRenderer)
431         return false;
432
433     if (!widget) {
434         if (!renderer->isPluginUnavailable())
435             renderer->setPluginUnavailabilityReason(RenderEmbeddedObject::PluginMissing);
436         return false;
437     }
438
439     pluginElement.subframeLoaderDidCreatePlugIn(*widget);
440     renderer->setWidget(WTFMove(widget));
441     m_containsPlugins = true;
442     return true;
443 }
444
445 URL SubframeLoader::completeURL(const String& url) const
446 {
447     ASSERT(m_frame.document());
448     return m_frame.document()->completeURL(url);
449 }
450
451 bool SubframeLoader::shouldConvertInvalidURLsToBlank() const
452 {
453     return m_frame.settings().shouldConvertInvalidURLsToBlank();
454 }
455
456 } // namespace WebCore