webapp-0.0.1: Web/App/Cookie.hs
{-# LANGUAGE OverloadedStrings, TupleSections #-}
{-|
Module : Web.App.Cookie
Copyright : (c) Nathaniel Symer, 2015
License : MIT
Maintainer : nate@symer.io
Stability : experimental
Portability : Cross-Platform
Manipulate HTTP cookies
-}
module Web.App.Cookie
(
-- * Data Structures
Cookie(..),
-- * Cookie Parsing
parseCookie
)
where
import Control.Applicative
import Data.Default
import Data.Char
import Data.Time.Format
import Data.Time.Clock (UTCTime)
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as H
import qualified Data.ByteString.Char8 as B
import Data.Attoparsec.ByteString.Char8 as A
-- ^ Data structure representing an HTTP cookie
data Cookie = Cookie {
cookiePairs :: HashMap String String,
cookiePath :: Maybe String,
cookieDomain :: Maybe String,
cookieExpires :: Maybe UTCTime,
cookieSecure :: Bool,
cookieHttpOnly :: Bool
} deriving (Show)
instance Default Cookie where
def = Cookie H.empty Nothing Nothing Nothing False False
-- |Parse a 'Cookie' from a @Cookie@ header
parseCookie :: B.ByteString -- ^ value of a @Cookie@ header
-> Maybe Cookie
parseCookie = fmap (lx def) . maybeResult . flip feed "" . parse pairs -- @flip feed ""@ is required because @pairs@ can take an indefinite amount of input
where
-- RFC 1123 date parser
parseCookieDate = parseTimeM True defaultTimeLocale "%a, %d %b %Y %H:%M:%S GMT"
-- lexer
lx c [] = c
lx c (("secure",_):xs) = lx (c { cookieSecure = True }) xs
lx c (("httponly",_):xs) = lx (c { cookieHttpOnly = True }) xs
lx c (("path",v):xs) = lx (c { cookiePath = Just v }) xs
lx c (("domain",v):xs) = lx (c { cookieDomain = Just v }) xs
lx c (("expires",v):xs) = lx (c { cookieExpires = parseCookieDate v }) xs
lx c ((k,v):xs) = lx (c { cookiePairs = H.insert k v $ cookiePairs c }) xs
-- grammar
pairs = (:) <$> pair <*> (many' $ ";" *> skipSpace *> pair)
pair = (,) <$> attr <*> val
attr = map toLower <$> many' letter_ascii
val = option "" $ "=" *> q *> many' letter_ascii <* q
q = "\"" <|> "'" <|> pure "" -- RFC 6265 states that cookies' values can be quoted