Index: Source/modules/serviceworkers/FetchManager.cpp |
diff --git a/Source/modules/serviceworkers/FetchManager.cpp b/Source/modules/serviceworkers/FetchManager.cpp |
index b4c32347f02e6f26d0cd7f3198346a5be58d90b0..c79f76f1746473978ab764848fc97b2bd86f8d78 100644 |
--- a/Source/modules/serviceworkers/FetchManager.cpp |
+++ b/Source/modules/serviceworkers/FetchManager.cpp |
@@ -14,16 +14,19 @@ |
#include "core/loader/ThreadableLoader.h" |
#include "core/loader/ThreadableLoaderClient.h" |
#include "core/xml/XMLHttpRequest.h" |
+#include "modules/serviceworkers/FetchRequestData.h" |
#include "modules/serviceworkers/Response.h" |
#include "modules/serviceworkers/ResponseInit.h" |
#include "platform/network/ResourceRequest.h" |
+#include "platform/weborigin/SecurityOrigin.h" |
+#include "public/platform/WebURLRequest.h" |
#include "wtf/HashSet.h" |
namespace blink { |
class FetchManager::Loader : public ThreadableLoaderClient { |
public: |
- Loader(ExecutionContext*, FetchManager*, PassRefPtr<ScriptPromiseResolver>, PassOwnPtr<ResourceRequest>); |
+ Loader(ExecutionContext*, FetchManager*, PassRefPtr<ScriptPromiseResolver>, PassRefPtr<FetchRequestData>); |
~Loader(); |
virtual void didReceiveResponse(unsigned long, const ResourceResponse&); |
virtual void didFinishLoading(unsigned long, double); |
@@ -36,25 +39,32 @@ public: |
void cleanup(); |
private: |
+ void performBasicFetch(); |
+ void performNetworkError(); |
+ void performHTTPFetch(); |
void failed(); |
void notifyFinished(); |
ExecutionContext* m_executionContext; |
FetchManager* m_fetchManager; |
RefPtr<ScriptPromiseResolver> m_resolver; |
- OwnPtr<ResourceRequest> m_request; |
+ RefPtr<FetchRequestData> m_request; |
RefPtr<ThreadableLoader> m_loader; |
ResourceResponse m_response; |
long long m_downloadedBlobLength; |
+ bool m_corsFlag; |
+ bool m_corsPreflightFlag; |
bool m_failed; |
}; |
-FetchManager::Loader::Loader(ExecutionContext* executionContext, FetchManager* fetchManager, PassRefPtr<ScriptPromiseResolver> resolver, PassOwnPtr<ResourceRequest> request) |
+FetchManager::Loader::Loader(ExecutionContext* executionContext, FetchManager* fetchManager, PassRefPtr<ScriptPromiseResolver> resolver, PassRefPtr<FetchRequestData> request) |
: m_executionContext(executionContext) |
, m_fetchManager(fetchManager) |
, m_resolver(resolver) |
- , m_request(request) |
+ , m_request(request->createCopy()) |
, m_downloadedBlobLength(0) |
+ , m_corsFlag(false) |
+ , m_corsPreflightFlag(false) |
, m_failed(false) |
{ |
} |
@@ -78,16 +88,28 @@ void FetchManager::Loader::didFinishLoading(unsigned long, double) |
blobData->appendFile(filePath); |
blobData->setContentType(m_response.mimeType()); |
} |
- ResponseInit responseInit; |
- // FIXME: We may have to filter the status when we support CORS. |
- // http://fetch.spec.whatwg.org/#concept-filtered-response-opaque |
- responseInit.status = m_response.httpStatusCode(); |
- responseInit.statusText = m_response.httpStatusText(); |
- // FIXME: fill options. |
- RefPtrWillBeRawPtr<Blob> blob = Blob::create(BlobDataHandle::create(blobData.release(), m_downloadedBlobLength)); |
- // FIXME: Handle response status correctly. |
- NonThrowableExceptionState exceptionState; |
- m_resolver->resolve(Response::create(blob.get(), responseInit, exceptionState)); |
+ RefPtr<FetchResponseData> response(FetchResponseData::create()); |
+ response->setStatus(m_response.httpStatusCode()); |
+ response->setStatusMessage(m_response.httpStatusText()); |
+ HTTPHeaderMap::const_iterator end = m_response.httpHeaderFields().end(); |
+ for (HTTPHeaderMap::const_iterator it = m_response.httpHeaderFields().begin(); it != end; ++it) { |
+ response->headerList()->append(it->key, it->value); |
+ } |
+ response->setBlobDataHandle(BlobDataHandle::create(blobData.release(), m_downloadedBlobLength)); |
+ response->setURL(m_request->url()); |
+ |
+ switch (m_request->tainting()) { |
+ case FetchRequestData::BasicTainting: |
+ response = response->createBasicFilteredResponse(); |
+ break; |
+ case FetchRequestData::CORSTainting: |
+ response = response->createCORSFilteredResponse(); |
+ break; |
+ case FetchRequestData::OpaqueTainting: |
+ response = response->createOpaqueFilteredResponse(); |
+ break; |
+ } |
+ m_resolver->resolve(Response::create(response.release())); |
notifyFinished(); |
} |
@@ -113,13 +135,91 @@ void FetchManager::Loader::didDownloadData(int dataLength) |
void FetchManager::Loader::start() |
{ |
- m_request->setDownloadToFile(true); |
- ThreadableLoaderOptions options; |
- // FIXME: Fill options. |
- ResourceLoaderOptions resourceLoaderOptions; |
- resourceLoaderOptions.dataBufferingPolicy = DoNotBufferData; |
- // FIXME: Fill resourceLoaderOptions. |
- m_loader = ThreadableLoader::create(*m_executionContext, this, *m_request, options, resourceLoaderOptions); |
+ // "1. If |request|'s url contains a Known HSTS Host, modify it per the |
+ // requirements of the 'URI [sic] Loading and Port Mapping' chapter of HTTP |
+ // Strict Transport Security." |
+ // FIXME: Implement this. |
+ |
+ // "2. If |request|'s referrer is not none, set |request|'s referrer to the |
+ // result of invoking determine |request|'s referrer." |
+ // We set the referrer using workerGlobalScope's URL in |
+ // WorkerThreadableLoader. |
+ |
+ // "3. If |request|'s synchronous flag is unset and fetch is not invoked |
+ // recursively, run the remaining steps asynchronously." |
+ // We don't support synchronous flag. |
+ |
+ // "4. Let response be the value corresponding to the first matching |
+ // statement:" |
+ |
+ // "- should fetching |request| be blocked as mixed content returns blocked |
+ // - should fetching |request| be blocked as content security returns |
+ // blocked |
+ // A network error." |
+ // We do mixed content checking and CSP checking in ResourceFetcher. |
+ |
+ // "- |request|'s url's origin is |request|'s origin and the |CORS flag| is |
+ // unset" |
+ // "- |request|'s url's scheme is 'data' and |request|'s same-origin data |
+ // URL flag is set" |
+ // "- |request|'s url's scheme is 'about'" |
+ if ((SecurityOrigin::create(m_request->url())->isSameSchemeHostPort(m_request->origin().get()) && !m_corsFlag) |
+ || (m_request->url().protocolIsData() && m_request->sameOriginDataURLFlag()) |
+ || (m_request->url().protocolIsAbout())) { |
+ // "The result of performing a basic fetch using request." |
+ performBasicFetch(); |
+ return; |
+ } |
+ |
+ // "- |request|'s mode is |same-origin|" |
+ if (m_request->mode() == FetchRequestData::SameOriginMode) { |
+ // "A network error." |
+ performNetworkError(); |
+ return; |
+ } |
+ |
+ // "- |request|'s mode is |no CORS|" |
+ if (m_request->mode() == FetchRequestData::NoCORSMode) { |
+ // "Set |request|'s response tainting to |opaque|." |
+ m_request->setResponseTainting(FetchRequestData::OpaqueTainting); |
+ // "The result of performing a basic fetch using |request|." |
+ performBasicFetch(); |
+ return; |
+ } |
+ |
+ // "- |request|'s url's scheme is not one of 'http' and 'https'" |
+ if (!m_request->url().protocolIsInHTTPFamily()) { |
+ // "A network error." |
+ performNetworkError(); |
+ return; |
+ } |
+ |
+ // "- |request|'s mode is |CORS-with-forced-preflight|. |
+ // "- |request|'s unsafe request flag is set and either |request|'s method |
+ // is not a simple method or a header in |request|'s header list is not a |
+ // simple header" |
+ if (m_request->mode() == FetchRequestData::CORSWithForcedPreflight |
+ || (m_request->unsafeRequestFlag() |
+ && (!isSimpleMethod(m_request->method()) |
+ || m_request->headerList()->containsNonSimpleHeader()))) { |
+ // "Set |request|'s response tainting to |CORS|." |
+ m_request->setResponseTainting(FetchRequestData::CORSTainting); |
+ // "The result of performing an HTTP fetch using |request| with the |
+ // |CORS flag| and |CORS preflight flag| set." |
+ m_corsFlag = true; |
+ m_corsPreflightFlag = true; |
+ performHTTPFetch(); |
+ return; |
+ } |
+ |
+ // "- Otherwise |
+ // Set |request|'s response tainting to |CORS|." |
+ m_request->setResponseTainting(FetchRequestData::CORSTainting); |
+ // "The result of performing an HTTP fetch using |request| with the |
+ // |CORS flag| set." |
+ m_corsFlag = true; |
+ m_corsPreflightFlag = false; |
+ performHTTPFetch(); |
} |
void FetchManager::Loader::cleanup() |
@@ -133,6 +233,80 @@ void FetchManager::Loader::cleanup() |
} |
} |
+void FetchManager::Loader::performBasicFetch() |
+{ |
+ // "To perform a basic fetch using |request|, switch on |request|'s url's |
+ // scheme, and run the associated steps:" |
+ if (m_request->url().protocolIsInHTTPFamily()) { |
+ // "Return the result of performing an HTTP fetch using |request|." |
+ m_corsFlag = false; |
+ m_corsPreflightFlag = false; |
+ performHTTPFetch(); |
+ } else { |
+ // FIXME: implement other protocols. |
+ performNetworkError(); |
+ } |
+} |
+ |
+void FetchManager::Loader::performNetworkError() |
+{ |
+ failed(); |
+} |
+ |
+void FetchManager::Loader::performHTTPFetch() |
+{ |
+ // CORS preflight fetch procedure is implemented inside DocumentThreadableLoader. |
+ |
+ // "1. Let |HTTPRequest| be a copy of |request|, except that |HTTPRequest|'s |
+ // body is a tee of |request|'s body." |
+ // We use ResourceRequest class for HTTPRequest. |
+ // FIXME: Support body. |
+ ResourceRequest request(m_request->url()); |
+ request.setRequestContext(blink::WebURLRequest::RequestContextFetch); |
+ request.setDownloadToFile(true); |
+ request.setHTTPMethod(m_request->method()); |
+ const Vector<OwnPtr<FetchHeaderList::Header> >& list = m_request->headerList()->list(); |
+ for (size_t i = 0; i < list.size(); ++i) { |
+ request.addHTTPHeaderField(AtomicString(list[i]->first), AtomicString(list[i]->second)); |
+ } |
+ |
+ // "2. Append `Referer`/empty byte sequence, if |HTTPRequest|'s |referrer| |
+ // is none, and `Referer`/|HTTPRequest|'s referrer, serialized and utf-8 |
+ // encoded, otherwise, to HTTPRequest's header list. |
+ // We set the referrer using workerGlobalScope's URL in |
+ // WorkerThreadableLoader. |
+ |
+ // "3. Append `Host`, ..." |
+ // FIXME: Implement this when the spec is fixed. |
+ |
+ // "4.If |HTTPRequest|'s force Origin header flag is set, append `Origin`/ |
+ // |HTTPRequest|'s origin, serialized and utf-8 encoded, to |HTTPRequest|'s |
+ // header list." |
+ // We set Origin header in updateRequestForAccessControl() called from |
+ // DocumentThreadableLoader::makeCrossOriginAccessRequest |
+ |
+ // "5. Let |credentials flag| be set if either |HTTPRequest|'s credentials |
+ // mode is |include|, or |HTTPRequest|'s credentials mode is |same-origin| |
+ // and the |CORS flag| is unset, and unset otherwise. |
+ ResourceLoaderOptions resourceLoaderOptions; |
+ resourceLoaderOptions.dataBufferingPolicy = DoNotBufferData; |
+ if (m_request->credentials() == FetchRequestData::IncludeCredentials |
+ || (m_request->credentials() == FetchRequestData::SameOriginCredentials && !m_corsFlag)) { |
+ resourceLoaderOptions.allowCredentials = AllowStoredCredentials; |
+ } |
+ |
+ ThreadableLoaderOptions threadableLoaderOptions; |
+ if (m_corsPreflightFlag) |
+ threadableLoaderOptions.preflightPolicy = ForcePreflight; |
+ if (m_corsFlag) |
+ threadableLoaderOptions.crossOriginRequestPolicy = UseAccessControl; |
+ else |
+ threadableLoaderOptions.crossOriginRequestPolicy = AllowCrossOriginRequests; |
+ |
+ |
+ m_loader = ThreadableLoader::create(*m_executionContext, this, request, threadableLoaderOptions, resourceLoaderOptions); |
+} |
+ |
void FetchManager::Loader::failed() |
{ |
if (m_failed) |
@@ -162,13 +336,14 @@ FetchManager::~FetchManager() |
} |
} |
-ScriptPromise FetchManager::fetch(ScriptState* scriptState, PassOwnPtr<ResourceRequest> request) |
+ScriptPromise FetchManager::fetch(ScriptState* scriptState, PassRefPtr<FetchRequestData> request) |
{ |
RefPtr<ScriptPromiseResolver> resolver = ScriptPromiseResolver::create(scriptState); |
ScriptPromise promise = resolver->promise(); |
- OwnPtr<Loader> loader(adoptPtr(new Loader(m_executionContext, this, resolver.release(), request))); |
- (*m_loaders.add(loader.release()).storedValue)->start(); |
+ OwnPtr<Loader> ownLoader(adoptPtr(new Loader(m_executionContext, this, resolver.release(), request))); |
+ Loader* loader = m_loaders.add(ownLoader.release()).storedValue->get(); |
+ loader->start(); |
return promise; |
} |
@@ -185,14 +360,16 @@ bool FetchManager::isSimpleMethod(const String& method) |
bool FetchManager::isForbiddenMethod(const String& method) |
{ |
- // "A forbidden method is a method that is a byte case-insensitive match for one of `CONNECT`, `TRACE`, and `TRACK`." |
+ // "A forbidden method is a method that is a byte case-insensitive match for |
+ // one of `CONNECT`, `TRACE`, and `TRACK`." |
return !XMLHttpRequest::isAllowedHTTPMethod(method); |
} |
bool FetchManager::isUsefulMethod(const String& method) |
{ |
// "A useful method is a method that is not a forbidden method." |
- // "A forbidden method is a method that is a byte case-insensitive match for one of `CONNECT`, `TRACE`, and `TRACK`." |
+ // "A forbidden method is a method that is a byte case-insensitive match for |
+ // one of `CONNECT`, `TRACE`, and `TRACK`." |
return XMLHttpRequest::isAllowedHTTPMethod(method); |
} |