----------------------------------------------------------------------------- -- | -- Module : Network.HTTP.Cookie -- Copyright : (c) Warrick Gray 2002, Bjorn Bringert 2003-2005, 2007 Robin Bate Boerop, 2008- Sigbjorn Finne -- License : BSD -- -- Maintainer : Sigbjorn Finne -- Stability : experimental -- Portability : non-portable (not tested) -- -- This module provides the data types and functions for working with HTTP cookies. -- Right now, it contains mostly functionality needed by 'Network.Browser'. -- ----------------------------------------------------------------------------- module Network.HTTP.Cookie ( Cookie(..) , cookieMatch -- :: (String,String) -> Cookie -> Bool -- functions for translating cookies and headers. , cookieToHeader -- :: Cookie -> Header , processCookieHeaders -- :: String -> [Header] -> ([String], [Cookie]) ) where import Network.HTTP.Headers import Data.Char import Data.List import Data.Maybe import Text.ParserCombinators.Parsec ( Parser, char, many, many1, satisfy, parse, option, try , (<|>), sepBy1 ) ------------------------------------------------------------------ ----------------------- Cookie Stuff ----------------------------- ------------------------------------------------------------------ -- | @Cookie@ is the Haskell representation of HTTP cookie values. -- See its relevant specs for authoritative details. data Cookie = MkCookie { ckDomain :: String , ckName :: String , ckValue :: String , ckPath :: Maybe String , ckComment :: Maybe String , ckVersion :: Maybe String } deriving(Show,Read) instance Eq Cookie where a == b = ckDomain a == ckDomain b && ckName a == ckName b && ckPath a == ckPath b -- | @cookieToHeader ck@ serialises a @Cookie@ to an HTTP request header. cookieToHeader :: Cookie -> Header cookieToHeader ck = Header HdrCookie text where path = maybe "" (";$Path="++) (ckPath ck) text = "$Version=" ++ fromMaybe "0" (ckVersion ck) ++ ';' : ckName ck ++ "=" ++ ckValue ck ++ path ++ (case ckPath ck of Nothing -> "" Just x -> ";$Path=" ++ x) ++ ";$Domain=" ++ ckDomain ck -- | @cookieMatch (domain,path) ck@ performs the standard cookie -- match wrt the given domain and path. cookieMatch :: (String, String) -> Cookie -> Bool cookieMatch (dom,path) ck = ckDomain ck `isSuffixOf` dom && case ckPath ck of Nothing -> True Just p -> p `isPrefixOf` path -- | @processCookieHeaders dom hdrs@ processCookieHeaders :: String -> [Header] -> ([String], [Cookie]) processCookieHeaders dom hdrs = foldr (headerToCookies dom) ([],[]) hdrs -- | @headerToCookies dom hdr acc@ headerToCookies :: String -> Header -> ([String], [Cookie]) -> ([String], [Cookie]) headerToCookies dom (Header HdrSetCookie val) (accErr, accCookie) = case parse cookies "" val of Left{} -> (val:accErr, accCookie) Right x -> (accErr, x ++ accCookie) where cookies :: Parser [Cookie] cookies = sepBy1 cookie (char ',') cookie :: Parser Cookie cookie = do { name <- word ; spaces_l ; char '=' ; spaces_l ; val1 <- cvalue ; args <- cdetail ; return $ mkCookie name val1 args } cvalue :: Parser String spaces_l = many (satisfy isSpace) cvalue = quotedstring <|> many1 (satisfy $ not . (==';')) <|> return "" -- all keys in the result list MUST be in lower case cdetail :: Parser [(String,String)] cdetail = many $ try (do { spaces_l ; char ';' ; spaces_l ; s1 <- word ; spaces_l ; s2 <- option "" (do { char '=' ; spaces_l ; v <- cvalue ; return v }) ; return (map toLower s1,s2) }) mkCookie :: String -> String -> [(String,String)] -> Cookie mkCookie nm cval more = MkCookie { ckName = nm , ckValue = cval , ckDomain = map toLower (fromMaybe dom (lookup "domain" more)) , ckPath = lookup "path" more , ckVersion = lookup "version" more , ckComment = lookup "comment" more } headerToCookies _ _ acc = acc word, quotedstring :: Parser String quotedstring = do { char '"' -- " ; str <- many (satisfy $ not . (=='"')) ; char '"' ; return str } word = many1 (satisfy (\x -> isAlphaNum x || x=='_' || x=='.' || x=='-' || x==':'))