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