Home | History | Annotate | Download | only in serviceworkers
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "config.h"
      6 #include "FetchManager.h"
      7 
      8 #include "bindings/core/v8/ExceptionState.h"
      9 #include "bindings/core/v8/ScriptPromiseResolver.h"
     10 #include "bindings/core/v8/ScriptState.h"
     11 #include "bindings/core/v8/V8ThrowException.h"
     12 #include "core/dom/ExceptionCode.h"
     13 #include "core/fetch/FetchUtils.h"
     14 #include "core/fileapi/Blob.h"
     15 #include "core/loader/ThreadableLoader.h"
     16 #include "core/loader/ThreadableLoaderClient.h"
     17 #include "modules/serviceworkers/FetchRequestData.h"
     18 #include "modules/serviceworkers/Response.h"
     19 #include "modules/serviceworkers/ResponseInit.h"
     20 #include "platform/network/ResourceRequest.h"
     21 #include "platform/weborigin/SecurityOrigin.h"
     22 #include "public/platform/WebURLRequest.h"
     23 #include "wtf/HashSet.h"
     24 
     25 namespace blink {
     26 
     27 class FetchManager::Loader : public ThreadableLoaderClient {
     28 public:
     29     Loader(ExecutionContext*, FetchManager*, PassRefPtr<ScriptPromiseResolver>, const FetchRequestData*);
     30     ~Loader();
     31     virtual void didReceiveResponse(unsigned long, const ResourceResponse&);
     32     virtual void didFinishLoading(unsigned long, double);
     33     virtual void didFail(const ResourceError&);
     34     virtual void didFailAccessControlCheck(const ResourceError&);
     35     virtual void didFailRedirectCheck();
     36     virtual void didDownloadData(int);
     37 
     38     void start();
     39     void cleanup();
     40 
     41 private:
     42     void performBasicFetch();
     43     void performNetworkError();
     44     void performHTTPFetch();
     45     void failed();
     46     void notifyFinished();
     47 
     48     ExecutionContext* m_executionContext;
     49     FetchManager* m_fetchManager;
     50     RefPtr<ScriptPromiseResolver> m_resolver;
     51     Persistent<FetchRequestData> m_request;
     52     RefPtr<ThreadableLoader> m_loader;
     53     ResourceResponse m_response;
     54     long long m_downloadedBlobLength;
     55     bool m_corsFlag;
     56     bool m_corsPreflightFlag;
     57     bool m_failed;
     58 };
     59 
     60 FetchManager::Loader::Loader(ExecutionContext* executionContext, FetchManager* fetchManager, PassRefPtr<ScriptPromiseResolver> resolver, const FetchRequestData* request)
     61     : m_executionContext(executionContext)
     62     , m_fetchManager(fetchManager)
     63     , m_resolver(resolver)
     64     , m_request(request->createCopy())
     65     , m_downloadedBlobLength(0)
     66     , m_corsFlag(false)
     67     , m_corsPreflightFlag(false)
     68     , m_failed(false)
     69 {
     70 }
     71 
     72 FetchManager::Loader::~Loader()
     73 {
     74     if (m_loader)
     75         m_loader->cancel();
     76 }
     77 
     78 void FetchManager::Loader::didReceiveResponse(unsigned long, const ResourceResponse& response)
     79 {
     80     m_response = response;
     81 }
     82 
     83 void FetchManager::Loader::didFinishLoading(unsigned long, double)
     84 {
     85     if (!m_resolver->executionContext() || m_resolver->executionContext()->activeDOMObjectsAreStopped())
     86         return;
     87 
     88     OwnPtr<BlobData> blobData = BlobData::create();
     89     String filePath = m_response.downloadedFilePath();
     90     if (!filePath.isEmpty() && m_downloadedBlobLength) {
     91         blobData->appendFile(filePath);
     92         blobData->setContentType(m_response.mimeType());
     93     }
     94     FetchResponseData* response = FetchResponseData::create();
     95     response->setStatus(m_response.httpStatusCode());
     96     response->setStatusMessage(m_response.httpStatusText());
     97     HTTPHeaderMap::const_iterator end = m_response.httpHeaderFields().end();
     98     for (HTTPHeaderMap::const_iterator it = m_response.httpHeaderFields().begin(); it != end; ++it) {
     99         response->headerList()->append(it->key, it->value);
    100     }
    101     response->setBlobDataHandle(BlobDataHandle::create(blobData.release(), m_downloadedBlobLength));
    102     response->setURL(m_request->url());
    103 
    104     switch (m_request->tainting()) {
    105     case FetchRequestData::BasicTainting:
    106         response = response->createBasicFilteredResponse();
    107         break;
    108     case FetchRequestData::CORSTainting:
    109         response = response->createCORSFilteredResponse();
    110         break;
    111     case FetchRequestData::OpaqueTainting:
    112         response = response->createOpaqueFilteredResponse();
    113         break;
    114     }
    115     m_resolver->resolve(Response::create(m_resolver->executionContext(), response));
    116     notifyFinished();
    117 }
    118 
    119 void FetchManager::Loader::didFail(const ResourceError& error)
    120 {
    121     failed();
    122 }
    123 
    124 void FetchManager::Loader::didFailAccessControlCheck(const ResourceError& error)
    125 {
    126     failed();
    127 }
    128 
    129 void FetchManager::Loader::didFailRedirectCheck()
    130 {
    131     failed();
    132 }
    133 
    134 void FetchManager::Loader::didDownloadData(int dataLength)
    135 {
    136     m_downloadedBlobLength += dataLength;
    137 }
    138 
    139 void FetchManager::Loader::start()
    140 {
    141     // "1. If |request|'s url contains a Known HSTS Host, modify it per the
    142     // requirements of the 'URI [sic] Loading and Port Mapping' chapter of HTTP
    143     // Strict Transport Security."
    144     // FIXME: Implement this.
    145 
    146     // "2. If |request|'s referrer is not none, set |request|'s referrer to the
    147     // result of invoking determine |request|'s referrer."
    148     // We set the referrer using workerGlobalScope's URL in
    149     // WorkerThreadableLoader.
    150 
    151     // "3. If |request|'s synchronous flag is unset and fetch is not invoked
    152     // recursively, run the remaining steps asynchronously."
    153     // We don't support synchronous flag.
    154 
    155     // "4. Let response be the value corresponding to the first matching
    156     // statement:"
    157 
    158     // "- should fetching |request| be blocked as mixed content returns blocked
    159     //  - should fetching |request| be blocked as content security returns
    160     //    blocked
    161     //      A network error."
    162     // We do mixed content checking and CSP checking in ResourceFetcher.
    163 
    164     // "- |request|'s url's origin is |request|'s origin and the |CORS flag| is
    165     //    unset"
    166     // "- |request|'s url's scheme is 'data' and |request|'s same-origin data
    167     //    URL flag is set"
    168     // "- |request|'s url's scheme is 'about'"
    169     if ((SecurityOrigin::create(m_request->url())->isSameSchemeHostPort(m_request->origin().get()) && !m_corsFlag)
    170         || (m_request->url().protocolIsData() && m_request->sameOriginDataURLFlag())
    171         || (m_request->url().protocolIsAbout())) {
    172         // "The result of performing a basic fetch using request."
    173         performBasicFetch();
    174         return;
    175     }
    176 
    177     // "- |request|'s mode is |same-origin|"
    178     if (m_request->mode() == FetchRequestData::SameOriginMode) {
    179         // "A network error."
    180         performNetworkError();
    181         return;
    182     }
    183 
    184     // "- |request|'s mode is |no CORS|"
    185     if (m_request->mode() == FetchRequestData::NoCORSMode) {
    186         // "Set |request|'s response tainting to |opaque|."
    187         m_request->setResponseTainting(FetchRequestData::OpaqueTainting);
    188         // "The result of performing a basic fetch using |request|."
    189         performBasicFetch();
    190         return;
    191     }
    192 
    193     // "- |request|'s url's scheme is not one of 'http' and 'https'"
    194     if (!m_request->url().protocolIsInHTTPFamily()) {
    195         // "A network error."
    196         performNetworkError();
    197         return;
    198     }
    199 
    200     // "- |request|'s mode is |CORS-with-forced-preflight|.
    201     // "- |request|'s unsafe request flag is set and either |request|'s method
    202     // is not a simple method or a header in |request|'s header list is not a
    203     // simple header"
    204     if (m_request->mode() == FetchRequestData::CORSWithForcedPreflight
    205         || (m_request->unsafeRequestFlag()
    206             && (!FetchUtils::isSimpleMethod(m_request->method())
    207                 || m_request->headerList()->containsNonSimpleHeader()))) {
    208         // "Set |request|'s response tainting to |CORS|."
    209         m_request->setResponseTainting(FetchRequestData::CORSTainting);
    210         // "The result of performing an HTTP fetch using |request| with the
    211         // |CORS flag| and |CORS preflight flag| set."
    212         m_corsFlag = true;
    213         m_corsPreflightFlag = true;
    214         performHTTPFetch();
    215         return;
    216     }
    217 
    218     // "- Otherwise
    219     //     Set |request|'s response tainting to |CORS|."
    220     m_request->setResponseTainting(FetchRequestData::CORSTainting);
    221     // "The result of performing an HTTP fetch using |request| with the
    222     // |CORS flag| set."
    223     m_corsFlag = true;
    224     m_corsPreflightFlag = false;
    225     performHTTPFetch();
    226 }
    227 
    228 void FetchManager::Loader::cleanup()
    229 {
    230     // Prevent notification
    231     m_fetchManager = 0;
    232 
    233     if (m_loader) {
    234         m_loader->cancel();
    235         m_loader.clear();
    236     }
    237 }
    238 
    239 void FetchManager::Loader::performBasicFetch()
    240 {
    241     // "To perform a basic fetch using |request|, switch on |request|'s url's
    242     // scheme, and run the associated steps:"
    243     if (m_request->url().protocolIsInHTTPFamily()) {
    244         // "Return the result of performing an HTTP fetch using |request|."
    245         m_corsFlag = false;
    246         m_corsPreflightFlag = false;
    247         performHTTPFetch();
    248     } else {
    249         // FIXME: implement other protocols.
    250         performNetworkError();
    251     }
    252 }
    253 
    254 void FetchManager::Loader::performNetworkError()
    255 {
    256     failed();
    257 }
    258 
    259 void FetchManager::Loader::performHTTPFetch()
    260 {
    261     ASSERT(m_request->url().protocolIsInHTTPFamily());
    262     // CORS preflight fetch procedure is implemented inside DocumentThreadableLoader.
    263 
    264     // "1. Let |HTTPRequest| be a copy of |request|, except that |HTTPRequest|'s
    265     //  body is a tee of |request|'s body."
    266     // We use ResourceRequest class for HTTPRequest.
    267     // FIXME: Support body.
    268     ResourceRequest request(m_request->url());
    269     request.setRequestContext(WebURLRequest::RequestContextFetch);
    270     request.setDownloadToFile(true);
    271     request.setHTTPMethod(m_request->method());
    272     const Vector<OwnPtr<FetchHeaderList::Header> >& list = m_request->headerList()->list();
    273     for (size_t i = 0; i < list.size(); ++i) {
    274         request.addHTTPHeaderField(AtomicString(list[i]->first), AtomicString(list[i]->second));
    275     }
    276 
    277     if (m_request->method() != "GET" && m_request->method() != "HEAD") {
    278         RefPtr<BlobDataHandle> blobDataHandle = m_request->blobDataHandle();
    279         if (blobDataHandle.get()) {
    280             RefPtr<FormData> httpBody(FormData::create());
    281             httpBody->appendBlob(blobDataHandle->uuid(), blobDataHandle);
    282             request.setHTTPBody(httpBody);
    283         }
    284     }
    285 
    286     // "2. Append `Referer`/empty byte sequence, if |HTTPRequest|'s |referrer|
    287     // is none, and `Referer`/|HTTPRequest|'s referrer, serialized and utf-8
    288     // encoded, otherwise, to HTTPRequest's header list.
    289     // We set the referrer using workerGlobalScope's URL in
    290     // WorkerThreadableLoader.
    291 
    292     // "3. Append `Host`, ..."
    293     // FIXME: Implement this when the spec is fixed.
    294 
    295     // "4.If |HTTPRequest|'s force Origin header flag is set, append `Origin`/
    296     // |HTTPRequest|'s origin, serialized and utf-8 encoded, to |HTTPRequest|'s
    297     // header list."
    298     // We set Origin header in updateRequestForAccessControl() called from
    299     // DocumentThreadableLoader::makeCrossOriginAccessRequest
    300 
    301     // "5. Let |credentials flag| be set if either |HTTPRequest|'s credentials
    302     // mode is |include|, or |HTTPRequest|'s credentials mode is |same-origin|
    303     // and the |CORS flag| is unset, and unset otherwise.
    304     ResourceLoaderOptions resourceLoaderOptions;
    305     resourceLoaderOptions.dataBufferingPolicy = DoNotBufferData;
    306     if (m_request->credentials() == FetchRequestData::IncludeCredentials
    307         || (m_request->credentials() == FetchRequestData::SameOriginCredentials && !m_corsFlag)) {
    308         resourceLoaderOptions.allowCredentials = AllowStoredCredentials;
    309     }
    310 
    311     ThreadableLoaderOptions threadableLoaderOptions;
    312     if (m_corsPreflightFlag)
    313         threadableLoaderOptions.preflightPolicy = ForcePreflight;
    314     if (m_corsFlag)
    315         threadableLoaderOptions.crossOriginRequestPolicy = UseAccessControl;
    316     else
    317         threadableLoaderOptions.crossOriginRequestPolicy = AllowCrossOriginRequests;
    318 
    319 
    320     m_loader = ThreadableLoader::create(*m_executionContext, this, request, threadableLoaderOptions, resourceLoaderOptions);
    321 }
    322 
    323 void FetchManager::Loader::failed()
    324 {
    325     if (m_failed)
    326         return;
    327     if (!m_resolver->executionContext() || m_resolver->executionContext()->activeDOMObjectsAreStopped())
    328         return;
    329     m_failed = true;
    330     ScriptState* state = m_resolver->scriptState();
    331     ScriptState::Scope scope(state);
    332     m_resolver->reject(V8ThrowException::createTypeError("Failed to fetch", state->isolate()));
    333     notifyFinished();
    334 }
    335 
    336 void FetchManager::Loader::notifyFinished()
    337 {
    338     if (m_fetchManager)
    339         m_fetchManager->onLoaderFinished(this);
    340 }
    341 
    342 FetchManager::FetchManager(ExecutionContext* executionContext)
    343     : m_executionContext(executionContext)
    344 {
    345 }
    346 
    347 FetchManager::~FetchManager()
    348 {
    349     for (HashSet<OwnPtr<Loader> >::iterator it = m_loaders.begin(); it != m_loaders.end(); ++it) {
    350         (*it)->cleanup();
    351     }
    352 }
    353 
    354 ScriptPromise FetchManager::fetch(ScriptState* scriptState, const FetchRequestData* request)
    355 {
    356     RefPtr<ScriptPromiseResolver> resolver = ScriptPromiseResolver::create(scriptState);
    357     ScriptPromise promise = resolver->promise();
    358 
    359     OwnPtr<Loader> ownLoader(adoptPtr(new Loader(m_executionContext, this, resolver.release(), request)));
    360     Loader* loader = m_loaders.add(ownLoader.release()).storedValue->get();
    361     loader->start();
    362     return promise;
    363 }
    364 
    365 void FetchManager::onLoaderFinished(Loader* loader)
    366 {
    367     m_loaders.remove(loader);
    368 }
    369 
    370 } // namespace blink
    371