WebCore:
authorabarth@webkit.org <abarth@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 5 Oct 2008 19:12:30 +0000 (19:12 +0000)
committerabarth@webkit.org <abarth@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 5 Oct 2008 19:12:30 +0000 (19:12 +0000)
2008-10-04  Adam Barth  <abarth@webkit.org>

        Reviewed by Darin Alder.

        Attach the Origin header to POST requests to help defend against
        cross-site request forgery.

        https://bugs.webkit.org/show_bug.cgi?id=20792

        Collin Jackson <collinj@webkit.org> also contributed to this patch.

        Tests: http/tests/security/originHeader/origin-header-for-data.html
               http/tests/security/originHeader/origin-header-for-empty.html
               http/tests/security/originHeader/origin-header-for-get.html
               http/tests/security/originHeader/origin-header-for-https.html
               http/tests/security/originHeader/origin-header-for-post.html

        * bindings/js/JSDOMWindowBase.cpp:
        (WebCore::createWindow):
        * loader/FrameLoader.cpp:
        (WebCore::FrameLoader::createWindow):
        (WebCore::FrameLoader::urlSelected):
        (WebCore::FrameLoader::submitForm):
        (WebCore::FrameLoader::outgoingOrigin):
        (WebCore::FrameLoader::loadURL):
        (WebCore::FrameLoader::addExtraFieldsToRequest):
        (WebCore::FrameLoader::loadPostRequest):
        (WebCore::FrameLoader::loadResourceSynchronously):
        (WebCore::FrameLoader::loadItem):
        * loader/FrameLoader.h:
        * loader/SubresourceLoader.cpp:
        (WebCore::SubresourceLoader::create):
        * loader/loader.cpp:
        (WebCore::Loader::Host::servePendingRequests):
        * platform/SecurityOrigin.cpp:
        (WebCore::SecurityOrigin::toHTTPOrigin):
        * platform/SecurityOrigin.h:
        * platform/network/ResourceRequestBase.h:
        (WebCore::ResourceRequestBase::httpOrigin):
        (WebCore::ResourceRequestBase::setHTTPOrigin):
        (WebCore::ResourceRequestBase::clearHTTPOrigin):
        * xml/XMLHttpRequest.cpp:
        (WebCore::XMLHttpRequest::makeSimpleCrossSiteAccessRequest):
        (WebCore::XMLHttpRequest::makeCrossSiteAccessRequestWithPreflight):
        (WebCore::XMLHttpRequest::handleAsynchronousPreflightResult):
        (WebCore::XMLHttpRequest::didReceiveResponsePreflight):

LayoutTests:

2008-10-04  Adam Barth  <abarth@webkit.org>

        Reviewed by Darin Adler.

        Tests for the new Origin header.

        https://bugs.webkit.org/show_bug.cgi?id=20792

        Collin Jackson <collinj@webkit.org> also contributed to this patch.

        * http/tests/security/originHeader: Added.
        * http/tests/security/originHeader/origin-header-for-data-expected.txt: Added.
        * http/tests/security/originHeader/origin-header-for-data.html: Added.
        * http/tests/security/originHeader/origin-header-for-empty-expected.txt: Added.
        * http/tests/security/originHeader/origin-header-for-empty.html: Added.
        * http/tests/security/originHeader/origin-header-for-get-expected.txt: Added.
        * http/tests/security/originHeader/origin-header-for-get.html: Added.
        * http/tests/security/originHeader/origin-header-for-https-expected.txt: Added.
        * http/tests/security/originHeader/origin-header-for-https.html: Added.
        * http/tests/security/originHeader/origin-header-for-post-expected.txt: Added.
        * http/tests/security/originHeader/origin-header-for-post.html: Added.
        * http/tests/security/originHeader/origin-header-for-xmlhttprequest-expected.txt: Added.
        * http/tests/security/originHeader/origin-header-for-xmlhttprequest.html: Added.
        * http/tests/security/originHeader/resources: Added.
        * http/tests/security/originHeader/resources/origin-header-post-to-http.html: Added.
        * http/tests/security/originHeader/resources/print-origin.cgi: Added.

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

39 files changed:
LayoutTests/ChangeLog
LayoutTests/http/tests/security/originHeader/origin-header-for-data-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/security/originHeader/origin-header-for-data.html [new file with mode: 0644]
LayoutTests/http/tests/security/originHeader/origin-header-for-empty-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/security/originHeader/origin-header-for-empty.html [new file with mode: 0644]
LayoutTests/http/tests/security/originHeader/origin-header-for-get-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/security/originHeader/origin-header-for-get.html [new file with mode: 0644]
LayoutTests/http/tests/security/originHeader/origin-header-for-https-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/security/originHeader/origin-header-for-https.html [new file with mode: 0644]
LayoutTests/http/tests/security/originHeader/origin-header-for-post-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/security/originHeader/origin-header-for-post.html [new file with mode: 0644]
LayoutTests/http/tests/security/originHeader/resources/origin-header-post-to-http.html [new file with mode: 0644]
LayoutTests/http/tests/security/originHeader/resources/print-origin.cgi [new file with mode: 0755]
LayoutTests/http/tests/xmlhttprequest/origin-header-cross-origin-get-async-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/xmlhttprequest/origin-header-cross-origin-get-async.html [new file with mode: 0644]
LayoutTests/http/tests/xmlhttprequest/origin-header-cross-origin-get-sync-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/xmlhttprequest/origin-header-cross-origin-get-sync.html [new file with mode: 0644]
LayoutTests/http/tests/xmlhttprequest/origin-header-cross-origin-post-async-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/xmlhttprequest/origin-header-cross-origin-post-async.html [new file with mode: 0644]
LayoutTests/http/tests/xmlhttprequest/origin-header-cross-origin-post-sync-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/xmlhttprequest/origin-header-cross-origin-post-sync.html [new file with mode: 0644]
LayoutTests/http/tests/xmlhttprequest/origin-header-same-origin-get-async-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/xmlhttprequest/origin-header-same-origin-get-async.html [new file with mode: 0644]
LayoutTests/http/tests/xmlhttprequest/origin-header-same-origin-get-sync-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/xmlhttprequest/origin-header-same-origin-get-sync.html [new file with mode: 0644]
LayoutTests/http/tests/xmlhttprequest/origin-header-same-origin-post-async-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/xmlhttprequest/origin-header-same-origin-post-async.html [new file with mode: 0644]
LayoutTests/http/tests/xmlhttprequest/origin-header-same-origin-post-sync-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/xmlhttprequest/origin-header-same-origin-post-sync.html [new file with mode: 0644]
WebCore/ChangeLog
WebCore/bindings/js/JSDOMWindowBase.cpp
WebCore/loader/FrameLoader.cpp
WebCore/loader/FrameLoader.h
WebCore/loader/SubresourceLoader.cpp
WebCore/loader/loader.cpp
WebCore/page/SecurityOrigin.cpp
WebCore/page/SecurityOrigin.h
WebCore/platform/network/ResourceRequestBase.h
WebCore/xml/XMLHttpRequest.cpp

index 015660e15750763f98ee6e5d4da5a2e4d0516970..d5a65c0702fdd9d857ec9fc329b10c0f5801e969 100644 (file)
@@ -1,3 +1,30 @@
+2008-10-05  Adam Barth  <abarth@webkit.org>
+
+        Reviewed by Darin Adler.
+
+        Tests for the new Origin header.
+
+        https://bugs.webkit.org/show_bug.cgi?id=20792
+
+        Collin Jackson <collinj@webkit.org> also contributed to this patch.
+
+        * http/tests/security/originHeader: Added.
+        * http/tests/security/originHeader/origin-header-for-data-expected.txt: Added.
+        * http/tests/security/originHeader/origin-header-for-data.html: Added.
+        * http/tests/security/originHeader/origin-header-for-empty-expected.txt: Added.
+        * http/tests/security/originHeader/origin-header-for-empty.html: Added.
+        * http/tests/security/originHeader/origin-header-for-get-expected.txt: Added.
+        * http/tests/security/originHeader/origin-header-for-get.html: Added.
+        * http/tests/security/originHeader/origin-header-for-https-expected.txt: Added.
+        * http/tests/security/originHeader/origin-header-for-https.html: Added.
+        * http/tests/security/originHeader/origin-header-for-post-expected.txt: Added.
+        * http/tests/security/originHeader/origin-header-for-post.html: Added.
+        * http/tests/security/originHeader/origin-header-for-xmlhttprequest-expected.txt: Added.
+        * http/tests/security/originHeader/origin-header-for-xmlhttprequest.html: Added.
+        * http/tests/security/originHeader/resources: Added.
+        * http/tests/security/originHeader/resources/origin-header-post-to-http.html: Added.
+        * http/tests/security/originHeader/resources/print-origin.cgi: Added.
+
 2008-10-05  Oliver Hunt  <oliver@apple.com>
 
         Reviewed by Reviewed by Tim Hatcher.
diff --git a/LayoutTests/http/tests/security/originHeader/origin-header-for-data-expected.txt b/LayoutTests/http/tests/security/originHeader/origin-header-for-data-expected.txt
new file mode 100644 (file)
index 0000000..0f1a6fe
--- /dev/null
@@ -0,0 +1 @@
+HTTP_ORIGIN: null
diff --git a/LayoutTests/http/tests/security/originHeader/origin-header-for-data.html b/LayoutTests/http/tests/security/originHeader/origin-header-for-data.html
new file mode 100644 (file)
index 0000000..37a1dd0
--- /dev/null
@@ -0,0 +1,17 @@
+<html>
+<head>
+<script>
+if (window.layoutTestController) {
+    layoutTestController.dumpAsText();
+    layoutTestController.waitUntilDone();
+}
+window.location = 'data:text/html,\
+<form action="http://127.0.0.1:8000/security/originHeader/resources/print-origin.cgi"\
+      method="POST">\
+</form>\
+<script>document.forms[0].submit();</scr' + 'ipt>';
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/security/originHeader/origin-header-for-empty-expected.txt b/LayoutTests/http/tests/security/originHeader/origin-header-for-empty-expected.txt
new file mode 100644 (file)
index 0000000..4b45ce0
--- /dev/null
@@ -0,0 +1 @@
+HTTP_ORIGIN: http://127.0.0.1:8000
diff --git a/LayoutTests/http/tests/security/originHeader/origin-header-for-empty.html b/LayoutTests/http/tests/security/originHeader/origin-header-for-empty.html
new file mode 100644 (file)
index 0000000..ec1f162
--- /dev/null
@@ -0,0 +1,24 @@
+<html>
+<head>
+<script>
+if (window.layoutTestController) {
+    layoutTestController.dumpAsText();
+    layoutTestController.waitUntilDone();
+    layoutTestController.setCanOpenWindows();
+    layoutTestController.setCloseRemainingWindowsWhenComplete(true);
+}
+window.name = 'opener';
+w = window.open("");
+w.document.open();
+w.document.write('\
+<form action="http://127.0.0.1:8000/security/originHeader/resources/print-origin.cgi"\
+      target="opener"\
+      method="POST">\
+</form>\
+<script>document.forms[0].submit();</scr' + 'ipt>');
+w.document.close();
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/security/originHeader/origin-header-for-get-expected.txt b/LayoutTests/http/tests/security/originHeader/origin-header-for-get-expected.txt
new file mode 100644 (file)
index 0000000..499e6d3
--- /dev/null
@@ -0,0 +1 @@
+HTTP_ORIGIN:
diff --git a/LayoutTests/http/tests/security/originHeader/origin-header-for-get.html b/LayoutTests/http/tests/security/originHeader/origin-header-for-get.html
new file mode 100644 (file)
index 0000000..bf9ad9a
--- /dev/null
@@ -0,0 +1,16 @@
+<html>
+<head>
+<script>
+if (window.layoutTestController) {
+    layoutTestController.dumpAsText();
+    layoutTestController.waitUntilDone();
+}
+</script>
+</head>
+<body>
+<form action="resources/print-origin.cgi"
+      method="GET">
+</form>
+<script>document.forms[0].submit()</script>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/security/originHeader/origin-header-for-https-expected.txt b/LayoutTests/http/tests/security/originHeader/origin-header-for-https-expected.txt
new file mode 100644 (file)
index 0000000..7a02941
--- /dev/null
@@ -0,0 +1 @@
+HTTP_ORIGIN: https://127.0.0.1:8443
diff --git a/LayoutTests/http/tests/security/originHeader/origin-header-for-https.html b/LayoutTests/http/tests/security/originHeader/origin-header-for-https.html
new file mode 100644 (file)
index 0000000..9350df1
--- /dev/null
@@ -0,0 +1,13 @@
+<html>
+<head>
+<script>
+if (window.layoutTestController) {
+    layoutTestController.dumpAsText();
+    layoutTestController.waitUntilDone();
+}
+window.location = "https://127.0.0.1:8443/security/originHeader/resources/origin-header-post-to-http.html";
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/security/originHeader/origin-header-for-post-expected.txt b/LayoutTests/http/tests/security/originHeader/origin-header-for-post-expected.txt
new file mode 100644 (file)
index 0000000..4b45ce0
--- /dev/null
@@ -0,0 +1 @@
+HTTP_ORIGIN: http://127.0.0.1:8000
diff --git a/LayoutTests/http/tests/security/originHeader/origin-header-for-post.html b/LayoutTests/http/tests/security/originHeader/origin-header-for-post.html
new file mode 100644 (file)
index 0000000..a37a2e2
--- /dev/null
@@ -0,0 +1,16 @@
+<html>
+<head>
+<script>
+if (window.layoutTestController) {
+    layoutTestController.dumpAsText();
+    layoutTestController.waitUntilDone();
+}
+</script>
+</head>
+<body>
+<form action="resources/print-origin.cgi"
+      method="POST">
+</form>
+<script>document.forms[0].submit()</script>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/security/originHeader/resources/origin-header-post-to-http.html b/LayoutTests/http/tests/security/originHeader/resources/origin-header-post-to-http.html
new file mode 100644 (file)
index 0000000..40e17aa
--- /dev/null
@@ -0,0 +1,4 @@
+<form action="http://127.0.0.1:8000/security/originHeader/resources/print-origin.cgi"
+      method="POST">
+</form>
+<script>document.forms[0].submit();</script>
diff --git a/LayoutTests/http/tests/security/originHeader/resources/print-origin.cgi b/LayoutTests/http/tests/security/originHeader/resources/print-origin.cgi
new file mode 100755 (executable)
index 0000000..fe3124a
--- /dev/null
@@ -0,0 +1,14 @@
+#!/usr/bin/perl -wT
+use strict;
+
+print "Content-Type: text/html\n";
+print "Access-Control-Origin: *\n";
+print "Cache-Control: no-store\n\n";
+
+print "HTTP_ORIGIN: " . $ENV{"HTTP_ORIGIN"} . "\n";
+print <<DONE
+<script>
+    if (window.layoutTestController)
+        window.layoutTestController.notifyDone();
+</script>
+DONE
diff --git a/LayoutTests/http/tests/xmlhttprequest/origin-header-cross-origin-get-async-expected.txt b/LayoutTests/http/tests/xmlhttprequest/origin-header-cross-origin-get-async-expected.txt
new file mode 100644 (file)
index 0000000..85f9a5c
--- /dev/null
@@ -0,0 +1,4 @@
+PASS: Cross-domain access allowed.
+HTTP_ORIGIN: http://127.0.0.1:8000
+
+
diff --git a/LayoutTests/http/tests/xmlhttprequest/origin-header-cross-origin-get-async.html b/LayoutTests/http/tests/xmlhttprequest/origin-header-cross-origin-get-async.html
new file mode 100644 (file)
index 0000000..53fbd15
--- /dev/null
@@ -0,0 +1,24 @@
+<html>
+<body>
+<pre id="console"></pre>
+<script>
+function log(message) {
+    document.getElementById('console').appendChild(document.createTextNode(message + '\n'));
+}
+
+if (window.layoutTestController) {
+    layoutTestController.dumpAsText();
+    layoutTestController.waitUntilDone();
+}
+
+var xhr = new XMLHttpRequest();
+xhr.open('GET', 'http://localhost:8000/xmlhttprequest/resources/access-control-basic-allow-access-control-origin-header.cgi', true);
+xhr.onload = function () {
+    log(xhr.responseText);
+    if (window.layoutTestController)
+        layoutTestController.notifyDone();
+}
+xhr.send(null);
+</script>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/xmlhttprequest/origin-header-cross-origin-get-sync-expected.txt b/LayoutTests/http/tests/xmlhttprequest/origin-header-cross-origin-get-sync-expected.txt
new file mode 100644 (file)
index 0000000..85f9a5c
--- /dev/null
@@ -0,0 +1,4 @@
+PASS: Cross-domain access allowed.
+HTTP_ORIGIN: http://127.0.0.1:8000
+
+
diff --git a/LayoutTests/http/tests/xmlhttprequest/origin-header-cross-origin-get-sync.html b/LayoutTests/http/tests/xmlhttprequest/origin-header-cross-origin-get-sync.html
new file mode 100644 (file)
index 0000000..c84a3ee
--- /dev/null
@@ -0,0 +1,18 @@
+<html>
+<body>
+<pre id="console"></pre>
+<script>
+function log(message) {
+    document.getElementById('console').appendChild(document.createTextNode(message + '\n'));
+}
+
+if (window.layoutTestController)
+    layoutTestController.dumpAsText();
+
+var xhr = new XMLHttpRequest();
+xhr.open('GET', 'http://localhost:8000/xmlhttprequest/resources/access-control-basic-allow-access-control-origin-header.cgi', false);
+xhr.send(null);
+log(xhr.responseText);
+</script>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/xmlhttprequest/origin-header-cross-origin-post-async-expected.txt b/LayoutTests/http/tests/xmlhttprequest/origin-header-cross-origin-post-async-expected.txt
new file mode 100644 (file)
index 0000000..85f9a5c
--- /dev/null
@@ -0,0 +1,4 @@
+PASS: Cross-domain access allowed.
+HTTP_ORIGIN: http://127.0.0.1:8000
+
+
diff --git a/LayoutTests/http/tests/xmlhttprequest/origin-header-cross-origin-post-async.html b/LayoutTests/http/tests/xmlhttprequest/origin-header-cross-origin-post-async.html
new file mode 100644 (file)
index 0000000..0c7af29
--- /dev/null
@@ -0,0 +1,24 @@
+<html>
+<body>
+<pre id="console"></pre>
+<script>
+function log(message) {
+    document.getElementById('console').appendChild(document.createTextNode(message + '\n'));
+}
+
+if (window.layoutTestController) {
+    layoutTestController.dumpAsText();
+    layoutTestController.waitUntilDone();
+}
+
+var xhr = new XMLHttpRequest();
+xhr.open('POST', 'http://localhost:8000/xmlhttprequest/resources/access-control-basic-allow-access-control-origin-header.cgi', true);
+xhr.onload = function () {
+    log(xhr.responseText);
+    if (window.layoutTestController)
+        layoutTestController.notifyDone();
+}
+xhr.send(null);
+</script>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/xmlhttprequest/origin-header-cross-origin-post-sync-expected.txt b/LayoutTests/http/tests/xmlhttprequest/origin-header-cross-origin-post-sync-expected.txt
new file mode 100644 (file)
index 0000000..85f9a5c
--- /dev/null
@@ -0,0 +1,4 @@
+PASS: Cross-domain access allowed.
+HTTP_ORIGIN: http://127.0.0.1:8000
+
+
diff --git a/LayoutTests/http/tests/xmlhttprequest/origin-header-cross-origin-post-sync.html b/LayoutTests/http/tests/xmlhttprequest/origin-header-cross-origin-post-sync.html
new file mode 100644 (file)
index 0000000..36fec86
--- /dev/null
@@ -0,0 +1,18 @@
+<html>
+<body>
+<pre id="console"></pre>
+<script>
+function log(message) {
+    document.getElementById('console').appendChild(document.createTextNode(message + '\n'));
+}
+
+if (window.layoutTestController)
+    layoutTestController.dumpAsText();
+
+var xhr = new XMLHttpRequest();
+xhr.open('POST', 'http://localhost:8000/xmlhttprequest/resources/access-control-basic-allow-access-control-origin-header.cgi', false);
+xhr.send(null);
+log(xhr.responseText);
+</script>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/xmlhttprequest/origin-header-same-origin-get-async-expected.txt b/LayoutTests/http/tests/xmlhttprequest/origin-header-same-origin-get-async-expected.txt
new file mode 100644 (file)
index 0000000..52db1b3
--- /dev/null
@@ -0,0 +1,4 @@
+PASS: Cross-domain access allowed.
+HTTP_ORIGIN: 
+
+
diff --git a/LayoutTests/http/tests/xmlhttprequest/origin-header-same-origin-get-async.html b/LayoutTests/http/tests/xmlhttprequest/origin-header-same-origin-get-async.html
new file mode 100644 (file)
index 0000000..03e6da1
--- /dev/null
@@ -0,0 +1,24 @@
+<html>
+<body>
+<pre id="console"></pre>
+<script>
+function log(message) {
+    document.getElementById('console').appendChild(document.createTextNode(message + '\n'));
+}
+
+if (window.layoutTestController) {
+    layoutTestController.dumpAsText();
+    layoutTestController.waitUntilDone();
+}
+
+var xhr = new XMLHttpRequest();
+xhr.open('GET', 'resources/access-control-basic-allow-access-control-origin-header.cgi', true);
+xhr.onload = function () {
+    log(xhr.responseText);
+    if (window.layoutTestController)
+        layoutTestController.notifyDone();
+}
+xhr.send(null);
+</script>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/xmlhttprequest/origin-header-same-origin-get-sync-expected.txt b/LayoutTests/http/tests/xmlhttprequest/origin-header-same-origin-get-sync-expected.txt
new file mode 100644 (file)
index 0000000..52db1b3
--- /dev/null
@@ -0,0 +1,4 @@
+PASS: Cross-domain access allowed.
+HTTP_ORIGIN: 
+
+
diff --git a/LayoutTests/http/tests/xmlhttprequest/origin-header-same-origin-get-sync.html b/LayoutTests/http/tests/xmlhttprequest/origin-header-same-origin-get-sync.html
new file mode 100644 (file)
index 0000000..73c697b
--- /dev/null
@@ -0,0 +1,18 @@
+<html>
+<body>
+<pre id="console"></pre>
+<script>
+function log(message) {
+    document.getElementById('console').appendChild(document.createTextNode(message + '\n'));
+}
+
+if (window.layoutTestController)
+    layoutTestController.dumpAsText();
+
+var xhr = new XMLHttpRequest();
+xhr.open('GET', 'resources/access-control-basic-allow-access-control-origin-header.cgi', false);
+xhr.send(null);
+log(xhr.responseText);
+</script>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/xmlhttprequest/origin-header-same-origin-post-async-expected.txt b/LayoutTests/http/tests/xmlhttprequest/origin-header-same-origin-post-async-expected.txt
new file mode 100644 (file)
index 0000000..85f9a5c
--- /dev/null
@@ -0,0 +1,4 @@
+PASS: Cross-domain access allowed.
+HTTP_ORIGIN: http://127.0.0.1:8000
+
+
diff --git a/LayoutTests/http/tests/xmlhttprequest/origin-header-same-origin-post-async.html b/LayoutTests/http/tests/xmlhttprequest/origin-header-same-origin-post-async.html
new file mode 100644 (file)
index 0000000..b19f448
--- /dev/null
@@ -0,0 +1,24 @@
+<html>
+<body>
+<pre id="console"></pre>
+<script>
+function log(message) {
+    document.getElementById('console').appendChild(document.createTextNode(message + '\n'));
+}
+
+if (window.layoutTestController) {
+    layoutTestController.dumpAsText();
+    layoutTestController.waitUntilDone();
+}
+
+var xhr = new XMLHttpRequest();
+xhr.open('POST', 'resources/access-control-basic-allow-access-control-origin-header.cgi', true);
+xhr.onload = function () {
+    log(xhr.responseText);
+    if (window.layoutTestController)
+        layoutTestController.notifyDone();
+}
+xhr.send(null);
+</script>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/xmlhttprequest/origin-header-same-origin-post-sync-expected.txt b/LayoutTests/http/tests/xmlhttprequest/origin-header-same-origin-post-sync-expected.txt
new file mode 100644 (file)
index 0000000..85f9a5c
--- /dev/null
@@ -0,0 +1,4 @@
+PASS: Cross-domain access allowed.
+HTTP_ORIGIN: http://127.0.0.1:8000
+
+
diff --git a/LayoutTests/http/tests/xmlhttprequest/origin-header-same-origin-post-sync.html b/LayoutTests/http/tests/xmlhttprequest/origin-header-same-origin-post-sync.html
new file mode 100644 (file)
index 0000000..d614aa0
--- /dev/null
@@ -0,0 +1,18 @@
+<html>
+<body>
+<pre id="console"></pre>
+<script>
+function log(message) {
+    document.getElementById('console').appendChild(document.createTextNode(message + '\n'));
+}
+
+if (window.layoutTestController)
+    layoutTestController.dumpAsText();
+
+var xhr = new XMLHttpRequest();
+xhr.open('POST', 'resources/access-control-basic-allow-access-control-origin-header.cgi', false);
+xhr.send(null);
+log(xhr.responseText);
+</script>
+</body>
+</html>
index d5aaf5be6099bf7f4a02ca780d342c51a46d6b7b..e858d7e4b0770b5f3a942752657469a7fd93b364 100644 (file)
@@ -1,3 +1,50 @@
+2008-10-05  Adam Barth  <abarth@webkit.org>
+
+        Reviewed by Darin Alder.
+
+        Attach the Origin header to POST requests to help defend against
+        cross-site request forgery.
+
+        https://bugs.webkit.org/show_bug.cgi?id=20792
+
+        Collin Jackson <collinj@webkit.org> also contributed to this patch.
+
+        Tests: http/tests/security/originHeader/origin-header-for-data.html
+               http/tests/security/originHeader/origin-header-for-empty.html
+               http/tests/security/originHeader/origin-header-for-get.html
+               http/tests/security/originHeader/origin-header-for-https.html
+               http/tests/security/originHeader/origin-header-for-post.html
+
+        * bindings/js/JSDOMWindowBase.cpp:
+        (WebCore::createWindow):
+        * loader/FrameLoader.cpp:
+        (WebCore::FrameLoader::createWindow):
+        (WebCore::FrameLoader::urlSelected):
+        (WebCore::FrameLoader::submitForm):
+        (WebCore::FrameLoader::outgoingOrigin):
+        (WebCore::FrameLoader::loadURL):
+        (WebCore::FrameLoader::addExtraFieldsToRequest):
+        (WebCore::FrameLoader::loadPostRequest):
+        (WebCore::FrameLoader::loadResourceSynchronously):
+        (WebCore::FrameLoader::loadItem):
+        * loader/FrameLoader.h:
+        * loader/SubresourceLoader.cpp:
+        (WebCore::SubresourceLoader::create):
+        * loader/loader.cpp:
+        (WebCore::Loader::Host::servePendingRequests):
+        * platform/SecurityOrigin.cpp:
+        (WebCore::SecurityOrigin::toHTTPOrigin):
+        * platform/SecurityOrigin.h:
+        * platform/network/ResourceRequestBase.h:
+        (WebCore::ResourceRequestBase::httpOrigin):
+        (WebCore::ResourceRequestBase::setHTTPOrigin):
+        (WebCore::ResourceRequestBase::clearHTTPOrigin):
+        * xml/XMLHttpRequest.cpp:
+        (WebCore::XMLHttpRequest::makeSimpleCrossSiteAccessRequest):
+        (WebCore::XMLHttpRequest::makeCrossSiteAccessRequestWithPreflight):
+        (WebCore::XMLHttpRequest::handleAsynchronousPreflightResult):
+        (WebCore::XMLHttpRequest::didReceiveResponsePreflight):
+
 2008-10-04  Oliver Hunt  <oliver@apple.com>
 
         Reviewed by Tim Hatcher.
index 948e00f4dfc7f47c51cf5485d294dcf4fc8c7a1c..258660be89d482ae20b3bb075388ff31ab86f28b 100644 (file)
@@ -272,6 +272,7 @@ static Frame* createWindow(ExecState* exec, Frame* openerFrame, const String& ur
     ResourceRequest request;
 
     request.setHTTPReferrer(activeFrame->loader()->outgoingReferrer());
+    FrameLoader::addHTTPOriginIfNeeded(request, activeFrame->loader()->outgoingOrigin());
     FrameLoadRequest frameRequest(request, frameName);
 
     // FIXME: It's much better for client API if a new window starts with a URL, here where we
index 040396cf8113997e818fbb3d200c722a3b1aead8..09f80b3c6efe3b2abddfa868ea40ac9221783997 100644 (file)
@@ -330,6 +330,7 @@ Frame* FrameLoader::createWindow(FrameLoader* frameLoaderForFrameLookup, const F
     // FIXME: Setting the referrer should be the caller's responsibility.
     FrameLoadRequest requestWithReferrer = request;
     requestWithReferrer.resourceRequest().setHTTPReferrer(m_outgoingReferrer);
+    addHTTPOriginIfNeeded(requestWithReferrer.resourceRequest(), outgoingOrigin());
 
     Page* oldPage = m_frame->page();
     if (!oldPage)
@@ -399,6 +400,7 @@ void FrameLoader::urlSelected(const FrameLoadRequest& request, Event* event, boo
     FrameLoadRequest copy = request;
     if (copy.resourceRequest().httpReferrer().isEmpty())
         copy.resourceRequest().setHTTPReferrer(m_outgoingReferrer);
+    addHTTPOriginIfNeeded(copy.resourceRequest(), outgoingOrigin());
 
     loadFrameRequestWithFormAndValues(copy, lockHistory, event, 0, HashMap<String, String>());
 }
@@ -574,6 +576,7 @@ void FrameLoader::submitForm(const char* action, const String& url, PassRefPtr<F
     }
 
     frameRequest.resourceRequest().setURL(u);
+    addHTTPOriginIfNeeded(frameRequest.resourceRequest(), outgoingOrigin());
 
     submitForm(frameRequest, event);
 }
@@ -1784,6 +1787,14 @@ String FrameLoader::outgoingReferrer() const
     return m_outgoingReferrer;
 }
 
+String FrameLoader::outgoingOrigin() const
+{
+    if (m_frame->document())
+        return m_frame->document()->securityOrigin()->toHTTPOrigin();
+
+    return SecurityOrigin::createEmpty()->toHTTPOrigin();
+}
+
 Frame* FrameLoader::opener()
 {
     return m_opener;
@@ -2124,8 +2135,11 @@ void FrameLoader::loadURL(const KURL& newURL, const String& referrer, const Stri
     bool isFormSubmission = formState;
     
     ResourceRequest request(newURL);
-    if (!referrer.isEmpty())
+    if (!referrer.isEmpty()) {
         request.setHTTPReferrer(referrer);
+        RefPtr<SecurityOrigin> referrerOrigin = SecurityOrigin::createFromString(referrer);
+        addHTTPOriginIfNeeded(request, referrerOrigin->toHTTPOrigin());
+    }
     addExtraFieldsToRequest(request, true, event || isFormSubmission);
     if (newLoadType == FrameLoadTypeReload)
         request.setCachePolicy(ReloadIgnoringCacheData);
@@ -3405,6 +3419,35 @@ void FrameLoader::addExtraFieldsToRequest(ResourceRequest& request, bool mainRes
     
     if (mainResource)
         request.setHTTPAccept("application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5");
+
+    // Make sure we send the Origin header.
+    addHTTPOriginIfNeeded(request, String());
+}
+
+void FrameLoader::addHTTPOriginIfNeeded(ResourceRequest& request, String origin)
+{
+    if (!request.httpOrigin().isEmpty())
+        return;  // Request already has an Origin header.
+
+    // Don't send an Origin header for GET or HEAD to avoid privacy issues.
+    // For example, if an intranet page has a hyperlink to an external web
+    // site, we don't want to include the Origin of the request because it
+    // will leak the internal host name. Similar privacy concerns have lead
+    // to the widespread suppression of the Referer header at the network
+    // layer.
+    if (request.httpMethod() == "GET" || request.httpMethod() == "HEAD")
+        return;
+
+    // For non-GET and non-HEAD methods, always send an Origin header so the
+    // server knows we support this feature.
+
+    if (origin.isEmpty()) {
+        // If we don't know what origin header to attach, we attach the value
+        // for an empty origin.
+        origin = SecurityOrigin::createEmpty()->toHTTPOrigin();
+    }
+
+    request.setHTTPOrigin(origin);
 }
 
 void FrameLoader::committedLoad(DocumentLoader* loader, const char* data, int length)
@@ -3432,15 +3475,17 @@ void FrameLoader::loadPostRequest(const ResourceRequest& inRequest, const String
     const KURL& url = inRequest.url();
     RefPtr<FormData> formData = inRequest.httpBody();
     const String& contentType = inRequest.httpContentType();
+    String origin = inRequest.httpOrigin();
 
     ResourceRequest workingResourceRequest(url);    
-    addExtraFieldsToRequest(workingResourceRequest, true, true);
 
     if (!referrer.isEmpty())
         workingResourceRequest.setHTTPReferrer(referrer);
+    workingResourceRequest.setHTTPOrigin(origin);
     workingResourceRequest.setHTTPMethod("POST");
     workingResourceRequest.setHTTPBody(formData);
     workingResourceRequest.setHTTPContentType(contentType);
+    addExtraFieldsToRequest(workingResourceRequest, true, true);
 
     NavigationAction action(url, FrameLoadTypeStandard, true, event);
 
@@ -3482,6 +3527,7 @@ unsigned long FrameLoader::loadResourceSynchronously(const ResourceRequest& requ
     
     if (!referrer.isEmpty())
         initialRequest.setHTTPReferrer(referrer);
+    addHTTPOriginIfNeeded(initialRequest, outgoingOrigin());
 
     if (Page* page = m_frame->page())
         initialRequest.setMainDocumentURL(page->mainFrame()->loader()->documentLoader()->request().url());
@@ -4293,8 +4339,6 @@ void FrameLoader::loadItem(HistoryItem* item, FrameLoadType loadType)
         if (!inPageCache) {
             ResourceRequest request(itemURL);
 
-            addExtraFieldsToRequest(request, true, formData);
-
             // If this was a repost that failed the page cache, we might try to repost the form.
             NavigationAction action;
             if (formData) {
@@ -4305,6 +4349,8 @@ void FrameLoader::loadItem(HistoryItem* item, FrameLoadType loadType)
                 request.setHTTPReferrer(item->formReferrer());
                 request.setHTTPBody(formData);
                 request.setHTTPContentType(item->formContentType());
+                RefPtr<SecurityOrigin> securityOrigin = SecurityOrigin::createFromString(item->formReferrer());
+                addHTTPOriginIfNeeded(request, securityOrigin->toHTTPOrigin());
         
                 // FIXME: Slight hack to test if the NSURL cache contains the page we're going to.
                 // We want to know this before talking to the policy delegate, since it affects whether 
@@ -4345,6 +4391,7 @@ void FrameLoader::loadItem(HistoryItem* item, FrameLoadType loadType)
                 action = NavigationAction(itemOriginalURL, loadType, false);
             }
 
+            addExtraFieldsToRequest(request, true, formData);
             loadWithNavigationAction(request, action, loadType, 0);
         }
     }
index 80b6ebb7c64883f5b65d47725fd06cd17dbf63be..edb59289cfd93f7565be65cb15d6f1c296918f6a 100644 (file)
@@ -193,6 +193,7 @@ namespace WebCore {
         bool isReloading() const;
         String referrer() const;
         String outgoingReferrer() const;
+        String outgoingOrigin() const;
         void loadEmptyDocumentSynchronously();
 
         DocumentLoader* activeDocumentLoader() const;
@@ -277,6 +278,7 @@ namespace WebCore {
         void detachChildren();
 
         void addExtraFieldsToRequest(ResourceRequest&, bool isMainResource, bool alwaysFromRequest);
+        static void addHTTPOriginIfNeeded(ResourceRequest&, String origin);
 
         FrameLoaderClient* client() const;
 
index a85b7eb500a4a4893a01363403cb9e422a373879..bff48b276b3831bea548496fa26aa8220eacd914 100644 (file)
@@ -93,6 +93,7 @@ PassRefPtr<SubresourceLoader> SubresourceLoader::create(Frame* frame, Subresourc
         newRequest.clearHTTPReferrer();
     else if (!request.httpReferrer())
         newRequest.setHTTPReferrer(fl->outgoingReferrer());
+    FrameLoader::addHTTPOriginIfNeeded(newRequest, fl->outgoingOrigin());
 
     // Use the original request's cache policy for two reasons:
     // 1. For POST requests, we mutate the cache policy for the main resource,
index bf5716fd352c3699e3ca34f8c3e69b2e247d3abd..2442d2130fdd695dd73fd373cfd7b15063ba428b 100644 (file)
@@ -36,6 +36,7 @@
 #include "ResourceHandle.h"
 #include "ResourceRequest.h"
 #include "ResourceResponse.h"
+#include "SecurityOrigin.h"
 #include "SubresourceLoader.h"
 #include <wtf/Assertions.h>
 #include <wtf/Vector.h>
@@ -239,6 +240,7 @@ void Loader::Host::servePendingRequests(RequestQueue& requestsPending, bool& ser
         if ((referrer.protocolIs("http") || referrer.protocolIs("https")) && referrer.path().isEmpty())
             referrer.setPath("/");
         resourceRequest.setHTTPReferrer(referrer.string());
+        FrameLoader::addHTTPOriginIfNeeded(resourceRequest, docLoader->doc()->securityOrigin()->toHTTPOrigin());
         
         if (resourceIsCacheValidator) {
             CachedResource* resourceToRevalidate = request->cachedResource()->resourceToRevalidate();
index 824fa88382d62dd709358b5f296d50a62e0f12db..67bef7c2b28809f06de130e86fa6104f4039ed55 100644 (file)
@@ -220,6 +220,15 @@ String SecurityOrigin::toString() const
     return String::adopt(result);
 }
 
+String SecurityOrigin::toHTTPOrigin() const
+{
+    String origin = toString();
+    if (origin.isEmpty())
+        return "null";
+
+    return origin;
+}
+
 PassRefPtr<SecurityOrigin> SecurityOrigin::createFromString(const String& originString)
 {
     return SecurityOrigin::create(KURL(originString));
index f76062e8c34cdb20940d4e79f0946609072aea6f..b2df5cc54cf361720f73dfe9e3a439246e4eabc1 100644 (file)
@@ -106,6 +106,11 @@ namespace WebCore {
         // SecurityOrigin is represented with the null string.
         String toString() const;
 
+        // Convert this SecurityOrigin into a string for use in the HTTP Origin
+        // header. This is similar to toString(), except that the empty
+        // SecurityOrigin is represented as the string "null".
+        String toHTTPOrigin() const;
+
         // Serialize the security origin for storage in the database. This format is
         // deprecated and should be used only for compatibility with old databases;
         // use toString() and createFromString() instead.
index afa20f64eb588cc380329e7f8b4452de3ac9ec55..32a3384b99197ca27b0d48fe040da94e2f82e8ce 100644 (file)
@@ -78,6 +78,10 @@ namespace WebCore {
         void setHTTPReferrer(const String& httpReferrer) { setHTTPHeaderField("Referer", httpReferrer); }
         void clearHTTPReferrer() { m_httpHeaderFields.remove("Referer"); }
         
+        String httpOrigin() const { return httpHeaderField("Origin"); }
+        void setHTTPOrigin(const String& httpOrigin) { setHTTPHeaderField("Origin", httpOrigin); }
+        void clearHTTPOrigin() { m_httpHeaderFields.remove("Origin"); }
+
         String httpUserAgent() const { return httpHeaderField("User-Agent"); }
         void setHTTPUserAgent(const String& httpUserAgent) { setHTTPHeaderField("User-Agent", httpUserAgent); }
 
index d8fc0a86814fc76126c9f64066312bb38b649295..257a2a410a0d44e5e174759393ed419ff9a54a62 100644 (file)
@@ -537,14 +537,6 @@ void XMLHttpRequest::makeCrossSiteAccessRequest(ExceptionCode& ec)
         makeCrossSiteAccessRequestWithPreflight(ec);
 }
 
-String XMLHttpRequest::accessControlOrigin() const
-{
-    String accessControlOrigin = m_doc->securityOrigin()->toString();
-    if (accessControlOrigin.isEmpty())
-        return "null";
-    return accessControlOrigin;
-}
-
 void XMLHttpRequest::makeSimpleCrossSiteAccessRequest(ExceptionCode& ec)
 {
     ASSERT(isSimpleCrossSiteAccessRequest());
@@ -556,7 +548,7 @@ void XMLHttpRequest::makeSimpleCrossSiteAccessRequest(ExceptionCode& ec)
     ResourceRequest request(url);
     request.setHTTPMethod(m_method);
     request.setAllowHTTPCookies(m_includeCredentials);
-    request.setHTTPHeaderField("Origin", accessControlOrigin());
+    request.setHTTPOrigin(m_doc->securityOrigin()->toHTTPOrigin());
 
     if (m_requestHeaders.size() > 0)
         request.addHTTPHeaderFields(m_requestHeaders);
@@ -587,7 +579,7 @@ static bool canSkipPrelight(PreflightResultCache::iterator cacheIt, bool include
 
 void XMLHttpRequest::makeCrossSiteAccessRequestWithPreflight(ExceptionCode& ec)
 {
-    String origin = accessControlOrigin();
+    String origin = m_doc->securityOrigin()->toHTTPOrigin();
     KURL url = m_url;
     url.setUser(String());
     url.setPass(String());
@@ -675,7 +667,7 @@ void XMLHttpRequest::handleAsynchronousPreflightResult()
     ResourceRequest request(url);
     request.setHTTPMethod(m_method);
     request.setAllowHTTPCookies(m_includeCredentials);
-    request.setHTTPHeaderField("Origin", accessControlOrigin());
+    request.setHTTPOrigin(m_doc->securityOrigin()->toHTTPOrigin());
 
     if (m_requestHeaders.size() > 0)
         request.addHTTPHeaderFields(m_requestHeaders);
@@ -1200,7 +1192,7 @@ void XMLHttpRequest::didReceiveResponsePreflight(SubresourceLoader*, const Resou
     if (!parseAccessControlMaxAge(response.httpHeaderField("Access-Control-Max-Age"), expiryDelta))
         expiryDelta = 5;
 
-    appendPreflightResultCacheEntry(accessControlOrigin(), m_url, expiryDelta, m_includeCredentials, methods.release(), headers.release());
+    appendPreflightResultCacheEntry(m_doc->securityOrigin()->toHTTPOrigin(), m_url, expiryDelta, m_includeCredentials, methods.release(), headers.release());
 }
 
 void XMLHttpRequest::receivedCancellation(SubresourceLoader*, const AuthenticationChallenge& challenge)