8a16142a44085fbb3436227777caa63d28bdc94b
[WebKit-https.git] / Source / WebCore / loader / SubframeLoader.cpp
1 /*
2  * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 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 "Chrome.h"
37 #include "ChromeClient.h"
38 #include "ContentSecurityPolicy.h"
39 #include "DiagnosticLoggingKeys.h"
40 #include "Frame.h"
41 #include "FrameLoader.h"
42 #include "FrameLoaderClient.h"
43 #include "HTMLAppletElement.h"
44 #include "HTMLAudioElement.h"
45 #include "HTMLFrameElementBase.h"
46 #include "HTMLNames.h"
47 #include "HTMLObjectElement.h"
48 #include "MIMETypeRegistry.h"
49 #include "Page.h"
50 #include "PluginData.h"
51 #include "PluginDocument.h"
52 #include "RenderEmbeddedObject.h"
53 #include "RenderView.h"
54 #include "ScriptController.h"
55 #include "SecurityOrigin.h"
56 #include "SecurityPolicy.h"
57 #include "Settings.h"
58
59 namespace WebCore {
60     
61 using namespace HTMLNames;
62
63 SubframeLoader::SubframeLoader(Frame& frame)
64     : m_containsPlugins(false)
65     , m_frame(frame)
66 {
67 }
68
69 void SubframeLoader::clear()
70 {
71     m_containsPlugins = false;
72 }
73
74 bool SubframeLoader::requestFrame(HTMLFrameOwnerElement& ownerElement, const String& urlString, const AtomicString& frameName, LockHistory lockHistory, LockBackForwardList lockBackForwardList)
75 {
76     // Support for <frame src="javascript:string">
77     URL scriptURL;
78     URL url;
79     if (protocolIsJavaScript(urlString)) {
80         scriptURL = completeURL(urlString); // completeURL() encodes the URL.
81         url = blankURL();
82     } else
83         url = completeURL(urlString);
84
85     Frame* frame = loadOrRedirectSubframe(ownerElement, url, frameName, lockHistory, lockBackForwardList);
86     if (!frame)
87         return false;
88
89     if (!scriptURL.isEmpty())
90         frame->script().executeIfJavaScriptURL(scriptURL);
91
92     return true;
93 }
94     
95 bool SubframeLoader::resourceWillUsePlugin(const String& url, const String& mimeType, bool shouldPreferPlugInsForImages)
96 {
97     URL completedURL;
98     if (!url.isEmpty())
99         completedURL = completeURL(url);
100
101     bool useFallback;
102     return shouldUsePlugin(completedURL, mimeType, shouldPreferPlugInsForImages, false, useFallback);
103 }
104
105 bool SubframeLoader::pluginIsLoadable(HTMLPlugInImageElement& pluginElement, const URL& url, const String& mimeType)
106 {
107     if (MIMETypeRegistry::isJavaAppletMIMEType(mimeType)) {
108         if (!m_frame.settings().isJavaEnabled())
109             return false;
110         if (document() && document()->securityOrigin()->isLocal() && !m_frame.settings().isJavaEnabledForLocalFiles())
111             return false;
112     }
113
114     if (document()) {
115         if (document()->isSandboxed(SandboxPlugins))
116             return false;
117
118         if (!document()->securityOrigin()->canDisplay(url)) {
119             FrameLoader::reportLocalLoadFailed(&m_frame, url.string());
120             return false;
121         }
122
123         String declaredMimeType = document()->isPluginDocument() && document()->ownerElement() ?
124             document()->ownerElement()->fastGetAttribute(HTMLNames::typeAttr) :
125             pluginElement.fastGetAttribute(HTMLNames::typeAttr);
126         if (!document()->contentSecurityPolicy()->allowObjectFromSource(url)
127             || !document()->contentSecurityPolicy()->allowPluginType(mimeType, declaredMimeType, url)) {
128             RenderEmbeddedObject* renderer = pluginElement.renderEmbeddedObject();
129             renderer->setPluginUnavailabilityReason(RenderEmbeddedObject::PluginBlockedByContentSecurityPolicy);
130             return false;
131         }
132
133         if (!m_frame.loader().mixedContentChecker().canRunInsecureContent(document()->securityOrigin(), url))
134             return false;
135     }
136
137     return true;
138 }
139
140 bool SubframeLoader::requestPlugin(HTMLPlugInImageElement& ownerElement, const URL& url, const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues, bool useFallback)
141 {
142     // Application plug-ins are plug-ins implemented by the user agent, for example Qt plug-ins,
143     // as opposed to third-party code such as Flash. The user agent decides whether or not they are
144     // permitted, rather than WebKit.
145     if ((!allowPlugins(AboutToInstantiatePlugin) && !MIMETypeRegistry::isApplicationPluginMIMEType(mimeType)))
146         return false;
147
148     if (!pluginIsLoadable(ownerElement, url, mimeType))
149         return false;
150
151     ASSERT(ownerElement.hasTagName(objectTag) || ownerElement.hasTagName(embedTag));
152     return loadPlugin(ownerElement, url, mimeType, paramNames, paramValues, useFallback);
153 }
154
155 static String findPluginMIMETypeFromURL(Page* page, const String& url)
156 {
157     if (!url)
158         return String();
159
160     size_t dotIndex = url.reverseFind('.');
161     if (dotIndex == notFound)
162         return String();
163
164     String extension = url.substring(dotIndex + 1);
165
166     const PluginData& pluginData = page->pluginData();
167
168     for (size_t i = 0; i < pluginData.mimes().size(); ++i) {
169         const MimeClassInfo& mimeClassInfo = pluginData.mimes()[i];
170         for (size_t j = 0; j < mimeClassInfo.extensions.size(); ++j) {
171             if (equalIgnoringCase(extension, mimeClassInfo.extensions[j]))
172                 return mimeClassInfo.type;
173         }
174     }
175
176     return String();
177 }
178
179 static void logPluginRequest(Page* page, const String& mimeType, const String& url, bool success)
180 {
181     if (!page || !page->settings().diagnosticLoggingEnabled())
182         return;
183
184     String newMIMEType = mimeType;
185     if (!newMIMEType) {
186         // Try to figure out the MIME type from the URL extension.
187         newMIMEType = findPluginMIMETypeFromURL(page, url);
188         if (!newMIMEType)
189             return;
190     }
191
192     String pluginFile = page->pluginData().pluginFileForMimeType(newMIMEType);
193     String description = !pluginFile ? newMIMEType : pluginFile;
194
195     ChromeClient& chromeClient = page->chrome().client();
196     chromeClient.logDiagnosticMessage(success ? DiagnosticLoggingKeys::pluginLoadedKey() : DiagnosticLoggingKeys::pluginLoadingFailedKey(), description, DiagnosticLoggingKeys::noopKey());
197
198     if (!page->hasSeenAnyPlugin())
199         chromeClient.logDiagnosticMessage(DiagnosticLoggingKeys::pageContainsAtLeastOnePluginKey(), emptyString(), DiagnosticLoggingKeys::noopKey());
200     
201     if (!page->hasSeenPlugin(description))
202         chromeClient.logDiagnosticMessage(DiagnosticLoggingKeys::pageContainsPluginKey(), description, DiagnosticLoggingKeys::noopKey());
203
204     page->sawPlugin(description);
205 }
206
207 bool SubframeLoader::requestObject(HTMLPlugInImageElement& ownerElement, const String& url, const AtomicString& frameName, const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues)
208 {
209     if (url.isEmpty() && mimeType.isEmpty())
210         return false;
211
212     URL completedURL;
213     if (!url.isEmpty())
214         completedURL = completeURL(url);
215
216     bool hasFallbackContent = is<HTMLObjectElement>(ownerElement) && downcast<HTMLObjectElement>(ownerElement).hasFallbackContent();
217
218     bool useFallback;
219     if (shouldUsePlugin(completedURL, mimeType, ownerElement.shouldPreferPlugInsForImages(), hasFallbackContent, useFallback)) {
220         bool success = requestPlugin(ownerElement, completedURL, mimeType, paramNames, paramValues, useFallback);
221         logPluginRequest(document()->page(), mimeType, completedURL, success);
222         return success;
223     }
224
225     // If the plug-in element already contains a subframe, loadOrRedirectSubframe will re-use it. Otherwise,
226     // it will create a new frame and set it as the RenderWidget's Widget, causing what was previously 
227     // in the widget to be torn down.
228     return loadOrRedirectSubframe(ownerElement, completedURL, frameName, LockHistory::Yes, LockBackForwardList::Yes);
229 }
230
231 PassRefPtr<Widget> SubframeLoader::createJavaAppletWidget(const IntSize& size, HTMLAppletElement& element, const Vector<String>& paramNames, const Vector<String>& paramValues)
232 {
233     String baseURLString;
234     String codeBaseURLString;
235
236     for (size_t i = 0; i < paramNames.size(); ++i) {
237         if (equalIgnoringCase(paramNames[i], "baseurl"))
238             baseURLString = paramValues[i];
239         else if (equalIgnoringCase(paramNames[i], "codebase"))
240             codeBaseURLString = paramValues[i];
241     }
242
243     if (!codeBaseURLString.isEmpty()) {
244         URL codeBaseURL = completeURL(codeBaseURLString);
245         if (!element.document().securityOrigin()->canDisplay(codeBaseURL)) {
246             FrameLoader::reportLocalLoadFailed(&m_frame, codeBaseURL.string());
247             return nullptr;
248         }
249
250         const char javaAppletMimeType[] = "application/x-java-applet";
251         if (!element.document().contentSecurityPolicy()->allowObjectFromSource(codeBaseURL)
252             || !element.document().contentSecurityPolicy()->allowPluginType(javaAppletMimeType, javaAppletMimeType, codeBaseURL))
253             return nullptr;
254     }
255
256     if (baseURLString.isEmpty())
257         baseURLString = m_frame.document()->baseURL().string();
258     URL baseURL = completeURL(baseURLString);
259
260     RefPtr<Widget> widget;
261     if (allowPlugins(AboutToInstantiatePlugin))
262         widget = m_frame.loader().client().createJavaAppletWidget(size, &element, baseURL, paramNames, paramValues);
263
264     logPluginRequest(document()->page(), element.serviceType(), String(), widget);
265
266     if (!widget) {
267         RenderEmbeddedObject* renderer = element.renderEmbeddedObject();
268
269         if (!renderer->isPluginUnavailable())
270             renderer->setPluginUnavailabilityReason(RenderEmbeddedObject::PluginMissing);
271         return nullptr;
272     }
273
274     m_containsPlugins = true;
275     return widget;
276 }
277
278 Frame* SubframeLoader::loadOrRedirectSubframe(HTMLFrameOwnerElement& ownerElement, const URL& url, const AtomicString& frameName, LockHistory lockHistory, LockBackForwardList lockBackForwardList)
279 {
280     Frame* frame = ownerElement.contentFrame();
281     if (frame)
282         frame->navigationScheduler().scheduleLocationChange(m_frame.document()->securityOrigin(), url, m_frame.loader().outgoingReferrer(), lockHistory, lockBackForwardList);
283     else
284         frame = loadSubframe(ownerElement, url, frameName, m_frame.loader().outgoingReferrer());
285
286     if (!frame)
287         return nullptr;
288
289     ASSERT(ownerElement.contentFrame() == frame || !ownerElement.contentFrame());
290     return ownerElement.contentFrame();
291 }
292
293 Frame* SubframeLoader::loadSubframe(HTMLFrameOwnerElement& ownerElement, const URL& url, const String& name, const String& referrer)
294 {
295     Ref<Frame> protect(m_frame);
296
297     bool allowsScrolling = true;
298     int marginWidth = -1;
299     int marginHeight = -1;
300     if (is<HTMLFrameElementBase>(ownerElement)) {
301         HTMLFrameElementBase& frameElementBase = downcast<HTMLFrameElementBase>(ownerElement);
302         allowsScrolling = frameElementBase.scrollingMode() != ScrollbarAlwaysOff;
303         marginWidth = frameElementBase.marginWidth();
304         marginHeight = frameElementBase.marginHeight();
305     }
306
307     if (!ownerElement.document().securityOrigin()->canDisplay(url)) {
308         FrameLoader::reportLocalLoadFailed(&m_frame, url.string());
309         return nullptr;
310     }
311
312     if (!SubframeLoadingDisabler::canLoadFrame(ownerElement))
313         return nullptr;
314
315     String referrerToUse = SecurityPolicy::generateReferrerHeader(ownerElement.document().referrerPolicy(), url, referrer);
316     RefPtr<Frame> frame = m_frame.loader().client().createFrame(url, name, &ownerElement, referrerToUse, allowsScrolling, marginWidth, marginHeight);
317
318     if (!frame)  {
319         m_frame.loader().checkCallImplicitClose();
320         return nullptr;
321     }
322     
323     // All new frames will have m_isComplete set to true at this point due to synchronously loading
324     // an empty document in FrameLoader::init(). But many frames will now be starting an
325     // asynchronous load of url, so we set m_isComplete to false and then check if the load is
326     // actually completed below. (Note that we set m_isComplete to false even for synchronous
327     // loads, so that checkCompleted() below won't bail early.)
328     // FIXME: Can we remove this entirely? m_isComplete normally gets set to false when a load is committed.
329     frame->loader().started();
330    
331     RenderObject* renderer = ownerElement.renderer();
332     FrameView* view = frame->view();
333     if (renderer && renderer->isWidget() && view)
334         toRenderWidget(renderer)->setWidget(view);
335     
336     m_frame.loader().checkCallImplicitClose();
337     
338     // Some loads are performed synchronously (e.g., about:blank and loads
339     // cancelled by returning a null ResourceRequest from requestFromDelegate).
340     // In these cases, the synchronous load would have finished
341     // before we could connect the signals, so make sure to send the 
342     // completed() signal for the child by hand and mark the load as being
343     // complete.
344     // FIXME: In this case the Frame will have finished loading before 
345     // it's being added to the child list. It would be a good idea to
346     // create the child first, then invoke the loader separately.
347     if (frame->loader().state() == FrameStateComplete && !frame->loader().policyDocumentLoader())
348         frame->loader().checkCompleted();
349
350     return frame.get();
351 }
352
353 bool SubframeLoader::allowPlugins(ReasonForCallingAllowPlugins)
354 {
355     return m_frame.settings().arePluginsEnabled();
356 }
357
358 bool SubframeLoader::shouldUsePlugin(const URL& url, const String& mimeType, bool shouldPreferPlugInsForImages, bool hasFallback, bool& useFallback)
359 {
360     if (m_frame.loader().client().shouldAlwaysUsePluginDocument(mimeType)) {
361         useFallback = false;
362         return true;
363     }
364
365     // Allow other plug-ins to win over QuickTime because if the user has installed a plug-in that
366     // can handle TIFF (which QuickTime can also handle) they probably intended to override QT.
367     if (m_frame.page() && (mimeType == "image/tiff" || mimeType == "image/tif" || mimeType == "image/x-tiff")) {
368         String pluginName = m_frame.page()->pluginData().pluginNameForMimeType(mimeType);
369         if (!pluginName.isEmpty() && !pluginName.contains("QuickTime", false)) 
370             return true;
371     }
372         
373     ObjectContentType objectType = m_frame.loader().client().objectContentType(url, mimeType, shouldPreferPlugInsForImages);
374     // If an object's content can't be handled and it has no fallback, let
375     // it be handled as a plugin to show the broken plugin icon.
376     useFallback = objectType == ObjectContentNone && hasFallback;
377     return objectType == ObjectContentNone || objectType == ObjectContentNetscapePlugin || objectType == ObjectContentOtherPlugin;
378 }
379
380 Document* SubframeLoader::document() const
381 {
382     return m_frame.document();
383 }
384
385 bool SubframeLoader::loadPlugin(HTMLPlugInImageElement& pluginElement, const URL& url, const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues, bool useFallback)
386 {
387     RenderEmbeddedObject* renderer = pluginElement.renderEmbeddedObject();
388
389     // FIXME: This code should not depend on renderer!
390     if (!renderer || useFallback)
391         return false;
392
393     pluginElement.subframeLoaderWillCreatePlugIn(url);
394
395     IntSize contentSize = roundedIntSize(LayoutSize(renderer->contentWidth(), renderer->contentHeight()));
396     bool loadManually = is<PluginDocument>(*document()) && !m_containsPlugins && downcast<PluginDocument>(*document()).shouldLoadPluginManually();
397
398 #if PLATFORM(IOS)
399     // On iOS, we only tell the plugin to be in full page mode if the containing plugin document is the top level document.
400     if (document()->ownerElement())
401         loadManually = false;
402 #endif
403
404     RefPtr<Widget> widget = m_frame.loader().client().createPlugin(contentSize, &pluginElement, url, paramNames, paramValues, mimeType, loadManually);
405
406     if (!widget) {
407         if (!renderer->isPluginUnavailable())
408             renderer->setPluginUnavailabilityReason(RenderEmbeddedObject::PluginMissing);
409         return false;
410     }
411
412     pluginElement.subframeLoaderDidCreatePlugIn(*widget);
413     renderer->setWidget(widget);
414     m_containsPlugins = true;
415     return true;
416 }
417
418 URL SubframeLoader::completeURL(const String& url) const
419 {
420     ASSERT(m_frame.document());
421     return m_frame.document()->completeURL(url);
422 }
423
424 } // namespace WebCore