Spock-core-0.11.0.0: src/Web/Spock/Internal/Cookies.hs
{-# 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