module Codec.MIME.String.Headers
(
Domain(Domain, LiteralDomain),
Mailbox(Mailbox),
RoutedEmailAddress(RoutedEmailAddress, NormalEmailAddress),
EmailAddress(EmailAddress), get_addr_spec,
Address(Address, Group),
ContentType(ContentType), get_content_type,
ContentDescription(ContentDescription),
get_content_description,
ContentTransferEncoding(ContentTransferEncoding),
get_content_transfer_encoding,
ContentID(ContentID), get_content_id,
MessageID(MessageID),
MIMEVersion(MIMEVersion), get_mime_version,
Parameter(Parameter), p_parameter,
From(From), To(To), Subject(Subject),
get_from, get_to, get_subject,
get_boundary, p_extension_token, p_value, p_quoted_string,
cws, p_ci_string,
)
where
import Prelude hiding ( (<*>), (<$>), (<*), (<$) )
import Codec.MIME.String.Internal.ABNF
(
Parser, parse, pSucceed, pFail,
(<$>), (<$), (<*>), (<*), (<|>), (<|), (<!>),
pEOI, pPred, pChar, pMany, pAtLeast, pMaybe, pOptDef, pString,
)
import qualified Codec.Binary.Base64.String as Base64 (decode)
import qualified Codec.Binary.EncodingQ.String as EncodingQ (decode)
import Codec.MIME.String.Internal.Utils
import Codec.Text.IConv
import qualified Data.ByteString.Lazy.Char8 as BS
import Data.Char
import Data.List
ignore :: Parser inp a -> Parser inp ()
ignore p = () <$ p
boxp :: Parser inp a -> Parser inp [a]
boxp p = box <$> p
p_CTL :: Parser Char Char
p_CTL = pPred (\c -> ord c < 32 || ord c == 127)
p_SP :: Parser Char Char
p_SP = pChar ' '
p_HTAB :: Parser Char Char
p_HTAB = pChar '\t'
p_WSP :: Parser Char Char
p_WSP = p_SP <|> p_HTAB
p_ci_string :: String -> Parser Char String
p_ci_string s = s <$ f s
where f "" = pSucceed ()
f (c:cs) = let p = if isAsciiAlpha c
then pChar (toLower c) <| pChar (toUpper c)
else pChar c
in () <$ p <* f cs
p_NO_WS_CTL :: Parser Char Char
p_NO_WS_CTL = pPred (\c -> let o = ord c in 1 <= o && o <= 8
|| o == 11
|| o == 12
|| 14 <= o && o <= 31
|| o == 127)
p_text :: Parser Char String
p_text = concat
<$> pMany (
p_encoded_words
<| boxp (pPred (\c -> let o = ord c in 0 <= o && o <= 9
|| o == 11
|| o == 12
|| 14 <= o && o <= 127))
)
p_encoded_words :: Parser Char String
p_encoded_words = (\x xs -> x ++ concat xs)
<$> p_encoded_word
<*> pMany (id <$ cws <*> p_encoded_word)
p_encoded_word :: Parser Char String
p_encoded_word = (\cs dec text -> BS.unpack $ convertFuzzy Transliterate cs "utf8" $ BS.pack $ dec text)
<$ pString "=?"
<*> p_charset
<* pChar '?'
<*> p_encoding
<* pChar '?'
<*> p_encoded_text
<* pString "?="
p_charset :: Parser Char String
p_charset = pAtLeast 1 (pPred isAscii <!> (p_SP <|> p_CTL <|> p_especials))
p_especials :: Parser Char Char
p_especials = pPred (`elem` "()<>@,;:\\\"/[]?.=")
p_encoding :: Parser Char (String -> String)
p_encoding = EncodingQ.decode <$ (pChar 'Q' <|> pChar 'q')
<|> Base64.decode <$ (pChar 'B' <|> pChar 'b')
p_encoded_text :: Parser Char String
p_encoded_text = pMany (pPred (\c -> isAsciiPrint c && c /= '?' && c /= ' '))
p_quoted_pair :: Parser Char String
p_quoted_pair = id <$ pChar '\\' <*> p_text <|> boxp p_obs_qp
p_obs_qp :: Parser Char Char
p_obs_qp = id <$ pChar '\\' <*> pPred isAscii
p_FWS :: Parser Char String
p_FWS = pMany p_WSP
p_ctext :: Parser Char Char
p_ctext = p_NO_WS_CTL
<|> pPred (\c -> let o = ord c in 33 <= o && o <= 39
|| 42 <= o && o <= 91
|| 93 <= o && o <= 126)
p_ccontent :: Parser Char ()
p_ccontent = ignore p_ctext <|> ignore p_quoted_pair <|> p_comment
p_comment :: Parser Char ()
p_comment = ()
<$ pChar '('
<* pMany (() <$ pMany p_NO_WS_CTL <* p_ccontent)
<* pMany p_NO_WS_CTL
<* pChar ')'
cws :: Parser Char ()
cws = ignore $ pMany (ignore (pAtLeast 1 p_WSP) <|> p_comment)
p_qtext :: Parser Char Char
p_qtext = p_NO_WS_CTL
<|> pPred (\c -> let o = ord c in o == 33
|| 35 <= o && o <= 91
|| 93 <= o && o <= 126)
p_qcontent :: Parser Char String
p_qcontent = boxp p_qtext
<|> p_quoted_pair
p_quoted_string :: Parser Char String
p_quoted_string = (++)
<$ cws
<* pChar '"'
<*> (concat <$> pMany ((++) <$> pOptDef "" p_FWS <*> p_qcontent))
<*> pOptDef "" p_FWS
<* pChar '"'
p_dcontent :: Parser Char String
p_dcontent = boxp p_dtext <|> p_quoted_pair
p_dtext :: Parser Char Char
p_dtext = p_NO_WS_CTL
<|> pPred (\c -> let o = ord c in 33 <= o && o <= 90
|| 94 <= o && o <= 126)
data MessageID = MessageID String Domain
deriving (Show, Read)
p_msg_id :: Parser Char MessageID
p_msg_id = MessageID
<$ cws
<* pChar '<'
<*> p_id_left
<* pChar '@'
<*> p_id_right
<* pChar '>'
<* cws
p_atom :: Parser Char String
p_atom = id
<$ cws
<*> pAtLeast 1 p_atext
<* cws
p_atext :: Parser Char Char
p_atext = pPred (\c -> isAsciiAlphaNum c || c `elem` "!#$%&'+-/=?^_`{|}~")
p_dot_atom :: Parser Char String
p_dot_atom = id
<$ cws
<*> p_dot_atom_text
<* cws
p_word :: Parser Char String
p_word = p_atom <|> p_quoted_string
p_phrase :: Parser Char [String]
p_phrase = (:)
<$> (p_encoded_words <| p_word)
<*> pMany (id <$ cws <*> (p_encoded_words <| p_word <| pString "."))
<|> boxp p_quoted_string
p_dot_atom_text :: Parser Char String
p_dot_atom_text = (\x xs -> x ++ concat xs)
<$> pAtLeast 1 p_atext
<*> pMany ((:) <$> pChar '.' <*> pAtLeast 1 p_atext)
p_id_left :: Parser Char String
p_id_left = p_dot_atom_text <|> p_no_fold_quote <|> p_obs_id_left
p_id_right :: Parser Char Domain
p_id_right = Domain <$> p_dot_atom_text
<|> p_no_fold_literal
<|> p_obs_id_right
p_obs_id_left :: Parser Char String
p_obs_id_left = p_local_part
p_local_part :: Parser Char String
p_local_part = p_dot_atom <|> p_quoted_string <|> p_obs_local_part
p_obs_local_part :: Parser Char String
p_obs_local_part = (\x xs -> x ++ concat xs)
<$> p_word
<*> pMany ((:) <$> pChar '.' <*> p_word)
p_domain :: Parser Char Domain
p_domain = Domain <$> p_dot_atom <|> p_domain_literal <|> p_obs_domain
p_domain_literal :: Parser Char Domain
p_domain_literal = (LiteralDomain . concat)
<$ cws
<* pChar '['
<*> pMany ( id
<$ p_FWS
<*> p_dcontent)
<* p_FWS
<* pChar ']'
<* cws
p_obs_domain :: Parser Char Domain
p_obs_domain = (\x xs -> Domain (x ++ concat xs))
<$> p_atom
<*> pMany ((:) <$> pChar '.' <*> p_atom)
p_obs_id_right :: Parser Char Domain
p_obs_id_right = p_domain
p_no_fold_quote :: Parser Char String
p_no_fold_quote = concat
<$ pChar '"'
<*> pMany (boxp p_qtext <|> p_quoted_pair)
<* pChar '"'
data Domain = Domain String | LiteralDomain String
deriving (Show, Read, Eq)
p_no_fold_literal :: Parser Char Domain
p_no_fold_literal = LiteralDomain . concat
<$ pChar '['
<*> pMany (boxp p_dtext <|> p_quoted_pair)
<* pChar ']'
newtype Subject = Subject String
deriving (Show, Read)
get_subject :: String -> Maybe Subject
get_subject xs
= case parse ph_subject xs of
Left cd -> Just cd
Right _ -> Nothing
ph_subject :: Parser Char Subject
ph_subject = Subject <$> p_text <* pEOI
newtype From = From [Mailbox]
deriving (Show, Read, Eq)
get_from :: String -> Maybe From
get_from xs
= case parse ph_from xs of
Left f -> Just f
Right _ -> Nothing
ph_from :: Parser Char From
ph_from = From <$ cws <*> p_mailbox_list <* cws <* pEOI
newtype To = To [Address]
deriving (Show, Read)
data Address = Address Mailbox
| Group String [Mailbox]
deriving (Show, Read)
get_to :: String -> Maybe To
get_to xs
= case parse ph_to xs of
Left t -> Just t
Right _ -> Nothing
ph_to :: Parser Char To
ph_to = To <$ cws <*> p_address_list <* cws <* pEOI
p_address_list :: Parser Char [Address]
p_address_list = (:)
<$ pMany (() <$ pChar ',' <* cws)
<*> p_address
<*> pMany ( id
<$ pAtLeast 1 (() <$ cws <* pChar ',')
<* cws
<*> p_address)
<* pMany (() <$ cws <* pChar ',')
p_address :: Parser Char Address
p_address = Address <$> p_mailbox
<|> p_group
p_group :: Parser Char Address
p_group = Group
<$> p_display_name
<* cws
<* pChar ':'
<* cws
<*> pOptDef [] p_mailbox_list
<* cws
<* pChar ';'
p_mailbox_list :: Parser Char [Mailbox]
p_mailbox_list = (:)
<$ pMany (() <$ pChar ',' <* cws)
<*> p_mailbox
<*> pMany ( id
<$ pAtLeast 1 (() <$ cws <* pChar ',')
<* cws
<*> p_mailbox)
<* pMany (() <$ cws <* pChar ',')
data Mailbox = Mailbox (Maybe String) RoutedEmailAddress
deriving (Show, Read, Eq)
p_mailbox :: Parser Char Mailbox
p_mailbox = p_name_addr
<|> (Mailbox Nothing . NormalEmailAddress) <$> p_addr_spec
p_name_addr :: Parser Char Mailbox
p_name_addr = Mailbox
<$> pMaybe p_display_name
<* cws
<*> p_angle_addr
data EmailAddress = EmailAddress String Domain
deriving (Show, Read, Eq)
data RoutedEmailAddress = NormalEmailAddress EmailAddress
| RoutedEmailAddress [Domain] EmailAddress
deriving (Show, Read, Eq)
p_angle_addr :: Parser Char RoutedEmailAddress
p_angle_addr = ($)
<$ pChar '<'
<* cws
<*> pOptDef NormalEmailAddress
(RoutedEmailAddress <$> p_obs_route <* cws)
<*> p_addr_spec
<* cws
<* pChar '>'
get_addr_spec :: String -> Maybe EmailAddress
get_addr_spec xs
= case parse p_addr_spec xs of
Left e -> Just e
Right _ -> Nothing
p_addr_spec :: Parser Char EmailAddress
p_addr_spec = EmailAddress
<$> p_local_part
<* cws
<* pChar '@'
<* cws
<*> p_domain
p_display_name :: Parser Char String
p_display_name = (concat . intersperse " ") <$> p_phrase
p_obs_route :: Parser Char [Domain]
p_obs_route = id <$> p_obs_domain_list <* pChar ':'
p_obs_domain_list :: Parser Char [Domain]
p_obs_domain_list = (:)
<$ pChar '@'
<* cws
<*> p_domain
<*> pMany ( id
<$ pMaybe (() <$ cws <* pChar ',')
<* cws
<* pChar '@'
<* cws
<*> p_domain)
data MIMEVersion = MIMEVersion Integer Integer
deriving (Show, Read)
get_mime_version :: String -> Maybe MIMEVersion
get_mime_version xs = case parse ph_mime_version xs of
Left ct -> Just ct
Right _ -> Nothing
ph_mime_version :: Parser Char MIMEVersion
ph_mime_version = MIMEVersion
<$ cws
<*> (read <$> pMany (pPred isAsciiDigit))
<* cws
<* pChar '.'
<* cws
<*> (read <$> pMany (pPred isAsciiDigit))
<* cws
<* pEOI
data ContentType = ContentType String
String
[Parameter]
deriving (Show, Read)
data Parameter = Parameter String
String
deriving (Show, Read)
get_content_type :: String -> Maybe ContentType
get_content_type xs = case parse ph_content_type xs of
Left ct -> Just ct
Right _ -> Nothing
ph_content_type :: Parser Char ContentType
ph_content_type = ContentType
<$ cws
<*> p_type
<* cws
<* pChar '/'
<* cws
<*> p_subtype
<*> pMany (id
<$ cws
<* pChar ';'
<* cws
<*> p_parameter)
<* cws
<* pEOI
p_type :: Parser Char String
p_type = pAtLeast 1 (pPred (\c -> isAsciiAlphaNum c || c `elem` "-.+"))
p_subtype :: Parser Char String
p_subtype = pAtLeast 1 (pPred (\c -> isAsciiAlphaNum c || c `elem` "-.+"))
p_extension_token :: Parser Char String
p_extension_token = p_ietf_token <|> p_x_token
p_ietf_token :: Parser Char String
p_ietf_token = pFail
p_x_token :: Parser Char String
p_x_token = (\x t -> x:'-':t)
<$> (pChar 'X' <|> pChar 'x')
<* pChar '-'
<*> p_token
p_parameter :: Parser Char Parameter
p_parameter = Parameter
<$> p_attribute
<* cws
<* pChar '='
<* cws
<*> p_value
p_attribute :: Parser Char String
p_attribute = map asciiToLower <$> p_token
p_value :: Parser Char String
p_value = p_token <|> p_quoted_string
p_token :: Parser Char String
p_token = pAtLeast 1 (pPred isAscii <!> (p_SP <|> p_CTL <|> p_tspecials))
p_tspecials :: Parser Char Char
p_tspecials = pPred (`elem` "()<>@,;:\\\"/[]?=")
newtype ContentTransferEncoding
= ContentTransferEncoding String
deriving (Show, Read)
get_content_transfer_encoding :: String -> Maybe ContentTransferEncoding
get_content_transfer_encoding xs
= case parse ph_content_transfer_encoding xs of
Left cte -> Just cte
Right _ -> Nothing
ph_content_transfer_encoding :: Parser Char ContentTransferEncoding
ph_content_transfer_encoding
= ContentTransferEncoding
<$ cws
<*> p_mechanism
<* cws
<* pEOI
p_mechanism :: Parser Char String
p_mechanism = p_ci_string "7bit"
<|> p_ci_string "8bit"
<|> p_ci_string "binary"
<|> p_ci_string "quoted-printable"
<|> p_ci_string "base64"
<|> map asciiToLower <$> p_ietf_token
<|> map asciiToLower <$> p_x_token
newtype ContentID = ContentID MessageID
deriving (Show, Read)
get_content_id :: String -> Maybe ContentID
get_content_id xs
= case parse ph_content_id xs of
Left ci -> Just ci
Right _ -> Nothing
ph_content_id :: Parser Char ContentID
ph_content_id
= ContentID
<$ cws
<*> p_msg_id
<* cws
<* pEOI
newtype ContentDescription = ContentDescription String
deriving (Show, Read)
get_content_description :: String -> Maybe ContentDescription
get_content_description xs
= case parse ph_content_description xs of
Left cd -> Just cd
Right _ -> Nothing
ph_content_description :: Parser Char ContentDescription
ph_content_description
= ContentDescription
<$ cws
<*> p_text
<* cws
<* pEOI
get_boundary :: String -> Maybe String
get_boundary xs
= case parse p_boundary xs of
Left b -> Just b
Right _ -> Nothing
p_boundary :: Parser Char String
p_boundary = (\ss b bs -> dropFromEndWhile (' ' ==) (ss ++ [b] ++ bs))
<$> pMany (pChar ' ')
<*> p_bchars
<*> pMany p_bchars
p_bchars :: Parser Char Char
p_bchars = p_bcharsnospace <|> pChar ' '
p_bcharsnospace :: Parser Char Char
p_bcharsnospace = pPred (\c -> isAsciiAlphaNum c || c `elem` "'()+_,-./:=?")