{-# LANGUAGE ApplicativeDo #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE OverloadedStrings #-} {-| Attoparsec 'Parser' for a cookie jar, and convenience function to run it |-} module Data.CURL.CookieJar.Parser ( cookieJarParser , cookieParser , parseCookieJar ) where import Control.Applicative ((<|>)) import Control.Monad (void) import Data.ByteString (ByteString) import Data.Char (ord) import Data.Attoparsec.ByteString.Char8 ( many' , endOfLine , endOfLine , isEndOfLine , takeWhile1 , decimal , skipSpace , skipWhile , char , parseOnly , try , Parser ) import Network.HTTP.Client (Cookie(..), CookieJar, createCookieJar) import Data.Time.Clock.POSIX (posixSecondsToUTCTime) -- | Parse a cookie jar in the Netscape/Mozilla format parseCookieJar :: ByteString -> Either String CookieJar parseCookieJar = parseOnly cookieJarParser -- | Parser a cookie jar in the Netscape/Mozilla format cookieJarParser :: Parser CookieJar cookieJarParser = createCookieJar <$> many' cookieParser -- | Parser for one cookie/line in a cookie jar in the Netscape/Mozilla format -- This will also consume any comment lines preceding the cookie line. -- -- This parser recognizes the magic prefix @#HttpOnly_# and sets the appropriate -- field in the Cookie datatype cookieParser :: Parser Cookie cookieParser = skipSpace *> httpOnlyLine <|> commentLine <|> cookieLine where httpOnlyLine = try $ "#HttpOnly_" *> cookieLineParser True commentLine = "#" *> skipWhile notEndOfLine *> endOfLine *> cookieParser cookieLine = cookieLineParser False cookieLineParser cookie_http_only = do let -- these are the fields not represented by the cookie jar format cookie_creation_time = epoch cookie_last_access_time = epoch cookie_persistent = True cookie_domain <- stringField tab cookie_host_only <- boolField tab cookie_path <- stringField tab cookie_secure_only <- boolField tab cookie_expiry_time <- timeField tab cookie_name <- stringField tab cookie_value <- lastField (endOfLine <|> pure ()) pure $ Cookie {..} where tab = void $ char '\t' stringField = takeWhile1 (/= '\t') boolField = (True <$ "TRUE") <|> (False <$ "FALSE") timeField = posixSecondsToUTCTime <$> fromInteger <$> decimal lastField = takeWhile1 (notEndOfLine) epoch = posixSecondsToUTCTime 0 notEndOfLine :: Char -> Bool notEndOfLine = not . isEndOfLine . fromIntegral . ord