SubframeLoader.cpp [plain text]
#include "config.h"
#include "SubframeLoader.h"
#include "ContentSecurityPolicy.h"
#include "DiagnosticLoggingClient.h"
#include "DiagnosticLoggingKeys.h"
#include "DocumentLoader.h"
#include "Frame.h"
#include "FrameLoader.h"
#include "FrameLoaderClient.h"
#include "HTMLAppletElement.h"
#include "HTMLFrameElement.h"
#include "HTMLIFrameElement.h"
#include "HTMLNames.h"
#include "HTMLObjectElement.h"
#include "MIMETypeRegistry.h"
#include "NavigationScheduler.h"
#include "Page.h"
#include "PluginData.h"
#include "PluginDocument.h"
#include "RenderEmbeddedObject.h"
#include "RenderView.h"
#include "ScriptController.h"
#include "SecurityOrigin.h"
#include "SecurityPolicy.h"
#include "Settings.h"
#include <wtf/CompletionHandler.h>
namespace WebCore {
using namespace HTMLNames;
FrameLoader::SubframeLoader::SubframeLoader(Frame& frame)
: m_frame(frame)
{
}
void FrameLoader::SubframeLoader::clear()
{
m_containsPlugins = false;
}
bool FrameLoader::SubframeLoader::requestFrame(HTMLFrameOwnerElement& ownerElement, const String& urlString, const AtomString& frameName, LockHistory lockHistory, LockBackForwardList lockBackForwardList)
{
URL scriptURL;
URL url;
if (WTF::protocolIsJavaScript(urlString)) {
scriptURL = completeURL(urlString); url = aboutBlankURL();
} else
url = completeURL(urlString);
if (shouldConvertInvalidURLsToBlank() && !url.isValid())
url = aboutBlankURL();
CompletionHandlerCallingScope stopDelayingLoadEvent;
if (!scriptURL.isEmpty()) {
ownerElement.document().incrementLoadEventDelayCount();
stopDelayingLoadEvent = CompletionHandlerCallingScope([ownerDocument = makeRef(ownerElement.document())] {
ownerDocument->decrementLoadEventDelayCount();
});
}
Frame* frame = loadOrRedirectSubframe(ownerElement, url, frameName, lockHistory, lockBackForwardList);
if (!frame)
return false;
if (!scriptURL.isEmpty() && ownerElement.canLoadScriptURL(scriptURL)) {
if (urlString == "javascript:''" || urlString == "javascript:\"\"")
frame->script().executeJavaScriptURL(scriptURL);
else
frame->navigationScheduler().scheduleLocationChange(ownerElement.document(), ownerElement.document().securityOrigin(), scriptURL, m_frame.loader().outgoingReferrer(), lockHistory, lockBackForwardList, stopDelayingLoadEvent.release());
}
return true;
}
bool FrameLoader::SubframeLoader::resourceWillUsePlugin(const String& url, const String& mimeType)
{
URL completedURL;
if (!url.isEmpty())
completedURL = completeURL(url);
bool useFallback;
return shouldUsePlugin(completedURL, mimeType, false, useFallback);
}
bool FrameLoader::SubframeLoader::pluginIsLoadable(const URL& url, const String& mimeType)
{
auto* document = m_frame.document();
if (MIMETypeRegistry::isJavaAppletMIMEType(mimeType)) {
if (!m_frame.settings().isJavaEnabled())
return false;
if (document && document->securityOrigin().isLocal() && !m_frame.settings().isJavaEnabledForLocalFiles())
return false;
}
if (document) {
if (document->isSandboxed(SandboxPlugins))
return false;
if (!document->securityOrigin().canDisplay(url)) {
FrameLoader::reportLocalLoadFailed(&m_frame, url.string());
return false;
}
if (!m_frame.loader().mixedContentChecker().canRunInsecureContent(document->securityOrigin(), url))
return false;
}
return true;
}
static String findPluginMIMETypeFromURL(Page& page, const URL& url)
{
auto lastPathComponent = url.lastPathComponent();
size_t dotIndex = lastPathComponent.reverseFind('.');
if (dotIndex == notFound)
return { };
auto extensionFromURL = lastPathComponent.substring(dotIndex + 1);
for (auto& type : page.pluginData().webVisibleMimeTypes()) {
for (auto& extension : type.extensions) {
if (equalIgnoringASCIICase(extensionFromURL, extension))
return type.type;
}
}
return { };
}
bool FrameLoader::SubframeLoader::requestPlugin(HTMLPlugInImageElement& ownerElement, const URL& url, const String& explicitMIMEType, const Vector<String>& paramNames, const Vector<String>& paramValues, bool useFallback)
{
String mimeType = explicitMIMEType;
if (mimeType.isEmpty()) {
if (auto page = ownerElement.document().page())
mimeType = findPluginMIMETypeFromURL(*page, url);
}
if (!(m_frame.settings().arePluginsEnabled() || MIMETypeRegistry::isApplicationPluginMIMEType(mimeType)))
return false;
if (!pluginIsLoadable(url, explicitMIMEType))
return false;
ASSERT(ownerElement.hasTagName(objectTag) || ownerElement.hasTagName(embedTag));
return loadPlugin(ownerElement, url, explicitMIMEType, paramNames, paramValues, useFallback);
}
static void logPluginRequest(Page* page, const String& mimeType, const URL& url, bool success)
{
if (!page)
return;
String newMIMEType = mimeType;
if (!newMIMEType) {
newMIMEType = findPluginMIMETypeFromURL(*page, url);
if (!newMIMEType)
return;
}
String pluginFile = page->pluginData().pluginFileForWebVisibleMimeType(newMIMEType);
String description = !pluginFile ? newMIMEType : pluginFile;
DiagnosticLoggingClient& diagnosticLoggingClient = page->diagnosticLoggingClient();
diagnosticLoggingClient.logDiagnosticMessage(success ? DiagnosticLoggingKeys::pluginLoadedKey() : DiagnosticLoggingKeys::pluginLoadingFailedKey(), description, ShouldSample::No);
if (!page->hasSeenAnyPlugin())
diagnosticLoggingClient.logDiagnosticMessage(DiagnosticLoggingKeys::pageContainsAtLeastOnePluginKey(), emptyString(), ShouldSample::No);
if (!page->hasSeenPlugin(description))
diagnosticLoggingClient.logDiagnosticMessage(DiagnosticLoggingKeys::pageContainsPluginKey(), description, ShouldSample::No);
page->sawPlugin(description);
}
bool FrameLoader::SubframeLoader::requestObject(HTMLPlugInImageElement& ownerElement, const String& url, const AtomString& frameName, const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues)
{
if (url.isEmpty() && mimeType.isEmpty())
return false;
auto& document = ownerElement.document();
URL completedURL;
if (!url.isEmpty())
completedURL = completeURL(url);
document.contentSecurityPolicy()->upgradeInsecureRequestIfNeeded(completedURL, ContentSecurityPolicy::InsecureRequestType::Load);
bool hasFallbackContent = is<HTMLObjectElement>(ownerElement) && downcast<HTMLObjectElement>(ownerElement).hasFallbackContent();
bool useFallback;
if (shouldUsePlugin(completedURL, mimeType, hasFallbackContent, useFallback)) {
bool success = requestPlugin(ownerElement, completedURL, mimeType, paramNames, paramValues, useFallback);
logPluginRequest(document.page(), mimeType, completedURL, success);
return success;
}
return loadOrRedirectSubframe(ownerElement, completedURL, frameName, LockHistory::Yes, LockBackForwardList::Yes);
}
RefPtr<Widget> FrameLoader::SubframeLoader::createJavaAppletWidget(const IntSize& size, HTMLAppletElement& element, const Vector<String>& paramNames, const Vector<String>& paramValues)
{
String baseURLString;
String codeBaseURLString;
for (size_t i = 0; i < paramNames.size(); ++i) {
if (equalLettersIgnoringASCIICase(paramNames[i], "baseurl"))
baseURLString = paramValues[i];
else if (equalLettersIgnoringASCIICase(paramNames[i], "codebase"))
codeBaseURLString = paramValues[i];
}
if (!codeBaseURLString.isEmpty()) {
URL codeBaseURL = completeURL(codeBaseURLString);
if (!element.document().securityOrigin().canDisplay(codeBaseURL)) {
FrameLoader::reportLocalLoadFailed(&m_frame, codeBaseURL.string());
return nullptr;
}
const char javaAppletMimeType[] = "application/x-java-applet";
ASSERT(element.document().contentSecurityPolicy());
auto& contentSecurityPolicy = *element.document().contentSecurityPolicy();
if (!element.isInUserAgentShadowTree()
&& (!contentSecurityPolicy.allowObjectFromSource(codeBaseURL) || !contentSecurityPolicy.allowPluginType(javaAppletMimeType, javaAppletMimeType, codeBaseURL)))
return nullptr;
}
if (baseURLString.isEmpty())
baseURLString = element.document().baseURL().string();
URL baseURL = completeURL(baseURLString);
RefPtr<Widget> widget;
if (m_frame.settings().arePluginsEnabled())
widget = m_frame.loader().client().createJavaAppletWidget(size, element, baseURL, paramNames, paramValues);
logPluginRequest(m_frame.page(), element.serviceType(), { }, widget);
if (!widget) {
RenderEmbeddedObject* renderer = element.renderEmbeddedObject();
if (!renderer->isPluginUnavailable())
renderer->setPluginUnavailabilityReason(RenderEmbeddedObject::PluginMissing);
return nullptr;
}
m_containsPlugins = true;
return widget;
}
Frame* FrameLoader::SubframeLoader::loadOrRedirectSubframe(HTMLFrameOwnerElement& ownerElement, const URL& requestURL, const AtomString& frameName, LockHistory lockHistory, LockBackForwardList lockBackForwardList)
{
auto& initiatingDocument = ownerElement.document();
URL upgradedRequestURL = requestURL;
initiatingDocument.contentSecurityPolicy()->upgradeInsecureRequestIfNeeded(upgradedRequestURL, ContentSecurityPolicy::InsecureRequestType::Load);
RefPtr<Frame> frame = makeRefPtr(ownerElement.contentFrame());
if (frame)
frame->navigationScheduler().scheduleLocationChange(initiatingDocument, initiatingDocument.securityOrigin(), upgradedRequestURL, m_frame.loader().outgoingReferrer(), lockHistory, lockBackForwardList);
else
frame = loadSubframe(ownerElement, upgradedRequestURL, frameName, m_frame.loader().outgoingReferrer());
if (!frame)
return nullptr;
ASSERT(ownerElement.contentFrame() == frame || !ownerElement.contentFrame());
return ownerElement.contentFrame();
}
RefPtr<Frame> FrameLoader::SubframeLoader::loadSubframe(HTMLFrameOwnerElement& ownerElement, const URL& url, const String& name, const String& referrer)
{
Ref<Frame> protect(m_frame);
auto document = makeRef(ownerElement.document());
if (!document->securityOrigin().canDisplay(url)) {
FrameLoader::reportLocalLoadFailed(&m_frame, url.string());
return nullptr;
}
if (!SubframeLoadingDisabler::canLoadFrame(ownerElement))
return nullptr;
if (!m_frame.page() || m_frame.page()->subframeCount() >= Page::maxNumberOfFrames)
return nullptr;
document->incrementLoadEventDelayCount();
auto frame = m_frame.loader().client().createFrame(name, ownerElement);
if (!frame) {
m_frame.loader().checkCallImplicitClose();
document->decrementLoadEventDelayCount();
return nullptr;
}
ReferrerPolicy policy = ownerElement.referrerPolicy();
if (policy == ReferrerPolicy::EmptyString)
policy = document->referrerPolicy();
String referrerToUse = SecurityPolicy::generateReferrerHeader(policy, url, referrer);
m_frame.loader().loadURLIntoChildFrame(url, referrerToUse, frame.get());
document->decrementLoadEventDelayCount();
if (!frame || !frame->tree().parent()) {
m_frame.loader().checkCallImplicitClose();
return nullptr;
}
frame->loader().started();
auto* renderer = ownerElement.renderer();
auto* view = frame->view();
if (is<RenderWidget>(renderer) && view)
downcast<RenderWidget>(*renderer).setWidget(view);
m_frame.loader().checkCallImplicitClose();
if (frame->loader().state() == FrameStateComplete && !frame->loader().policyDocumentLoader())
frame->loader().checkCompleted();
if (!frame->tree().parent())
return nullptr;
return frame;
}
bool FrameLoader::SubframeLoader::shouldUsePlugin(const URL& url, const String& mimeType, bool hasFallback, bool& useFallback)
{
if (m_frame.loader().client().shouldAlwaysUsePluginDocument(mimeType)) {
useFallback = false;
return true;
}
ObjectContentType objectType = m_frame.loader().client().objectContentType(url, mimeType);
useFallback = objectType == ObjectContentType::None && hasFallback;
return objectType == ObjectContentType::None || objectType == ObjectContentType::PlugIn;
}
bool FrameLoader::SubframeLoader::loadPlugin(HTMLPlugInImageElement& pluginElement, const URL& url, const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues, bool useFallback)
{
if (useFallback)
return false;
auto& document = pluginElement.document();
auto* renderer = pluginElement.renderEmbeddedObject();
if (!renderer)
return false;
pluginElement.subframeLoaderWillCreatePlugIn(url);
IntSize contentSize = roundedIntSize(LayoutSize(renderer->contentWidth(), renderer->contentHeight()));
bool loadManually = is<PluginDocument>(document) && !m_containsPlugins && downcast<PluginDocument>(document).shouldLoadPluginManually();
#if PLATFORM(IOS_FAMILY)
if (document.ownerElement())
loadManually = false;
#endif
auto weakRenderer = makeWeakPtr(*renderer);
auto widget = m_frame.loader().client().createPlugin(contentSize, pluginElement, url, paramNames, paramValues, mimeType, loadManually);
if (!weakRenderer)
return false;
if (!widget) {
if (!renderer->isPluginUnavailable())
renderer->setPluginUnavailabilityReason(RenderEmbeddedObject::PluginMissing);
return false;
}
pluginElement.subframeLoaderDidCreatePlugIn(*widget);
renderer->setWidget(WTFMove(widget));
m_containsPlugins = true;
return true;
}
URL FrameLoader::SubframeLoader::completeURL(const String& url) const
{
ASSERT(m_frame.document());
return m_frame.document()->completeURL(url);
}
bool FrameLoader::SubframeLoader::shouldConvertInvalidURLsToBlank() const
{
return m_frame.settings().shouldConvertInvalidURLsToBlank();
}
}