/* * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. * Copyright (C) 2008 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "MainResourceLoader.h" #include "ApplicationCacheHost.h" #include "BackForwardController.h" #include "Console.h" #include "DOMWindow.h" #include "Document.h" #include "DocumentLoadTiming.h" #include "DocumentLoader.h" #include "FormState.h" #include "Frame.h" #include "FrameLoader.h" #include "FrameLoaderClient.h" #include "HTMLFormElement.h" #include "HistoryItem.h" #include "InspectorInstrumentation.h" #include "LoaderStrategy.h" #include "Page.h" #include "PlatformStrategies.h" #include "ResourceError.h" #include "ResourceHandle.h" #include "ResourceLoadScheduler.h" #include "SchemeRegistry.h" #include "SecurityOrigin.h" #include "Settings.h" #include #if PLATFORM(QT) #include "PluginDatabase.h" #endif #if PLATFORM(MAC) && !PLATFORM(IOS) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 #include "WebCoreSystemInterface.h" #endif // FIXME: More that is in common with SubresourceLoader should move up into ResourceLoader. namespace WebCore { MainResourceLoader::MainResourceLoader(Frame* frame) : ResourceLoader(frame, ResourceLoaderOptions(SendCallbacks, SniffContent, BufferData, AllowStoredCredentials, AskClientForCrossOriginCredentials, SkipSecurityCheck)) , m_dataLoadTimer(this, &MainResourceLoader::handleSubstituteDataLoadNow) , m_loadingMultipartContent(false) , m_waitingForContentPolicy(false) , m_timeOfLastDataReceived(0.0) #if PLATFORM(MAC) && !PLATFORM(IOS) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 , m_filter(0) #endif { } MainResourceLoader::~MainResourceLoader() { #if PLATFORM(MAC) && !PLATFORM(IOS) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 ASSERT(!m_filter); #endif } PassRefPtr MainResourceLoader::create(Frame* frame) { return adoptRef(new MainResourceLoader(frame)); } void MainResourceLoader::receivedError(const ResourceError& error) { // Calling receivedMainResourceError will likely result in the last reference to this object to go away. RefPtr protect(this); RefPtr protectFrame(m_frame); // It is important that we call DocumentLoader::mainReceivedError before calling // ResourceLoadNotifier::didFailToLoad because mainReceivedError clears out the relevant // document loaders. Also, mainReceivedError ends up calling a FrameLoadDelegate method // and didFailToLoad calls a ResourceLoadDelegate method and they need to be in the correct order. documentLoader()->mainReceivedError(error); if (!cancelled()) { ASSERT(!reachedTerminalState()); frameLoader()->notifier()->didFailToLoad(this, error); releaseResources(); } ASSERT(reachedTerminalState()); } void MainResourceLoader::willCancel(const ResourceError&) { m_dataLoadTimer.stop(); if (m_waitingForContentPolicy) { frameLoader()->policyChecker()->cancelCheck(); ASSERT(m_waitingForContentPolicy); m_waitingForContentPolicy = false; deref(); // balances ref in didReceiveResponse } } void MainResourceLoader::didCancel(const ResourceError& error) { // We should notify the frame loader after fully canceling the load, because it can do complicated work // like calling DOMWindow::print(), during which a half-canceled load could try to finish. documentLoader()->mainReceivedError(error); #if PLATFORM(MAC) && !PLATFORM(IOS) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 if (m_filter) { wkFilterRelease(m_filter); m_filter = 0; } #endif } ResourceError MainResourceLoader::interruptedForPolicyChangeError() const { return frameLoader()->client()->interruptedForPolicyChangeError(request()); } void MainResourceLoader::stopLoadingForPolicyChange() { ResourceError error = interruptedForPolicyChangeError(); error.setIsCancellation(true); cancel(error); } void MainResourceLoader::callContinueAfterNavigationPolicy(void* argument, const ResourceRequest& request, PassRefPtr, bool shouldContinue) { static_cast(argument)->continueAfterNavigationPolicy(request, shouldContinue); } void MainResourceLoader::continueAfterNavigationPolicy(const ResourceRequest& request, bool shouldContinue) { if (!shouldContinue) stopLoadingForPolicyChange(); else if (m_substituteData.isValid()) { // A redirect resulted in loading substitute data. ASSERT(documentLoader()->timing()->redirectCount()); handle()->cancel(); handleSubstituteDataLoadSoon(request); } deref(); // balances ref in willSendRequest } bool MainResourceLoader::isPostOrRedirectAfterPost(const ResourceRequest& newRequest, const ResourceResponse& redirectResponse) { if (newRequest.httpMethod() == "POST") return true; int status = redirectResponse.httpStatusCode(); if (((status >= 301 && status <= 303) || status == 307) && frameLoader()->initialRequest().httpMethod() == "POST") return true; return false; } void MainResourceLoader::addData(const char* data, int length, bool allAtOnce) { ResourceLoader::addData(data, length, allAtOnce); documentLoader()->receivedData(data, length); } void MainResourceLoader::willSendRequest(ResourceRequest& newRequest, const ResourceResponse& redirectResponse) { // Note that there are no asserts here as there are for the other callbacks. This is due to the // fact that this "callback" is sent when starting every load, and the state of callback // deferrals plays less of a part in this function in preventing the bad behavior deferring // callbacks is meant to prevent. ASSERT(!newRequest.isNull()); // The additional processing can do anything including possibly removing the last // reference to this object; one example of this is 3266216. RefPtr protect(this); if (!frameLoader()->checkIfFormActionAllowedByCSP(newRequest.url())) { cancel(); return; } ASSERT(documentLoader()->timing()->fetchStart()); if (!redirectResponse.isNull()) { // If the redirecting url is not allowed to display content from the target origin, // then block the redirect. RefPtr redirectingOrigin = SecurityOrigin::create(redirectResponse.url()); if (!redirectingOrigin->canDisplay(newRequest.url())) { FrameLoader::reportLocalLoadFailed(m_frame.get(), newRequest.url().string()); cancel(); return; } documentLoader()->timing()->addRedirect(redirectResponse.url(), newRequest.url()); } // Update cookie policy base URL as URL changes, except for subframes, which use the // URL of the main frame which doesn't change when we redirect. if (frameLoader()->isLoadingMainFrame()) newRequest.setFirstPartyForCookies(newRequest.url()); // If we're fielding a redirect in response to a POST, force a load from origin, since // this is a common site technique to return to a page viewing some data that the POST // just modified. // Also, POST requests always load from origin, but this does not affect subresources. if (newRequest.cachePolicy() == UseProtocolCachePolicy && isPostOrRedirectAfterPost(newRequest, redirectResponse)) newRequest.setCachePolicy(ReloadIgnoringCacheData); Frame* top = m_frame->tree()->top(); if (top != m_frame) { if (!frameLoader()->mixedContentChecker()->canDisplayInsecureContent(top->document()->securityOrigin(), newRequest.url())) { cancel(); return; } } ResourceLoader::willSendRequest(newRequest, redirectResponse); // Don't set this on the first request. It is set when the main load was started. m_documentLoader->setRequest(newRequest); if (!redirectResponse.isNull()) { // We checked application cache for initial URL, now we need to check it for redirected one. ASSERT(!m_substituteData.isValid()); documentLoader()->applicationCacheHost()->maybeLoadMainResourceForRedirect(newRequest, m_substituteData); } // FIXME: Ideally we'd stop the I/O until we hear back from the navigation policy delegate // listener. But there's no way to do that in practice. So instead we cancel later if the // listener tells us to. In practice that means the navigation policy needs to be decided // synchronously for these redirect cases. if (!redirectResponse.isNull()) { ref(); // balanced by deref in continueAfterNavigationPolicy frameLoader()->policyChecker()->checkNavigationPolicy(newRequest, callContinueAfterNavigationPolicy, this); } } void MainResourceLoader::continueAfterContentPolicy(PolicyAction contentPolicy, const ResourceResponse& r) { KURL url = request().url(); const String& mimeType = r.mimeType(); switch (contentPolicy) { case PolicyUse: { // Prevent remote web archives from loading because they can claim to be from any domain and thus avoid cross-domain security checks (4120255). bool isRemoteWebArchive = (equalIgnoringCase("application/x-webarchive", mimeType) #if PLATFORM(GTK) || equalIgnoringCase("message/rfc822", mimeType) #endif || equalIgnoringCase("multipart/related", mimeType)) && !m_substituteData.isValid() && !SchemeRegistry::shouldTreatURLSchemeAsLocal(url.protocol()); if (!frameLoader()->client()->canShowMIMEType(mimeType) || isRemoteWebArchive) { frameLoader()->policyChecker()->cannotShowMIMEType(r); // Check reachedTerminalState since the load may have already been canceled inside of _handleUnimplementablePolicyWithErrorCode::. if (!reachedTerminalState()) stopLoadingForPolicyChange(); return; } break; } case PolicyDownload: { // m_handle can be null, e.g. when loading a substitute resource from application cache. if (!m_handle) { receivedError(cannotShowURLError()); return; } InspectorInstrumentation::continueWithPolicyDownload(m_frame.get(), documentLoader(), identifier(), r); // When starting the request, we didn't know that it would result in download and not navigation. Now we know that main document URL didn't change. // Download may use this knowledge for purposes unrelated to cookies, notably for setting file quarantine data. ResourceRequest request = this->request(); frameLoader()->setOriginalURLForDownloadRequest(request); frameLoader()->client()->download(m_handle.get(), request, r); // It might have gone missing if (frameLoader()) receivedError(interruptedForPolicyChangeError()); return; } case PolicyIgnore: InspectorInstrumentation::continueWithPolicyIgnore(m_frame.get(), documentLoader(), identifier(), r); stopLoadingForPolicyChange(); return; default: ASSERT_NOT_REACHED(); } RefPtr protect(this); if (r.isHTTP()) { int status = r.httpStatusCode(); if (status < 200 || status >= 300) { bool hostedByObject = frameLoader()->isHostedByObjectElement(); frameLoader()->handleFallbackContent(); // object elements are no longer rendered after we fallback, so don't // keep trying to process data from their load if (hostedByObject) cancel(); } } // we may have cancelled this load as part of switching to fallback content if (!reachedTerminalState()) ResourceLoader::didReceiveResponse(r); if (frameLoader() && !frameLoader()->activeDocumentLoader()->isStopping() && m_substituteData.isValid()) { if (m_substituteData.content()->size()) didReceiveData(m_substituteData.content()->data(), m_substituteData.content()->size(), m_substituteData.content()->size(), true); if (frameLoader() && !frameLoader()->activeDocumentLoader()->isStopping()) didFinishLoading(0); } } void MainResourceLoader::callContinueAfterContentPolicy(void* argument, PolicyAction policy) { static_cast(argument)->continueAfterContentPolicy(policy); } void MainResourceLoader::continueAfterContentPolicy(PolicyAction policy) { ASSERT(m_waitingForContentPolicy); m_waitingForContentPolicy = false; if (frameLoader() && !frameLoader()->activeDocumentLoader()->isStopping()) continueAfterContentPolicy(policy, m_response); deref(); // balances ref in didReceiveResponse } void MainResourceLoader::didReceiveResponse(const ResourceResponse& r) { if (documentLoader()->applicationCacheHost()->maybeLoadFallbackForMainResponse(request(), r)) return; DEFINE_STATIC_LOCAL(AtomicString, xFrameOptionHeader, ("x-frame-options", AtomicString::ConstructFromLiteral)); HTTPHeaderMap::const_iterator it = r.httpHeaderFields().find(xFrameOptionHeader); if (it != r.httpHeaderFields().end()) { String content = it->value; if (m_frame->loader()->shouldInterruptLoadForXFrameOptions(content, r.url(), identifier())) { InspectorInstrumentation::continueAfterXFrameOptionsDenied(m_frame.get(), documentLoader(), identifier(), r); String message = "Refused to display '" + r.url().string() + "' in a frame because it set 'X-Frame-Options' to '" + content + "'."; m_frame->document()->addConsoleMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, message, r.url().string(), 0, 0, identifier()); cancel(); return; } } // There is a bug in CFNetwork where callbacks can be dispatched even when loads are deferred. // See for more details. #if !USE(CF) ASSERT(!defersLoading()); #endif if (m_loadingMultipartContent) { m_documentLoader->setupForReplace(); clearResourceData(); } if (r.isMultipart()) m_loadingMultipartContent = true; // The additional processing can do anything including possibly removing the last // reference to this object; one example of this is 3266216. RefPtr protect(this); m_documentLoader->setResponse(r); m_response = r; ASSERT(!m_waitingForContentPolicy); m_waitingForContentPolicy = true; ref(); // balanced by deref in continueAfterContentPolicy and didCancel ASSERT(frameLoader()->activeDocumentLoader()); // Always show content with valid substitute data. if (frameLoader()->activeDocumentLoader()->substituteData().isValid()) { callContinueAfterContentPolicy(this, PolicyUse); return; } #if ENABLE(FTPDIR) // Respect the hidden FTP Directory Listing pref so it can be tested even if the policy delegate might otherwise disallow it Settings* settings = m_frame->settings(); if (settings && settings->forceFTPDirectoryListings() && m_response.mimeType() == "application/x-ftp-directory") { callContinueAfterContentPolicy(this, PolicyUse); return; } #endif #if PLATFORM(MAC) && !PLATFORM(IOS) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 if (r.url().protocolIs("https") && wkFilterIsManagedSession()) m_filter = wkFilterCreateInstance(r.nsURLResponse()); #endif frameLoader()->policyChecker()->checkContentPolicy(m_response, callContinueAfterContentPolicy, this); } void MainResourceLoader::didReceiveData(const char* data, int length, long long encodedDataLength, bool allAtOnce) { ASSERT(data); ASSERT(length != 0); ASSERT(!m_response.isNull()); #if USE(CFNETWORK) || PLATFORM(MAC) // Workaround for if (m_response.isNull()) { m_response = ResourceResponse(KURL(), "text/html", 0, String(), String()); if (DocumentLoader* documentLoader = frameLoader()->activeDocumentLoader()) documentLoader->setResponse(m_response); } #endif // There is a bug in CFNetwork where callbacks can be dispatched even when loads are deferred. // See for more details. #if !USE(CF) ASSERT(!defersLoading()); #endif #if PLATFORM(MAC) && !PLATFORM(IOS) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 if (m_filter) { ASSERT(!wkFilterWasBlocked(m_filter)); const char* blockedData = wkFilterAddData(m_filter, data, &length); // If we don't have blockedData, that means we're still accumulating data if (!blockedData) { // Transition to committed state. ResourceLoader::didReceiveData("", 0, 0, false); return; } data = blockedData; encodedDataLength = -1; } #endif documentLoader()->applicationCacheHost()->mainResourceDataReceived(data, length, encodedDataLength, allAtOnce); // The additional processing can do anything including possibly removing the last // reference to this object; one example of this is 3266216. RefPtr protect(this); m_timeOfLastDataReceived = monotonicallyIncreasingTime(); ResourceLoader::didReceiveData(data, length, encodedDataLength, allAtOnce); #if PLATFORM(MAC) && !PLATFORM(IOS) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 if (WebFilterEvaluator *filter = m_filter) { // If we got here, it means we know if we were blocked or not. If we were blocked, we're // done loading the page altogether. Either way, we don't need the filter anymore. // Remove this->m_filter early so didFinishLoading doesn't see it. m_filter = 0; if (wkFilterWasBlocked(filter)) cancel(); wkFilterRelease(filter); } #endif } void MainResourceLoader::didFinishLoading(double finishTime) { // There is a bug in CFNetwork where callbacks can be dispatched even when loads are deferred. // See for more details. #if !USE(CF) ASSERT(!defersLoading() || InspectorInstrumentation::isDebuggerPaused(m_frame.get())); #endif // The additional processing can do anything including possibly removing the last // reference to this object. RefPtr protect(this); RefPtr dl = documentLoader(); #if PLATFORM(MAC) && !PLATFORM(IOS) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 if (m_filter) { int length; const char* data = wkFilterDataComplete(m_filter, &length); WebFilterEvaluator *filter = m_filter; // Remove this->m_filter early so didReceiveData doesn't see it. m_filter = 0; if (data) didReceiveData(data, length, -1, false); wkFilterRelease(filter); } #endif if (m_loadingMultipartContent) dl->maybeFinishLoadingMultipartContent(); documentLoader()->timing()->setResponseEnd(finishTime ? finishTime : (m_timeOfLastDataReceived ? m_timeOfLastDataReceived : monotonicallyIncreasingTime())); documentLoader()->finishedLoading(); ResourceLoader::didFinishLoading(finishTime); dl->applicationCacheHost()->finishedLoadingMainResource(); } void MainResourceLoader::didFail(const ResourceError& error) { #if PLATFORM(MAC) && !PLATFORM(IOS) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 if (m_filter) { wkFilterRelease(m_filter); m_filter = 0; } #endif if (documentLoader()->applicationCacheHost()->maybeLoadFallbackForMainError(request(), error)) return; // There is a bug in CFNetwork where callbacks can be dispatched even when loads are deferred. // See for more details. #if !USE(CF) ASSERT(!defersLoading()); #endif receivedError(error); } void MainResourceLoader::reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const { MemoryClassInfo info(memoryObjectInfo, this, WebCoreMemoryTypes::Loader); ResourceLoader::reportMemoryUsage(memoryObjectInfo); info.addMember(m_initialRequest); info.addMember(m_substituteData); info.addMember(m_dataLoadTimer); } void MainResourceLoader::handleSubstituteDataLoadNow(MainResourceLoaderTimer*) { RefPtr protect(this); KURL url = m_substituteData.responseURL(); if (url.isEmpty()) url = m_initialRequest.url(); // Clear the initial request here so that subsequent entries into the // loader will not think there's still a deferred load left to do. m_initialRequest = ResourceRequest(); ResourceResponse response(url, m_substituteData.mimeType(), m_substituteData.content()->size(), m_substituteData.textEncoding(), ""); didReceiveResponse(response); } void MainResourceLoader::startDataLoadTimer() { m_dataLoadTimer.startOneShot(0); #if HAVE(RUNLOOP_TIMER) if (SchedulePairHashSet* scheduledPairs = m_frame->page()->scheduledRunLoopPairs()) m_dataLoadTimer.schedule(*scheduledPairs); #endif } void MainResourceLoader::handleSubstituteDataLoadSoon(const ResourceRequest& r) { m_initialRequest = r; if (m_documentLoader->deferMainResourceDataLoad()) startDataLoadTimer(); else handleSubstituteDataLoadNow(0); } void MainResourceLoader::loadNow(ResourceRequest& r) { ASSERT(!m_handle); ASSERT(!defersLoading()); #if USE(PLATFORM_STRATEGIES) platformStrategies()->loaderStrategy()->resourceLoadScheduler()->addMainResourceLoad(this); #else resourceLoadScheduler()->addMainResourceLoad(this); #endif if (m_substituteData.isValid()) handleSubstituteDataLoadSoon(r); else m_handle = ResourceHandle::create(m_frame->loader()->networkingContext(), r, this, false, true); return; } void MainResourceLoader::load(const ResourceRequest& r, const SubstituteData& substituteData) { ASSERT(!m_handle); // It appears that it is possible for this load to be cancelled and derefenced by the DocumentLoader // in willSendRequest() if loadNow() is called. RefPtr protect(this); m_substituteData = substituteData; ASSERT(documentLoader()->timing()->navigationStart()); ASSERT(!documentLoader()->timing()->fetchStart()); documentLoader()->timing()->markFetchStart(); ResourceRequest request(r); // Send this synthetic delegate callback since clients expect it, and // we no longer send the callback from within NSURLConnection for // initial requests. willSendRequest(request, ResourceResponse()); ASSERT(!deletionHasBegun()); // // willSendRequest() is liable to make the call to frameLoader() return null, so we need to check that here if (!frameLoader() || request.isNull()) { if (!reachedTerminalState()) releaseResources(); return; } documentLoader()->applicationCacheHost()->maybeLoadMainResource(request, m_substituteData); if (defersLoading()) m_initialRequest = request; else loadNow(request); } void MainResourceLoader::setDefersLoading(bool defers) { ResourceLoader::setDefersLoading(defers); if (defers) { if (m_dataLoadTimer.isActive()) m_dataLoadTimer.stop(); } else { if (m_initialRequest.isNull()) return; ResourceRequest initialRequest(m_initialRequest); m_initialRequest = ResourceRequest(); loadNow(initialRequest); } } }