module Network.CGI.Accept (
Acceptable
, Accept
, Charset(..), ContentEncoding(..), Language(..)
, negotiate
) where
import Data.Function
import Data.List
import Data.Maybe
import Numeric
import Text.ParserCombinators.Parsec
import Network.Multipart
import Network.Multipart.Header
newtype Accept a = Accept [(a, Quality)]
deriving (Show)
type Quality = Double
class Eq a => Acceptable a where
includes :: a -> a -> Bool
top :: a
instance HeaderValue a => HeaderValue (Accept a) where
parseHeaderValue = fmap Accept $ sepBy p (lexeme (char ','))
where p = do a <- parseHeaderValue
q <- option 1 $ do _ <- lexeme $ char ';'
_ <- lexeme $ char 'q'
_ <- lexeme $ char '='
lexeme pQuality
return (a,q)
pQuality = (char '0' >> option "0" (char '.' >> many digit) >>= \ds -> return (read ("0." ++ ds ++ "0")))
<|> (char '1' >> optional (char '.' >> many (char '0')) >> return 1)
prettyHeaderValue (Accept xs) = concat $ intersperse ", " [prettyHeaderValue a ++ "; q=" ++ showQuality q | (a,q) <- xs]
where showQuality q = showFFloat (Just 3) q ""
starOrEqualTo :: String -> String -> Bool
starOrEqualTo x y = x == "*" || x == y
negotiate :: Acceptable a => [a] -> Maybe (Accept a) -> [a]
negotiate ys Nothing = ys
negotiate ys (Just xs) = reverse [ z | (q,z) <- sortBy (compare `on` fst) [ (quality xs y,y) | y <- ys], q > 0]
quality :: Acceptable a => Accept a -> a -> Quality
quality (Accept xs) y = fromMaybe 0 $ listToMaybe $ sort $ map snd $ sortBy (compareSpecificity `on` fst) $ filter ((`includes` y) . fst) xs
compareSpecificity :: Acceptable a => a -> a -> Ordering
compareSpecificity x y
| x `includes` y && y `includes` x = EQ
| x `includes` y = GT
| y `includes` x = LT
| otherwise = error "Non-comparable Acceptables"
instance Acceptable ContentType where
includes x y = ctType x `starOrEqualTo` ctType y
&& ctSubtype x `starOrEqualTo` ctSubtype y
&& all (hasParameter y) (ctParameters x)
top = ContentType "*" "*" []
hasParameter :: ContentType -> (String, String) -> Bool
hasParameter t (k,v) = maybe False (==v) $ lookup k (ctParameters t)
newtype Charset = Charset String
deriving (Show)
instance Eq Charset where
Charset x == Charset y = caseInsensitiveEq x y
instance Ord Charset where
Charset x `compare` Charset y = caseInsensitiveCompare x y
instance HeaderValue Charset where
parseHeaderValue = fmap Charset $ many ws1 >> lexeme p_token
prettyHeaderValue (Charset s) = s
instance Acceptable Charset where
Charset x `includes` Charset y = starOrEqualTo x y
top = Charset "*"
newtype ContentEncoding = ContentEncoding String
deriving (Show)
instance Eq ContentEncoding where
ContentEncoding x == ContentEncoding y = caseInsensitiveEq x y
instance Ord ContentEncoding where
ContentEncoding x `compare` ContentEncoding y = caseInsensitiveCompare x y
instance HeaderValue ContentEncoding where
parseHeaderValue = fmap ContentEncoding $ many ws1 >> lexeme p_token
prettyHeaderValue (ContentEncoding s) = s
instance Acceptable ContentEncoding where
ContentEncoding x `includes` ContentEncoding y = starOrEqualTo x y
top = ContentEncoding "*"
newtype Language = Language String
deriving (Show)
instance Eq Language where
Language x == Language y = caseInsensitiveEq x y
instance Ord Language where
Language x `compare` Language y = caseInsensitiveCompare x y
instance HeaderValue Language where
parseHeaderValue = fmap Language $ many ws1 >> lexeme p_token
prettyHeaderValue (Language s) = s
instance Acceptable Language where
Language x `includes` Language y =
x == "*" || x == y || (x `isPrefixOf` y && "-" `isPrefixOf` drop (length x) y)
top = Language "*"