module Happstack.Server.Cookie
( Cookie(..)
, mkCookie
, mkCookieHeader
, getCookies
, getCookie
, getCookies'
, getCookie'
, parseCookies
, cookiesParser
)
where
import qualified Data.ByteString.Char8 as C
import Data.Either
import Data.Char
import Data.List
import Data.Generics
import Happstack.Util.Common (Seconds)
import Text.ParserCombinators.Parsec hiding (token)
data Cookie = Cookie
{ cookieVersion :: String
, cookiePath :: String
, cookieDomain :: String
, cookieName :: String
, cookieValue :: String
, secure :: Bool
} deriving(Show,Eq,Read,Typeable,Data)
mkCookie :: String -> String -> Cookie
mkCookie key val = Cookie "1" "/" "" key val False
mkCookieHeader :: Seconds -> Cookie -> String
mkCookieHeader sec cookie =
let l = [("Domain=",s cookieDomain)
,("Max-Age=",if sec < 0 then "" else show sec)
,("Path=", cookiePath cookie)
,("Version=", s cookieVersion)]
s f | f cookie == "" = ""
s f = '\"' : concatMap e (f cookie) ++ "\""
e c | fctl c || c == '"' = ['\\',c]
| otherwise = [c]
in concat $ intersperse ";" ((cookieName cookie++"="++s cookieValue):[ (k++v) | (k,v) <- l, "" /= v ] ++ if secure cookie then ["Secure"] else [])
fctl :: Char -> Bool
fctl ch = ch == chr 127 || ch <= chr 31
parseCookies :: String -> Either String [Cookie]
parseCookies str = either (Left . show) Right $ parse cookiesParser str str
cookiesParser :: GenParser Char st [Cookie]
cookiesParser = cookies
where
cookies = do
ws
ver<-option "" $ try (cookie_version >>= (\x -> cookieSep >> return x))
cookieList<-(cookie_value ver) `sepBy1` try cookieSep
ws
eof
return cookieList
cookie_value ver = do
name<-attr
cookieEq
val<-value
path<-option "" $ try (cookieSep >> cookie_path)
domain<-option "" $ try (cookieSep >> cookie_domain)
return $ Cookie ver path domain (low name) val False
cookie_version = cookie_special "$Version"
cookie_path = cookie_special "$Path"
cookie_domain = cookie_special "$Domain"
cookie_special s = do
string s
cookieEq
value
cookieSep = ws >> oneOf ",;" >> ws
cookieEq = ws >> char '=' >> ws
ws = spaces
attr = token
value = word
word = try (quoted_string) <|> incomp_token
token = many1 $ oneOf ((chars \\ ctl) \\ tspecials)
quoted_string = do
char '"'
r <-many (oneOf qdtext)
char '"'
return r
incomp_token = many1 $ oneOf ((chars \\ ctl) \\ " \t\";")
tspecials = "()<>@,;:\\\"/[]?={} \t"
ctl = map chr (127:[0..31])
chars = map chr [0..127]
octet = map chr [0..255]
text = octet \\ ctl
qdtext = text \\ "\""
getCookies :: Monad m => C.ByteString -> m [Cookie]
getCookies h = getCookies' h >>= either (fail. ("Cookie parsing failed!"++)) return
getCookie :: Monad m => String -> C.ByteString -> m Cookie
getCookie s h = getCookie' s h >>= either (const $ fail ("getCookie: " ++ show s)) return
getCookies' :: Monad m => C.ByteString -> m (Either String [Cookie])
getCookies' header | C.null header = return $ Right []
| otherwise = return $ parseCookies (C.unpack header)
getCookie' :: Monad m => String -> C.ByteString -> m (Either String Cookie)
getCookie' s h = do
cs <- getCookies' h
return $ do
cooks <- cs
case filter (\x->(==) (low s) (cookieName x) ) cooks of
[] -> fail "No cookie found"
f -> return $ head f
low :: String -> String
low = map toLower