Home | History | Annotate | Download | only in loader
      1 /*
      2  * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
      3  * Copyright (C) 2008 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions
      7  * are met:
      8  *
      9  * 1.  Redistributions of source code must retain the above copyright
     10  *     notice, this list of conditions and the following disclaimer.
     11  * 2.  Redistributions in binary form must reproduce the above copyright
     12  *     notice, this list of conditions and the following disclaimer in the
     13  *     documentation and/or other materials provided with the distribution.
     14  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
     15  *     its contributors may be used to endorse or promote products derived
     16  *     from this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
     19  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     21  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
     22  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     25  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     28  */
     29 
     30 #include "config.h"
     31 #include "MainResourceLoader.h"
     32 
     33 #include "ApplicationCacheHost.h"
     34 #include "DocumentLoader.h"
     35 #include "FormState.h"
     36 #include "Frame.h"
     37 #include "FrameLoader.h"
     38 #include "FrameLoaderClient.h"
     39 #include "HTMLFormElement.h"
     40 #include "Page.h"
     41 #if PLATFORM(QT)
     42 #include "PluginDatabase.h"
     43 #endif
     44 #include "ResourceError.h"
     45 #include "ResourceHandle.h"
     46 #include "Settings.h"
     47 
     48 // FIXME: More that is in common with SubresourceLoader should move up into ResourceLoader.
     49 
     50 namespace WebCore {
     51 
     52 MainResourceLoader::MainResourceLoader(Frame* frame)
     53     : ResourceLoader(frame, true, true)
     54     , m_dataLoadTimer(this, &MainResourceLoader::handleDataLoadNow)
     55     , m_loadingMultipartContent(false)
     56     , m_waitingForContentPolicy(false)
     57 {
     58 }
     59 
     60 MainResourceLoader::~MainResourceLoader()
     61 {
     62 }
     63 
     64 PassRefPtr<MainResourceLoader> MainResourceLoader::create(Frame* frame)
     65 {
     66     return adoptRef(new MainResourceLoader(frame));
     67 }
     68 
     69 void MainResourceLoader::receivedError(const ResourceError& error)
     70 {
     71     // Calling receivedMainResourceError will likely result in the last reference to this object to go away.
     72     RefPtr<MainResourceLoader> protect(this);
     73     RefPtr<Frame> protectFrame(m_frame);
     74 
     75     // It is important that we call FrameLoader::receivedMainResourceError before calling
     76     // FrameLoader::didFailToLoad because receivedMainResourceError clears out the relevant
     77     // document loaders. Also, receivedMainResourceError ends up calling a FrameLoadDelegate method
     78     // and didFailToLoad calls a ResourceLoadDelegate method and they need to be in the correct order.
     79     frameLoader()->receivedMainResourceError(error, true);
     80 
     81     if (!cancelled()) {
     82         ASSERT(!reachedTerminalState());
     83         frameLoader()->notifier()->didFailToLoad(this, error);
     84 
     85         releaseResources();
     86     }
     87 
     88     ASSERT(reachedTerminalState());
     89 }
     90 
     91 void MainResourceLoader::didCancel(const ResourceError& error)
     92 {
     93     m_dataLoadTimer.stop();
     94 
     95     // Calling receivedMainResourceError will likely result in the last reference to this object to go away.
     96     RefPtr<MainResourceLoader> protect(this);
     97 
     98     if (m_waitingForContentPolicy) {
     99         frameLoader()->policyChecker()->cancelCheck();
    100         ASSERT(m_waitingForContentPolicy);
    101         m_waitingForContentPolicy = false;
    102         deref(); // balances ref in didReceiveResponse
    103     }
    104     frameLoader()->receivedMainResourceError(error, true);
    105     ResourceLoader::didCancel(error);
    106 }
    107 
    108 ResourceError MainResourceLoader::interruptionForPolicyChangeError() const
    109 {
    110     return frameLoader()->interruptionForPolicyChangeError(request());
    111 }
    112 
    113 void MainResourceLoader::stopLoadingForPolicyChange()
    114 {
    115     cancel(interruptionForPolicyChangeError());
    116 }
    117 
    118 void MainResourceLoader::callContinueAfterNavigationPolicy(void* argument, const ResourceRequest& request, PassRefPtr<FormState>, bool shouldContinue)
    119 {
    120     static_cast<MainResourceLoader*>(argument)->continueAfterNavigationPolicy(request, shouldContinue);
    121 }
    122 
    123 void MainResourceLoader::continueAfterNavigationPolicy(const ResourceRequest&, bool shouldContinue)
    124 {
    125     if (!shouldContinue)
    126         stopLoadingForPolicyChange();
    127     deref(); // balances ref in willSendRequest
    128 }
    129 
    130 bool MainResourceLoader::isPostOrRedirectAfterPost(const ResourceRequest& newRequest, const ResourceResponse& redirectResponse)
    131 {
    132     if (newRequest.httpMethod() == "POST")
    133         return true;
    134 
    135     int status = redirectResponse.httpStatusCode();
    136     if (((status >= 301 && status <= 303) || status == 307)
    137         && frameLoader()->initialRequest().httpMethod() == "POST")
    138         return true;
    139 
    140     return false;
    141 }
    142 
    143 void MainResourceLoader::addData(const char* data, int length, bool allAtOnce)
    144 {
    145     ResourceLoader::addData(data, length, allAtOnce);
    146     frameLoader()->receivedData(data, length);
    147 }
    148 
    149 void MainResourceLoader::willSendRequest(ResourceRequest& newRequest, const ResourceResponse& redirectResponse)
    150 {
    151     // Note that there are no asserts here as there are for the other callbacks. This is due to the
    152     // fact that this "callback" is sent when starting every load, and the state of callback
    153     // deferrals plays less of a part in this function in preventing the bad behavior deferring
    154     // callbacks is meant to prevent.
    155     ASSERT(!newRequest.isNull());
    156 
    157     // The additional processing can do anything including possibly removing the last
    158     // reference to this object; one example of this is 3266216.
    159     RefPtr<MainResourceLoader> protect(this);
    160 
    161     // Update cookie policy base URL as URL changes, except for subframes, which use the
    162     // URL of the main frame which doesn't change when we redirect.
    163     if (frameLoader()->isLoadingMainFrame())
    164         newRequest.setFirstPartyForCookies(newRequest.url());
    165 
    166     // If we're fielding a redirect in response to a POST, force a load from origin, since
    167     // this is a common site technique to return to a page viewing some data that the POST
    168     // just modified.
    169     // Also, POST requests always load from origin, but this does not affect subresources.
    170     if (newRequest.cachePolicy() == UseProtocolCachePolicy && isPostOrRedirectAfterPost(newRequest, redirectResponse))
    171         newRequest.setCachePolicy(ReloadIgnoringCacheData);
    172 
    173     ResourceLoader::willSendRequest(newRequest, redirectResponse);
    174 
    175     // Don't set this on the first request. It is set when the main load was started.
    176     m_documentLoader->setRequest(newRequest);
    177 
    178     Frame* top = m_frame->tree()->top();
    179     if (top != m_frame)
    180         frameLoader()->checkIfDisplayInsecureContent(top->document()->securityOrigin(), newRequest.url());
    181 
    182     // FIXME: Ideally we'd stop the I/O until we hear back from the navigation policy delegate
    183     // listener. But there's no way to do that in practice. So instead we cancel later if the
    184     // listener tells us to. In practice that means the navigation policy needs to be decided
    185     // synchronously for these redirect cases.
    186     if (!redirectResponse.isNull()) {
    187         ref(); // balanced by deref in continueAfterNavigationPolicy
    188         frameLoader()->policyChecker()->checkNavigationPolicy(newRequest, callContinueAfterNavigationPolicy, this);
    189     }
    190 }
    191 
    192 static bool shouldLoadAsEmptyDocument(const KURL& url)
    193 {
    194 #if PLATFORM(TORCHMOBILE)
    195     return url.isEmpty() || (url.protocolIs("about") && equalIgnoringRef(url, blankURL()));
    196 #else
    197     return url.isEmpty() || url.protocolIs("about");
    198 #endif
    199 }
    200 
    201 void MainResourceLoader::continueAfterContentPolicy(PolicyAction contentPolicy, const ResourceResponse& r)
    202 {
    203     KURL url = request().url();
    204     const String& mimeType = r.mimeType();
    205 
    206     switch (contentPolicy) {
    207     case PolicyUse: {
    208         // Prevent remote web archives from loading because they can claim to be from any domain and thus avoid cross-domain security checks (4120255).
    209         bool isRemoteWebArchive = equalIgnoringCase("application/x-webarchive", mimeType) && !m_substituteData.isValid() && !url.isLocalFile();
    210         if (!frameLoader()->canShowMIMEType(mimeType) || isRemoteWebArchive) {
    211             frameLoader()->policyChecker()->cannotShowMIMEType(r);
    212             // Check reachedTerminalState since the load may have already been cancelled inside of _handleUnimplementablePolicyWithErrorCode::.
    213             if (!reachedTerminalState())
    214                 stopLoadingForPolicyChange();
    215             return;
    216         }
    217         break;
    218     }
    219 
    220     case PolicyDownload:
    221         // m_handle can be null, e.g. when loading a substitute resource from application cache.
    222         if (!m_handle) {
    223             receivedError(cannotShowURLError());
    224             return;
    225         }
    226         frameLoader()->client()->download(m_handle.get(), request(), m_handle.get()->request(), r);
    227         // It might have gone missing
    228         if (frameLoader())
    229             receivedError(interruptionForPolicyChangeError());
    230         return;
    231 
    232     case PolicyIgnore:
    233         stopLoadingForPolicyChange();
    234         return;
    235 
    236     default:
    237         ASSERT_NOT_REACHED();
    238     }
    239 
    240     RefPtr<MainResourceLoader> protect(this);
    241 
    242     if (r.isHTTP()) {
    243         int status = r.httpStatusCode();
    244         if (status < 200 || status >= 300) {
    245             bool hostedByObject = frameLoader()->isHostedByObjectElement();
    246 
    247             frameLoader()->handleFallbackContent();
    248             // object elements are no longer rendered after we fallback, so don't
    249             // keep trying to process data from their load
    250 
    251             if (hostedByObject)
    252                 cancel();
    253         }
    254     }
    255 
    256     // we may have cancelled this load as part of switching to fallback content
    257     if (!reachedTerminalState())
    258         ResourceLoader::didReceiveResponse(r);
    259 
    260     if (frameLoader() && !frameLoader()->isStopping()) {
    261         if (m_substituteData.isValid()) {
    262             if (m_substituteData.content()->size())
    263                 didReceiveData(m_substituteData.content()->data(), m_substituteData.content()->size(), m_substituteData.content()->size(), true);
    264             if (frameLoader() && !frameLoader()->isStopping())
    265                 didFinishLoading();
    266         } else if (shouldLoadAsEmptyDocument(url) || frameLoader()->representationExistsForURLScheme(url.protocol()))
    267             didFinishLoading();
    268     }
    269 }
    270 
    271 void MainResourceLoader::callContinueAfterContentPolicy(void* argument, PolicyAction policy)
    272 {
    273     static_cast<MainResourceLoader*>(argument)->continueAfterContentPolicy(policy);
    274 }
    275 
    276 void MainResourceLoader::continueAfterContentPolicy(PolicyAction policy)
    277 {
    278     ASSERT(m_waitingForContentPolicy);
    279     m_waitingForContentPolicy = false;
    280     if (frameLoader() && !frameLoader()->isStopping())
    281         continueAfterContentPolicy(policy, m_response);
    282     deref(); // balances ref in didReceiveResponse
    283 }
    284 
    285 #if PLATFORM(QT)
    286 void MainResourceLoader::substituteMIMETypeFromPluginDatabase(const ResourceResponse& r)
    287 {
    288     if (!m_frame->settings()->arePluginsEnabled())
    289         return;
    290 
    291     String filename = r.url().lastPathComponent();
    292     if (filename.endsWith("/"))
    293         return;
    294 
    295     int extensionPos = filename.reverseFind('.');
    296     if (extensionPos == -1)
    297         return;
    298 
    299     String extension = filename.substring(extensionPos + 1);
    300     String mimeType = PluginDatabase::installedPlugins()->MIMETypeForExtension(extension);
    301     if (!mimeType.isEmpty()) {
    302         ResourceResponse* response = const_cast<ResourceResponse*>(&r);
    303         response->setMimeType(mimeType);
    304     }
    305 }
    306 #endif
    307 
    308 void MainResourceLoader::didReceiveResponse(const ResourceResponse& r)
    309 {
    310 #if ENABLE(OFFLINE_WEB_APPLICATIONS)
    311     if (documentLoader()->applicationCacheHost()->maybeLoadFallbackForMainResponse(request(), r))
    312         return;
    313 #endif
    314 
    315     HTTPHeaderMap::const_iterator it = r.httpHeaderFields().find(AtomicString("x-frame-options"));
    316     if (it != r.httpHeaderFields().end()) {
    317         String content = it->second;
    318         if (m_frame->loader()->shouldInterruptLoadForXFrameOptions(content, r.url())) {
    319             cancel();
    320             return;
    321         }
    322     }
    323 
    324     // There is a bug in CFNetwork where callbacks can be dispatched even when loads are deferred.
    325     // See <rdar://problem/6304600> for more details.
    326 #if !PLATFORM(CF)
    327     ASSERT(shouldLoadAsEmptyDocument(r.url()) || !defersLoading());
    328 #endif
    329 
    330 #if PLATFORM(QT)
    331     if (r.mimeType() == "application/octet-stream")
    332         substituteMIMETypeFromPluginDatabase(r);
    333 #endif
    334 
    335     if (m_loadingMultipartContent) {
    336         frameLoader()->setupForReplaceByMIMEType(r.mimeType());
    337         clearResourceData();
    338     }
    339 
    340     if (r.isMultipart())
    341         m_loadingMultipartContent = true;
    342 
    343     // The additional processing can do anything including possibly removing the last
    344     // reference to this object; one example of this is 3266216.
    345     RefPtr<MainResourceLoader> protect(this);
    346 
    347     m_documentLoader->setResponse(r);
    348 
    349     m_response = r;
    350 
    351     ASSERT(!m_waitingForContentPolicy);
    352     m_waitingForContentPolicy = true;
    353     ref(); // balanced by deref in continueAfterContentPolicy and didCancel
    354 
    355     ASSERT(frameLoader()->activeDocumentLoader());
    356 
    357     // Always show content with valid substitute data.
    358     if (frameLoader()->activeDocumentLoader()->substituteData().isValid()) {
    359         callContinueAfterContentPolicy(this, PolicyUse);
    360         return;
    361     }
    362 
    363 #if ENABLE(FTPDIR)
    364     // Respect the hidden FTP Directory Listing pref so it can be tested even if the policy delegate might otherwise disallow it
    365     Settings* settings = m_frame->settings();
    366     if (settings && settings->forceFTPDirectoryListings() && m_response.mimeType() == "application/x-ftp-directory") {
    367         callContinueAfterContentPolicy(this, PolicyUse);
    368         return;
    369     }
    370 #endif
    371 
    372     frameLoader()->policyChecker()->checkContentPolicy(m_response.mimeType(), callContinueAfterContentPolicy, this);
    373 }
    374 
    375 void MainResourceLoader::didReceiveData(const char* data, int length, long long lengthReceived, bool allAtOnce)
    376 {
    377     ASSERT(data);
    378     ASSERT(length != 0);
    379 
    380     ASSERT(!m_response.isNull());
    381 
    382 #if USE(CFNETWORK) || (PLATFORM(MAC) && !defined(BUILDING_ON_TIGER))
    383     // Workaround for <rdar://problem/6060782>
    384     if (m_response.isNull()) {
    385         m_response = ResourceResponse(KURL(), "text/html", 0, String(), String());
    386         if (DocumentLoader* documentLoader = frameLoader()->activeDocumentLoader())
    387             documentLoader->setResponse(m_response);
    388     }
    389 #endif
    390 
    391     // There is a bug in CFNetwork where callbacks can be dispatched even when loads are deferred.
    392     // See <rdar://problem/6304600> for more details.
    393 #if !PLATFORM(CF)
    394     ASSERT(!defersLoading());
    395 #endif
    396 
    397  #if ENABLE(OFFLINE_WEB_APPLICATIONS)
    398     documentLoader()->applicationCacheHost()->mainResourceDataReceived(data, length, lengthReceived, allAtOnce);
    399 #endif
    400 
    401     // The additional processing can do anything including possibly removing the last
    402     // reference to this object; one example of this is 3266216.
    403     RefPtr<MainResourceLoader> protect(this);
    404 
    405     ResourceLoader::didReceiveData(data, length, lengthReceived, allAtOnce);
    406 }
    407 
    408 void MainResourceLoader::didFinishLoading()
    409 {
    410     // There is a bug in CFNetwork where callbacks can be dispatched even when loads are deferred.
    411     // See <rdar://problem/6304600> for more details.
    412 #if !PLATFORM(CF)
    413     ASSERT(shouldLoadAsEmptyDocument(frameLoader()->activeDocumentLoader()->url()) || !defersLoading());
    414 #endif
    415 
    416     // The additional processing can do anything including possibly removing the last
    417     // reference to this object.
    418     RefPtr<MainResourceLoader> protect(this);
    419 
    420 #if ENABLE(OFFLINE_WEB_APPLICATIONS)
    421     RefPtr<DocumentLoader> dl = documentLoader();
    422 #endif
    423 
    424     frameLoader()->finishedLoading();
    425     ResourceLoader::didFinishLoading();
    426 
    427 #if ENABLE(OFFLINE_WEB_APPLICATIONS)
    428     dl->applicationCacheHost()->finishedLoadingMainResource();
    429 #endif
    430 }
    431 
    432 void MainResourceLoader::didFail(const ResourceError& error)
    433 {
    434 #if ENABLE(OFFLINE_WEB_APPLICATIONS)
    435     if (documentLoader()->applicationCacheHost()->maybeLoadFallbackForMainError(request(), error))
    436         return;
    437 #endif
    438 
    439     // There is a bug in CFNetwork where callbacks can be dispatched even when loads are deferred.
    440     // See <rdar://problem/6304600> for more details.
    441 #if !PLATFORM(CF)
    442     ASSERT(!defersLoading());
    443 #endif
    444 
    445     receivedError(error);
    446 }
    447 
    448 void MainResourceLoader::handleEmptyLoad(const KURL& url, bool forURLScheme)
    449 {
    450     String mimeType;
    451     if (forURLScheme)
    452         mimeType = frameLoader()->generatedMIMETypeForURLScheme(url.protocol());
    453     else
    454         mimeType = "text/html";
    455 
    456     ResourceResponse response(url, mimeType, 0, String(), String());
    457     didReceiveResponse(response);
    458 }
    459 
    460 void MainResourceLoader::handleDataLoadNow(MainResourceLoaderTimer*)
    461 {
    462     RefPtr<MainResourceLoader> protect(this);
    463 
    464     KURL url = m_substituteData.responseURL();
    465     if (url.isEmpty())
    466         url = m_initialRequest.url();
    467 
    468     ResourceResponse response(url, m_substituteData.mimeType(), m_substituteData.content()->size(), m_substituteData.textEncoding(), "");
    469     didReceiveResponse(response);
    470 }
    471 
    472 void MainResourceLoader::startDataLoadTimer()
    473 {
    474     m_dataLoadTimer.startOneShot(0);
    475 
    476 #if HAVE(RUNLOOP_TIMER)
    477     if (SchedulePairHashSet* scheduledPairs = m_frame->page()->scheduledRunLoopPairs())
    478         m_dataLoadTimer.schedule(*scheduledPairs);
    479 #endif
    480 }
    481 
    482 void MainResourceLoader::handleDataLoadSoon(ResourceRequest& r)
    483 {
    484     m_initialRequest = r;
    485 
    486     if (m_documentLoader->deferMainResourceDataLoad())
    487         startDataLoadTimer();
    488     else
    489         handleDataLoadNow(0);
    490 }
    491 
    492 bool MainResourceLoader::loadNow(ResourceRequest& r)
    493 {
    494     bool shouldLoadEmptyBeforeRedirect = shouldLoadAsEmptyDocument(r.url());
    495 
    496     ASSERT(!m_handle);
    497     ASSERT(shouldLoadEmptyBeforeRedirect || !defersLoading());
    498 
    499     // Send this synthetic delegate callback since clients expect it, and
    500     // we no longer send the callback from within NSURLConnection for
    501     // initial requests.
    502     willSendRequest(r, ResourceResponse());
    503 
    504     // <rdar://problem/4801066>
    505     // willSendRequest() is liable to make the call to frameLoader() return NULL, so we need to check that here
    506     if (!frameLoader())
    507         return false;
    508 
    509     const KURL& url = r.url();
    510     bool shouldLoadEmpty = shouldLoadAsEmptyDocument(url) && !m_substituteData.isValid();
    511 
    512     if (shouldLoadEmptyBeforeRedirect && !shouldLoadEmpty && defersLoading())
    513         return true;
    514 
    515     if (m_substituteData.isValid())
    516         handleDataLoadSoon(r);
    517     else if (shouldLoadEmpty || frameLoader()->representationExistsForURLScheme(url.protocol()))
    518         handleEmptyLoad(url, !shouldLoadEmpty);
    519     else
    520         m_handle = ResourceHandle::create(r, this, m_frame.get(), false, true, true);
    521 
    522     return false;
    523 }
    524 
    525 bool MainResourceLoader::load(const ResourceRequest& r, const SubstituteData& substituteData)
    526 {
    527     ASSERT(!m_handle);
    528 
    529     m_substituteData = substituteData;
    530 
    531     ResourceRequest request(r);
    532 
    533 #if ENABLE(OFFLINE_WEB_APPLICATIONS)
    534     documentLoader()->applicationCacheHost()->maybeLoadMainResource(request, m_substituteData);
    535 #endif
    536 
    537     bool defer = defersLoading();
    538     if (defer) {
    539         bool shouldLoadEmpty = shouldLoadAsEmptyDocument(request.url());
    540         if (shouldLoadEmpty)
    541             defer = false;
    542     }
    543     if (!defer) {
    544         if (loadNow(request)) {
    545             // Started as an empty document, but was redirected to something non-empty.
    546             ASSERT(defersLoading());
    547             defer = true;
    548         }
    549     }
    550     if (defer)
    551         m_initialRequest = request;
    552 
    553     return true;
    554 }
    555 
    556 void MainResourceLoader::setDefersLoading(bool defers)
    557 {
    558     ResourceLoader::setDefersLoading(defers);
    559 
    560     if (defers) {
    561         if (m_dataLoadTimer.isActive())
    562             m_dataLoadTimer.stop();
    563     } else {
    564         if (m_initialRequest.isNull())
    565             return;
    566 
    567         if (m_substituteData.isValid() && m_documentLoader->deferMainResourceDataLoad())
    568             startDataLoadTimer();
    569         else {
    570             ResourceRequest r(m_initialRequest);
    571             m_initialRequest = ResourceRequest();
    572             loadNow(r);
    573         }
    574     }
    575 }
    576 
    577 }
    578