{-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-} module Web.Spock.Internal.Cookies where import Data.Time import qualified Data.ByteString as BS import qualified Data.ByteString.Builder as B import qualified Data.ByteString.Lazy as BSL import qualified Data.Text as T import qualified Data.Text.Encoding as T import qualified Web.Cookie as C import qualified Network.HTTP.Types.URI as URI (urlEncode, urlDecode) #if MIN_VERSION_base(4,8,0) #else import Control.Applicative #endif -- | Cookie settings data CookieSettings = CookieSettings { cs_EOL :: CookieEOL -- ^ cookie expiration setting, see 'CookieEOL' , cs_path :: Maybe BS.ByteString -- ^ a path for the cookie , cs_domain :: Maybe BS.ByteString -- ^ a domain for the cookie. 'Nothing' means no domain is set , cs_HTTPOnly :: Bool -- ^ whether the cookie should be set as HttpOnly , cs_secure :: Bool -- ^ whether the cookie should be marked secure (sent over HTTPS only) } -- | Setting cookie expiration data CookieEOL = CookieValidUntil UTCTime -- ^ a point in time in UTC until the cookie is valid | CookieValidFor NominalDiffTime -- ^ a period (in seconds) for which the cookie is valid | CookieValidForSession -- ^ the cookie expires with the browser session | CookieValidForever -- ^ the cookie will have an expiration date in the far future -- | Default cookie settings, equals -- -- > CookieSettings -- > { cs_EOL = CookieValidForSession -- > , cs_HTTPOnly = False -- > , cs_secure = False -- > , cs_domain = Nothing -- > , cs_path = Just "/" -- > } -- defaultCookieSettings :: CookieSettings defaultCookieSettings = CookieSettings { cs_EOL = CookieValidForSession , cs_HTTPOnly = False , cs_secure = False , cs_domain = Nothing , cs_path = Just "/" } parseCookies :: BS.ByteString -> [(T.Text, T.Text)] parseCookies = map (\(a, b) -> (T.decodeUtf8 a, T.decodeUtf8 $ URI.urlDecode True b)) . C.parseCookies generateCookieHeaderString :: T.Text -> T.Text -> CookieSettings -> UTCTime -> BS.ByteString generateCookieHeaderString name value cs now = let farFuture = -- don't forget to bump this ... UTCTime (fromGregorian 2030 1 1) 0 (expire, maxAge) = case cs_EOL cs of CookieValidUntil t -> (Just t, Just (t `diffUTCTime` now)) CookieValidFor x -> (Just (x `addUTCTime` now), Just x) CookieValidForSession -> (Nothing, Nothing) CookieValidForever -> (Just farFuture, Just (farFuture `diffUTCTime` now)) adjustMaxAge t = if t < 0 then 0 else t cookieVal = C.def { C.setCookieName = T.encodeUtf8 name , C.setCookieValue = URI.urlEncode True $ T.encodeUtf8 value , C.setCookiePath = cs_path cs , C.setCookieExpires = expire , C.setCookieMaxAge = (fromRational . adjustMaxAge . toRational) <$> maxAge , C.setCookieDomain = cs_domain cs , C.setCookieHttpOnly = cs_HTTPOnly cs , C.setCookieSecure = cs_secure cs } in renderCookie cookieVal renderCookie :: C.SetCookie -> BS.ByteString renderCookie = BSL.toStrict . B.toLazyByteString . C.renderSetCookie