module Happstack.Server.Internal.RFC822Headers
(
Header,
pHeader,
pHeaders,
parseHeaders,
ContentType(..),
getContentType,
parseContentType,
showContentType,
ContentTransferEncoding(..),
getContentTransferEncoding,
parseContentTransferEncoding,
ContentDisposition(..),
getContentDisposition,
parseContentDisposition,
parseM
) where
import Data.Char
import Data.List
import Text.ParserCombinators.Parsec
type Header = (String, String)
pHeaders :: Parser [Header]
pHeaders = many pHeader
parseHeaders :: Monad m => SourceName -> String -> m [Header]
parseHeaders = parseM pHeaders
pHeader :: Parser Header
pHeader =
do name <- many1 headerNameChar
char ':'
many ws1
line <- lineString
crLf
extraLines <- many extraFieldLine
return (map toLower name, concat (line:extraLines))
extraFieldLine :: Parser String
extraFieldLine =
do sp <- ws1
line <- lineString
crLf
return (sp:line)
showParameters :: [(String,String)] -> String
showParameters = concatMap f
where f (n,v) = "; " ++ n ++ "=\"" ++ concatMap esc v ++ "\""
esc '\\' = "\\\\"
esc '"' = "\\\""
esc c | c `elem` ['\\','"'] = '\\':[c]
| otherwise = [c]
p_parameter :: Parser (String,String)
p_parameter =
do lexeme $ char ';'
p_name <- lexeme $ p_token
lexeme $ char '='
let litStr = if p_name == "filename"
then buggyLiteralString
else literalString
p_value <- litStr <|> p_token
return (map toLower p_name, p_value)
data ContentType =
ContentType {
ctType :: String,
ctSubtype :: String,
ctParameters :: [(String, String)]
}
deriving (Show, Read, Eq, Ord)
showContentType :: ContentType -> String
showContentType (ContentType x y ps) = x ++ "/" ++ y ++ showParameters ps
pContentType :: Parser ContentType
pContentType =
do many ws1
c_type <- p_token
lexeme $ char '/'
c_subtype <- lexeme $ p_token
c_parameters <- many p_parameter
return $ ContentType (map toLower c_type) (map toLower c_subtype) c_parameters
parseContentType :: Monad m => String -> m ContentType
parseContentType = parseM pContentType "Content-type"
getContentType :: Monad m => [Header] -> m ContentType
getContentType hs = lookupM "content-type" hs >>= parseContentType
data ContentTransferEncoding =
ContentTransferEncoding String
deriving (Show, Read, Eq, Ord)
pContentTransferEncoding :: Parser ContentTransferEncoding
pContentTransferEncoding =
do many ws1
c_cte <- p_token
return $ ContentTransferEncoding (map toLower c_cte)
parseContentTransferEncoding :: Monad m => String -> m ContentTransferEncoding
parseContentTransferEncoding =
parseM pContentTransferEncoding "Content-transfer-encoding"
getContentTransferEncoding :: Monad m => [Header] -> m ContentTransferEncoding
getContentTransferEncoding hs =
lookupM "content-transfer-encoding" hs >>= parseContentTransferEncoding
data ContentDisposition =
ContentDisposition String [(String, String)]
deriving (Show, Read, Eq, Ord)
pContentDisposition :: Parser ContentDisposition
pContentDisposition =
do many ws1
c_cd <- p_token
c_parameters <- many p_parameter
return $ ContentDisposition (map toLower c_cd) c_parameters
parseContentDisposition :: Monad m => String -> m ContentDisposition
parseContentDisposition = parseM pContentDisposition "Content-disposition"
getContentDisposition :: Monad m => [Header] -> m ContentDisposition
getContentDisposition hs =
lookupM "content-disposition" hs >>= parseContentDisposition
parseM :: Monad m => Parser a -> SourceName -> String -> m a
parseM p n inp =
case parse p n inp of
Left e -> fail (show e)
Right x -> return x
lookupM :: (Monad m, Eq a, Show a) => a -> [(a,b)] -> m b
lookupM n = maybe (fail ("No such field: " ++ show n)) return . lookup n
ws1 :: Parser Char
ws1 = oneOf " \t"
lexeme :: Parser a -> Parser a
lexeme p = do x <- p; many ws1; return x
crLf :: Parser String
crLf = try (string "\n\r" <|> string "\r\n") <|> string "\n" <|> string "\r"
lineString :: Parser String
lineString = many (noneOf "\n\r")
literalString :: Parser String
literalString = do char '\"'
str <- many (noneOf "\"\\" <|> quoted_pair)
char '\"'
return str
buggyLiteralString :: Parser String
buggyLiteralString =
do char '\"'
str <- manyTill anyChar (try lastQuote)
return str
where lastQuote = do char '\"'
notFollowedBy (try (many (noneOf "\"") >> char '\"'))
headerNameChar :: Parser Char
headerNameChar = noneOf "\n\r:"
especials, tokenchar :: [Char]
especials = "()<>@,;:\\\"/[]?.="
tokenchar = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" \\ especials
p_token :: Parser String
p_token = many1 (oneOf tokenchar)
text_chars :: [Char]
text_chars = map chr ([1..9] ++ [11,12] ++ [14..127])
p_text :: Parser Char
p_text = oneOf text_chars
quoted_pair :: Parser Char
quoted_pair = do char '\\'
p_text