module Network.Mail.Parse.Parsers.Message (messageParser) where import Data.Attoparsec.ByteString import Data.List (find) import Data.Maybe import Data.Either (isRight) import Control.Monad (liftM) import Network.Mail.Parse.Types import Network.Mail.Parse.Utils import Network.Mail.Parse.Parsers.Utils (isMIME, discoverAttachment) import Network.Mail.Parse.Parsers.Multipart (parseMultipart) import Network.Mail.Parse.Decoders.BodyDecoder (decodeBody, decodeTextBody) import Network.Mail.Parse.Parsers.Header (headerParser) import Network.Mail.Parse.Parsers.HeaderFields import qualified Data.Text as T import qualified Data.ByteString.Char8 as BSC import Data.Either.Utils (maybeToEither) import Data.Text.Encoding (encodeUtf8) import Data.Either.Combinators (fromRight', fromRight, mapLeft) import Codec.MIME.Parse (parseMIMEType) import Codec.MIME.Type import Control.Monad (join) parseHeader :: Header -> Header parseHeader header = fromRight header parsedHeader where hname = headerName header contents = headerContents header references = parseTextList " " contents >>= mapM parseMessageId parsedHeader = case T.toLower hname of "date" -> Date <$> parseTime contents "from" -> From <$> parseEmailAddress contents "reply-to" -> ReplyTo <$> parseEmailAddress contents "to" -> To <$> parseEmailAddressList contents "cc" -> CC <$> parseEmailAddressList contents "bcc" -> BCC <$> parseEmailAddressList contents "message-id" -> MessageId <$> parseMessageId contents "in-reply-to" -> InReplyTo <$> parseMessageId contents "references" -> References <$> references "subject" -> Right $ Subject contents "comments" -> Right $ Comments contents "keywords" -> Keywords <$> parseTextList "," contents _ -> Right header -- |Parses a single message messageParser :: Maybe [Header] -> -- ^ Headers, if they were already parsed Maybe [Header] -> -- ^ Context headers, useful is encoding is only -- defined in the message above, for instance Parser (Either ErrorMessage EmailMessage) messageParser headersIn helperHeadersIn = do headers <- if isJust headersIn then return . fromJust $ headersIn else manyTill' headerParser $ string "\r\n" let helperHeaders = if isJust helperHeadersIn then fromJust helperHeadersIn else [] body <- takeByteString let parsedHeaders = map parseHeader headers -- Parse MIME if the message is in a MIME format let parsedBody = if isJust $ find isMIME headers then parseMIME (headers ++ helperHeaders) body else Right [TextBody $ decodeTextBody (headers ++ helperHeaders) body] return $! parsedBody >>= return . EmailMessage parsedHeaders -- |Parses a MIME message part. Needs headers from the actual message -- in case the MIME block misses some encoding blocks mimeParser :: [Header] -> Parser (Either ErrorMessage EmailBody) mimeParser bodyHeaders = do headers <- manyTill' headerParser $ string "\r\n" let isAttachment = discoverAttachment headers if isJust isAttachment then do body <- takeByteString let filename = fromJust isAttachment let decodedBody = decodeBody headers body return . Right $ Attachment headers filename (Just decodedBody) Nothing else (liftM . liftM) MessageBody $ messageParser (Just headers) (Just bodyHeaders) -- |Parse a set of parts. multipartParser :: [Header] -> [BSC.ByteString] -> Either ErrorMessage [EmailBody] multipartParser bodyHeaders parts = do mapM (\p -> join $ mapLeft T.pack $ parseOnly (mimeParser bodyHeaders) p) parts -- |Parse a mime encoded body. parseMIME :: [Header] -> BSC.ByteString -> Either ErrorMessage [EmailBody] parseMIME headers body = if isRight msgType then (case mimeType . fromRight' $ msgType of Multipart _ -> multiParsed >>= multipartParser headers Text _ -> Right decodedBody _ -> Left "mimetype not supported") else Right decodedBody where msgType = findHeader "Content-Type" headers >>= Right . parseMIMEType . headerContents >>= maybeToEither "Couldn't parse message type" multiParsed = msgType >>= \x -> maybeToEither "" $ find (\p -> paramName p == "boundary") (mimeParams x) >>= return . encodeUtf8 . paramValue >>= \b -> eitherToMaybe $ parseOnly (parseMultipart b) body decodedBody = [TextBody $ decodeTextBody headers body]