diff --git a/ring-core/project.clj b/ring-core/project.clj index c73c5f7..3e06697 100644 --- a/ring-core/project.clj +++ b/ring-core/project.clj @@ -5,6 +5,7 @@ [commons-codec "1.6"] [commons-io "2.1"] [commons-fileupload "1.2.1"] - [javax.servlet/servlet-api "2.5"]] + [javax.servlet/servlet-api "2.5"] + [clj-time "0.3.7"]] :profiles {:dev {:dependencies [[org.clojure/clojure-contrib "1.2.0"]]}}) diff --git a/ring-core/src/ring/middleware/cookies.clj b/ring-core/src/ring/middleware/cookies.clj index 5ca7596..5c995a7 100644 --- a/ring-core/src/ring/middleware/cookies.clj +++ b/ring-core/src/ring/middleware/cookies.clj @@ -1,6 +1,9 @@ (ns ring.middleware.cookies "Cookie manipulation." - (:require [ring.util.codec :as codec])) + (:require [ring.util.codec :as codec]) + (:use [clj-time.core :only (in-secs)] + [clj-time.format :only (formatters unparse)]) + (:import (org.joda.time Interval DateTime))) (def ^{:private true :doc "HTTP token: 1*. See RFC2068"} @@ -91,7 +94,11 @@ "Is the attribute valid?" [[key value]] (and (contains? set-cookie-attrs key) - (not (.contains (str value) ";")))) + (not (.contains (str value) ";")) + (case key + :max-age (or (instance? Interval value) (integer? value)) + :expires (or (instance? DateTime value) (string? value)) + true))) (defn- write-attr-map "Write a map of cookie attributes to a string." @@ -100,9 +107,11 @@ (for [[key value] attrs] (let [attr-name (name (set-cookie-attrs key))] (cond - (true? value) (str ";" attr-name) - (false? value) "" - :else (str ";" attr-name "=" value))))) + (instance? Interval value) (str ";" attr-name "=" (in-secs value)) + (instance? DateTime value) (str ";" attr-name "=" (unparse (formatters :rfc822) value)) + (true? value) (str ";" attr-name) + (false? value) "" + :else (str ";" attr-name "=" value))))) (defn- write-cookies "Turn a map of cookies into a seq of strings for a Set-Cookie header." diff --git a/ring-core/test/ring/middleware/test/cookies.clj b/ring-core/test/ring/middleware/test/cookies.clj index 9747338..15cd5de 100644 --- a/ring-core/test/ring/middleware/test/cookies.clj +++ b/ring-core/test/ring/middleware/test/cookies.clj @@ -1,6 +1,7 @@ (ns ring.middleware.test.cookies (:use clojure.test - ring.middleware.cookies)) + ring.middleware.cookies + [clj-time.core :only (interval date-time)])) (deftest wrap-cookies-basic-cookie (let [req {:headers {"cookie" "a=b"}} @@ -88,3 +89,57 @@ (let [response {:cookies {"a" {:value "foo" :invalid true}}} handler (wrap-cookies (constantly response))] (is (thrown? AssertionError (handler {}))))) + +(deftest wrap-cookies-accepts-max-age + (let [cookies {"a" {:value "b", :path "/", + :secure true, :http-only true, + :max-age 123}} + handler (constantly {:cookies cookies}) + resp ((wrap-cookies handler) {})] + (is (= {"Set-Cookie" (list "a=b;Path=/;Secure;HttpOnly;Max-Age=123")} + (:headers resp))))) + +(deftest wrap-cookies-accepts-expires + (let [cookies {"a" {:value "b", :path "/", + :secure true, :http-only true, + :expires "123"}} + handler (constantly {:cookies cookies}) + resp ((wrap-cookies handler) {})] + (is (= {"Set-Cookie" (list "a=b;Path=/;Secure;HttpOnly;Expires=123")} + (:headers resp))))) + +(deftest wrap-cookies-accepts-max-age-from-clj-time + (let [cookies {"a" {:value "b", :path "/", + :secure true, :http-only true, + :max-age (interval (date-time 2012) + (date-time 2015))}} + handler (constantly {:cookies cookies}) + resp ((wrap-cookies handler) {}) + max-age 94694400] + (is (= {"Set-Cookie" (list (str "a=b;Path=/;Secure;HttpOnly;Max-Age=" max-age))} + (:headers resp))))) + +(deftest wrap-cookies-accepts-expires-from-clj-time + (let [cookies {"a" {:value "b", :path "/", + :secure true, :http-only true, + :expires (date-time 2015 12 31)}} + handler (constantly {:cookies cookies}) + resp ((wrap-cookies handler) {}) + expires "Thu, 31 Dec 2015 00:00:00 +0000"] + (is (= {"Set-Cookie" (list (str "a=b;Path=/;Secure;HttpOnly;Expires=" expires))} + (:headers resp))))) + +(deftest wrap-cookies-throws-exception-when-not-using-intervals-correctly + (let [cookies {"a" {:value "b", :path "/", + :secure true, :http-only true, + :expires (interval (date-time 2012) + (date-time 2015))}} + handler (constantly {:cookies cookies})] + (is (thrown? AssertionError ((wrap-cookies handler) {}))))) + +(deftest wrap-cookies-throws-exception-when-not-using-datetime-correctly + (let [cookies {"a" {:value "b", :path "/", + :secure true, :http-only true, + :max-age (date-time 2015 12 31)}} + handler (constantly {:cookies cookies})] + (is (thrown? AssertionError ((wrap-cookies handler) {})))))