{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE FlexibleContexts #-} module Cookies ( Name , Value , parseCookies , removeCookie , formatCookies , setCookie , invalidateCookie ) where import Data.String import Control.Applicative import qualified Data.ByteString as BS import Data.Char hiding (isSpace) import Data.List (partition, intercalate) import Network.HTTP.Types.Header (Header, hCookie) import System.Posix.Types (EpochTime) import Data.Attoparsec.ByteString.Char8 type Name = String type Value = String removeCookie :: String -> [(Name, Value)] -> Maybe (Value, [(Name, Value)]) removeCookie name cookies = case partition ((== name) . fst) cookies of ((_, x):_, xs) -> Just (x, xs) _ -> Nothing setCookie :: String -> String -> String -> EpochTime -> String setCookie domain name value maxAge = mkCookie domain [ (name, value) , ("Max-Age", show maxAge) ] invalidateCookie :: String -> String -> String invalidateCookie domain name = mkCookie domain [ (name, "deleted") , ("expires", "Thu, 01 Jan 1970 00:00:00 GMT") ] mkCookie :: String -> [(String, String)] -> String mkCookie domain = intercalate "; " . map format . (++ defaults) . map (fmap Just) where defaults = [ ("Domain", Just domain) , ("path", Just "/") , ("HttpOnly", Nothing) , ("Secure", Nothing) ] format (name, mValue) = maybe name (\value -> name ++ "=" ++ value) mValue formatCookies :: [(Name, Value)] -> BS.ByteString formatCookies = mconcat . intercalate ["; "] . map formatCookie where formatCookie (name, value) = [fromString name, "=", fromString value] parseCookies :: [Header] -> [(Name, Value)] parseCookies = foldr headerToCookies [] headerToCookies :: Header -> [(Name, Value)] -> [(Name, Value)] headerToCookies (name, val) acc | name == hCookie = case parseOnly cookies val of Left{} -> acc Right x -> x ++ acc | otherwise = acc where cookies :: Parser [(Name, Value)] cookies = sepBy1 cookie (";" *> skipSpace) cookie :: Parser (Name, Value) cookie = (,) <$> word <*> (skipSpace *> "=" *> skipSpace *> value) value :: Parser String value = quotedstring <|> many1 (satisfy (/= ';')) <|> return "" quotedstring :: Parser String quotedstring = char '"' *> many (satisfy (/= '"')) <* char '"' word :: Parser String word = many1 (satisfy (\x -> isAlphaNum x || x=='_' || x=='.' || x=='-' || x==':'))