Compute fetch response type in case of cross-origin requests
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 28 Jul 2016 08:30:14 +0000 (08:30 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 28 Jul 2016 08:30:14 +0000 (08:30 +0000)
https://bugs.webkit.org/show_bug.cgi?id=158565

Patch by Youenn Fablet <youenn@apple.com> on 2016-07-28
Reviewed by Alex Christensen.

LayoutTests/imported/w3c:

Rebasing fetch API tests as filtering is now done.
Rebasing XHR tests as console messages are no longer available when trying to access non-exposed headers.

* web-platform-tests/XMLHttpRequest/getresponseheader-cookies-and-more-expected.txt:
* web-platform-tests/fetch/api/basic/mode-no-cors-expected.txt:
* web-platform-tests/fetch/api/basic/mode-no-cors-worker-expected.txt:
* web-platform-tests/fetch/api/cors/cors-basic-expected.txt:
* web-platform-tests/fetch/api/cors/cors-filtering-expected.txt:
* web-platform-tests/fetch/api/request/request-cache-expected.txt:

Source/WebCore:

Covered by rebased tests.

Implementing Response filtering based on Response tainting in ResourceResponse.
Refactoring code in FetchHeaders and CrossOriginAccessControl.cpp accordingly.

Computing response tainting in SubresourceLoader for all resources.
This is used by DocumentThreadableLoader which now filters responses accordingly for all its clients including fetch and XHR.

Response tainting notably allows computing the response type and filtering out headers in case of cross origin responses.

Removing the filtering implemented in XMLHttpRequest as this is done before it gets access to the headers.
This is triggering some rebasing in the XHR tests as error messages triggered by trying to access unsafe headers no longer happen.

This filtering currently requires creating a new ResourceResponse object from the one sent from CachedResource.
This is done so as the same ResourceResponse may be reused accross loads and may be filtered differently by given to two different DocumentThreadableLoader
This can be mitigated in the future by changing ThreadableLoaderClient API to pass a ResourceResponse&&.

* Modules/fetch/FetchHeaders.cpp: Moving header checking in HTTParsers.h/.cpp
(WebCore::isForbiddenHeaderName): Deleted.
(WebCore::isForbiddenResponseHeaderName): Deleted.
(WebCore::isSimpleHeader): Deleted.
* loader/CrossOriginAccessControl.cpp:
(WebCore::parseAccessControlExposeHeadersAllowList): Deleted.
* loader/CrossOriginAccessControl.h: Moving header checking in HTTParsers.h/.cpp
* loader/DocumentThreadableLoader.cpp:
(WebCore::DocumentThreadableLoader::responseReceived):
(WebCore::DocumentThreadableLoader::didReceiveResponse): Doing response filtering. Since underlying loaders are
not yet aware that fetch mode may be cors (it is always no-cors currently), the tainting needs to be updated.
(WebCore::DocumentThreadableLoader::loadRequest): Computing response tainting in case of synchronous calls to ensure headers are filtered for synchronous XHR.
* loader/DocumentThreadableLoader.h:
* loader/SubresourceLoader.cpp:
(WebCore::SubresourceLoader::init): Getting origin from its resource and setting response tainting accordingly
(WebCore::SubresourceLoader::willSendRequestInternal): Always calling checkRedirectionCrossOriginAccessControl
to ensure response tainting is computed, even for no-cors resources.
(WebCore::SubresourceLoader::didReceiveResponse):
(WebCore::SubresourceLoader::checkRedirectionCrossOriginAccessControl): Computing response tainting in case of redirection.
* loader/SubresourceLoader.h:
* loader/cache/CachedResource.cpp:
(WebCore::CachedResource::load): Computing resource origin from the HTTP headers or from the document if none is
set in the HTTP headers.
(WebCore::CachedResource::setCrossOrigin): Helper routine to set response tainting.
(WebCore::CachedResource::isCrossOrigin): Helper routine to know whether resource is cross origin
(WebCore::CachedResource::isClean):
(WebCore::CachedResource::setResponse): Removing m_responseType
* loader/cache/CachedResource.h:
(WebCore::CachedResource::responseTainting):
(WebCore::CachedResource::origin):
(WebCore::CachedResource::setOpaqueRedirect): Deleted.
* platform/network/HTTPParsers.cpp: Implementing safe response header checking
(WebCore::parseAccessControlExposeHeadersAllowList):
(WebCore::isForbiddenHeaderName):
(WebCore::isForbiddenResponseHeaderName):
(WebCore::isSimpleHeader):
(WebCore::isCrossOriginSafeHeader):
* platform/network/HTTPParsers.h:
* platform/network/ResourceRequestBase.cpp:
(WebCore::ResourceRequestBase::hasHTTPOrigin): Added.
(WebCore::ResourceRequestBase::clearHTTPOrigin):
* platform/network/ResourceRequestBase.h:
* platform/network/ResourceResponseBase.cpp: Implementation of response filtering.
(WebCore::ResourceResponseBase::filterResponse):
* platform/network/ResourceResponseBase.h:
* xml/XMLHttpRequest.cpp:
(WebCore::isSetCookieHeader): Deleted.
(WebCore::XMLHttpRequest::getAllResponseHeaders): Removing header filtering since DocumentThreadableLoader does it.
(WebCore::XMLHttpRequest::getResponseHeader): Ditto.

LayoutTests:

Rebasing fetch API tests as filtering is now done.
Rebasing XHR tests as console messages are no longer available when trying to access non-exposed headers.

* http/tests/xmlhttprequest/access-control-basic-whitelist-response-headers-expected.txt:
* http/tests/xmlhttprequest/access-control-response-with-expose-headers-expected.txt:
* http/tests/xmlhttprequest/get-dangerous-headers-expected.txt:
* http/tests/xmlhttprequest/getResponseHeader-expected.txt:
* platform/ios-simulator-wk2/imported/w3c/web-platform-tests/fetch/api/basic/mode-no-cors-expected.txt:
* platform/ios-simulator-wk2/imported/w3c/web-platform-tests/fetch/api/basic/mode-no-cors-worker-expected.txt:
* platform/ios-simulator-wk2/imported/w3c/web-platform-tests/fetch/api/cors/cors-basic-expected.txt:
* platform/mac-wk2/imported/w3c/web-platform-tests/fetch/api/basic/mode-no-cors-expected.txt:
* platform/mac-wk2/imported/w3c/web-platform-tests/fetch/api/basic/mode-no-cors-worker-expected.txt:
* platform/mac-wk2/imported/w3c/web-platform-tests/fetch/api/cors/cors-basic-expected.txt:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@203815 268f45cc-cd09-0410-ab3c-d52691b4dbfc

35 files changed:
LayoutTests/ChangeLog
LayoutTests/http/tests/xmlhttprequest/access-control-basic-whitelist-response-headers-expected.txt
LayoutTests/http/tests/xmlhttprequest/access-control-response-with-expose-headers-expected.txt
LayoutTests/http/tests/xmlhttprequest/get-dangerous-headers-expected.txt
LayoutTests/http/tests/xmlhttprequest/getResponseHeader-expected.txt
LayoutTests/imported/w3c/ChangeLog
LayoutTests/imported/w3c/web-platform-tests/XMLHttpRequest/getresponseheader-cookies-and-more-expected.txt
LayoutTests/imported/w3c/web-platform-tests/fetch/api/basic/mode-no-cors-expected.txt
LayoutTests/imported/w3c/web-platform-tests/fetch/api/basic/mode-no-cors-worker-expected.txt
LayoutTests/imported/w3c/web-platform-tests/fetch/api/cors/cors-basic-expected.txt
LayoutTests/imported/w3c/web-platform-tests/fetch/api/cors/cors-filtering-expected.txt
LayoutTests/imported/w3c/web-platform-tests/fetch/api/request/request-cache-expected.txt
LayoutTests/platform/ios-simulator-wk2/imported/w3c/web-platform-tests/fetch/api/basic/mode-no-cors-expected.txt
LayoutTests/platform/ios-simulator-wk2/imported/w3c/web-platform-tests/fetch/api/basic/mode-no-cors-worker-expected.txt
LayoutTests/platform/ios-simulator-wk2/imported/w3c/web-platform-tests/fetch/api/cors/cors-basic-expected.txt
LayoutTests/platform/mac-wk2/imported/w3c/web-platform-tests/fetch/api/basic/mode-no-cors-expected.txt
LayoutTests/platform/mac-wk2/imported/w3c/web-platform-tests/fetch/api/basic/mode-no-cors-worker-expected.txt
LayoutTests/platform/mac-wk2/imported/w3c/web-platform-tests/fetch/api/cors/cors-basic-expected.txt
Source/WebCore/ChangeLog
Source/WebCore/Modules/fetch/FetchHeaders.cpp
Source/WebCore/loader/CrossOriginAccessControl.cpp
Source/WebCore/loader/CrossOriginAccessControl.h
Source/WebCore/loader/DocumentThreadableLoader.cpp
Source/WebCore/loader/DocumentThreadableLoader.h
Source/WebCore/loader/SubresourceLoader.cpp
Source/WebCore/loader/SubresourceLoader.h
Source/WebCore/loader/cache/CachedResource.cpp
Source/WebCore/loader/cache/CachedResource.h
Source/WebCore/platform/network/HTTPParsers.cpp
Source/WebCore/platform/network/HTTPParsers.h
Source/WebCore/platform/network/ResourceRequestBase.cpp
Source/WebCore/platform/network/ResourceRequestBase.h
Source/WebCore/platform/network/ResourceResponseBase.cpp
Source/WebCore/platform/network/ResourceResponseBase.h
Source/WebCore/xml/XMLHttpRequest.cpp

index 7a77be3..46ce701 100644 (file)
@@ -1,3 +1,24 @@
+2016-07-28  Youenn Fablet  <youenn@apple.com>
+
+        Compute fetch response type in case of cross-origin requests
+        https://bugs.webkit.org/show_bug.cgi?id=158565
+
+        Reviewed by Alex Christensen.
+
+        Rebasing fetch API tests as filtering is now done.
+        Rebasing XHR tests as console messages are no longer available when trying to access non-exposed headers.
+
+        * http/tests/xmlhttprequest/access-control-basic-whitelist-response-headers-expected.txt:
+        * http/tests/xmlhttprequest/access-control-response-with-expose-headers-expected.txt:
+        * http/tests/xmlhttprequest/get-dangerous-headers-expected.txt:
+        * http/tests/xmlhttprequest/getResponseHeader-expected.txt:
+        * platform/ios-simulator-wk2/imported/w3c/web-platform-tests/fetch/api/basic/mode-no-cors-expected.txt:
+        * platform/ios-simulator-wk2/imported/w3c/web-platform-tests/fetch/api/basic/mode-no-cors-worker-expected.txt:
+        * platform/ios-simulator-wk2/imported/w3c/web-platform-tests/fetch/api/cors/cors-basic-expected.txt:
+        * platform/mac-wk2/imported/w3c/web-platform-tests/fetch/api/basic/mode-no-cors-expected.txt:
+        * platform/mac-wk2/imported/w3c/web-platform-tests/fetch/api/basic/mode-no-cors-worker-expected.txt:
+        * platform/mac-wk2/imported/w3c/web-platform-tests/fetch/api/cors/cors-basic-expected.txt:
+
 2016-07-27  Ryan Haddad  <ryanhaddad@apple.com>
 
         Marking http/tests/loading/basic-credentials-sent-automatically.html as flaky on mac and ios-sim wk2
index 10646a3..6e676bd 100644 (file)
@@ -1,4 +1,3 @@
-CONSOLE MESSAGE: line 25: Refused to get unsafe header "x-webkit"
 PASS: Response header cache-control allowed.
 PASS: Response header content-language allowed.
 PASS: Response header content-type allowed.
index f18e6a7..080578d 100644 (file)
@@ -1,4 +1,3 @@
-CONSOLE MESSAGE: line 1: Refused to get unsafe header "X-TEST"
 Test for bug 41210: Cross Origin XMLHttpRequest can not expose headers indicated in Access-Control-Expose-Headers HTTP Response Header.
 
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
index 109325f..51d2e2e 100644 (file)
@@ -1,7 +1,3 @@
-CONSOLE MESSAGE: line 15: Refused to get unsafe header "Set-Cookie"
-CONSOLE MESSAGE: line 17: Refused to get unsafe header "set-cookie"
-CONSOLE MESSAGE: line 19: Refused to get unsafe header "Set-Cookie2"
-CONSOLE MESSAGE: line 21: Refused to get unsafe header "set-cookie2"
 Test that getResponseHeader and getAllResponseHeaders cannot be used to get the cookie header fields.
 
 PASS
index 928ac76..415a173 100644 (file)
@@ -1,9 +1,3 @@
-CONSOLE MESSAGE: line 1: Refused to get unsafe header "SeT-COoKie"
-CONSOLE MESSAGE: line 1: Refused to get unsafe header "sEt-coOkIE2"
-CONSOLE MESSAGE: line 1: Refused to get unsafe header "SeT-COoKie"
-CONSOLE MESSAGE: line 1: Refused to get unsafe header "sEt-coOkIE2"
-CONSOLE MESSAGE: line 1: Refused to get unsafe header "SeT-COoKie"
-CONSOLE MESSAGE: line 1: Refused to get unsafe header "sEt-coOkIE2"
 Test the required behavior of XMLHttpRequest.getResponseHeader()
 
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
index ccb877d..ced982f 100644 (file)
@@ -1,3 +1,20 @@
+2016-07-28  Youenn Fablet  <youenn@apple.com>
+
+        Compute fetch response type in case of cross-origin requests
+        https://bugs.webkit.org/show_bug.cgi?id=158565
+
+        Reviewed by Alex Christensen.
+
+        Rebasing fetch API tests as filtering is now done.
+        Rebasing XHR tests as console messages are no longer available when trying to access non-exposed headers.
+
+        * web-platform-tests/XMLHttpRequest/getresponseheader-cookies-and-more-expected.txt:
+        * web-platform-tests/fetch/api/basic/mode-no-cors-expected.txt:
+        * web-platform-tests/fetch/api/basic/mode-no-cors-worker-expected.txt:
+        * web-platform-tests/fetch/api/cors/cors-basic-expected.txt:
+        * web-platform-tests/fetch/api/cors/cors-filtering-expected.txt:
+        * web-platform-tests/fetch/api/request/request-cache-expected.txt:
+
 2016-07-27  Chris Dumez  <cdumez@apple.com>
 
         First parameter to HTMLMediaElement.canPlayType() should be mandatory
index 5d7e379..6cd62c1 100644 (file)
@@ -1,9 +1,3 @@
-CONSOLE MESSAGE: line 23: Refused to get unsafe header "set-cookie"
-CONSOLE MESSAGE: line 24: Refused to get unsafe header "set-cookie2"
-CONSOLE MESSAGE: line 23: Refused to get unsafe header "set-cookie"
-CONSOLE MESSAGE: line 24: Refused to get unsafe header "set-cookie2"
-CONSOLE MESSAGE: line 23: Refused to get unsafe header "set-cookie"
-CONSOLE MESSAGE: line 24: Refused to get unsafe header "set-cookie2"
 
 PASS XMLHttpRequest: getResponseHeader() custom/non-existent headers and cookies 
 
index 121ebe0..3e9fe85 100644 (file)
@@ -1,6 +1,6 @@
 
 PASS Fetch ../resources/top.txt with no-cors mode 
 PASS Fetch http://localhost:8800/fetch/api/resources/top.txt with no-cors mode 
-FAIL Fetch https://localhost:9443/fetch/api/resources/top.txt with no-cors mode assert_equals: Opaque filter: status is 0 expected 0 but got 200
-FAIL Fetch http://localhost:8801/fetch/api/resources/top.txt with no-cors mode assert_equals: Opaque filter: status is 0 expected 0 but got 200
+PASS Fetch https://localhost:9443/fetch/api/resources/top.txt with no-cors mode 
+PASS Fetch http://localhost:8801/fetch/api/resources/top.txt with no-cors mode 
 
index 121ebe0..3e9fe85 100644 (file)
@@ -1,6 +1,6 @@
 
 PASS Fetch ../resources/top.txt with no-cors mode 
 PASS Fetch http://localhost:8800/fetch/api/resources/top.txt with no-cors mode 
-FAIL Fetch https://localhost:9443/fetch/api/resources/top.txt with no-cors mode assert_equals: Opaque filter: status is 0 expected 0 but got 200
-FAIL Fetch http://localhost:8801/fetch/api/resources/top.txt with no-cors mode assert_equals: Opaque filter: status is 0 expected 0 but got 200
+PASS Fetch https://localhost:9443/fetch/api/resources/top.txt with no-cors mode 
+PASS Fetch http://localhost:8801/fetch/api/resources/top.txt with no-cors mode 
 
index ed0d2e0..4debfb4 100644 (file)
@@ -1,17 +1,17 @@
 
-FAIL Same domain different port [no-cors mode] assert_equals: Opaque filter: status is 0 expected 0 but got 200
+PASS Same domain different port [no-cors mode] 
 PASS Same domain different port [server forbid CORS] 
-FAIL Same domain different port [cors mode] assert_equals: CORS response's type is cors expected "cors" but got "basic"
-FAIL Same domain different protocol different port [no-cors mode] assert_equals: Opaque filter: status is 0 expected 0 but got 200
+PASS Same domain different port [cors mode] 
+PASS Same domain different protocol different port [no-cors mode] 
 PASS Same domain different protocol different port [server forbid CORS] 
-FAIL Same domain different protocol different port [cors mode] assert_equals: CORS response's type is cors expected "cors" but got "basic"
-FAIL Cross domain basic usage [no-cors mode] assert_equals: Opaque filter: status is 0 expected 0 but got 200
+PASS Same domain different protocol different port [cors mode] 
+PASS Cross domain basic usage [no-cors mode] 
 PASS Cross domain basic usage [server forbid CORS] 
-FAIL Cross domain basic usage [cors mode] assert_equals: CORS response's type is cors expected "cors" but got "basic"
-FAIL Cross domain different port [no-cors mode] assert_equals: Opaque filter: status is 0 expected 0 but got 200
+PASS Cross domain basic usage [cors mode] 
+PASS Cross domain different port [no-cors mode] 
 PASS Cross domain different port [server forbid CORS] 
-FAIL Cross domain different port [cors mode] assert_equals: CORS response's type is cors expected "cors" but got "basic"
-FAIL Cross domain different protocol [no-cors mode] assert_equals: Opaque filter: status is 0 expected 0 but got 200
+PASS Cross domain different port [cors mode] 
+PASS Cross domain different protocol [no-cors mode] 
 PASS Cross domain different protocol [server forbid CORS] 
-FAIL Cross domain different protocol [cors mode] assert_equals: CORS response's type is cors expected "cors" but got "basic"
+PASS Cross domain different protocol [cors mode] 
 
index 58083e4..abb8b84 100644 (file)
@@ -1,20 +1,20 @@
 
-FAIL CORS filter on Cache-Control header assert_equals: CORS fetch's response has cors type expected "cors" but got "basic"
-FAIL CORS filter on Content-Language header assert_equals: CORS fetch's response has cors type expected "cors" but got "basic"
-FAIL CORS filter on Content-Type header assert_equals: CORS fetch's response has cors type expected "cors" but got "basic"
-FAIL CORS filter on Expires header assert_equals: CORS fetch's response has cors type expected "cors" but got "basic"
-FAIL CORS filter on Last-Modified header assert_equals: CORS fetch's response has cors type expected "cors" but got "basic"
-FAIL CORS filter on Pragma header assert_equals: CORS fetch's response has cors type expected "cors" but got "basic"
-FAIL CORS filter on Age header assert_equals: CORS fetch's response has cors type expected "cors" but got "basic"
-FAIL CORS filter on Server header assert_equals: CORS fetch's response has cors type expected "cors" but got "basic"
-FAIL CORS filter on Warning header assert_equals: CORS fetch's response has cors type expected "cors" but got "basic"
-FAIL CORS filter on Content-Length header assert_equals: CORS fetch's response has cors type expected "cors" but got "basic"
-FAIL CORS filter on Set-Cookie header assert_equals: CORS fetch's response has cors type expected "cors" but got "basic"
-FAIL CORS filter on Set-Cookie2 header assert_equals: CORS fetch's response has cors type expected "cors" but got "basic"
-FAIL CORS filter on Age header, header is exposed assert_equals: CORS fetch's response has cors type expected "cors" but got "basic"
-FAIL CORS filter on Server header, header is exposed assert_equals: CORS fetch's response has cors type expected "cors" but got "basic"
-FAIL CORS filter on Warning header, header is exposed assert_equals: CORS fetch's response has cors type expected "cors" but got "basic"
-FAIL CORS filter on Content-Length header, header is exposed assert_equals: CORS fetch's response has cors type expected "cors" but got "basic"
-FAIL CORS filter on Set-Cookie header, header is exposed assert_equals: CORS fetch's response has cors type expected "cors" but got "basic"
-FAIL CORS filter on Set-Cookie2 header, header is exposed assert_equals: CORS fetch's response has cors type expected "cors" but got "basic"
+PASS CORS filter on Cache-Control header 
+PASS CORS filter on Content-Language header 
+PASS CORS filter on Content-Type header 
+PASS CORS filter on Expires header 
+PASS CORS filter on Last-Modified header 
+PASS CORS filter on Pragma header 
+PASS CORS filter on Age header 
+PASS CORS filter on Server header 
+PASS CORS filter on Warning header 
+PASS CORS filter on Content-Length header 
+PASS CORS filter on Set-Cookie header 
+PASS CORS filter on Set-Cookie2 header 
+PASS CORS filter on Age header, header is exposed 
+PASS CORS filter on Server header, header is exposed 
+PASS CORS filter on Warning header, header is exposed 
+PASS CORS filter on Content-Length header, header is exposed 
+PASS CORS filter on Set-Cookie header, header is exposed 
+PASS CORS filter on Set-Cookie2 header, header is exposed 
 
index f99a969..5e779cc 100644 (file)
@@ -37,12 +37,12 @@ PASS RequestCache "only-if-cached" (with "same-origin") does not follow redirect
 PASS RequestCache "only-if-cached" (with "same-origin") does not follow redirects across origins and rejects with date and fresh response 
 PASS RequestCache "only-if-cached" (with "same-origin") does not follow redirects across origins and rejects with Etag and stale response 
 PASS RequestCache "only-if-cached" (with "same-origin") does not follow redirects across origins and rejects with date and stale response 
-FAIL RequestCache "no-store" mode does not check the cache for previously cached content and goes to the network regardless with Etag and stale response assert_equals: expected (undefined) undefined but got (string) "\"0.5708867760543104\""
-FAIL RequestCache "no-store" mode does not check the cache for previously cached content and goes to the network regardless with date and stale response assert_equals: expected (undefined) undefined but got (string) "Tue, 26 Jul 2016 18:03:51 GMT"
+FAIL RequestCache "no-store" mode does not check the cache for previously cached content and goes to the network regardless with Etag and stale response assert_equals: expected (undefined) undefined but got (string) "\"0.5142751701087829\""
+FAIL RequestCache "no-store" mode does not check the cache for previously cached content and goes to the network regardless with date and stale response assert_equals: expected (undefined) undefined but got (string) "Wed, 27 Jul 2016 15:14:16 GMT"
 FAIL RequestCache "no-store" mode does not check the cache for previously cached content and goes to the network regardless with Etag and fresh response assert_equals: expected 2 but got 1
 FAIL RequestCache "no-store" mode does not check the cache for previously cached content and goes to the network regardless with date and fresh response assert_equals: expected 2 but got 1
-FAIL RequestCache "no-store" mode does not store the response in the cache with Etag and stale response assert_equals: expected (undefined) undefined but got (string) "\"0.03236160265570209\""
-FAIL RequestCache "no-store" mode does not store the response in the cache with date and stale response assert_equals: expected (undefined) undefined but got (string) "Tue, 26 Jul 2016 18:03:51 GMT"
+FAIL RequestCache "no-store" mode does not store the response in the cache with Etag and stale response assert_equals: expected (undefined) undefined but got (string) "\"0.8439838765231941\""
+FAIL RequestCache "no-store" mode does not store the response in the cache with date and stale response assert_equals: expected (undefined) undefined but got (string) "Wed, 27 Jul 2016 15:14:16 GMT"
 FAIL RequestCache "no-store" mode does not store the response in the cache with Etag and fresh response assert_equals: expected 2 but got 1
 FAIL RequestCache "no-store" mode does not store the response in the cache with date and fresh response assert_equals: expected 2 but got 1
 PASS RequestCache "default" mode with an If-Modified-Since header is treated similarly to "no-store" with Etag and stale response 
@@ -89,16 +89,16 @@ PASS Responses with the "Cache-Control: no-store" header are not stored in the c
 PASS Responses with the "Cache-Control: no-store" header are not stored in the cache with date and stale response 
 PASS Responses with the "Cache-Control: no-store" header are not stored in the cache with Etag and fresh response 
 PASS Responses with the "Cache-Control: no-store" header are not stored in the cache with date and fresh response 
-FAIL RequestCache "reload" mode does not check the cache for previously cached content and goes to the network regardless with Etag and stale response assert_equals: expected (undefined) undefined but got (string) "\"0.32037580965802115\""
-FAIL RequestCache "reload" mode does not check the cache for previously cached content and goes to the network regardless with date and stale response assert_equals: expected (undefined) undefined but got (string) "Tue, 26 Jul 2016 18:03:51 GMT"
+FAIL RequestCache "reload" mode does not check the cache for previously cached content and goes to the network regardless with Etag and stale response assert_equals: expected (undefined) undefined but got (string) "\"0.7100561973865757\""
+FAIL RequestCache "reload" mode does not check the cache for previously cached content and goes to the network regardless with date and stale response assert_equals: expected (undefined) undefined but got (string) "Wed, 27 Jul 2016 15:14:16 GMT"
 FAIL RequestCache "reload" mode does not check the cache for previously cached content and goes to the network regardless with Etag and fresh response assert_equals: expected 2 but got 1
 FAIL RequestCache "reload" mode does not check the cache for previously cached content and goes to the network regardless with date and fresh response assert_equals: expected 2 but got 1
 PASS RequestCache "reload" mode does store the response in the cache with Etag and stale response 
 PASS RequestCache "reload" mode does store the response in the cache with date and stale response 
 PASS RequestCache "reload" mode does store the response in the cache with Etag and fresh response 
 PASS RequestCache "reload" mode does store the response in the cache with date and fresh response 
-FAIL RequestCache "reload" mode does store the response in the cache even if a previous response is already stored with Etag and stale response assert_equals: expected (undefined) undefined but got (string) "\"0.702774010433134\""
-FAIL RequestCache "reload" mode does store the response in the cache even if a previous response is already stored with date and stale response assert_equals: expected (undefined) undefined but got (string) "Tue, 26 Jul 2016 18:03:51 GMT"
+FAIL RequestCache "reload" mode does store the response in the cache even if a previous response is already stored with Etag and stale response assert_equals: expected (undefined) undefined but got (string) "\"0.45824924441966697\""
+FAIL RequestCache "reload" mode does store the response in the cache even if a previous response is already stored with date and stale response assert_equals: expected (undefined) undefined but got (string) "Wed, 27 Jul 2016 15:14:16 GMT"
 FAIL RequestCache "reload" mode does store the response in the cache even if a previous response is already stored with Etag and fresh response assert_equals: expected 2 but got 1
 FAIL RequestCache "reload" mode does store the response in the cache even if a previous response is already stored with date and fresh response assert_equals: expected 2 but got 1
 
index b34c75a..86ba6c8 100644 (file)
@@ -2,5 +2,5 @@
 PASS Fetch ../resources/top.txt with no-cors mode 
 PASS Fetch http://localhost:8800/fetch/api/resources/top.txt with no-cors mode 
 FAIL Fetch https://localhost:9443/fetch/api/resources/top.txt with no-cors mode promise_test: Unhandled rejection with value: object "TypeError: Type error"
-FAIL Fetch http://localhost:8801/fetch/api/resources/top.txt with no-cors mode assert_equals: Opaque filter: status is 0 expected 0 but got 200
+PASS Fetch http://localhost:8801/fetch/api/resources/top.txt with no-cors mode 
 
index b34c75a..86ba6c8 100644 (file)
@@ -2,5 +2,5 @@
 PASS Fetch ../resources/top.txt with no-cors mode 
 PASS Fetch http://localhost:8800/fetch/api/resources/top.txt with no-cors mode 
 FAIL Fetch https://localhost:9443/fetch/api/resources/top.txt with no-cors mode promise_test: Unhandled rejection with value: object "TypeError: Type error"
-FAIL Fetch http://localhost:8801/fetch/api/resources/top.txt with no-cors mode assert_equals: Opaque filter: status is 0 expected 0 but got 200
+PASS Fetch http://localhost:8801/fetch/api/resources/top.txt with no-cors mode 
 
index a8a822a..3e46cf6 100644 (file)
@@ -1,16 +1,16 @@
 
-FAIL Same domain different port [no-cors mode] assert_equals: Opaque filter: status is 0 expected 0 but got 200
+PASS Same domain different port [no-cors mode] 
 PASS Same domain different port [server forbid CORS] 
-FAIL Same domain different port [cors mode] assert_equals: CORS response's type is cors expected "cors" but got "basic"
+PASS Same domain different port [cors mode] 
 FAIL Same domain different protocol different port [no-cors mode] promise_test: Unhandled rejection with value: object "TypeError: Type error"
 PASS Same domain different protocol different port [server forbid CORS] 
 FAIL Same domain different protocol different port [cors mode] promise_test: Unhandled rejection with value: object "TypeError: Type error"
-FAIL Cross domain basic usage [no-cors mode] assert_equals: Opaque filter: status is 0 expected 0 but got 200
+PASS Cross domain basic usage [no-cors mode] 
 PASS Cross domain basic usage [server forbid CORS] 
-FAIL Cross domain basic usage [cors mode] assert_equals: CORS response's type is cors expected "cors" but got "basic"
-FAIL Cross domain different port [no-cors mode] assert_equals: Opaque filter: status is 0 expected 0 but got 200
+PASS Cross domain basic usage [cors mode] 
+PASS Cross domain different port [no-cors mode] 
 PASS Cross domain different port [server forbid CORS] 
-FAIL Cross domain different port [cors mode] assert_equals: CORS response's type is cors expected "cors" but got "basic"
+PASS Cross domain different port [cors mode] 
 FAIL Cross domain different protocol [no-cors mode] promise_test: Unhandled rejection with value: object "TypeError: Type error"
 PASS Cross domain different protocol [server forbid CORS] 
 FAIL Cross domain different protocol [cors mode] promise_test: Unhandled rejection with value: object "TypeError: Type error"
index b34c75a..86ba6c8 100644 (file)
@@ -2,5 +2,5 @@
 PASS Fetch ../resources/top.txt with no-cors mode 
 PASS Fetch http://localhost:8800/fetch/api/resources/top.txt with no-cors mode 
 FAIL Fetch https://localhost:9443/fetch/api/resources/top.txt with no-cors mode promise_test: Unhandled rejection with value: object "TypeError: Type error"
-FAIL Fetch http://localhost:8801/fetch/api/resources/top.txt with no-cors mode assert_equals: Opaque filter: status is 0 expected 0 but got 200
+PASS Fetch http://localhost:8801/fetch/api/resources/top.txt with no-cors mode 
 
index b34c75a..86ba6c8 100644 (file)
@@ -2,5 +2,5 @@
 PASS Fetch ../resources/top.txt with no-cors mode 
 PASS Fetch http://localhost:8800/fetch/api/resources/top.txt with no-cors mode 
 FAIL Fetch https://localhost:9443/fetch/api/resources/top.txt with no-cors mode promise_test: Unhandled rejection with value: object "TypeError: Type error"
-FAIL Fetch http://localhost:8801/fetch/api/resources/top.txt with no-cors mode assert_equals: Opaque filter: status is 0 expected 0 but got 200
+PASS Fetch http://localhost:8801/fetch/api/resources/top.txt with no-cors mode 
 
index a8a822a..3e46cf6 100644 (file)
@@ -1,16 +1,16 @@
 
-FAIL Same domain different port [no-cors mode] assert_equals: Opaque filter: status is 0 expected 0 but got 200
+PASS Same domain different port [no-cors mode] 
 PASS Same domain different port [server forbid CORS] 
-FAIL Same domain different port [cors mode] assert_equals: CORS response's type is cors expected "cors" but got "basic"
+PASS Same domain different port [cors mode] 
 FAIL Same domain different protocol different port [no-cors mode] promise_test: Unhandled rejection with value: object "TypeError: Type error"
 PASS Same domain different protocol different port [server forbid CORS] 
 FAIL Same domain different protocol different port [cors mode] promise_test: Unhandled rejection with value: object "TypeError: Type error"
-FAIL Cross domain basic usage [no-cors mode] assert_equals: Opaque filter: status is 0 expected 0 but got 200
+PASS Cross domain basic usage [no-cors mode] 
 PASS Cross domain basic usage [server forbid CORS] 
-FAIL Cross domain basic usage [cors mode] assert_equals: CORS response's type is cors expected "cors" but got "basic"
-FAIL Cross domain different port [no-cors mode] assert_equals: Opaque filter: status is 0 expected 0 but got 200
+PASS Cross domain basic usage [cors mode] 
+PASS Cross domain different port [no-cors mode] 
 PASS Cross domain different port [server forbid CORS] 
-FAIL Cross domain different port [cors mode] assert_equals: CORS response's type is cors expected "cors" but got "basic"
+PASS Cross domain different port [cors mode] 
 FAIL Cross domain different protocol [no-cors mode] promise_test: Unhandled rejection with value: object "TypeError: Type error"
 PASS Cross domain different protocol [server forbid CORS] 
 FAIL Cross domain different protocol [cors mode] promise_test: Unhandled rejection with value: object "TypeError: Type error"
index 6412e7d..742ac67 100644 (file)
@@ -1,3 +1,77 @@
+2016-07-28  Youenn Fablet  <youenn@apple.com>
+
+        Compute fetch response type in case of cross-origin requests
+        https://bugs.webkit.org/show_bug.cgi?id=158565
+
+        Reviewed by Alex Christensen.
+
+        Covered by rebased tests.
+
+        Implementing Response filtering based on Response tainting in ResourceResponse.
+        Refactoring code in FetchHeaders and CrossOriginAccessControl.cpp accordingly.
+
+        Computing response tainting in SubresourceLoader for all resources.
+        This is used by DocumentThreadableLoader which now filters responses accordingly for all its clients including fetch and XHR.
+
+        Response tainting notably allows computing the response type and filtering out headers in case of cross origin responses.
+
+        Removing the filtering implemented in XMLHttpRequest as this is done before it gets access to the headers.
+        This is triggering some rebasing in the XHR tests as error messages triggered by trying to access unsafe headers no longer happen.
+
+        This filtering currently requires creating a new ResourceResponse object from the one sent from CachedResource.
+        This is done so as the same ResourceResponse may be reused accross loads and may be filtered differently by given to two different DocumentThreadableLoader
+        This can be mitigated in the future by changing ThreadableLoaderClient API to pass a ResourceResponse&&.
+
+        * Modules/fetch/FetchHeaders.cpp: Moving header checking in HTTParsers.h/.cpp
+        (WebCore::isForbiddenHeaderName): Deleted.
+        (WebCore::isForbiddenResponseHeaderName): Deleted.
+        (WebCore::isSimpleHeader): Deleted.
+        * loader/CrossOriginAccessControl.cpp:
+        (WebCore::parseAccessControlExposeHeadersAllowList): Deleted.
+        * loader/CrossOriginAccessControl.h: Moving header checking in HTTParsers.h/.cpp
+        * loader/DocumentThreadableLoader.cpp:
+        (WebCore::DocumentThreadableLoader::responseReceived):
+        (WebCore::DocumentThreadableLoader::didReceiveResponse): Doing response filtering. Since underlying loaders are
+        not yet aware that fetch mode may be cors (it is always no-cors currently), the tainting needs to be updated.
+        (WebCore::DocumentThreadableLoader::loadRequest): Computing response tainting in case of synchronous calls to ensure headers are filtered for synchronous XHR.
+        * loader/DocumentThreadableLoader.h:
+        * loader/SubresourceLoader.cpp:
+        (WebCore::SubresourceLoader::init): Getting origin from its resource and setting response tainting accordingly
+        (WebCore::SubresourceLoader::willSendRequestInternal): Always calling checkRedirectionCrossOriginAccessControl
+        to ensure response tainting is computed, even for no-cors resources.
+        (WebCore::SubresourceLoader::didReceiveResponse):
+        (WebCore::SubresourceLoader::checkRedirectionCrossOriginAccessControl): Computing response tainting in case of redirection.
+        * loader/SubresourceLoader.h:
+        * loader/cache/CachedResource.cpp:
+        (WebCore::CachedResource::load): Computing resource origin from the HTTP headers or from the document if none is
+        set in the HTTP headers.
+        (WebCore::CachedResource::setCrossOrigin): Helper routine to set response tainting.
+        (WebCore::CachedResource::isCrossOrigin): Helper routine to know whether resource is cross origin
+        (WebCore::CachedResource::isClean):
+        (WebCore::CachedResource::setResponse): Removing m_responseType
+        * loader/cache/CachedResource.h:
+        (WebCore::CachedResource::responseTainting):
+        (WebCore::CachedResource::origin):
+        (WebCore::CachedResource::setOpaqueRedirect): Deleted.
+        * platform/network/HTTPParsers.cpp: Implementing safe response header checking
+        (WebCore::parseAccessControlExposeHeadersAllowList):
+        (WebCore::isForbiddenHeaderName):
+        (WebCore::isForbiddenResponseHeaderName):
+        (WebCore::isSimpleHeader):
+        (WebCore::isCrossOriginSafeHeader):
+        * platform/network/HTTPParsers.h:
+        * platform/network/ResourceRequestBase.cpp:
+        (WebCore::ResourceRequestBase::hasHTTPOrigin): Added.
+        (WebCore::ResourceRequestBase::clearHTTPOrigin):
+        * platform/network/ResourceRequestBase.h:
+        * platform/network/ResourceResponseBase.cpp: Implementation of response filtering.
+        (WebCore::ResourceResponseBase::filterResponse):
+        * platform/network/ResourceResponseBase.h:
+        * xml/XMLHttpRequest.cpp:
+        (WebCore::isSetCookieHeader): Deleted.
+        (WebCore::XMLHttpRequest::getAllResponseHeaders): Removing header filtering since DocumentThreadableLoader does it.
+        (WebCore::XMLHttpRequest::getResponseHeader): Ditto.
+
 2016-07-27  Romain Bellessort  <romain.bellessort@crf.canon.fr>
 
         [Streams API] Use makeThisTypeError in ReadableStreamDefaultReader.js
index 67d766a..0308d4b 100644 (file)
 
 namespace WebCore {
 
-// FIXME: Optimize these routines for HTTPHeaderMap keys and/or refactor them with XMLHttpRequest code.
-static bool isForbiddenHeaderName(const String& name)
-{
-    HTTPHeaderName headerName;
-    if (findHTTPHeaderName(name, headerName)) {
-        switch (headerName) {
-        case HTTPHeaderName::AcceptCharset:
-        case HTTPHeaderName::AcceptEncoding:
-        case HTTPHeaderName::AccessControlRequestHeaders:
-        case HTTPHeaderName::AccessControlRequestMethod:
-        case HTTPHeaderName::Connection:
-        case HTTPHeaderName::ContentLength:
-        case HTTPHeaderName::Cookie:
-        case HTTPHeaderName::Cookie2:
-        case HTTPHeaderName::Date:
-        case HTTPHeaderName::DNT:
-        case HTTPHeaderName::Expect:
-        case HTTPHeaderName::Host:
-        case HTTPHeaderName::KeepAlive:
-        case HTTPHeaderName::Origin:
-        case HTTPHeaderName::Referer:
-        case HTTPHeaderName::TE:
-        case HTTPHeaderName::Trailer:
-        case HTTPHeaderName::TransferEncoding:
-        case HTTPHeaderName::Upgrade:
-        case HTTPHeaderName::Via:
-            return true;
-        default:
-            break;
-        }
-    }
-    return startsWithLettersIgnoringASCIICase(name, "sec-") || startsWithLettersIgnoringASCIICase(name, "proxy-");
-}
-
-static bool isForbiddenResponseHeaderName(const String& name)
-{
-    return equalLettersIgnoringASCIICase(name, "set-cookie") || equalLettersIgnoringASCIICase(name, "set-cookie2");
-}
-
-static bool isSimpleHeader(const String& name, const String& value)
-{
-    HTTPHeaderName headerName;
-    if (!findHTTPHeaderName(name, headerName))
-        return false;
-    switch (headerName) {
-    case HTTPHeaderName::Accept:
-    case HTTPHeaderName::AcceptLanguage:
-    case HTTPHeaderName::ContentLanguage:
-        return true;
-    case HTTPHeaderName::ContentType: {
-        String mimeType = extractMIMETypeFromMediaType(value);
-        return equalLettersIgnoringASCIICase(mimeType, "application/x-www-form-urlencoded") || equalLettersIgnoringASCIICase(mimeType, "multipart/form-data") || equalLettersIgnoringASCIICase(mimeType, "text/plain");
-    }
-    default:
-        return false;
-    }
-}
-
 static bool canWriteHeader(const String& name, const String& value, FetchHeaders::Guard guard, ExceptionCode& ec)
 {
     if (!isValidHTTPToken(name) || !isValidHTTPHeaderValue(value)) {
index dc7570a..d2505bc 100644 (file)
@@ -180,15 +180,4 @@ bool passesAccessControlCheck(const ResourceResponse& response, StoredCredential
     return true;
 }
 
-void parseAccessControlExposeHeadersAllowList(const String& headerValue, HTTPHeaderSet& headerSet)
-{
-    Vector<String> headers;
-    headerValue.split(',', false, headers);
-    for (auto& header : headers) {
-        String strippedHeader = header.stripWhiteSpace();
-        if (!strippedHeader.isEmpty())
-            headerSet.add(strippedHeader);
-    }
-}
-
 } // namespace WebCore
index 26c497d..b7239dc 100644 (file)
 
 #include "ResourceHandleTypes.h"
 #include <wtf/Forward.h>
-#include <wtf/HashSet.h>
-#include <wtf/text/StringHash.h>
 
 namespace WebCore {
 
-typedef HashSet<String, ASCIICaseInsensitiveHash> HTTPHeaderSet;
-
 class HTTPHeaderMap;
 enum class HTTPHeaderName;
 class ResourceRequest;
@@ -55,7 +51,6 @@ bool isValidCrossOriginRedirectionURL(const URL&);
 void cleanRedirectedRequestForAccessControl(ResourceRequest&);
 
 bool passesAccessControlCheck(const ResourceResponse&, StoredCredentials, SecurityOrigin&, String& errorDescription);
-void parseAccessControlExposeHeadersAllowList(const String& headerValue, HTTPHeaderSet&);
 
 } // namespace WebCore
 
index 6e699cf..a16b9c9 100644 (file)
@@ -268,10 +268,10 @@ void DocumentThreadableLoader::dataSent(CachedResource* resource, unsigned long
 void DocumentThreadableLoader::responseReceived(CachedResource* resource, const ResourceResponse& response)
 {
     ASSERT_UNUSED(resource, resource == m_resource);
-    didReceiveResponse(m_resource->identifier(), response);
+    didReceiveResponse(m_resource->identifier(), response, m_resource->responseTainting());
 }
 
-void DocumentThreadableLoader::didReceiveResponse(unsigned long identifier, const ResourceResponse& response)
+void DocumentThreadableLoader::didReceiveResponse(unsigned long identifier, const ResourceResponse& response, ResourceResponse::Tainting tainting)
 {
     ASSERT(m_client);
 
@@ -283,7 +283,16 @@ void DocumentThreadableLoader::didReceiveResponse(unsigned long identifier, cons
         }
     }
 
-    m_client->didReceiveResponse(identifier, response);
+    ASSERT(response.type() != ResourceResponse::Type::Error);
+    if (response.type() == ResourceResponse::Type::Default) {
+        // FIXME: To be removed once the real fetch mode is passed to underlying loaders.
+        if (options().mode == FetchOptions::Mode::Cors && tainting == ResourceResponse::Tainting::Opaque)
+            tainting = ResourceResponse::Tainting::Cors;
+        m_client->didReceiveResponse(identifier, ResourceResponse::filterResponse(response, tainting));
+    } else {
+        ASSERT(response.isNull() && response.type() == ResourceResponse::Type::Opaqueredirect);
+        m_client->didReceiveResponse(identifier, response);
+    }
 }
 
 void DocumentThreadableLoader::dataReceived(CachedResource* resource, const char* data, int dataLength)
@@ -386,7 +395,7 @@ void DocumentThreadableLoader::loadRequest(const ResourceRequest& request, Secur
         if (requestURL.isLocalFile()) {
             // We don't want XMLHttpRequest to raise an exception for file:// resources, see <rdar://problem/4962298>.
             // FIXME: XMLHttpRequest quirks should be in XMLHttpRequest code, not in DocumentThreadableLoader.cpp.
-            didReceiveResponse(identifier, response);
+            didReceiveResponse(identifier, response, ResourceResponse::Tainting::Basic);
             didFinishLoading(identifier, 0.0);
             return;
         }
@@ -409,7 +418,10 @@ void DocumentThreadableLoader::loadRequest(const ResourceRequest& request, Secur
         }
     }
 
-    didReceiveResponse(identifier, response);
+    ResourceResponse::Tainting tainting = ResourceResponse::Tainting::Basic;
+    if (!m_sameOriginRequest)
+        tainting = m_options.mode == FetchOptions::Mode::Cors ? ResourceResponse::Tainting::Cors : ResourceResponse::Tainting::Opaque;
+    didReceiveResponse(identifier, response, tainting);
 
     if (data)
         didReceiveData(identifier, data->data(), data->size());
index 87c8a8b..51f83d4 100644 (file)
@@ -32,6 +32,7 @@
 #define DocumentThreadableLoader_h
 
 #include "CrossOriginPreflightChecker.h"
+#include "ResourceResponse.h"
 #include "SecurityOrigin.h"
 #include "ThreadableLoader.h"
 
@@ -81,7 +82,7 @@ namespace WebCore {
         void redirectReceived(CachedResource*, ResourceRequest&, const ResourceResponse&) override;
         void notifyFinished(CachedResource*) override;
 
-        void didReceiveResponse(unsigned long identifier, const ResourceResponse&);
+        void didReceiveResponse(unsigned long identifier, const ResourceResponse&, ResourceResponse::Tainting);
         void didReceiveData(unsigned long identifier, const char* data, int dataLength);
         void didFinishLoading(unsigned long identifier, double finishTime);
         void didFail(unsigned long identifier, const ResourceError&);
index ef288f2..676a239 100644 (file)
@@ -6,13 +6,13 @@
  * are met:
  *
  * 1.  Redistributions of source code must retain the above copyright
- *     notice, this list of conditions and the following disclaimer. 
+ *     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. 
+ *     documentation and/or other materials provided with the distribution.
  * 3.  Neither the name of Apple 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. 
+ *     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
@@ -151,8 +151,10 @@ bool SubresourceLoader::init(const ResourceRequest& request)
     // FIXME: https://bugs.webkit.org/show_bug.cgi?id=155633.
     // SubresourceLoader could use the document origin as a default and set PotentiallyCrossOriginEnabled requests accordingly.
     // This would simplify resource loader users as they would only need to set fetch mode to Cors.
-    if (options().mode == FetchOptions::Mode::Cors)
-        m_origin = SecurityOrigin::createFromString(request.httpOrigin());
+    m_origin = m_resource->origin();
+    // https://fetch.spec.whatwg.org/#main-fetch, step 11, data URL here is considered not cross origin.
+    if (!request.url().protocolIsData() && m_origin && !m_origin->canRequest(request.url()))
+        m_resource->setCrossOrigin();
 
     return true;
 }
@@ -180,8 +182,10 @@ void SubresourceLoader::willSendRequestInternal(ResourceRequest& newRequest, con
                 cancel();
                 return;
             }
-            m_resource->setOpaqueRedirect();
-            m_resource->responseReceived({ });
+
+            ResourceResponse opaqueRedirectedResponse;
+            opaqueRedirectedResponse.setType(ResourceResponse::Type::Opaqueredirect);
+            m_resource->responseReceived(opaqueRedirectedResponse);
             didFinishLoading(currentTime());
             return;
         }
@@ -202,7 +206,7 @@ void SubresourceLoader::willSendRequestInternal(ResourceRequest& newRequest, con
             return;
         }
 
-        if (options().mode == FetchOptions::Mode::Cors && !checkCrossOriginAccessControl(request(), redirectResponse, newRequest)) {
+        if (!checkRedirectionCrossOriginAccessControl(request(), redirectResponse, newRequest)) {
             cancel();
             return;
         }
@@ -295,8 +299,8 @@ void SubresourceLoader::didReceiveResponse(const ResourceResponse& response)
         // The resource data will change as the next part is loaded, so we need to make a copy.
         m_resource->finishLoading(buffer->copy().ptr());
         clearResourceData();
-        // Since a subresource loader does not load multipart sections progressively, data was delivered to the loader all at once.        
-        // After the first multipart section is complete, signal to delegates that this load is "finished" 
+        // Since a subresource loader does not load multipart sections progressively, data was delivered to the loader all at once.
+        // After the first multipart section is complete, signal to delegates that this load is "finished"
         m_documentLoader->subresourceLoaderFinishedLoadingOnePart(this);
         didFinishLoadingOnePart(0);
     }
@@ -396,11 +400,19 @@ static void logResourceLoaded(Frame* frame, CachedResource::Type type)
     frame->page()->diagnosticLoggingClient().logDiagnosticMessageWithValue(DiagnosticLoggingKeys::resourceKey(), DiagnosticLoggingKeys::loadedKey(), resourceType, ShouldSample::Yes);
 }
 
-bool SubresourceLoader::checkCrossOriginAccessControl(const ResourceRequest& previousRequest, const ResourceResponse& redirectResponse, ResourceRequest& newRequest)
+bool SubresourceLoader::checkRedirectionCrossOriginAccessControl(const ResourceRequest& previousRequest, const ResourceResponse& redirectResponse, ResourceRequest& newRequest)
 {
-    if (m_origin->canRequest(newRequest.url()))
+    ASSERT(options().mode != FetchOptions::Mode::SameOrigin);
+
+    bool shouldCheckCrossOrigin = options().mode == FetchOptions::Mode::Cors && m_resource->isCrossOrigin();
+
+    if (!(m_origin && m_origin->canRequest(newRequest.url())))
+        m_resource->setCrossOrigin();
+
+    if (!shouldCheckCrossOrigin)
         return true;
 
+    ASSERT(m_origin);
     String errorDescription;
     bool responsePassesCORS = m_origin->canRequest(previousRequest.url())
         || passesAccessControlCheck(redirectResponse, options().allowCredentials(), *m_origin, errorDescription);
index 5ec7311..31c4b3e 100644 (file)
@@ -6,13 +6,13 @@
  * are met:
  *
  * 1.  Redistributions of source code must retain the above copyright
- *     notice, this list of conditions and the following disclaimer. 
+ *     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. 
+ *     documentation and/or other materials provided with the distribution.
  * 3.  Neither the name of Apple 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. 
+ *     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
@@ -91,7 +91,7 @@ private:
 #endif
 
     bool checkForHTTPStatusCodeError();
-    bool checkCrossOriginAccessControl(const ResourceRequest&, const ResourceResponse&, ResourceRequest& newRequest);
+    bool checkRedirectionCrossOriginAccessControl(const ResourceRequest&, const ResourceResponse&, ResourceRequest& newRequest);
 
     void didReceiveDataOrBuffer(const char*, int, RefPtr<SharedBuffer>&&, long long encodedDataLength, DataPayloadType);
 
index ddaa491..dd57875 100644 (file)
@@ -281,7 +281,14 @@ void CachedResource::load(CachedResourceLoader& cachedResourceLoader, const Reso
 #endif
     m_resourceRequest.setPriority(loadPriority());
 
-    addAdditionalRequestHeaders(cachedResourceLoader);
+    if (type() != MainResource) {
+        if (m_resourceRequest.hasHTTPOrigin())
+            m_origin = SecurityOrigin::createFromString(m_resourceRequest.httpOrigin());
+        else
+            m_origin = cachedResourceLoader.document()->securityOrigin();
+        ASSERT(m_origin);
+        addAdditionalRequestHeaders(cachedResourceLoader);
+    }
 
     // FIXME: It's unfortunate that the cache layer and below get to know anything about fragment identifiers.
     // We should look into removing the expectation of that knowledge from the platform network stacks.
@@ -367,6 +374,23 @@ bool CachedResource::passesSameOriginPolicyCheck(SecurityOrigin& securityOrigin)
     return passesAccessControlCheck(securityOrigin);
 }
 
+void CachedResource::setCrossOrigin()
+{
+    ASSERT(m_options.mode != FetchOptions::Mode::SameOrigin);
+    m_responseTainting = (m_options.mode == FetchOptions::Mode::Cors) ? ResourceResponse::Tainting::Cors : ResourceResponse::Tainting::Opaque;
+}
+
+bool CachedResource::isCrossOrigin() const
+{
+    return m_responseTainting != ResourceResponse::Tainting::Basic;
+}
+
+bool CachedResource::isClean() const
+{
+    // https://html.spec.whatwg.org/multipage/infrastructure.html#cors-same-origin
+    return !loadFailedOrCanceled() && m_responseTainting != ResourceResponse::Tainting::Opaque;
+}
+
 bool CachedResource::isExpired() const
 {
     if (m_response.isNull())
@@ -426,8 +450,8 @@ const ResourceResponse& CachedResource::responseForSameOriginPolicyChecks() cons
 
 void CachedResource::setResponse(const ResourceResponse& response)
 {
+    ASSERT(m_response.type() == ResourceResponse::Type::Default);
     m_response = response;
-    m_response.setType(m_responseType);
     m_response.setRedirected(m_redirectChainCacheStatus.status != RedirectChainCacheStatus::NoRedirection);
 
     m_varyingHeaderValues = collectVaryingRequestHeaders(m_resourceRequest, m_response, m_sessionID);
index d40e958..254dd2b 100644 (file)
@@ -200,11 +200,17 @@ public:
     virtual void responseReceived(const ResourceResponse&);
     virtual bool shouldCacheResponse(const ResourceResponse&) { return true; }
     void setResponse(const ResourceResponse&);
-    void setOpaqueRedirect() { m_responseType = ResourceResponseBase::Type::Opaqueredirect; }
     const ResourceResponse& response() const { return m_response; }
     // This is the same as response() except after HTTP redirect to data: URL.
     const ResourceResponse& responseForSameOriginPolicyChecks() const;
 
+    void setCrossOrigin();
+    bool isCrossOrigin() const;
+    bool isClean() const;
+    ResourceResponse::Tainting responseTainting() const { return m_responseTainting; }
+
+    SecurityOrigin* origin() const { return m_origin.get(); }
+
     bool canDelete() const { return !hasClients() && !m_loader && !m_preloadCount && !m_handleCount && !m_resourceToRevalidate && !m_proxyResource; }
     bool hasOneHandle() const { return m_handleCount == 1; }
 
@@ -224,7 +230,7 @@ public:
     DataBufferingPolicy dataBufferingPolicy() const { return m_options.dataBufferingPolicy(); }
 
     bool allowsCaching() const { return m_options.cachingPolicy() == CachingPolicy::AllowCaching; }
-    
+
     virtual void destroyDecodedData() { }
 
     void setOwningCachedResourceLoader(CachedResourceLoader* cachedResourceLoader) { m_owningCachedResourceLoader = cachedResourceLoader; }
@@ -284,7 +290,7 @@ protected:
     RefPtr<SubresourceLoader> m_loader;
     ResourceLoaderOptions m_options;
     ResourceResponse m_response;
-    ResourceResponseBase::Type m_responseType { ResourceResponseBase::Type::Basic };
+    ResourceResponse::Tainting m_responseTainting { ResourceResponse::Tainting::Basic };
     ResourceResponse m_redirectResponseForSameOriginPolicyChecks;
     RefPtr<SharedBuffer> m_data;
     DeferrableOneShotTimer m_decodedDataDeletionTimer;
@@ -313,6 +319,7 @@ private:
     String m_fragmentIdentifierForRequest;
 
     ResourceError m_error;
+    RefPtr<SecurityOrigin> m_origin;
 
     double m_lastDecodedAccessTime; // Used as a "thrash guard" in the cache
     double m_loadFinishTime;
index fc1a319..bcbfcd9 100644 (file)
@@ -33,6 +33,7 @@
 #include "config.h"
 #include "HTTPParsers.h"
 
+#include "HTTPHeaderNames.h"
 #include <wtf/DateMath.h>
 #include <wtf/NeverDestroyed.h>
 #include <wtf/text/CString.h>
@@ -755,4 +756,102 @@ size_t parseHTTPRequestBody(const char* data, size_t length, Vector<unsigned cha
     return length;
 }
 
+void parseAccessControlExposeHeadersAllowList(const String& headerValue, HTTPHeaderSet& headerSet)
+{
+    Vector<String> headers;
+    headerValue.split(',', false, headers);
+    for (auto& header : headers) {
+        String strippedHeader = header.stripWhiteSpace();
+        if (!strippedHeader.isEmpty())
+            headerSet.add(strippedHeader);
+    }
+}
+
+// Implememtnation of https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name
+bool isForbiddenHeaderName(const String& name)
+{
+    HTTPHeaderName headerName;
+    if (findHTTPHeaderName(name, headerName)) {
+        switch (headerName) {
+        case HTTPHeaderName::AcceptCharset:
+        case HTTPHeaderName::AcceptEncoding:
+        case HTTPHeaderName::AccessControlRequestHeaders:
+        case HTTPHeaderName::AccessControlRequestMethod:
+        case HTTPHeaderName::Connection:
+        case HTTPHeaderName::ContentLength:
+        case HTTPHeaderName::Cookie:
+        case HTTPHeaderName::Cookie2:
+        case HTTPHeaderName::Date:
+        case HTTPHeaderName::DNT:
+        case HTTPHeaderName::Expect:
+        case HTTPHeaderName::Host:
+        case HTTPHeaderName::KeepAlive:
+        case HTTPHeaderName::Origin:
+        case HTTPHeaderName::Referer:
+        case HTTPHeaderName::TE:
+        case HTTPHeaderName::Trailer:
+        case HTTPHeaderName::TransferEncoding:
+        case HTTPHeaderName::Upgrade:
+        case HTTPHeaderName::Via:
+            return true;
+        default:
+            break;
+        }
+    }
+    return startsWithLettersIgnoringASCIICase(name, "sec-") || startsWithLettersIgnoringASCIICase(name, "proxy-");
+}
+
+bool isForbiddenResponseHeaderName(const String& name)
+{
+    return equalLettersIgnoringASCIICase(name, "set-cookie") || equalLettersIgnoringASCIICase(name, "set-cookie2");
+}
+
+bool isSimpleHeader(const String& name, const String& value)
+{
+    HTTPHeaderName headerName;
+    if (!findHTTPHeaderName(name, headerName))
+        return false;
+    switch (headerName) {
+    case HTTPHeaderName::Accept:
+    case HTTPHeaderName::AcceptLanguage:
+    case HTTPHeaderName::ContentLanguage:
+        return true;
+    case HTTPHeaderName::ContentType: {
+        String mimeType = extractMIMETypeFromMediaType(value);
+        return equalLettersIgnoringASCIICase(mimeType, "application/x-www-form-urlencoded") || equalLettersIgnoringASCIICase(mimeType, "multipart/form-data") || equalLettersIgnoringASCIICase(mimeType, "text/plain");
+    }
+    default:
+        return false;
+    }
+}
+
+bool isCrossOriginSafeHeader(HTTPHeaderName name, const HTTPHeaderSet& accessControlExposeHeaderSet)
+{
+    switch (name) {
+    case HTTPHeaderName::CacheControl:
+    case HTTPHeaderName::ContentLanguage:
+    case HTTPHeaderName::ContentType:
+    case HTTPHeaderName::Expires:
+    case HTTPHeaderName::LastModified:
+    case HTTPHeaderName::Pragma:
+    case HTTPHeaderName::Accept:
+        return true;
+    case HTTPHeaderName::SetCookie:
+    case HTTPHeaderName::SetCookie2:
+        return false;
+    default:
+        break;
+    }
+    return accessControlExposeHeaderSet.contains(httpHeaderNameString(name).toStringWithoutCopying());
+}
+
+bool isCrossOriginSafeHeader(const String& name, const HTTPHeaderSet& accessControlExposeHeaderSet)
+{
+#ifndef ASSERT_DISABLED
+    HTTPHeaderName headerName;
+    ASSERT(!findHTTPHeaderName(name, headerName));
+#endif
+    return accessControlExposeHeaderSet.contains(name);
+}
+
 }
index dd2e1bd..ab75481 100644 (file)
 #define HTTPParsers_h
 
 #include <wtf/Forward.h>
+#include <wtf/HashSet.h>
 #include <wtf/Optional.h>
 #include <wtf/Vector.h>
+#include <wtf/text/StringHash.h>
 #include <wtf/text/WTFString.h>
 
 namespace WebCore {
 
+typedef HashSet<String, ASCIICaseInsensitiveHash> HTTPHeaderSet;
+
+enum class HTTPHeaderName;
+
 enum class XSSProtectionDisposition {
     Invalid,
     Disabled,
@@ -95,6 +101,15 @@ size_t parseHTTPRequestLine(const char* data, size_t length, String& failureReas
 size_t parseHTTPHeader(const char* data, size_t length, String& failureReason, StringView& nameStr, String& valueStr, bool strict = true);
 size_t parseHTTPRequestBody(const char* data, size_t length, Vector<unsigned char>& body);
 
+void parseAccessControlExposeHeadersAllowList(const String& headerValue, HTTPHeaderSet&);
+
+// HTTP Header routine as per https://fetch.spec.whatwg.org/#terminology-headers
+bool isForbiddenHeaderName(const String&);
+bool isForbiddenResponseHeaderName(const String&);
+bool isSimpleHeader(const String& name, const String& value);
+bool isCrossOriginSafeHeader(HTTPHeaderName, const HTTPHeaderSet&);
+bool isCrossOriginSafeHeader(const String&, const HTTPHeaderSet&);
+
 inline bool isHTTPSpace(UChar character)
 {
     return character <= ' ' && (character == ' ' || character == '\n' || character == '\t' || character == '\r');
index a48be4f..174963e 100644 (file)
@@ -313,9 +313,14 @@ void ResourceRequestBase::setHTTPOrigin(const String& httpOrigin)
     setHTTPHeaderField(HTTPHeaderName::Origin, httpOrigin);
 }
 
+bool ResourceRequestBase::hasHTTPOrigin() const
+{
+    return m_httpHeaderFields.contains(HTTPHeaderName::Origin);
+}
+
 void ResourceRequestBase::clearHTTPOrigin()
 {
-    updateResourceRequest(); 
+    updateResourceRequest();
 
     m_httpHeaderFields.remove(HTTPHeaderName::Origin);
 
index 6e0bbe0..d3ac391 100644 (file)
@@ -22,7 +22,7 @@
  * 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. 
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 #ifndef ResourceRequestBase_h
@@ -100,8 +100,9 @@ public:
     WEBCORE_EXPORT String httpReferrer() const;
     WEBCORE_EXPORT void setHTTPReferrer(const String&);
     WEBCORE_EXPORT void clearHTTPReferrer();
-    
+
     String httpOrigin() const;
+    bool hasHTTPOrigin() const;
     void setHTTPOrigin(const String&);
     WEBCORE_EXPORT void clearHTTPOrigin();
 
index de181f2..fcbe776 100644 (file)
@@ -99,6 +99,40 @@ ResourceResponse ResourceResponseBase::fromCrossThreadData(CrossThreadData&& dat
     return response;
 }
 
+ResourceResponse ResourceResponseBase::filterResponse(const ResourceResponse& response, ResourceResponse::Tainting tainting)
+{
+    if (tainting == ResourceResponse::Tainting::Opaque) {
+        ResourceResponse opaqueResponse;
+        opaqueResponse.setType(ResourceResponse::Type::Opaque);
+        return opaqueResponse;
+    }
+
+    ResourceResponse filteredResponse = response;
+    // Let's initialize filteredResponse to remove some header fields.
+    filteredResponse.lazyInit(AllFields);
+
+    if (tainting == ResourceResponse::Tainting::Basic) {
+        filteredResponse.setType(ResourceResponse::Type::Basic);
+        filteredResponse.m_httpHeaderFields.remove(HTTPHeaderName::SetCookie);
+        filteredResponse.m_httpHeaderFields.remove(HTTPHeaderName::SetCookie2);
+        return filteredResponse;
+    }
+
+    ASSERT(tainting == ResourceResponse::Tainting::Cors);
+    filteredResponse.setType(ResourceResponse::Type::Cors);
+
+    HTTPHeaderSet accessControlExposeHeaderSet;
+    parseAccessControlExposeHeadersAllowList(response.httpHeaderField(HTTPHeaderName::AccessControlExposeHeaders), accessControlExposeHeaderSet);
+    filteredResponse.m_httpHeaderFields.uncommonHeaders().removeIf([&](auto& entry) {
+        return !isCrossOriginSafeHeader(entry.key, accessControlExposeHeaderSet);
+    });
+    filteredResponse.m_httpHeaderFields.commonHeaders().removeIf([&](auto& entry) {
+        return !isCrossOriginSafeHeader(entry.key, accessControlExposeHeaderSet);
+    });
+
+    return filteredResponse;
+}
+
 // FIXME: Name does not make it clear this is true for HTTPS!
 bool ResourceResponseBase::isHTTP() const
 {
@@ -111,7 +145,7 @@ const URL& ResourceResponseBase::url() const
 {
     lazyInit(CommonFieldsOnly);
 
-    return m_url; 
+    return m_url;
 }
 
 void ResourceResponseBase::setURL(const URL& url)
index b552c10..498500c 100644 (file)
@@ -65,6 +65,9 @@ public:
     CrossThreadData crossThreadData() const;
     static ResourceResponse fromCrossThreadData(CrossThreadData&&);
 
+    enum class Tainting { Basic, Cors, Opaque };
+    static ResourceResponse filterResponse(const ResourceResponse&, Tainting);
+
     bool isNull() const { return m_isNull; }
     WEBCORE_EXPORT bool isHTTP() const;
     bool isSuccessful() const;
index ff6105c..6f4e7d2 100644 (file)
@@ -75,11 +75,6 @@ enum XMLHttpRequestSendArrayBufferOrView {
     XMLHttpRequestSendArrayBufferOrViewMax,
 };
 
-static bool isSetCookieHeader(const String& name)
-{
-    return equalLettersIgnoringASCIICase(name, "set-cookie") || equalLettersIgnoringASCIICase(name, "set-cookie2");
-}
-
 static void replaceCharsetInMediaType(String& mediaType, const String& charsetValue)
 {
     unsigned pos = 0, len = 0;
@@ -906,22 +901,7 @@ String XMLHttpRequest::getAllResponseHeaders() const
 
     StringBuilder stringBuilder;
 
-    HTTPHeaderSet accessControlExposeHeaderSet;
-    parseAccessControlExposeHeadersAllowList(m_response.httpHeaderField(HTTPHeaderName::AccessControlExposeHeaders), accessControlExposeHeaderSet);
-    
     for (const auto& header : m_response.httpHeaderFields()) {
-        // Hide Set-Cookie header fields from the XMLHttpRequest client for these reasons:
-        //     1) If the client did have access to the fields, then it could read HTTP-only
-        //        cookies; those cookies are supposed to be hidden from scripts.
-        //     2) There's no known harm in hiding Set-Cookie header fields entirely; we don't
-        //        know any widely used technique that requires access to them.
-        //     3) Firefox has implemented this policy.
-        if (isSetCookieHeader(header.key) && !securityOrigin()->canLoadLocalResources())
-            continue;
-
-        if (!m_sameOriginRequest && !isOnAccessControlResponseHeaderWhitelist(header.key) && !accessControlExposeHeaderSet.contains(header.key))
-            continue;
-
         stringBuilder.append(header.key);
         stringBuilder.append(':');
         stringBuilder.append(' ');
@@ -938,19 +918,6 @@ String XMLHttpRequest::getResponseHeader(const String& name) const
     if (m_state < HEADERS_RECEIVED || m_error)
         return String();
 
-    // See comment in getAllResponseHeaders above.
-    if (isSetCookieHeader(name) && !securityOrigin()->canLoadLocalResources()) {
-        logConsoleError(scriptExecutionContext(), "Refused to get unsafe header \"" + name + "\"");
-        return String();
-    }
-
-    HTTPHeaderSet accessControlExposeHeaderSet;
-    parseAccessControlExposeHeadersAllowList(m_response.httpHeaderField(HTTPHeaderName::AccessControlExposeHeaders), accessControlExposeHeaderSet);
-
-    if (!m_sameOriginRequest && !isOnAccessControlResponseHeaderWhitelist(name) && !accessControlExposeHeaderSet.contains(name)) {
-        logConsoleError(scriptExecutionContext(), "Refused to get unsafe header \"" + name + "\"");
-        return String();
-    }
     return m_response.httpHeaderField(name);
 }