diff --git a/src/ring/middleware/cors.clj b/src/ring/middleware/cors.clj index 29da84e..cd41292 100644 --- a/src/ring/middleware/cors.clj +++ b/src/ring/middleware/cors.clj @@ -1,13 +1,26 @@ (ns ring.middleware.cors "Ring middleware for Cross-Origin Resource Sharing." - (:require [clojure.set :as set] - [clojure.string :as str] + (:require [clojure + [set :as set] + [string :as str]] [ring.util.response :refer [get-header]])) (defn origin "Returns the Origin request header." [request] (get-header request "origin")) +(defn host + "Returns the Host request header or empty string for convenience" + [request] (or (get-header request "host") "")) + +(defn xdomain? + "Given a request check if Host and Origin headers mismatch" + [request] + (boolean + (when-let [orig (origin request)] + (not (= (str/replace orig #"^.*?//" "") + (host request)))))) + (defn preflight? "Returns true if the request is a preflight request" [request] @@ -112,7 +125,6 @@ response)) response)) - (defn add-access-control "Add the access-control headers to the response based on the rules and what came on the header." @@ -149,7 +161,7 @@ :headers {} :body "preflight complete"}] (add-access-control request access-control blank-response)) - (if (origin request) + (if (xdomain? request) (if (allow-request? request access-control) (if-let [response (handler request)] (add-access-control request access-control response)) diff --git a/test/ring/middleware/cors_test.clj b/test/ring/middleware/cors_test.clj index 5ee23df..a072186 100644 --- a/test/ring/middleware/cors_test.clj +++ b/test/ring/middleware/cors_test.clj @@ -2,38 +2,56 @@ (:require [clojure.test :refer :all] [ring.middleware.cors :refer :all])) +(deftest test-xdomain? + (testing "only mismatching origin and host is considered xdomain" + (are [host origin expected] + (is (= expected + (xdomain? + {:headers {"host" host + "origin" origin} + :request-method :get}))) + "burningswell.com" "somedomain.com" true + "www.burningswell.com" "http://www.burningswell.com" false + "burningswell.com" "http://www.burningswell.com:4242" true))) + (deftest test-allow-request? (testing "with empty vector" - (is (not (allow-request? {:headers {"origin" "http://eample.com"}} + (is (not (allow-request? {:headers {"host" "example.com" + "origin" "http://eample.com"}} {:access-control-allow-origin []})))) (testing "with one regular expressions" - (are [origin expected] - (is (= expected - (allow-request? - {:headers {"origin" origin} - :request-method :get} - {:access-control-allow-origin [#"http://(.*\.)?burningswell.com"] - :access-control-allow-methods #{:get :put :post}}))) - nil false - "" false - "http://example.com" false - "http://burningswell.com" true)) + (are [host origin expected] + (is (= expected + (allow-request? + {:headers {"host" host + "origin" origin} + :request-method :get} + {:access-control-allow-origin + [#"http://(.*\.)?burningswell.com"] + :access-control-allow-methods + #{:get :put :post}}))) + "" nil false + "" "" false + "anotherexample.com" "http://example.com" false + "anotherburningswell.com" "http://burningswell.com" true)) + (testing "with multiple regular expressions" - (are [origin expected] - (is (= expected - (allow-request? - {:headers {"origin" origin} - :request-method :get} - {:access-control-allow-origin - [#"http://(.*\.)?burningswell.com" - #"http://example.com"] - :access-control-allow-methods #{:get :put :post}}))) - nil false - "" false - "http://example.com" true - "http://burningswell.com" true - "http://api.burningswell.com" true - "http://dev.burningswell.com" true))) + (are [host origin expected] + (is (= expected + (allow-request? + {:headers {"host" host + "origin" origin} + :request-method :get} + {:access-control-allow-origin + [#"http://(.*\.)?burningswell.com" + #"http://example.com"] + :access-control-allow-methods #{:get :put :post}}))) + "" nil false + "" "" false + "anotherdomain.com" "http://example.com" true + "anotherdomain.com" "http://burningswell.com" true + "anotherdomain.com" "http://api.burningswell.com" true + "anotherdomain.com" "http://dev.burningswell.com" true))) (defn handler [request] ((wrap-cors (fn [_] {}) @@ -44,7 +62,8 @@ (deftest test-preflight (testing "whitelist concrete headers" - (let [headers {"origin" "http://example.com" + (let [headers {"host" "anotherdomain.com" + "origin" "http://example.com" "access-control-request-method" "POST" "access-control-request-headers" "Accept, Content-Type"}] (is (= {:status 200, @@ -67,14 +86,16 @@ :access-control-allow-methods #{:get :put :post}) {:request-method :options :uri "/" - :headers {"origin" "http://example.com" + :headers {"host" "anotherdomain.com" + "origin" "http://example.com" "access-control-request-method" "POST" "access-control-request-headers" "x-foo, x-bar"}})))) (testing "whitelist headers ignore case" (is (= (handler {:request-method :options :uri "/" - :headers {"origin" "http://example.com" + :headers {"host" "anotherdomain.com" + "origin" "http://example.com" "access-control-request-method" "POST" "access-control-request-headers" "ACCEPT, CONTENT-TYPE"}}) @@ -88,11 +109,13 @@ (is (empty? (handler {:request-method :options :uri "/" - :headers {"origin" "http://example.com" + :headers {"host" "anotherdomain.com" + "origin" "http://example.com" "access-control-request-method" "DELETE"}})))) (testing "header not allowed" - (let [headers {"origin" "http://example.com" + (let [headers {"host" "anotherdomain.com" + "origin" "http://example.com" "access-control-request-method" "GET" "access-control-request-headers" "x-another-custom-header"}] (is (empty? (handler @@ -103,7 +126,8 @@ (deftest test-preflight-header-subset (is (= (handler {:request-method :options :uri "/" - :headers {"origin" "http://example.com" + :headers {"host" "anotherdomain.com" + "origin" "http://example.com" "access-control-request-method" "POST" "access-control-request-headers" "Accept"}}) {:status 200 @@ -118,11 +142,13 @@ "Access-Control-Allow-Origin" "http://example.com"}} (handler {:request-method :post :uri "/" - :headers {"origin" "http://example.com"}})))) + :headers {"host" "anotherdomain.com" + "origin" "http://example.com"}})))) (testing "failure" (is (empty? (handler {:request-method :get :uri "/" - :headers {"origin" "http://foo.com"}}))))) + :headers {"host" "anotherdomain.com" + "origin" "http://foo.com"}}))))) (deftest test-no-cors-header-when-handler-returns-nil (is (nil? ((wrap-cors (fn [_] nil) @@ -130,7 +156,8 @@ :access-control-allow-methods [:get]) {:request-method :get :uri "/" - :headers {"origin" "http://example.com"}})))) + :headers {"host" "anotherdomain.com" + "origin" "http://example.com"}})))) (deftest test-options-without-cors-header (is (empty? ((wrap-cors @@ -144,7 +171,8 @@ :access-control-allow-origin #".*" :access-control-allow-methods [:get :post :patch :put :delete]) {:request-method :options - :headers {"origin" "http://foo.com"} + :headers {"host" "anotherdomain.com" + "origin" "http://foo.com"} :uri "/"})))) (deftest additional-headers @@ -155,7 +183,8 @@ :access-control-expose-headers "Etag") {:request-method :get :uri "/" - :headers {"origin" "http://example.com"}})] + :headers {"host" "anotherdomain.com" + "origin" "http://example.com"}})] (is (= {:status 200 :headers {"Access-Control-Allow-Credentials" "true"