Blame Network/HTTP/Cookie.hs

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