Index: Source/modules/serviceworkers/FetchManager.cpp |
diff --git a/Source/modules/serviceworkers/FetchManager.cpp b/Source/modules/serviceworkers/FetchManager.cpp |
index 13e416c26af123d8ed16bbe52b637ee4b7304fb5..4f56b4710b31d7218d8de2f4beaa070eaf7415ad 100644 |
--- a/Source/modules/serviceworkers/FetchManager.cpp |
+++ b/Source/modules/serviceworkers/FetchManager.cpp |
@@ -5,6 +5,7 @@ |
#include "config.h" |
#include "FetchManager.h" |
+#include "bindings/core/v8/ExceptionState.h" |
#include "bindings/core/v8/ScriptPromiseResolver.h" |
#include "bindings/core/v8/ScriptState.h" |
#include "bindings/core/v8/V8ThrowException.h" |
@@ -16,13 +17,15 @@ |
#include "modules/serviceworkers/Response.h" |
#include "modules/serviceworkers/ResponseInit.h" |
#include "platform/network/ResourceRequest.h" |
+#include "platform/weborigin/SecurityOrigin.h" |
+#include "public/platform/WebServiceWorkerRequest.h" |
#include "wtf/HashSet.h" |
namespace WebCore { |
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); |
@@ -35,20 +38,23 @@ public: |
void cleanup(); |
private: |
+ void performBasicFetch(); |
+ void performNetworkError(); |
+ void performeHTTPFetch(bool, bool); |
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_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) |
@@ -77,15 +83,28 @@ void FetchManager::Loader::didFinishLoading(unsigned long, double) |
blobData->appendFile(filePath); |
// FIXME: Set the ContentType correctly. |
} |
- 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. |
- m_resolver->resolve(Response::create(blob.get(), responseInit)); |
+ 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(); |
} |
@@ -111,13 +130,79 @@ 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); |
+ |
+ // FIXME: 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. [HSTS] |
+ |
+ // FIXME: 2. If request's referrer is not none, set request's referrer to the result of invoking determine request's referrer. [REFERRER] |
+ |
+ // FIXME: 3. If request's synchronous flag is unset and fetch is not invoked recursively, run the remaining steps asynchronously. |
+ |
+ // FIXME: 4. Let response be the value corresponding to the first matching statement: |
+ // - should fetching request be blocked as mixed content returns blocked [MIX] |
+ // - should fetching request be blocked as content security returns blocked [CSP] |
+ // A network error. |
+ |
+ // - 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 ((m_request->mode() != FetchRequestData::CORSMode |
+ && m_request->mode() != FetchRequestData::CORSWithForcedPreflight |
+ && SecurityOrigin::create(m_request->url())->isSameSchemeHostPort(m_request->origin().get())) |
+ || (m_request->url().protocol() == "data" && m_request->sameOriginDataURLFlag()) |
+ || (m_request->url().protocol() == "about")) { |
+ // 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().protocol() != "http" && m_request->url().protocol() != "https") { |
+ // A network error. |
+ performNetworkError(); |
+ return; |
+ } |
+ |
+ // - request's mode is CORS-with-forced-preflight |
+ if (m_request->mode() == FetchRequestData::CORSWithForcedPreflight) { |
+ // 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. |
+ performeHTTPFetch(true, true); |
+ return; |
+ } |
+ |
+ // - 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->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. |
+ performeHTTPFetch(true, true); |
+ 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. |
+ performeHTTPFetch(true, false); |
} |
void FetchManager::Loader::cleanup() |
@@ -131,6 +216,87 @@ 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: |
+ String protocol(m_request->url().protocol()); |
+ if (protocol == "about") { |
+ // If request's url's scheme data is "blank", return a response whose |
+ // header list consist of a single header whose name is `Content-Type` |
+ // and value is `text/html;charset=utf-8`, and body is the empty byte |
+ // sequence. |
+ // FIXME: implement this. |
+ performNetworkError(); |
+ } else if (protocol == "blob") { |
+ // It has been argued this should be handled outside of fetching. |
+ performNetworkError(); |
+ } else if (protocol == "data") { |
+ // If request's method is `GET` and obtaining a resource from request's |
+ // url does not return failure, return a response whose header list |
+ // consist of a single header whose name is `Content-Type` and value is |
+ // the MIME type and parameters returned from obtaining a resource, and |
+ // body is the data returned from obtaining a resource. [DATAURL] |
+ // Otherwise, return a network error. |
+ // FIXME: implement this. |
+ performNetworkError(); |
+ } else if (protocol == "file" || protocol == "ftp") { |
+ // For now, unfortunate as it is, file and ftp URLs are left as an |
+ // exercise for the reader. |
+ // When in doubt, return a network error. |
+ performNetworkError(); |
+ } else if (protocol == "http" || protocol == "https") { |
+ // Return the result of performing an HTTP fetch using request. |
+ performeHTTPFetch(false, false); |
+ } else { |
+ // Return a network error. |
+ performNetworkError(); |
+ } |
+} |
+ |
+void FetchManager::Loader::performNetworkError() |
+{ |
+ failed(); |
+} |
+ |
+void FetchManager::Loader::performeHTTPFetch(bool CORSFlag, bool CORSPreflightFlag) |
+{ |
+ ResourceRequest request(m_request->url()); |
+ request.setDownloadToFile(true); |
+ request.setHTTPMethod(m_request->method()); |
+ |
+ // 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. |
+ // FIXME: Support Referer. |
+ 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)); |
+ } |
+ |
+ |
+ ThreadableLoaderOptions threadableLoaderOptions; |
+ if (CORSPreflightFlag) |
+ threadableLoaderOptions.preflightPolicy = ForcePreflight; |
+ if (CORSFlag) |
+ threadableLoaderOptions.crossOriginRequestPolicy = UseAccessControl; |
+ else |
+ threadableLoaderOptions.crossOriginRequestPolicy = AllowCrossOriginRequests; |
+ |
+ ResourceLoaderOptions resourceLoaderOptions; |
+ resourceLoaderOptions.dataBufferingPolicy = DoNotBufferData; |
+ // 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. |
+ if (m_request->credentials() == FetchRequestData::IncludeCredentials |
+ || (m_request->credentials() == FetchRequestData::SameOriginCredentials && !CORSFlag)) { |
+ resourceLoaderOptions.allowCredentials = AllowStoredCredentials; |
+ } |
+ if (m_request->credentials() == FetchRequestData::IncludeCredentials) |
+ resourceLoaderOptions.credentialsRequested = ClientRequestedCredentials; |
+ |
+ m_loader = ThreadableLoader::create(*m_executionContext, this, request, threadableLoaderOptions, resourceLoaderOptions); |
+} |
+ |
void FetchManager::Loader::failed() |
{ |
if (m_failed) |
@@ -160,16 +326,36 @@ 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; |
} |
+bool FetchManager::isSimpleMethod(const String& method) |
+{ |
+ // A simple method is a method that is `GET`, `HEAD`, or `POST`. |
+ return isOnAccessControlSimpleRequestMethodWhitelist(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`. |
+ 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`. |
+ return XMLHttpRequest::isAllowedHTTPMethod(method); |
+} |
+ |
void FetchManager::onLoaderFinished(Loader* loader) |
{ |
m_loaders.remove(loader); |