From 75c90aa29787c4b60e2bf331158303df30fd8af8 Mon Sep 17 00:00:00 2001 From: Kushal Pisavadia Date: Sat, 17 Mar 2012 15:14:57 +0000 Subject: [PATCH] Add clj-time Intervals/DateTime for :max-age and :expires (fixes #55) - Add a test for :max-age and :expires to (wrap-cookies ...). There were no real tests to exercise the base input cases (int, string) which the comment block states. This test just makes sure it fulfils that contract. - clj-time is now a project dependency - :max-age accepts an Interval as input. Updated (write-attr-map ...) to accept an Interval (from JodaTime) as well as an int. The interface from clj-time is used. - :expires accepts a DateTime object. It converts the DateTime object in the equivalent RFC822 which the cookie spec requires. - Added pre-conditions for :max-age and :expires to make sure that they only accept Interval and DateTime, respectively. --- ring-core/project.clj | 3 +- ring-core/src/ring/middleware/cookies.clj | 19 ++++++--- ring-core/test/ring/middleware/test/cookies.clj | 57 ++++++++++++++++++++++++- 3 files changed, 72 insertions(+), 7 deletions(-) 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) {})))))