Spock-core-0.14.0.1: src/Web/Spock/Internal/Cookies.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
module Web.Spock.Internal.Cookies where
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 Data.Time
import qualified Network.HTTP.Types.URI as URI (urlDecode, urlEncode)
import qualified Web.Cookie as C
#if MIN_VERSION_base(4,8,0)
#else
import Control.Applicative
#endif
-- | Cookie settings
data CookieSettings = CookieSettings
{ -- | cookie expiration setting, see 'CookieEOL'
cs_EOL :: CookieEOL,
-- | a path for the cookie
cs_path :: Maybe BS.ByteString,
-- | a domain for the cookie. 'Nothing' means no domain is set
cs_domain :: Maybe BS.ByteString,
-- | whether the cookie should be set as HttpOnly
cs_HTTPOnly :: Bool,
-- | whether the cookie should be marked secure (sent over HTTPS only)
cs_secure :: Bool
}
-- | Setting cookie expiration
data CookieEOL
= -- | a point in time in UTC until the cookie is valid
CookieValidUntil UTCTime
| -- | a period (in seconds) for which the cookie is valid
CookieValidFor NominalDiffTime
| -- | the cookie expires with the browser session
CookieValidForSession
| -- | the cookie will have an expiration date in the far future
CookieValidForever
-- | 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 2147483000)
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