[Beacon][NetworkSession] Support CORS-preflighting on redirects
authorcdumez@apple.com <cdumez@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 9 Aug 2017 23:23:58 +0000 (23:23 +0000)
committercdumez@apple.com <cdumez@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 9 Aug 2017 23:23:58 +0000 (23:23 +0000)
https://bugs.webkit.org/show_bug.cgi?id=175386
<rdar://problem/33801370>

Reviewed by Youenn Fablet.

Source/WebCore:

Export a couple of WebCore symbols so I can use them in WebKit2.

Tests: http/wpt/beacon/cors/cors-preflight-redirect-failure.html
       http/wpt/beacon/cors/cors-preflight-redirect-from-crossorigin-to-sameorigin.html
       http/wpt/beacon/cors/cors-preflight-redirect-success.html

* loader/CrossOriginAccessControl.h:
* page/SecurityOrigin.h:

Source/WebKit:

Add support to Beacon for doing CORS-preflighting upon redirect to a different
domain.

* NetworkProcess/NetworkCORSPreflightChecker.h:
* NetworkProcess/PingLoad.cpp:
(WebKit::PingLoad::PingLoad):
(WebKit::PingLoad::~PingLoad):
(WebKit::PingLoad::loadRequest):
(WebKit::PingLoad::securityOrigin const):
(WebKit::PingLoad::willPerformHTTPRedirection):
(WebKit::PingLoad::didReceiveResponseNetworkSession):
(WebKit::PingLoad::needsCORSPreflight const):
(WebKit::PingLoad::doCORSPreflight):
* NetworkProcess/PingLoad.h:

LayoutTests:

Add layout test coverage.

* http/wpt/beacon/cors/cors-preflight-redirect-failure-expected.txt: Added.
* http/wpt/beacon/cors/cors-preflight-redirect-failure.html: Added.
* http/wpt/beacon/cors/cors-preflight-redirect-from-crossorigin-to-sameorigin-expected.txt: Added.
* http/wpt/beacon/cors/cors-preflight-redirect-from-crossorigin-to-sameorigin.html: Added.
* http/wpt/beacon/cors/cors-preflight-redirect-success-expected.txt: Added.
* http/wpt/beacon/cors/cors-preflight-redirect-success.html: Added.
* http/wpt/beacon/resources/beacon-preflight.py:
(main):
* http/wpt/beacon/resources/redirect.py: Added.
(main):

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

21 files changed:
LayoutTests/ChangeLog
LayoutTests/http/wpt/beacon/cors/cors-preflight-arraybufferview-failure.html
LayoutTests/http/wpt/beacon/cors/cors-preflight-arraybufferview-success.html
LayoutTests/http/wpt/beacon/cors/cors-preflight-blob-failure.html
LayoutTests/http/wpt/beacon/cors/cors-preflight-blob-success.html
LayoutTests/http/wpt/beacon/cors/cors-preflight-cookie.html
LayoutTests/http/wpt/beacon/cors/cors-preflight-redirect-failure-expected.txt [new file with mode: 0644]
LayoutTests/http/wpt/beacon/cors/cors-preflight-redirect-failure.html [new file with mode: 0644]
LayoutTests/http/wpt/beacon/cors/cors-preflight-redirect-from-crossorigin-to-sameorigin-expected.txt [new file with mode: 0644]
LayoutTests/http/wpt/beacon/cors/cors-preflight-redirect-from-crossorigin-to-sameorigin.html [new file with mode: 0644]
LayoutTests/http/wpt/beacon/cors/cors-preflight-redirect-success-expected.txt [new file with mode: 0644]
LayoutTests/http/wpt/beacon/cors/cors-preflight-redirect-success.html [new file with mode: 0644]
LayoutTests/http/wpt/beacon/resources/beacon-preflight.py
LayoutTests/http/wpt/beacon/resources/redirect.py [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/loader/CrossOriginAccessControl.h
Source/WebCore/page/SecurityOrigin.h
Source/WebKit/ChangeLog
Source/WebKit/NetworkProcess/NetworkCORSPreflightChecker.h
Source/WebKit/NetworkProcess/PingLoad.cpp
Source/WebKit/NetworkProcess/PingLoad.h

index 80b52dd..64a1d6f 100644 (file)
@@ -1,5 +1,26 @@
 2017-08-09  Chris Dumez  <cdumez@apple.com>
 
+        [Beacon][NetworkSession] Support CORS-preflighting on redirects
+        https://bugs.webkit.org/show_bug.cgi?id=175386
+        <rdar://problem/33801370>
+
+        Reviewed by Youenn Fablet.
+
+        Add layout test coverage.
+
+        * http/wpt/beacon/cors/cors-preflight-redirect-failure-expected.txt: Added.
+        * http/wpt/beacon/cors/cors-preflight-redirect-failure.html: Added.
+        * http/wpt/beacon/cors/cors-preflight-redirect-from-crossorigin-to-sameorigin-expected.txt: Added.
+        * http/wpt/beacon/cors/cors-preflight-redirect-from-crossorigin-to-sameorigin.html: Added.
+        * http/wpt/beacon/cors/cors-preflight-redirect-success-expected.txt: Added.
+        * http/wpt/beacon/cors/cors-preflight-redirect-success.html: Added.
+        * http/wpt/beacon/resources/beacon-preflight.py:
+        (main):
+        * http/wpt/beacon/resources/redirect.py: Added.
+        (main):
+
+2017-08-09  Chris Dumez  <cdumez@apple.com>
+
         Unreviewed, deflake http/wpt/beacon/keepalive-after-navigation.html
 
         Temporarily restore previous timeout on this test to address flakiness on the
index 73c3b2d..c83acf5 100644 (file)
@@ -18,7 +18,7 @@ function pollResult(test, id) {
   return new Promise(resolve => {
     step_timeout(test.step_func(() => {
       fetch(checkUrl).then(response => {
-        response.text().then(body => {
+        response.json().then(body => {
           resolve(body);
         });
       });
@@ -33,8 +33,7 @@ function testCORSPreflightFailure(what) {
 
   promise_test(function(test) {
     assert_true(navigator.sendBeacon(testUrl, what), "SendBeacon Succeeded");
-    return pollResult(test, id) .then(json => {
-      result = JSON.parse(json);
+    return pollResult(test, id) .then(result => {
       assert_equals(result['preflight'], 1, "Received preflight")
       assert_equals(result['preflight_referer'], document.URL, "Preflight referer header")
       assert_equals(result['preflight_requested_method'], "POST", "Preflight requested method")
index c711313..083f91b 100644 (file)
@@ -18,7 +18,7 @@ function pollResult(test, id) {
   return new Promise(resolve => {
     step_timeout(test.step_func(() => {
       fetch(checkUrl).then(response => {
-        response.text().then(body => {
+        response.json().then(body => {
           resolve(body);
         });
       });
@@ -33,8 +33,7 @@ function testCORSPreflightSuccess(what) {
 
   promise_test(function(test) {
     assert_true(navigator.sendBeacon(testUrl, what), "SendBeacon Succeeded");
-    return pollResult(test, id) .then(json => {
-      result = JSON.parse(json);
+    return pollResult(test, id) .then(result => {
       assert_equals(result['preflight'], 1, "Received preflight")
       assert_equals(result['preflight_referer'], document.URL, "Preflight referer header")
       assert_equals(result['preflight_requested_method'], "POST", "Preflight requested method")
index 061fcd2..6eda9a3 100644 (file)
@@ -18,7 +18,7 @@ function pollResult(test, id) {
   return new Promise(resolve => {
     step_timeout(test.step_func(() => {
       fetch(checkUrl).then(response => {
-        response.text().then(body => {
+        response.json().then(body => {
           resolve(body);
         });
       });
@@ -33,8 +33,7 @@ function testCORSPreflightFailure(what) {
 
   promise_test(function(test) {
     assert_true(navigator.sendBeacon(testUrl, what), "SendBeacon Succeeded");
-    return pollResult(test, id) .then(json => {
-      result = JSON.parse(json);
+    return pollResult(test, id) .then(result => {
       assert_equals(result['preflight'], 1, "Received preflight")
       assert_equals(result['preflight_referer'], document.URL, "Preflight referer header")
       assert_equals(result['preflight_requested_method'], "POST", "Preflight requested method")
index a0d9092..f64b3b7 100644 (file)
@@ -18,7 +18,7 @@ function pollResult(test, id) {
   return new Promise(resolve => {
     step_timeout(test.step_func(() => {
       fetch(checkUrl).then(response => {
-        response.text().then(body => {
+        response.json().then(body => {
           resolve(body);
         });
       });
@@ -33,8 +33,7 @@ function testCORSPreflightSuccess(what) {
 
   promise_test(function(test) {
     assert_true(navigator.sendBeacon(testUrl, what), "SendBeacon Succeeded");
-    return pollResult(test, id) .then(json => {
-      result = JSON.parse(json);
+    return pollResult(test, id) .then(result => {
       assert_equals(result['preflight'], 1, "Received preflight")
       assert_equals(result['preflight_referer'], document.URL, "Preflight referer header")
       assert_equals(result['preflight_requested_method'], "POST", "Preflight requested method")
index b7957ee..3d49c8a 100644 (file)
@@ -21,7 +21,7 @@ function pollResult(test, id) {
   return new Promise(resolve => {
     step_timeout(test.step_func(() => {
       fetch(checkUrl).then(response => {
-        response.text().then(body => {
+        response.json().then(body => {
           resolve(body);
         });
       });
@@ -46,8 +46,7 @@ function testCORSPreflightSuccessWithCookie(what) {
   promise_test(function(test) {
     return fetchCORSCookie(testBase, "testCookie", "/").then(() => {
       assert_true(navigator.sendBeacon(testUrl, what), "SendBeacon Succeeded");
-      return pollResult(test, id).then(json => {
-        result = JSON.parse(json);
+      return pollResult(test, id).then(result => {
         assert_equals(result['preflight'], 1, "Received preflight")
         assert_equals(result['preflight_cookie_header'], "", "Preflight cookie header")
         assert_equals(result['beacon'], 1, "Received beacon")
diff --git a/LayoutTests/http/wpt/beacon/cors/cors-preflight-redirect-failure-expected.txt b/LayoutTests/http/wpt/beacon/cors/cors-preflight-redirect-failure-expected.txt
new file mode 100644 (file)
index 0000000..11932f7
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS CORS preflight failure test 
+
diff --git a/LayoutTests/http/wpt/beacon/cors/cors-preflight-redirect-failure.html b/LayoutTests/http/wpt/beacon/cors/cors-preflight-redirect-failure.html
new file mode 100644 (file)
index 0000000..a963da0
--- /dev/null
@@ -0,0 +1,52 @@
+<!doctype html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>SendBeacon CORS preflight on redirect test</title>
+    <script src=/resources/testharness.js></script>
+    <script src=/resources/testharnessreport.js></script>
+  </head>
+  <body>
+    <script src="/common/utils.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+    <script>
+var RESOURCES_DIR = "/WebKit/beacon/resources/";
+
+function pollResult(test, id) {
+  var checkUrl = RESOURCES_DIR + "beacon-preflight.py?cmd=get&id=" + id;
+
+  return new Promise(resolve => {
+    step_timeout(test.step_func(() => {
+      fetch(checkUrl).then(response => {
+        response.json().then(body => {
+          resolve(body);
+        });
+      });
+    }), 1000);
+  });
+}
+
+function testCORSPreflightRedirectSuccess(what) {
+  var testBase = get_host_info().HTTP_REMOTE_ORIGIN + RESOURCES_DIR;
+  var id = self.token();
+  var target = encodeURIComponent(testBase + "beacon-preflight.py?allowCors=0&cmd=put&id=" + id);
+
+  // 307 & 308 redirections are the only ones that maintain the POST method.
+  var testUrl = RESOURCES_DIR + "redirect.py?redirect_status=307&location=" + target;
+
+  promise_test(function(test) {
+    assert_true(navigator.sendBeacon(testUrl, what), "SendBeacon Succeeded");
+    return pollResult(test, id) .then(result => {
+      assert_equals(result['preflight'], 1, "Received preflight")
+      assert_equals(result['preflight_referer'], document.URL, "Preflight referer header")
+      assert_equals(result['preflight_requested_method'], "POST", "Preflight requested method")
+      assert_equals(result['beacon'], 0, "Did not receive beacon")
+    });
+  }, "CORS preflight failure test");
+}
+
+let blob = new Blob(["123"], {type: "application/octet-stream"});
+testCORSPreflightRedirectSuccess(blob);
+    </script>
+  </body>
+</html>
diff --git a/LayoutTests/http/wpt/beacon/cors/cors-preflight-redirect-from-crossorigin-to-sameorigin-expected.txt b/LayoutTests/http/wpt/beacon/cors/cors-preflight-redirect-from-crossorigin-to-sameorigin-expected.txt
new file mode 100644 (file)
index 0000000..1544982
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS CORS preflight success test 
+
diff --git a/LayoutTests/http/wpt/beacon/cors/cors-preflight-redirect-from-crossorigin-to-sameorigin.html b/LayoutTests/http/wpt/beacon/cors/cors-preflight-redirect-from-crossorigin-to-sameorigin.html
new file mode 100644 (file)
index 0000000..9c1093f
--- /dev/null
@@ -0,0 +1,54 @@
+<!doctype html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>SendBeacon CORS preflight on redirect test from cross origin to same origin</title>
+    <script src=/resources/testharness.js></script>
+    <script src=/resources/testharnessreport.js></script>
+  </head>
+  <body>
+    <script src="/common/utils.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+    <script>
+var RESOURCES_DIR = "/WebKit/beacon/resources/";
+
+function pollResult(test, id) {
+  var checkUrl = RESOURCES_DIR + "beacon-preflight.py?cmd=get&id=" + id;
+
+  return new Promise(resolve => {
+    step_timeout(test.step_func(() => {
+      fetch(checkUrl).then(response => {
+        response.json().then(body => {
+          resolve(body);
+        });
+      });
+    }), 1000);
+  });
+}
+
+function testCORSPreflightRedirectSuccess(what) {
+  var testBase = get_host_info().HTTP_REMOTE_ORIGIN + RESOURCES_DIR;
+  var id = self.token();
+  var target = encodeURIComponent(get_host_info().HTTP_ORIGIN + RESOURCES_DIR + "beacon-preflight.py?allowCors=1&cmd=put&id=" + id);
+
+  // 307 & 308 redirections are the only ones that maintain the POST method.
+  var testUrl = testBase + "redirect.py?redirect_status=307&location=" + target;
+
+  promise_test(function(test) {
+    assert_true(navigator.sendBeacon(testUrl, what), "SendBeacon Succeeded");
+    return pollResult(test, id) .then(result => {
+      assert_equals(result['preflight'], 1, "Received preflight")
+      assert_equals(result['preflight_referer'], document.URL, "Preflight referer header")
+      assert_equals(result['preflight_requested_method'], "POST", "Preflight requested method")
+      assert_equals(result['preflight_origin'], "null", "Received beacon")
+      assert_equals(result['beacon'], 1, "Received beacon")
+      assert_equals(result['beacon_origin'], "null", "Received beacon")
+    });
+  }, "CORS preflight success test");
+}
+
+let blob = new Blob(["123"], {type: "application/octet-stream"});
+testCORSPreflightRedirectSuccess(blob);
+    </script>
+  </body>
+</html>
diff --git a/LayoutTests/http/wpt/beacon/cors/cors-preflight-redirect-success-expected.txt b/LayoutTests/http/wpt/beacon/cors/cors-preflight-redirect-success-expected.txt
new file mode 100644 (file)
index 0000000..1544982
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS CORS preflight success test 
+
diff --git a/LayoutTests/http/wpt/beacon/cors/cors-preflight-redirect-success.html b/LayoutTests/http/wpt/beacon/cors/cors-preflight-redirect-success.html
new file mode 100644 (file)
index 0000000..4d7fecd
--- /dev/null
@@ -0,0 +1,52 @@
+<!doctype html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>SendBeacon CORS preflight on redirect test</title>
+    <script src=/resources/testharness.js></script>
+    <script src=/resources/testharnessreport.js></script>
+  </head>
+  <body>
+    <script src="/common/utils.js"></script>
+    <script src="/common/get-host-info.sub.js"></script>
+    <script>
+var RESOURCES_DIR = "/WebKit/beacon/resources/";
+
+function pollResult(test, id) {
+  var checkUrl = RESOURCES_DIR + "beacon-preflight.py?cmd=get&id=" + id;
+
+  return new Promise(resolve => {
+    step_timeout(test.step_func(() => {
+      fetch(checkUrl).then(response => {
+        response.json().then(body => {
+          resolve(body);
+        });
+      });
+    }), 1000);
+  });
+}
+
+function testCORSPreflightRedirectSuccess(what) {
+  var testBase = get_host_info().HTTP_REMOTE_ORIGIN + RESOURCES_DIR;
+  var id = self.token();
+  var target = encodeURIComponent(testBase + "beacon-preflight.py?allowCors=1&cmd=put&id=" + id);
+
+  // 307 & 308 redirections are the only ones that maintain the POST method.
+  var testUrl = RESOURCES_DIR + "redirect.py?redirect_status=307&location=" + target;
+
+  promise_test(function(test) {
+    assert_true(navigator.sendBeacon(testUrl, what), "SendBeacon Succeeded");
+    return pollResult(test, id) .then(result => {
+      assert_equals(result['preflight'], 1, "Received preflight")
+      assert_equals(result['preflight_referer'], document.URL, "Preflight referer header")
+      assert_equals(result['preflight_requested_method'], "POST", "Preflight requested method")
+      assert_equals(result['beacon'], 1, "Received beacon")
+    });
+  }, "CORS preflight success test");
+}
+
+let blob = new Blob(["123"], {type: "application/octet-stream"});
+testCORSPreflightRedirectSuccess(blob);
+    </script>
+  </body>
+</html>
index cce4cd1..9e64e76 100644 (file)
@@ -35,11 +35,13 @@ def main(request, response):
       stashed_data['preflight_requested_headers'] = request.headers.get("Access-Control-Request-Headers", "")
       stashed_data['preflight_cookie_header'] = request.headers.get("Cookie", "");
       stashed_data['preflight_referer'] = request.headers.get("Referer", "")
+      stashed_data['preflight_origin'] = request.headers.get("Origin", "")
       request.server.stash.put(test_id, stashed_data)
       return respondToCORSPreflight(request, response)
     elif request.method == "POST":
       stashed_data['beacon'] = 1;
       stashed_data['beacon_cookie_header'] = request.headers.get("Cookie", "")
+      stashed_data['beacon_origin'] = request.headers.get("Origin", "")
       request.server.stash.put(test_id, stashed_data)
     return [("Content-Type", "text/plain")], ""
   
diff --git a/LayoutTests/http/wpt/beacon/resources/redirect.py b/LayoutTests/http/wpt/beacon/resources/redirect.py
new file mode 100644 (file)
index 0000000..39c574c
--- /dev/null
@@ -0,0 +1,63 @@
+from urllib import urlencode
+from urlparse import urlparse
+
+def main(request, response):
+    stashed_data = {'count': 0, 'preflight': "0"}
+    status = 302
+    headers = [("Content-Type", "text/plain"),
+               ("Cache-Control", "no-cache"),
+               ("Pragma", "no-cache"),
+               ("Access-Control-Allow-Credentials", "true")]
+    headers.append(("Access-Control-Allow-Origin", request.headers.get("Origin", "*")))
+    token = None
+
+    if "token" in request.GET:
+        token = request.GET.first("token")
+        data = request.server.stash.take(token)
+        if data:
+            stashed_data = data
+
+    if request.method == "OPTIONS":
+        requested_method = request.headers.get("Access-Control-Request-Method", None)
+        headers.append(("Access-Control-Allow-Methods", requested_method))
+        requested_headers = request.headers.get("Access-Control-Request-Headers", None)
+        headers.append(("Access-Control-Allow-Headers", requested_headers))
+        stashed_data['preflight'] = "1"
+        #Preflight is not redirected: return 200
+        if not "redirect_preflight" in request.GET:
+            if token:
+              request.server.stash.put(request.GET.first("token"), stashed_data)
+            return 200, headers, ""
+
+    if "redirect_status" in request.GET:
+        status = int(request.GET['redirect_status'])
+
+    stashed_data['count'] += 1
+
+    if "location" in request.GET:
+        url = request.GET['location']
+        scheme = urlparse(url).scheme
+        if scheme == "" or scheme == "http" or scheme == "https":
+            url += "&" if '?' in url else "?"
+            #keep url parameters in location
+            url_parameters = {}
+            for item in request.GET.items():
+                url_parameters[item[0]] = item[1][0]
+            url += urlencode(url_parameters)
+            #make sure location changes during redirection loop
+            url += "&count=" + str(stashed_data['count'])
+        headers.append(("Location", url))
+
+    if "redirect_referrerpolicy" in request.GET:
+        headers.append(("Referrer-Policy", request.GET['redirect_referrerpolicy']))
+
+    if token:
+        request.server.stash.put(request.GET.first("token"), stashed_data)
+        if "max_count" in request.GET:
+            max_count =  int(request.GET['max_count'])
+            #stop redirecting and return count
+            if stashed_data['count'] > max_count:
+                # -1 because the last is not a redirection
+                return str(stashed_data['count'] - 1)
+
+    return status, headers, ""
index 752bba7..f30fd5c 100644 (file)
@@ -1,3 +1,20 @@
+2017-08-09  Chris Dumez  <cdumez@apple.com>
+
+        [Beacon][NetworkSession] Support CORS-preflighting on redirects
+        https://bugs.webkit.org/show_bug.cgi?id=175386
+        <rdar://problem/33801370>
+
+        Reviewed by Youenn Fablet.
+
+        Export a couple of WebCore symbols so I can use them in WebKit2.
+
+        Tests: http/wpt/beacon/cors/cors-preflight-redirect-failure.html
+               http/wpt/beacon/cors/cors-preflight-redirect-from-crossorigin-to-sameorigin.html
+               http/wpt/beacon/cors/cors-preflight-redirect-success.html
+
+        * loader/CrossOriginAccessControl.h:
+        * page/SecurityOrigin.h:
+
 2017-08-09  Jeremy Jones  <jeremyj@apple.com>
 
         Use MPAVRoutingController instead of deprecated versions.
index 5e87b1f..94f2230 100644 (file)
@@ -40,7 +40,7 @@ class URL;
 bool isSimpleCrossOriginAccessRequest(const String& method, const HTTPHeaderMap&);
 bool isOnAccessControlSimpleRequestMethodWhitelist(const String&);
 
-void updateRequestForAccessControl(ResourceRequest&, SecurityOrigin&, StoredCredentials);
+WEBCORE_EXPORT void updateRequestForAccessControl(ResourceRequest&, SecurityOrigin&, StoredCredentials);
 WEBCORE_EXPORT ResourceRequest createAccessControlPreflightRequest(const ResourceRequest&, SecurityOrigin&, const String&);
 
 bool isValidCrossOriginRedirectionURL(const URL&);
index 0ead4bc..63bef48 100644 (file)
@@ -50,7 +50,7 @@ public:
     };
 
     WEBCORE_EXPORT static Ref<SecurityOrigin> create(const URL&);
-    static Ref<SecurityOrigin> createUnique();
+    WEBCORE_EXPORT static Ref<SecurityOrigin> createUnique();
 
     WEBCORE_EXPORT static Ref<SecurityOrigin> createFromString(const String&);
     WEBCORE_EXPORT static Ref<SecurityOrigin> create(const String& protocol, const String& host, std::optional<uint16_t> port);
index 34ad979..579cf04 100644 (file)
@@ -1,3 +1,26 @@
+2017-08-09  Chris Dumez  <cdumez@apple.com>
+
+        [Beacon][NetworkSession] Support CORS-preflighting on redirects
+        https://bugs.webkit.org/show_bug.cgi?id=175386
+        <rdar://problem/33801370>
+
+        Reviewed by Youenn Fablet.
+
+        Add support to Beacon for doing CORS-preflighting upon redirect to a different
+        domain.
+
+        * NetworkProcess/NetworkCORSPreflightChecker.h:
+        * NetworkProcess/PingLoad.cpp:
+        (WebKit::PingLoad::PingLoad):
+        (WebKit::PingLoad::~PingLoad):
+        (WebKit::PingLoad::loadRequest):
+        (WebKit::PingLoad::securityOrigin const):
+        (WebKit::PingLoad::willPerformHTTPRedirection):
+        (WebKit::PingLoad::didReceiveResponseNetworkSession):
+        (WebKit::PingLoad::needsCORSPreflight const):
+        (WebKit::PingLoad::doCORSPreflight):
+        * NetworkProcess/PingLoad.h:
+
 2017-08-09  Jeremy Jones  <jeremyj@apple.com>
 
         Use MPAVRoutingController instead of deprecated versions.
index 446b67d..6522d98 100644 (file)
@@ -54,6 +54,7 @@ public:
 
     NetworkCORSPreflightChecker(Parameters&&, CompletionCallback&&);
     ~NetworkCORSPreflightChecker();
+    const WebCore::ResourceRequest& originalRequest() const { return m_parameters.originalRequest; }
 
     void startPreflight();
 
index 2df014c..ee8118e 100644 (file)
@@ -32,6 +32,7 @@
 #include "Logging.h"
 #include "NetworkCORSPreflightChecker.h"
 #include "SessionTracker.h"
+#include <WebCore/CrossOriginAccessControl.h>
 
 #define RELEASE_LOG_IF_ALLOWED(fmt, ...) RELEASE_LOG_IF(m_parameters.sessionID.isAlwaysOnLoggingAllowed(), Network, "%p - PingLoad::" fmt, this, ##__VA_ARGS__)
 
@@ -42,6 +43,7 @@ using namespace WebCore;
 PingLoad::PingLoad(NetworkResourceLoadParameters&& parameters)
     : m_parameters(WTFMove(parameters))
     , m_timeoutTimer(*this, &PingLoad::timeoutTimerFired)
+    , m_isSameOriginRequest(securityOrigin().canRequest(m_parameters.request.url()))
 {
     // If the server never responds, this object will hang around forever.
     // Set a very generous timeout, just in case.
@@ -50,11 +52,14 @@ PingLoad::PingLoad(NetworkResourceLoadParameters&& parameters)
     if (needsCORSPreflight(m_parameters.request))
         doCORSPreflight(m_parameters.request);
     else
-        startNetworkLoad();
+        loadRequest(m_parameters.request);
 }
 
 PingLoad::~PingLoad()
 {
+    if (m_redirectHandler)
+        m_redirectHandler({ });
+
     if (m_task) {
         ASSERT(m_task->client() == this);
         m_task->clearClient();
@@ -62,22 +67,53 @@ PingLoad::~PingLoad()
     }
 }
 
-void PingLoad::startNetworkLoad()
+void PingLoad::loadRequest(const ResourceRequest& request)
 {
     RELEASE_LOG_IF_ALLOWED("startNetworkLoad");
     if (auto* networkSession = SessionTracker::networkSession(m_parameters.sessionID)) {
-        m_task = NetworkDataTask::create(*networkSession, *this, m_parameters);
+        auto loadParameters = m_parameters;
+        loadParameters.request = request;
+        m_task = NetworkDataTask::create(*networkSession, *this, WTFMove(loadParameters));
         m_task->resume();
     } else
         ASSERT_NOT_REACHED();
 }
 
-void PingLoad::willPerformHTTPRedirection(ResourceResponse&&, ResourceRequest&& request, RedirectCompletionHandler&& completionHandler)
+SecurityOrigin& PingLoad::securityOrigin() const
+{
+    return m_origin ? *m_origin : *m_parameters.sourceOrigin;
+}
+
+void PingLoad::willPerformHTTPRedirection(ResourceResponse&& redirectResponse, ResourceRequest&& request, RedirectCompletionHandler&& completionHandler)
 {
-    RELEASE_LOG_IF_ALLOWED("willPerformHTTPRedirection");
-    // FIXME: Do a CORS preflight if necessary.
+    RELEASE_LOG_IF_ALLOWED("willPerformHTTPRedirection - shouldFollowRedirects? %d", m_parameters.shouldFollowRedirects);
+    if (!m_parameters.shouldFollowRedirects) {
+        completionHandler({ });
+        return;
+    }
+    // FIXME: Do CSP check.
     // FIXME: We should ensure the number of redirects does not exceed 20.
-    completionHandler(m_parameters.shouldFollowRedirects ? request : ResourceRequest());
+    if (!needsCORSPreflight(request)) {
+        completionHandler(request);
+        return;
+    }
+    RELEASE_LOG_IF_ALLOWED("willPerformHTTPRedirection - Redirect requires a CORS preflight");
+
+    // Use a unique origin for subsequent loads if needed.
+    // https://fetch.spec.whatwg.org/#concept-http-redirect-fetch (Step 10).
+    ASSERT(m_parameters.mode == FetchOptions::Mode::Cors);
+    if (!securityOrigin().canRequest(redirectResponse.url()) && !protocolHostAndPortAreEqual(redirectResponse.url(), request.url())) {
+        if (!m_origin || !m_origin->isUnique())
+            m_origin = SecurityOrigin::createUnique();
+    }
+
+    m_isSameOriginRequest = false;
+    m_redirectHandler = WTFMove(completionHandler);
+
+    // Let's fetch the request with the original headers (equivalent to request cloning specified by fetch algorithm).
+    request.setHTTPHeaderFields(m_parameters.request.httpHeaderFields());
+
+    doCORSPreflight(request);
 }
 
 void PingLoad::didReceiveChallenge(const AuthenticationChallenge&, ChallengeCompletionHandler&& completionHandler)
@@ -87,9 +123,9 @@ void PingLoad::didReceiveChallenge(const AuthenticationChallenge&, ChallengeComp
     delete this;
 }
 
-void PingLoad::didReceiveResponseNetworkSession(ResourceResponse&&, ResponseCompletionHandler&& completionHandler)
+void PingLoad::didReceiveResponseNetworkSession(ResourceResponse&& response, ResponseCompletionHandler&& completionHandler)
 {
-    RELEASE_LOG_IF_ALLOWED("didReceiveResponseNetworkSession");
+    RELEASE_LOG_IF_ALLOWED("didReceiveResponseNetworkSession - httpStatusCode: %d", response.httpStatusCode());
     completionHandler(PolicyAction::PolicyIgnore);
     delete this;
 }
@@ -133,30 +169,33 @@ void PingLoad::timeoutTimerFired()
 
 bool PingLoad::needsCORSPreflight(const ResourceRequest& request) const
 {
-    if (m_parameters.mode == FetchOptions::Mode::Cors) {
-        ASSERT(m_parameters.sourceOrigin);
-        return !m_parameters.sourceOrigin->canRequest(request.url());
-    }
-    return false;
+    if (m_parameters.mode == FetchOptions::Mode::NoCors)
+        return false;
+
+    return !m_isSameOriginRequest || !securityOrigin().canRequest(request.url());
 }
 
 void PingLoad::doCORSPreflight(const ResourceRequest& request)
 {
     RELEASE_LOG_IF_ALLOWED("doCORSPreflight");
     ASSERT(!m_corsPreflightChecker);
-    ASSERT(m_parameters.sourceOrigin);
 
     NetworkCORSPreflightChecker::Parameters parameters = {
         request,
-        *m_parameters.sourceOrigin,
+        securityOrigin(),
         m_parameters.sessionID,
         m_parameters.allowStoredCredentials
     };
     m_corsPreflightChecker = std::make_unique<NetworkCORSPreflightChecker>(WTFMove(parameters), [this](NetworkCORSPreflightChecker::Result result) {
-        RELEASE_LOG_IF_ALLOWED("doCORSPreflight complete, success: %d", result == NetworkCORSPreflightChecker::Result::Success);
+        RELEASE_LOG_IF_ALLOWED("doCORSPreflight complete, success: %d forRedirect? %d", result == NetworkCORSPreflightChecker::Result::Success, !!m_redirectHandler);
+        auto corsPreflightChecker = WTFMove(m_corsPreflightChecker);
         if (result == NetworkCORSPreflightChecker::Result::Success) {
-            m_corsPreflightChecker = nullptr;
-            startNetworkLoad();
+            ResourceRequest actualRequest = corsPreflightChecker->originalRequest();
+            updateRequestForAccessControl(actualRequest, securityOrigin(), m_parameters.allowStoredCredentials);
+            if (auto redirectHandler = std::exchange(m_redirectHandler, nullptr))
+                redirectHandler(actualRequest);
+            else
+                loadRequest(actualRequest);
         } else
             delete this;
     });
index 8872d2f..0144496 100644 (file)
@@ -51,14 +51,19 @@ private:
     void cannotShowURL() final;
     void timeoutTimerFired();
 
-    void startNetworkLoad();
+    void loadRequest(const WebCore::ResourceRequest&);
     bool needsCORSPreflight(const WebCore::ResourceRequest&) const;
     void doCORSPreflight(const WebCore::ResourceRequest&);
+
+    WebCore::SecurityOrigin& securityOrigin() const;
     
     NetworkResourceLoadParameters m_parameters;
     RefPtr<NetworkDataTask> m_task;
     WebCore::Timer m_timeoutTimer;
     std::unique_ptr<NetworkCORSPreflightChecker> m_corsPreflightChecker;
+    RefPtr<WebCore::SecurityOrigin> m_origin;
+    bool m_isSameOriginRequest;
+    RedirectCompletionHandler m_redirectHandler;
 };
 
 }