salvia-0.1: src/Network/Protocol/Cookie.hs
{- |
For more information: http://www.ietf.org/rfc/rfc2109.txt
-}
module Network.Protocol.Cookie (
Cookie (..)
, empty
, Cookies
, cookies
, cookie
, showCookies
, parseCookies
) where
import Control.Applicative hiding (empty)
import Control.Monad ()
import Data.Char (toLower)
import Data.List (intercalate)
import Misc.Misc (intersperseS, (@@), trim)
import Network.Protocol.Uri (URI)
import Text.ParserCombinators.Parsec hiding (many, optional, (<|>))
import qualified Data.Map as M
{- |
The `Cookie` data type containg one key/value pair with all the (potentially
optional) meta-data.
-}
data Cookie =
Cookie {
name :: String
, value :: String
, comment :: Maybe String
, commentURL :: Maybe URI
, discard :: Bool
, domain :: Maybe String
, maxAge :: Maybe Int
, expires :: Maybe String
, path :: Maybe String
, port :: [Int]
, secure :: Bool
, version :: Int
}
{- | Create an empty cookie. -}
empty :: Cookie
empty = Cookie {
name = ""
, value = ""
, comment = Nothing
, commentURL = Nothing
, discard = False
, domain = Nothing
, maxAge = Nothing
, expires = Nothing
, path = Nothing
, port = []
, secure = False
, version = 0
}
{- |
A collection of multiple cookies. These can all be set in one single HTTP
/Set-Cookie/ header field.
-}
type Cookies = M.Map String Cookie
{- |
Convert a list of cookies into a cookie mapping. The name will be used as the
key, the cookie itself as the value.
-}
cookies :: [Cookie] -> Cookies
cookies = M.fromList . map (\a -> (name a, a))
{- | Case-insensitive way of getting a cookie out of a collection by name. -}
cookie :: String -> Cookies -> Maybe Cookie
cookie n = M.lookup (map toLower n)
-------[ cookie show instance ]------------------------------------------------
instance Show Cookie where
show = flip showsCookie ""
-- Show a semicolon separated list of attribute/value pairs. Only meta pairs
-- with significant values will be pretty printed.
showsCookie :: Cookie -> ShowS
showsCookie c =
pair (name c) (value c)
. opt "comment" (comment c)
. opt "commentURL" (fmap show $ commentURL c)
. bool "discard" (discard c)
. opt "domain" (domain c)
. opt "maxAge" (fmap show $ maxAge c)
. opt "expires" (expires c)
. opt "path" (path c)
. list "port" (map show $ port c)
. bool "secure" (secure c)
. opt "version" (optval $ version c)
where
attr a = showString a
val v = showString ("=" ++ v)
end = showString "; "
single a = attr a . end
pair a v = attr a . val v . end
opt a = maybe id (pair a)
list _ [] = id
list a xs = pair a $ intercalate "," xs
bool _ False = id
bool a True = single a
optval 0 = Nothing
optval i = Just (show i)
{- | Show multiple cookies, pretty printed using a comma separator. -}
showCookies :: Cookies -> String
showCookies = ($"")
. intersperseS (showString ", ")
. map (shows . snd)
. M.toList
-------[ cookie parser ]-------------------------------------------------------
{- |
Parse a set of cookie values and turn this into a collection of real cookies.
As the specification states, only the name, value, domain, path and port will
be recognized.
-}
parseCookies :: String -> Maybe Cookies
parseCookies = fmap cookies . (pCookie @@)
pCookie :: GenParser Char st [Cookie]
pCookie = map ck <$> pCookieValues
where
ck ((n, v), m) =
empty {
name = n
, value = v
, domain = "$domain" `M.lookup` m
, path = "$path" `M.lookup` m
, port = maybe [] pPorts $
"$port" `M.lookup` m
}
-- Parse a list of comma separated portnumbers. Returns a list of integers or
-- an empty list on failure.
pPorts :: String -> [Int]
pPorts = either (const []) id . parse p ""
where
p = char '"'
*> sepBy1 (read <$> many1 digit) (char ',')
<* char '"'
-- Parse a collection of cookie name/value pairs.
pCookieValues :: GenParser Char st [((String, String), M.Map String String)]
pCookieValues =
flip sepBy1 sep ((,)
<$> pair key
<*> (M.fromList
<$> filter (not . null . fst)
<$> many (try (sep *> pair skey))))
where
f = trim . map toLower
key = f <$> many1 (noneOf "=;")
skey = f <$> ((:) <$> char '$' <*> many (noneOf "=;"))
val = trim <$> many (noneOf ";")
pair k = (,) <$> k <*> (char '=' *> val)
sep = char ';' *> many (oneOf " \t\r\n")