EventSource should support CORS
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 19 Dec 2012 00:19:42 +0000 (00:19 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 19 Dec 2012 00:19:42 +0000 (00:19 +0000)
https://bugs.webkit.org/show_bug.cgi?id=61862

Patch by Per-Erik Brodin <per-erik.brodin@ericsson.com> on 2012-12-18
Reviewed by Alexey Proskuryakov.

Source/WebCore:

Enabled CORS in EventSource with optional constructor argument to
indicate whether credentials should be included or not, as per the spec.
Added didFailAccessControlCheck to ThreadableLoaderClient to be able to
properly distinguish CORS failures from other errors.

Tests: http/tests/eventsource/eventsource-cors-basic.html
       http/tests/eventsource/eventsource-cors-no-server.html
       http/tests/eventsource/eventsource-cors-with-credentials.html

* loader/DocumentThreadableLoader.cpp:
(WebCore::DocumentThreadableLoader::makeSimpleCrossOriginAccessRequest):
(WebCore::DocumentThreadableLoader::didReceiveResponse):
(WebCore::DocumentThreadableLoader::preflightFailure):
* loader/ThreadableLoaderClient.h:
(WebCore::ThreadableLoaderClient::didFailAccessControlCheck):
* loader/ThreadableLoaderClientWrapper.h:
(WebCore::ThreadableLoaderClientWrapper::didFailAccessControlCheck):
(ThreadableLoaderClientWrapper):
* loader/WorkerThreadableLoader.cpp:
(WebCore::workerContextDidFailAccessControlCheck):
(WebCore):
(WebCore::WorkerThreadableLoader::MainThreadBridge::didFailAccessControlCheck):
* loader/WorkerThreadableLoader.h:
(MainThreadBridge):
* page/EventSource.cpp:
(WebCore::EventSource::EventSource):
(WebCore::EventSource::create):
(WebCore::EventSource::connect):
(WebCore::EventSource::withCredentials):
(WebCore):
(WebCore::EventSource::didReceiveResponse):
(WebCore::EventSource::didFailAccessControlCheck):
(WebCore::EventSource::didFailRedirectCheck):
(WebCore::EventSource::abortConnectionAttempt):
(WebCore::EventSource::parseEventStreamLine):
(WebCore::EventSource::createMessageEvent):
* page/EventSource.h:
(WebCore):
(EventSource):
(WebCore::EventSource::refEventTarget):
(WebCore::EventSource::derefEventTarget):
* page/EventSource.idl:

LayoutTests:

Added new CORS tests for EventSource. Modified existing test to verify
that the new constructor argument can be passed to the constructor.

* fast/eventsource/eventsource-constructor-expected.txt:
* fast/eventsource/eventsource-constructor.html:
* fast/js/constructor-length.html:
* http/tests/eventsource/eventsource-cors-basic-expected.txt: Added.
* http/tests/eventsource/eventsource-cors-basic.html: Added.
* http/tests/eventsource/eventsource-cors-no-server-expected.txt: Added.
* http/tests/eventsource/eventsource-cors-no-server.html: Added.
* http/tests/eventsource/eventsource-cors-with-credentials-expected.txt: Added.
* http/tests/eventsource/eventsource-cors-with-credentials.html: Added.
* http/tests/eventsource/resources/es-cors-basic.php: Added.
* http/tests/eventsource/resources/es-cors-credentials.php: Added.
* platform/blackberry/fast/js/constructor-length-expected.txt:
* platform/chromium/fast/js/constructor-length-expected.txt:
* platform/gtk/fast/js/constructor-length-expected.txt:
* platform/mac/fast/js/constructor-length-expected.txt:
* platform/qt/fast/js/constructor-length-expected.txt:

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

26 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/eventsource/eventsource-constructor-expected.txt
LayoutTests/fast/eventsource/eventsource-constructor.html
LayoutTests/fast/js/constructor-length.html
LayoutTests/http/tests/eventsource/eventsource-cors-basic-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/eventsource/eventsource-cors-basic.html [new file with mode: 0644]
LayoutTests/http/tests/eventsource/eventsource-cors-no-server-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/eventsource/eventsource-cors-no-server.html [new file with mode: 0644]
LayoutTests/http/tests/eventsource/eventsource-cors-with-credentials-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/eventsource/eventsource-cors-with-credentials.html [new file with mode: 0644]
LayoutTests/http/tests/eventsource/resources/es-cors-basic.php [new file with mode: 0644]
LayoutTests/http/tests/eventsource/resources/es-cors-credentials.php [new file with mode: 0644]
LayoutTests/platform/blackberry/fast/js/constructor-length-expected.txt
LayoutTests/platform/chromium/fast/js/constructor-length-expected.txt
LayoutTests/platform/gtk/fast/js/constructor-length-expected.txt
LayoutTests/platform/mac/fast/js/constructor-length-expected.txt
LayoutTests/platform/qt/fast/js/constructor-length-expected.txt
Source/WebCore/ChangeLog
Source/WebCore/loader/DocumentThreadableLoader.cpp
Source/WebCore/loader/ThreadableLoaderClient.h
Source/WebCore/loader/ThreadableLoaderClientWrapper.h
Source/WebCore/loader/WorkerThreadableLoader.cpp
Source/WebCore/loader/WorkerThreadableLoader.h
Source/WebCore/page/EventSource.cpp
Source/WebCore/page/EventSource.h
Source/WebCore/page/EventSource.idl

index f8111ac..0b25eaa 100644 (file)
@@ -1,3 +1,30 @@
+2012-12-18  Per-Erik Brodin  <per-erik.brodin@ericsson.com>
+
+        EventSource should support CORS
+        https://bugs.webkit.org/show_bug.cgi?id=61862
+
+        Reviewed by Alexey Proskuryakov.
+
+        Added new CORS tests for EventSource. Modified existing test to verify
+        that the new constructor argument can be passed to the constructor.
+
+        * fast/eventsource/eventsource-constructor-expected.txt:
+        * fast/eventsource/eventsource-constructor.html:
+        * fast/js/constructor-length.html:
+        * http/tests/eventsource/eventsource-cors-basic-expected.txt: Added.
+        * http/tests/eventsource/eventsource-cors-basic.html: Added.
+        * http/tests/eventsource/eventsource-cors-no-server-expected.txt: Added.
+        * http/tests/eventsource/eventsource-cors-no-server.html: Added.
+        * http/tests/eventsource/eventsource-cors-with-credentials-expected.txt: Added.
+        * http/tests/eventsource/eventsource-cors-with-credentials.html: Added.
+        * http/tests/eventsource/resources/es-cors-basic.php: Added.
+        * http/tests/eventsource/resources/es-cors-credentials.php: Added.
+        * platform/blackberry/fast/js/constructor-length-expected.txt:
+        * platform/chromium/fast/js/constructor-length-expected.txt:
+        * platform/gtk/fast/js/constructor-length-expected.txt:
+        * platform/mac/fast/js/constructor-length-expected.txt:
+        * platform/qt/fast/js/constructor-length-expected.txt:
+
 2012-12-18  Yuki Sekiguchi  <yuki.sekiguchi@access-company.com>
 
         Cannot click an element at 2nd line or more inside inline-block in vertical writing mode.
index 5e57ad7..6836689 100644 (file)
@@ -3,5 +3,6 @@ Test EventSource constructor functionality. Should print a series of PASS messag
 PASS: missing argument to EventSource constructor resulted in an exception (TypeError: Not enough arguments)
 PASS: passing an empty string to the EventSource constructor resulted in an exception (Error: SyntaxError: DOM Exception 12)
 PASS: passing an invalid URL to the EventSource constructor resulted in an exception (Error: SyntaxError: DOM Exception 12)
+PASS: no exception when passing a second argument to the EventSource constructor
 DONE
 
index fb53d2e..f7b6733 100644 (file)
@@ -34,7 +34,19 @@ try {
 }
 catch (ex) {
     log("PASS: passing an invalid URL to the EventSource constructor resulted in an exception (" + ex + ")");
-    log("DONE");
+}
+
+try {
+    var es = new EventSource("http://127.0.0.1/", {"withCredentials": false});
+    es.close();
+    if (!es.withCredentials) {
+        log("PASS: no exception when passing a second argument to the EventSource constructor");
+        log("DONE");
+    } else
+        log("FAIL: the 'withCredentials' property is unexpectedly true");
+}
+catch (ex) {
+    log("FAIL: passing a second argument to the EventSource constructor resulted in an exception (" + ex + ")");
 }
 
 if (window.testRunner)
index b5d1b06..91b3a02 100644 (file)
@@ -18,7 +18,7 @@ shouldBe('DOMParser.length', '0');
 shouldBe('DataView.length', '3');
 shouldBe('ErrorEvent.length', '2');
 shouldBe('Event.length', '2');
-shouldBe('EventSource.length', '1');
+shouldBe('EventSource.length', '2');
 shouldBe('Float32Array.length', '1');
 shouldBe('Float64Array.length', '1');
 shouldBe('FileReader.length', '0');
diff --git a/LayoutTests/http/tests/eventsource/eventsource-cors-basic-expected.txt b/LayoutTests/http/tests/eventsource/eventsource-cors-basic-expected.txt
new file mode 100644 (file)
index 0000000..fef7e91
--- /dev/null
@@ -0,0 +1,10 @@
+CONSOLE MESSAGE: EventSource cannot load http://localhost:8000/eventsource/resources/es-cors-basic.php?count=1. Origin http://127.0.0.1:8000 is not allowed by Access-Control-Allow-Origin.
+CONSOLE MESSAGE: EventSource cannot load http://localhost:8000/eventsource/resources/es-cors-basic.php?count=2. Origin http://127.0.0.1:8000 is not allowed by Access-Control-Allow-Origin.
+Test that basic EventSource cross-origin requests fail until they are allowed by the Access-Control-Allow-Origin header. Should print a series of PASS messages followed by DONE.
+
+PASS: got error event and readyState is CLOSED
+PASS: got error event and readyState is CLOSED
+PASS: got cross-origin message event with data "DATA1" and lastEventId "77"
+PASS: got cross-origin message event with data "DATA2"
+DONE
+
diff --git a/LayoutTests/http/tests/eventsource/eventsource-cors-basic.html b/LayoutTests/http/tests/eventsource/eventsource-cors-basic.html
new file mode 100644 (file)
index 0000000..9f9573b
--- /dev/null
@@ -0,0 +1,82 @@
+<html>
+<body>
+<p>Test that basic EventSource cross-origin requests fail until they are allowed by the Access-Control-Allow-Origin header. Should print a series of PASS messages followed by DONE.</p>
+<div id="result"></div>
+<script>
+function log(msg) {
+    document.getElementById("result").innerHTML += msg + "<br>";
+}
+
+if (window.testRunner) {
+    testRunner.dumpAsText();
+    testRunner.waitUntilDone();
+}
+
+function end() {
+    if (window.testRunner)
+        testRunner.notifyDone();
+}
+
+var count = 1;
+var base_url = location.href.substr(0, location.href.lastIndexOf('/')).replace("127.0.0.1", "localhost");
+
+function create_es() {
+    try {
+        var es = new EventSource(base_url + "/resources/es-cors-basic.php?count=" + count);
+    }
+    catch (ex) {
+        log("FAIL: EventSource constructor threw exception: " + ex);
+        end();
+        return;
+    }
+
+    if (es.withCredentials) {
+        log("FAIL: withCredentials is true");
+        es.close();
+        end();
+    }
+
+    es.onerror = function () {
+        if (es.readyState == es.CLOSED) {
+            if (count != 3 && count != 4) {
+                log("PASS: got error event and readyState is CLOSED");
+                count++;
+                setTimeout(create_es);
+            }
+            else {
+                log("FAIL: got unexpected error event");
+                end();
+            }
+        }
+        else if (count != 4)  {
+            log("FAIL: got error event but readyState is not CLOSED");
+            es.close();
+            end();
+        }
+    };
+
+    es.onmessage = function (evt) {
+        if (evt.origin != location.origin && !evt.origin.indexOf("http://localhost")) {
+            if (count == 3 && evt.data == "DATA1" && evt.lastEventId == "77") {
+                log("PASS: got cross-origin message event with data \"DATA1\" and lastEventId \"77\"");
+                count++;
+                return;
+            }
+            if (count == 4 && evt.data == "DATA2") {
+                log("PASS: got cross-origin message event with data \"DATA2\"");
+                log("DONE");
+            }
+            else
+                log("FAIL: got unexpected cross-origin message event");
+        }
+        else
+            log("FAIL: got message event from same or unexpected origin");
+
+        es.close();
+        end();
+    };
+}
+create_es();
+</script>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/eventsource/eventsource-cors-no-server-expected.txt b/LayoutTests/http/tests/eventsource/eventsource-cors-no-server-expected.txt
new file mode 100644 (file)
index 0000000..910efc4
--- /dev/null
@@ -0,0 +1,4 @@
+Test that EventSource tries to reconnect if there's no server response when making cross-origin requests. Should print a series of PASS messages followed by DONE.
+
+PASS: got error event and readyState is CONNECTING
+
diff --git a/LayoutTests/http/tests/eventsource/eventsource-cors-no-server.html b/LayoutTests/http/tests/eventsource/eventsource-cors-no-server.html
new file mode 100644 (file)
index 0000000..e8798d2
--- /dev/null
@@ -0,0 +1,55 @@
+<html>
+<body>
+<p>Test that EventSource tries to reconnect if there's no server response when making cross-origin requests. Should print a series of PASS messages followed by DONE.</p>
+<div id="result"></div>
+<script>
+function log(msg) {
+    document.getElementById("result").innerHTML += msg + "<br>";
+}
+
+if (window.testRunner) {
+    testRunner.dumpAsText();
+    testRunner.waitUntilDone();
+}
+
+function end() {
+    if (window.testRunner)
+        testRunner.notifyDone();
+}
+
+var count = 0;
+var hosts = ["http://127.0.0.1:12345/event-stream", "http://localhost:54321/event-stream"];
+
+function create_es() {
+    try {
+        var es = new EventSource(hosts[count]);
+    }
+    catch (ex) {
+        log("FAIL: EventSource constructor threw exception: " + ex);
+        end();
+        return;
+    }
+
+    es.onerror = function () {
+        if (es.readyState == es.CONNECTING) {
+            log("PASS: got error event and readyState is CONNECTING");
+            es.close();
+            end();
+            return;
+        }
+
+        if (es.readyState == es.CLOSED)
+            log("FAIL: got error event but readyState is CLOSED");
+
+        if (++count == hosts.length) {
+            log("DONE");
+            end();
+        }
+        else
+            setTimeout(create_es);
+    };
+}
+create_es();
+</script>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/eventsource/eventsource-cors-with-credentials-expected.txt b/LayoutTests/http/tests/eventsource/eventsource-cors-with-credentials-expected.txt
new file mode 100644 (file)
index 0000000..261d0f0
--- /dev/null
@@ -0,0 +1,12 @@
+CONSOLE MESSAGE: EventSource cannot load http://localhost:8000/eventsource/resources/es-cors-credentials.php?count=1. Cannot use wildcard in Access-Control-Allow-Origin when credentials flag is true.
+CONSOLE MESSAGE: EventSource cannot load http://localhost:8000/eventsource/resources/es-cors-credentials.php?count=2. Cannot use wildcard in Access-Control-Allow-Origin when credentials flag is true.
+CONSOLE MESSAGE: EventSource cannot load http://localhost:8000/eventsource/resources/es-cors-credentials.php?count=3. Credentials flag is true, but Access-Control-Allow-Credentials is not "true".
+Test that EventSource cross-origin requests with credentials fail until the correct CORS headers are sent. Should print a series of PASS messages followed by DONE.
+
+PASS: got error event and readyState is CLOSED
+PASS: got error event and readyState is CLOSED
+PASS: got error event and readyState is CLOSED
+PASS: got cross-origin message event with data "DATA1" and lastEventId "77"
+PASS: got cross-origin message event with data "DATA2"
+DONE
+
diff --git a/LayoutTests/http/tests/eventsource/eventsource-cors-with-credentials.html b/LayoutTests/http/tests/eventsource/eventsource-cors-with-credentials.html
new file mode 100644 (file)
index 0000000..68c429e
--- /dev/null
@@ -0,0 +1,82 @@
+<html>
+<body>
+<p>Test that EventSource cross-origin requests with credentials fail until the correct CORS headers are sent. Should print a series of PASS messages followed by DONE.</p>
+<div id="result"></div>
+<script>
+function log(msg) {
+    document.getElementById("result").innerHTML += msg + "<br>";
+}
+
+if (window.testRunner) {
+    testRunner.dumpAsText();
+    testRunner.waitUntilDone();
+}
+
+function end() {
+    if (window.testRunner)
+        testRunner.notifyDone();
+}
+
+var count = 1;
+var base_url = location.href.substr(0, location.href.lastIndexOf('/')).replace("127.0.0.1", "localhost");
+
+function create_es() {
+    try {
+        var es = new EventSource(base_url + "/resources/es-cors-credentials.php?count=" + count, {"withCredentials": true});
+    }
+    catch (ex) {
+        log("FAIL: EventSource constructor threw exception: " + ex);
+        end();
+        return;
+    }
+
+    if (!es.withCredentials) {
+        log("FAIL: withCredentials is false");
+        es.close();
+        end();
+    }
+
+    es.onerror = function () {
+        if (es.readyState == es.CLOSED) {
+            if (count != 4 && count != 5) {
+                log("PASS: got error event and readyState is CLOSED");
+                count++;
+                setTimeout(create_es);
+            }
+            else {
+                log("FAIL: got unexpected error event");
+                end();
+            }
+        }
+        else if (count != 5)  {
+            log("FAIL: got error event but readyState is not CLOSED");
+            es.close();
+            end();
+        }
+    };
+
+    es.onmessage = function (evt) {
+        if (evt.origin != location.origin && !evt.origin.indexOf("http://localhost")) {
+            if (count == 4 && evt.data == "DATA1" && evt.lastEventId == "77") {
+                log("PASS: got cross-origin message event with data \"DATA1\" and lastEventId \"77\"");
+                count++;
+                return;
+            }
+            if (count == 5 && evt.data == "DATA2") {
+                log("PASS: got cross-origin message event with data \"DATA2\"");
+                log("DONE");
+            }
+            else
+                log("FAIL: got unexpected cross-origin message event");
+        }
+        else
+            log("FAIL: got message event from same or unexpected origin");
+
+        es.close();
+        end();
+    };
+}
+create_es();
+</script>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/eventsource/resources/es-cors-basic.php b/LayoutTests/http/tests/eventsource/resources/es-cors-basic.php
new file mode 100644 (file)
index 0000000..d632540
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+if ($_SERVER["REQUEST_METHOD"] == "OPTIONS")
+    die("Got unexpected preflight request");
+
+header("Content-Type: text/event-stream");
+
+$count = intval($_GET["count"]);
+
+if ($count == 2)
+    header("Access-Control-Allow-Origin: http://some.other.origin:80");
+else if ($count == 3)
+    header("Access-Control-Allow-Origin: *");
+else if ($count > 3)
+    header("Access-Control-Allow-Origin: " . $_SERVER["HTTP_ORIGIN"]);
+
+if ($_SERVER["HTTP_LAST_EVENT_ID"] != "77")
+    echo "id: 77\ndata: DATA1\nretry: 0\n\n";
+else
+    echo "data: DATA2\n\n";
+?>
diff --git a/LayoutTests/http/tests/eventsource/resources/es-cors-credentials.php b/LayoutTests/http/tests/eventsource/resources/es-cors-credentials.php
new file mode 100644 (file)
index 0000000..68d0273
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+if ($_SERVER["REQUEST_METHOD"] == "OPTIONS")
+    die("Got unexpected preflight request");
+
+header("Content-Type: text/event-stream");
+
+$count = intval($_GET["count"]);
+
+if ($count == 1 || $count == 2)
+    header("Access-Control-Allow-Origin: *");
+else if ($count > 2)
+    header("Access-Control-Allow-Origin: " . $_SERVER["HTTP_ORIGIN"]);
+
+if ($count == 2 || $count > 3)
+    header("Access-Control-Allow-Credentials: true");
+
+if ($_SERVER["HTTP_LAST_EVENT_ID"] != "77")
+    echo "id: 77\ndata: DATA1\nretry: 0\n\n";
+else
+    echo "data: DATA2\n\n";
+?>
index f96d149..a55d8d4 100644 (file)
@@ -14,7 +14,7 @@ PASS DOMParser.length is 0
 PASS DataView.length is 3
 PASS ErrorEvent.length is 2
 PASS Event.length is 2
-PASS EventSource.length is 1
+PASS EventSource.length is 2
 PASS Float32Array.length is 1
 PASS Float64Array.length is 1
 PASS FileReader.length is 0
index 31b669a..d167476 100644 (file)
@@ -14,7 +14,7 @@ PASS DOMParser.length is 0
 FAIL DataView.length should be 3. Was 0.
 FAIL ErrorEvent.length should be 2. Was 0.
 FAIL Event.length should be 2. Was 0.
-FAIL EventSource.length should be 1. Was 0.
+FAIL EventSource.length should be 2. Was 0.
 FAIL Float32Array.length should be 1. Was 0.
 FAIL Float64Array.length should be 1. Was 0.
 PASS FileReader.length is 0
index 394414b..5a155ef 100644 (file)
@@ -14,7 +14,7 @@ PASS DOMParser.length is 0
 PASS DataView.length is 3
 PASS ErrorEvent.length is 2
 PASS Event.length is 2
-PASS EventSource.length is 1
+PASS EventSource.length is 2
 PASS Float32Array.length is 1
 PASS Float64Array.length is 1
 PASS FileReader.length is 0
index 394414b..5a155ef 100644 (file)
@@ -14,7 +14,7 @@ PASS DOMParser.length is 0
 PASS DataView.length is 3
 PASS ErrorEvent.length is 2
 PASS Event.length is 2
-PASS EventSource.length is 1
+PASS EventSource.length is 2
 PASS Float32Array.length is 1
 PASS Float64Array.length is 1
 PASS FileReader.length is 0
index dc4c874..99899af 100644 (file)
@@ -14,7 +14,7 @@ PASS DOMParser.length is 0
 PASS DataView.length is 3
 PASS ErrorEvent.length is 2
 PASS Event.length is 2
-PASS EventSource.length is 1
+PASS EventSource.length is 2
 PASS Float32Array.length is 1
 PASS Float64Array.length is 1
 PASS FileReader.length is 0
index 6f5bb5f..e42ab11 100644 (file)
@@ -1,3 +1,53 @@
+2012-12-18  Per-Erik Brodin  <per-erik.brodin@ericsson.com>
+
+        EventSource should support CORS
+        https://bugs.webkit.org/show_bug.cgi?id=61862
+
+        Reviewed by Alexey Proskuryakov.
+
+        Enabled CORS in EventSource with optional constructor argument to
+        indicate whether credentials should be included or not, as per the spec.
+        Added didFailAccessControlCheck to ThreadableLoaderClient to be able to
+        properly distinguish CORS failures from other errors.
+
+        Tests: http/tests/eventsource/eventsource-cors-basic.html
+               http/tests/eventsource/eventsource-cors-no-server.html
+               http/tests/eventsource/eventsource-cors-with-credentials.html
+
+        * loader/DocumentThreadableLoader.cpp:
+        (WebCore::DocumentThreadableLoader::makeSimpleCrossOriginAccessRequest):
+        (WebCore::DocumentThreadableLoader::didReceiveResponse):
+        (WebCore::DocumentThreadableLoader::preflightFailure):
+        * loader/ThreadableLoaderClient.h:
+        (WebCore::ThreadableLoaderClient::didFailAccessControlCheck):
+        * loader/ThreadableLoaderClientWrapper.h:
+        (WebCore::ThreadableLoaderClientWrapper::didFailAccessControlCheck):
+        (ThreadableLoaderClientWrapper):
+        * loader/WorkerThreadableLoader.cpp:
+        (WebCore::workerContextDidFailAccessControlCheck):
+        (WebCore):
+        (WebCore::WorkerThreadableLoader::MainThreadBridge::didFailAccessControlCheck):
+        * loader/WorkerThreadableLoader.h:
+        (MainThreadBridge):
+        * page/EventSource.cpp:
+        (WebCore::EventSource::EventSource):
+        (WebCore::EventSource::create):
+        (WebCore::EventSource::connect):
+        (WebCore::EventSource::withCredentials):
+        (WebCore):
+        (WebCore::EventSource::didReceiveResponse):
+        (WebCore::EventSource::didFailAccessControlCheck):
+        (WebCore::EventSource::didFailRedirectCheck):
+        (WebCore::EventSource::abortConnectionAttempt):
+        (WebCore::EventSource::parseEventStreamLine):
+        (WebCore::EventSource::createMessageEvent):
+        * page/EventSource.h:
+        (WebCore):
+        (EventSource):
+        (WebCore::EventSource::refEventTarget):
+        (WebCore::EventSource::derefEventTarget):
+        * page/EventSource.idl:
+
 2012-12-18  Michael Pruett  <michael@68k.org>
 
         IndexedDB: Implement custom bindings for parsing options
index 88f52f4..3458863 100644 (file)
@@ -127,7 +127,7 @@ void DocumentThreadableLoader::makeSimpleCrossOriginAccessRequest(const Resource
 
     // Cross-origin requests are only allowed for HTTP and registered schemes. We would catch this when checking response headers later, but there is no reason to send a request that's guaranteed to be denied.
     if (!SchemeRegistry::shouldTreatURLSchemeAsCORSEnabled(request.url().protocol())) {
-        m_client->didFail(ResourceError(errorDomainWebKitInternal, 0, request.url().string(), "Cross origin requests are only supported for HTTP."));
+        m_client->didFailAccessControlCheck(ResourceError(errorDomainWebKitInternal, 0, request.url().string(), "Cross origin requests are only supported for HTTP."));
         return;
     }
 
@@ -274,7 +274,7 @@ void DocumentThreadableLoader::didReceiveResponse(unsigned long identifier, cons
     } else {
         if (!m_sameOriginRequest && m_options.crossOriginRequestPolicy == UseAccessControl) {
             if (!passesAccessControlCheck(response, m_options.allowCredentials, securityOrigin(), accessControlErrorDescription)) {
-                m_client->didFail(ResourceError(errorDomainWebKitInternal, 0, response.url().string(), accessControlErrorDescription));
+                m_client->didFailAccessControlCheck(ResourceError(errorDomainWebKitInternal, 0, response.url().string(), accessControlErrorDescription));
                 return;
             }
         }
@@ -352,7 +352,7 @@ void DocumentThreadableLoader::preflightSuccess()
 void DocumentThreadableLoader::preflightFailure(const String& url, const String& errorDescription)
 {
     m_actualRequest = nullptr; // Prevent didFinishLoading() from bypassing access check.
-    m_client->didFail(ResourceError(errorDomainWebKitInternal, 0, url, errorDescription));
+    m_client->didFailAccessControlCheck(ResourceError(errorDomainWebKitInternal, 0, url, errorDescription));
 }
 
 void DocumentThreadableLoader::loadRequest(const ResourceRequest& request, SecurityCheckPolicy securityCheck)
index fb345a7..6399231 100644 (file)
@@ -47,6 +47,7 @@ namespace WebCore {
         virtual void didReceiveCachedMetadata(const char*, int /*dataLength*/) { }
         virtual void didFinishLoading(unsigned long /*identifier*/, double /*finishTime*/) { }
         virtual void didFail(const ResourceError&) { }
+        virtual void didFailAccessControlCheck(const ResourceError& error) { didFail(error); }
         virtual void didFailRedirectCheck() { }
 
         virtual bool isDocumentThreadableLoaderClient() { return false; }
index b1a1e2d..774b992 100644 (file)
@@ -94,6 +94,13 @@ public:
             m_client->didFail(error);
     }
 
+    void didFailAccessControlCheck(const ResourceError& error)
+    {
+        m_done = true;
+        if (m_client)
+            m_client->didFailAccessControlCheck(error);
+    }
+
     void didFailRedirectCheck()
     {
         m_done = true;
index 76dd33c..3a26244 100644 (file)
@@ -238,6 +238,17 @@ void WorkerThreadableLoader::MainThreadBridge::didFail(const ResourceError& erro
     m_loaderProxy.postTaskForModeToWorkerContext(createCallbackTask(&workerContextDidFail, m_workerClientWrapper, error), m_taskMode);
 }
 
+static void workerContextDidFailAccessControlCheck(ScriptExecutionContext* context, PassRefPtr<ThreadableLoaderClientWrapper> workerClientWrapper, const ResourceError& error)
+{
+    ASSERT_UNUSED(context, context->isWorkerContext());
+    workerClientWrapper->didFailAccessControlCheck(error);
+}
+
+void WorkerThreadableLoader::MainThreadBridge::didFailAccessControlCheck(const ResourceError& error)
+{
+    m_loaderProxy.postTaskForModeToWorkerContext(createCallbackTask(&workerContextDidFailAccessControlCheck, m_workerClientWrapper, error), m_taskMode);
+}
+
 static void workerContextDidFailRedirectCheck(ScriptExecutionContext* context, RefPtr<ThreadableLoaderClientWrapper> workerClientWrapper)
 {
     ASSERT_UNUSED(context, context->isWorkerContext());
index 055dfa7..aa53196 100644 (file)
@@ -118,6 +118,7 @@ namespace WebCore {
             virtual void didReceiveCachedMetadata(const char*, int dataLength);
             virtual void didFinishLoading(unsigned long identifier, double finishTime);
             virtual void didFail(const ResourceError&);
+            virtual void didFailAccessControlCheck(const ResourceError&);
             virtual void didFailRedirectCheck();
 
             // Only to be used on the main thread.
index 401df36..f044345 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * Copyright (C) 2009 Ericsson AB
- * All rights reserved.
+ * Copyright (C) 2009, 2012 Ericsson AB. All rights reserved.
  * Copyright (C) 2010 Apple Inc. All rights reserved.
  * Copyright (C) 2011, Code Aurora Forum. All rights reserved.
  *
@@ -36,6 +35,7 @@
 
 #include "ContentSecurityPolicy.h"
 #include "DOMWindow.h"
+#include "Dictionary.h"
 #include "Event.h"
 #include "EventException.h"
 #include "ExceptionCode.h"
@@ -56,20 +56,21 @@ namespace WebCore {
 
 const unsigned long long EventSource::defaultReconnectDelay = 3000;
 
-inline EventSource::EventSource(const KURL& url, ScriptExecutionContext* context)
+inline EventSource::EventSource(ScriptExecutionContext* context, const KURL& url, const Dictionary& eventSourceInit)
     : ActiveDOMObject(context, this)
     , m_url(url)
+    , m_withCredentials(false)
     , m_state(CONNECTING)
     , m_decoder(TextResourceDecoder::create("text/plain", "UTF-8"))
     , m_reconnectTimer(this, &EventSource::reconnectTimerFired)
     , m_discardTrailingNewline(false)
     , m_requestInFlight(false)
     , m_reconnectDelay(defaultReconnectDelay)
-    , m_origin(context->securityOrigin()->toString())
 {
+    eventSourceInit.get("withCredentials", m_withCredentials);
 }
 
-PassRefPtr<EventSource> EventSource::create(ScriptExecutionContext* context, const String& url, ExceptionCode& ec)
+PassRefPtr<EventSource> EventSource::create(ScriptExecutionContext* context, const String& url, const Dictionary& eventSourceInit, ExceptionCode& ec)
 {
     if (url.isEmpty()) {
         ec = SYNTAX_ERR;
@@ -82,19 +83,13 @@ PassRefPtr<EventSource> EventSource::create(ScriptExecutionContext* context, con
         return 0;
     }
 
-    // FIXME: Should support at least some cross-origin requests.
-    if (!context->securityOrigin()->canRequest(fullURL)) {
-        ec = SECURITY_ERR;
-        return 0;
-    }
-
     if (!context->contentSecurityPolicy()->allowConnectToSource(fullURL)) {
         // FIXME: Should this be throwing an exception?
         ec = SECURITY_ERR;
         return 0;
     }
 
-    RefPtr<EventSource> source = adoptRef(new EventSource(fullURL, context));
+    RefPtr<EventSource> source = adoptRef(new EventSource(context, fullURL, eventSourceInit));
 
     source->setPendingActivity(source.get());
     source->connect();
@@ -121,11 +116,16 @@ void EventSource::connect()
     if (!m_lastEventId.isEmpty())
         request.setHTTPHeaderField("Last-Event-ID", m_lastEventId);
 
+    SecurityOrigin* origin = scriptExecutionContext()->securityOrigin();
+
     ThreadableLoaderOptions options;
     options.sendLoadCallbacks = SendCallbacks;
     options.sniffContent = DoNotSniffContent;
-    options.allowCredentials = AllowStoredCredentials;
+    options.allowCredentials = (origin->canRequest(m_url) || m_withCredentials) ? AllowStoredCredentials : DoNotAllowStoredCredentials;
+    options.preflightPolicy = PreventPreflight;
+    options.crossOriginRequestPolicy = UseAccessControl;
     options.shouldBufferData = DoNotBufferData;
+    options.securityOrigin = origin;
 
     m_loader = ThreadableLoader::create(scriptExecutionContext(), this, request, options);
 
@@ -163,6 +163,11 @@ String EventSource::url() const
     return m_url.string();
 }
 
+bool EventSource::withCredentials() const
+{
+    return m_withCredentials;
+}
+
 EventSource::State EventSource::readyState() const
 {
     return m_state;
@@ -202,6 +207,7 @@ void EventSource::didReceiveResponse(unsigned long, const ResourceResponse& resp
     ASSERT(m_state == CONNECTING);
     ASSERT(m_requestInFlight);
 
+    m_eventStreamOrigin = SecurityOrigin::create(response.url())->toString();
     int statusCode = response.httpStatusCode();
     bool mimeTypeIsValid = response.mimeType() == "text/event-stream";
     bool responseIsValid = statusCode == 200 && mimeTypeIsValid;
@@ -274,8 +280,21 @@ void EventSource::didFail(const ResourceError& error)
     networkRequestEnded();
 }
 
+void EventSource::didFailAccessControlCheck(const ResourceError& error)
+{
+    String message = makeString("EventSource cannot load ", error.failingURL(), ". ", error.localizedDescription());
+    scriptExecutionContext()->addConsoleMessage(JSMessageSource, ErrorMessageLevel, message);
+
+    abortConnectionAttempt();
+}
+
 void EventSource::didFailRedirectCheck()
 {
+    abortConnectionAttempt();
+}
+
+void EventSource::abortConnectionAttempt()
+{
     ASSERT(m_state == CONNECTING);
     ASSERT(m_requestInFlight);
 
@@ -330,7 +349,7 @@ void EventSource::parseEventStream()
         m_receiveBuf.remove(0, bufPos);
 }
 
-void EventSource::parseEventStreamLine(unsigned int bufPos, int fieldLength, int lineLength)
+void EventSource::parseEventStreamLine(unsigned bufPos, int fieldLength, int lineLength)
 {
     if (!lineLength) {
         if (!m_data.isEmpty()) {
@@ -387,7 +406,7 @@ void EventSource::stop()
 PassRefPtr<MessageEvent> EventSource::createMessageEvent()
 {
     RefPtr<MessageEvent> event = MessageEvent::create();
-    event->initMessageEvent(m_eventName.isEmpty() ? eventNames().messageEvent : AtomicString(m_eventName), false, false, SerializedScriptValue::create(String::adopt(m_data)), m_origin, m_lastEventId, 0, 0);
+    event->initMessageEvent(m_eventName.isEmpty() ? eventNames().messageEvent : AtomicString(m_eventName), false, false, SerializedScriptValue::create(String::adopt(m_data)), m_eventStreamOrigin, m_lastEventId, 0, 0);
     return event.release();
 }
 
index 0518182..e97f896 100644 (file)
@@ -1,6 +1,5 @@
 /*
- * Copyright (C) 2009 Ericsson AB
- * All rights reserved.
+ * Copyright (C) 2009, 2012 Ericsson AB. All rights reserved.
  * Copyright (C) 2010 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
 
 namespace WebCore {
 
-    class MessageEvent;
-    class ResourceResponse;
-    class TextResourceDecoder;
-    class ThreadableLoader;
-
-    class EventSource : public RefCounted<EventSource>, public EventTarget, private ThreadableLoaderClient, public ActiveDOMObject {
-        WTF_MAKE_FAST_ALLOCATED;
-    public:
-        static PassRefPtr<EventSource> create(ScriptExecutionContext*, const String& url, ExceptionCode&);
-        virtual ~EventSource();
-
-        static const unsigned long long defaultReconnectDelay;
-
-        String url() const;
-
-        enum State {
-            CONNECTING = 0,
-            OPEN = 1,
-            CLOSED = 2,
-        };
-
-        State readyState() const;
-
-        DEFINE_ATTRIBUTE_EVENT_LISTENER(open);
-        DEFINE_ATTRIBUTE_EVENT_LISTENER(message);
-        DEFINE_ATTRIBUTE_EVENT_LISTENER(error);
-
-        void close();
-
-        using RefCounted<EventSource>::ref;
-        using RefCounted<EventSource>::deref;
-
-        virtual const AtomicString& interfaceName() const;
-        virtual ScriptExecutionContext* scriptExecutionContext() const;
-
-        virtual void stop();
-
-    private:
-        EventSource(const KURL&, ScriptExecutionContext*);
-
-        virtual void refEventTarget() { ref(); }
-        virtual void derefEventTarget() { deref(); }
-        virtual EventTargetData* eventTargetData();
-        virtual EventTargetData* ensureEventTargetData();
-
-        virtual void didReceiveResponse(unsigned long, const ResourceResponse&);
-        virtual void didReceiveData(const char*, int);
-        virtual void didFinishLoading(unsigned long, double);
-        virtual void didFail(const ResourceError&);
-        virtual void didFailRedirectCheck();
-
-        void connect();
-        void networkRequestEnded();
-        void scheduleReconnect();
-        void reconnectTimerFired(Timer<EventSource>*);
-        void parseEventStream();
-        void parseEventStreamLine(unsigned int pos, int fieldLength, int lineLength);
-        PassRefPtr<MessageEvent> createMessageEvent();
-
-        KURL m_url;
-        State m_state;
-
-        RefPtr<TextResourceDecoder> m_decoder;
-        RefPtr<ThreadableLoader> m_loader;
-        Timer<EventSource> m_reconnectTimer;
-        Vector<UChar> m_receiveBuf;
-        bool m_discardTrailingNewline;
-        bool m_requestInFlight;
-
-        String m_eventName;
-        Vector<UChar> m_data;
-        String m_currentlyParsedEventId;
-        String m_lastEventId;
-        unsigned long long m_reconnectDelay;
-        String m_origin;
-        
-        EventTargetData m_eventTargetData;
-    };
+class Dictionary;
+class MessageEvent;
+class ResourceResponse;
+class TextResourceDecoder;
+class ThreadableLoader;
+
+class EventSource : public RefCounted<EventSource>, public EventTarget, private ThreadableLoaderClient, public ActiveDOMObject {
+    WTF_MAKE_FAST_ALLOCATED;
+public:
+    static PassRefPtr<EventSource> create(ScriptExecutionContext*, const String& url, const Dictionary&, ExceptionCode&);
+    virtual ~EventSource();
+
+    static const unsigned long long defaultReconnectDelay;
+
+    String url() const;
+    bool withCredentials() const;
+
+    typedef short State;
+    static const State CONNECTING = 0;
+    static const State OPEN = 1;
+    static const State CLOSED = 2;
+
+    State readyState() const;
+
+    DEFINE_ATTRIBUTE_EVENT_LISTENER(open);
+    DEFINE_ATTRIBUTE_EVENT_LISTENER(message);
+    DEFINE_ATTRIBUTE_EVENT_LISTENER(error);
+
+    void close();
+
+    using RefCounted<EventSource>::ref;
+    using RefCounted<EventSource>::deref;
+
+    virtual const AtomicString& interfaceName() const;
+    virtual ScriptExecutionContext* scriptExecutionContext() const;
+
+    virtual void stop();
+
+private:
+    EventSource(ScriptExecutionContext*, const KURL&, const Dictionary&);
+
+    virtual void refEventTarget() { ref(); }
+    virtual void derefEventTarget() { deref(); }
+    virtual EventTargetData* eventTargetData();
+    virtual EventTargetData* ensureEventTargetData();
+
+    virtual void didReceiveResponse(unsigned long, const ResourceResponse&);
+    virtual void didReceiveData(const char*, int);
+    virtual void didFinishLoading(unsigned long, double);
+    virtual void didFail(const ResourceError&);
+    virtual void didFailAccessControlCheck(const ResourceError&);
+    virtual void didFailRedirectCheck();
+
+    void connect();
+    void networkRequestEnded();
+    void scheduleReconnect();
+    void reconnectTimerFired(Timer<EventSource>*);
+    void abortConnectionAttempt();
+    void parseEventStream();
+    void parseEventStreamLine(unsigned pos, int fieldLength, int lineLength);
+    PassRefPtr<MessageEvent> createMessageEvent();
+
+    KURL m_url;
+    bool m_withCredentials;
+    State m_state;
+
+    RefPtr<TextResourceDecoder> m_decoder;
+    RefPtr<ThreadableLoader> m_loader;
+    Timer<EventSource> m_reconnectTimer;
+    Vector<UChar> m_receiveBuf;
+    bool m_discardTrailingNewline;
+    bool m_requestInFlight;
+
+    String m_eventName;
+    Vector<UChar> m_data;
+    String m_currentlyParsedEventId;
+    String m_lastEventId;
+    unsigned long long m_reconnectDelay;
+    String m_eventStreamOrigin;
+
+    EventTargetData m_eventTargetData;
+};
 
 } // namespace WebCore
 
index 60d36fa..6dcf2d6 100644 (file)
@@ -31,7 +31,7 @@
 
 [
     ActiveDOMObject,
-    Constructor(in DOMString scriptUrl),
+    Constructor(in DOMString url, in [Optional] Dictionary eventSourceInit),
     CallWith=ScriptExecutionContext,
     ConstructorRaisesException,
     EventTarget,
@@ -40,6 +40,7 @@
 
     readonly attribute DOMString URL; // Lowercased .url is the one in the spec, but leaving .URL for compatibility reasons.
     readonly attribute DOMString url;
+    readonly attribute boolean withCredentials;
 
     // ready state
     const unsigned short CONNECTING = 0;