diff --git a/src/ring/middleware/proxy_headers.clj b/src/ring/middleware/proxy_headers.clj index 738dc9a..db5b82b 100644 --- a/src/ring/middleware/proxy_headers.clj +++ b/src/ring/middleware/proxy_headers.clj @@ -5,18 +5,28 @@ (defn forwarded-remote-addr-request "Change the :remote-addr key of the request map to the last value present in the X-Forwarded-For header. See: wrap-forwarded-remote-addr." - [request] + [request {:keys [proxy-count] :as options :or {proxy-count 1}}] (if-let [forwarded-for (get-in request [:headers "x-forwarded-for"])] - (let [remote-addr (str/trim (re-find #"[^,]*$" forwarded-for))] - (assoc request :remote-addr remote-addr)) + (let [forwarded-addrs (str/split forwarded-for #",") + remote-addr (some-> (nth forwarded-addrs (- (count forwarded-addrs) proxy-count) nil) + (str/trim))] + (if remote-addr + (assoc request :remote-addr remote-addr) + request)) request)) (defn wrap-forwarded-remote-addr "Middleware that changes the :remote-addr of the request map to the - last value present in the X-Forwarded-For header." - [handler] - (fn - ([request] - (handler (forwarded-remote-addr-request request))) - ([request respond raise] - (handler (forwarded-remote-addr-request request) respond raise)))) + last value present in the X-Forwarded-For header. + + If a request passes through multiple trusted proxies before reaching your server, + you can set :proxy-count in options." + ([handler] + (wrap-forwarded-remote-addr handler nil)) + ([handler options] + {:pre [(or (nil? options) (< 0 (:proxy-count options)))]} + (fn + ([request] + (handler (forwarded-remote-addr-request request options))) + ([request respond raise] + (handler (forwarded-remote-addr-request request options) respond raise))))) diff --git a/test/ring/middleware/proxy_headers_test.clj b/test/ring/middleware/proxy_headers_test.clj index 35b4fec..934708b 100644 --- a/test/ring/middleware/proxy_headers_test.clj +++ b/test/ring/middleware/proxy_headers_test.clj @@ -25,6 +25,36 @@ resp (handler req)] (is (= (:body resp) "1.2.3.4")))))) +(deftest test-wrap-forwarded-remote-addr-multiple-proxies + (let [handler (wrap-forwarded-remote-addr (comp response :remote-addr) {:proxy-count 2})] + (testing "without x-forwarded-for" + (let [req (assoc (request :get "/") :remote-addr "1.2.3.4") + resp (handler req)] + (is (= (:body resp) "1.2.3.4")))) + ;; TODO: what should happen if there aren't enough Forwarded addresses? Nothing? + (testing "with not enough proxies" + (let [req (-> (request :get "/") + (assoc :remote-addr "127.0.0.1") + (header "x-forwarded-for" "1.2.3.4")) + resp (handler req)] + (is (= (:body resp) "127.0.0.1")))) + (testing "request with two proxies" + (let [req (-> (request :get "/") + (assoc :remote-addr "127.0.0.1") + (header "x-forwarded-for" "122.54.196.223, 1.2.3.4")) + resp (handler req)] + (is (= (:body resp) "122.54.196.223")))) + (testing "request with two trusted proxies and one untrusted proxy" + (let [req (-> (request :get "/") + (assoc :remote-addr "127.0.0.1") + (header "x-forwarded-for" "10.0.1.9, 122.54.196.223, 1.2.3.4")) + resp (handler req)] + (is (= (:body resp) "122.54.196.223"))))) + (testing "proxy count of 0" + (is (thrown? + AssertionError + (wrap-forwarded-remote-addr (comp response :remote-addr) {:proxy-count 0}))))) + (deftest test-wrap-forwarded-remote-addr-cps (let [handler (wrap-forwarded-remote-addr (fn [request respond _] (respond (response (:remote-addr request)))))]