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
61namespace WebCore {
62
63using namespace HTMLNames;
64
65SubframeLoader::SubframeLoader(Frame& frame)
66 : m_containsPlugins(false)
67 , m_frame(frame)
68{
69}
70
71void SubframeLoader::clear()
72{
73 m_containsPlugins = false;
74}
75
76bool SubframeLoader::requestFrame(HTMLFrameOwnerElement& ownerElement, const String& urlString, const AtomicString& 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.isURLAllowed(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
115bool 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
125bool 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
152bool 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
167static 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
193static 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
221bool SubframeLoader::requestObject(HTMLPlugInImageElement& ownerElement, const String& url, const AtomicString& 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
249RefPtr<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
299Frame* SubframeLoader::loadOrRedirectSubframe(HTMLFrameOwnerElement& ownerElement, const URL& requestURL, const AtomicString& 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
319Frame* 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 ReferrerPolicy policy = ownerElement.referrerPolicy();
333 if (policy == ReferrerPolicy::EmptyString)
334 policy = document->referrerPolicy();
335 String referrerToUse = SecurityPolicy::generateReferrerHeader(policy, url, referrer);
336
337 // Prevent initial empty document load from triggering load events.
338 document->incrementLoadEventDelayCount();
339
340 auto frame = m_frame.loader().client().createFrame(url, name, ownerElement, referrerToUse);
341
342 document->decrementLoadEventDelayCount();
343
344 if (!frame) {
345 m_frame.loader().checkCallImplicitClose();
346 return nullptr;
347 }
348
349 // All new frames will have m_isComplete set to true at this point due to synchronously loading
350 // an empty document in FrameLoader::init(). But many frames will now be starting an
351 // asynchronous load of url, so we set m_isComplete to false and then check if the load is
352 // actually completed below. (Note that we set m_isComplete to false even for synchronous
353 // loads, so that checkCompleted() below won't bail early.)
354 // FIXME: Can we remove this entirely? m_isComplete normally gets set to false when a load is committed.
355 frame->loader().started();
356
357 auto* renderer = ownerElement.renderer();
358 auto* view = frame->view();
359 if (is<RenderWidget>(renderer) && view)
360 downcast<RenderWidget>(*renderer).setWidget(view);
361
362 m_frame.loader().checkCallImplicitClose();
363
364 // Some loads are performed synchronously (e.g., about:blank and loads
365 // cancelled by returning a null ResourceRequest from requestFromDelegate).
366 // In these cases, the synchronous load would have finished
367 // before we could connect the signals, so make sure to send the
368 // completed() signal for the child by hand and mark the load as being
369 // complete.
370 // FIXME: In this case the Frame will have finished loading before
371 // it's being added to the child list. It would be a good idea to
372 // create the child first, then invoke the loader separately.
373 if (frame->loader().state() == FrameStateComplete && !frame->loader().policyDocumentLoader())
374 frame->loader().checkCompleted();
375
376 return frame.get();
377}
378
379bool SubframeLoader::allowPlugins()
380{
381 return m_frame.settings().arePluginsEnabled();
382}
383
384bool SubframeLoader::shouldUsePlugin(const URL& url, const String& mimeType, bool hasFallback, bool& useFallback)
385{
386 if (m_frame.loader().client().shouldAlwaysUsePluginDocument(mimeType)) {
387 useFallback = false;
388 return true;
389 }
390
391 ObjectContentType objectType = m_frame.loader().client().objectContentType(url, mimeType);
392 // If an object's content can't be handled and it has no fallback, let
393 // it be handled as a plugin to show the broken plugin icon.
394 useFallback = objectType == ObjectContentType::None && hasFallback;
395
396 return objectType == ObjectContentType::None || objectType == ObjectContentType::PlugIn;
397}
398
399bool SubframeLoader::loadPlugin(HTMLPlugInImageElement& pluginElement, const URL& url, const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues, bool useFallback)
400{
401 if (useFallback)
402 return false;
403
404 auto& document = pluginElement.document();
405 auto* renderer = pluginElement.renderEmbeddedObject();
406
407 // FIXME: This code should not depend on renderer!
408 if (!renderer)
409 return false;
410
411 pluginElement.subframeLoaderWillCreatePlugIn(url);
412
413 IntSize contentSize = roundedIntSize(LayoutSize(renderer->contentWidth(), renderer->contentHeight()));
414 bool loadManually = is<PluginDocument>(document) && !m_containsPlugins && downcast<PluginDocument>(document).shouldLoadPluginManually();
415
416#if PLATFORM(IOS_FAMILY)
417 // On iOS, we only tell the plugin to be in full page mode if the containing plugin document is the top level document.
418 if (document.ownerElement())
419 loadManually = false;
420#endif
421
422 auto weakRenderer = makeWeakPtr(*renderer);
423
424 auto widget = m_frame.loader().client().createPlugin(contentSize, pluginElement, url, paramNames, paramValues, mimeType, loadManually);
425
426 // The call to createPlugin *may* cause this renderer to disappear from underneath.
427 if (!weakRenderer)
428 return false;
429
430 if (!widget) {
431 if (!renderer->isPluginUnavailable())
432 renderer->setPluginUnavailabilityReason(RenderEmbeddedObject::PluginMissing);
433 return false;
434 }
435
436 pluginElement.subframeLoaderDidCreatePlugIn(*widget);
437 renderer->setWidget(WTFMove(widget));
438 m_containsPlugins = true;
439 return true;
440}
441
442URL SubframeLoader::completeURL(const String& url) const
443{
444 ASSERT(m_frame.document());
445 return m_frame.document()->completeURL(url);
446}
447
448bool SubframeLoader::shouldConvertInvalidURLsToBlank() const
449{
450 return m_frame.settings().shouldConvertInvalidURLsToBlank();
451}
452
453} // namespace WebCore
454