123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146 |
- {-# LANGUAGE OverloadedStrings #-}
- module Data.SMTP.Mime (
- module Data.SMTP.Types.Mime,
- module Data.SMTP.Parser.Mime,
- getMimeData,
- reencodeMessage,
- requiredEncoding,
- splitEach
- ) where
- import Data.SMTP.Types.Mime
- import Data.SMTP.Parser.Mime
- import Data.SMTP.Types.Resource
- import Data.SMTP.Parser.Resource
- import Text.StringConvert
- import qualified Data.Attoparsec.ByteString.Char8.Extras as P
- import qualified Data.Attoparsec.ByteString.Char8 as A
- import qualified Data.Char as C
- import Data.ByteString (ByteString)
- import Data.Word8 (Word8)
- import qualified Data.Word8 as W
- import qualified Data.ByteString as BS
- import qualified Data.ByteString.Char8 as C8
- import qualified Data.ByteString.Lazy as LBS
- import qualified Data.ByteString.Base64.Lazy as B64
- import qualified Data.ByteString.Lazy.Search as LSearch
- --import qualified Data.ByteString.Search as SSearch
- import Data.Default.Class
- getMimeData :: [Header] -> (ContentTypeHeader, TransferEncoding)
- getMimeData hh = (ct, te)
- where
- ct = case getHeader hh contentTypeHeaderName of
- Nothing -> def
- Just (Header (_, cth)) -> case A.parseOnly parseContentType (s cth) of
- Left _ -> def
- Right (ctv, ctp) -> ContentTypeHeader ctv ctp
- te = case getHeader hh transferEncodingHeaderName of
- Nothing -> def
- Just (Header (_, teh)) -> case A.parseOnly parseTransferEncoding (C8.pack teh) of
- Left _ -> def
- Right t -> t
- {- |
- Required encoding for the given data.
- Will evaluate the entire data, thus using a lot of memory.
- 7/8 bit mime is available for data where all lines are shorter then 1000 bytes.
- 7 bit mime is available ofr that where no byte is larger than 128 or equal 0.
- othersiwe data requires binarymime.
- -}
- requiredEncoding :: ResourceData -> BodyEncoding
- requiredEncoding dt = case requiredFeatures 0 $ LBS.unpack dt of
- (False, False) -> B7BitEncoding
- (False, True) -> B8BitEncoding
- (True, _) -> BBinaryEncoding
- where
- requiredFeatures :: Int -> [Word8] -> (Bool, Bool)
- requiredFeatures _ [] = (False, False)
- requiredFeatures len (c:cc) = let
- nlen = if c == (fromIntegral . C.ord $ '\n') then 0 else len+1
- (ll, ee) = requiredFeatures nlen cc
- in (len >= 1000 || ll, c == 0 || c >= 128 || ee)
- reencodeMessage :: BodyEncoding -> ResourceData -> ResourceData
- reencodeMessage toEncoding dt = LBS.concat [
- if newEncoding == fromEncoding then LBS.fromStrict $ BS.concat dthh else replaceEncodingHeader newEncoding dthh,
- "\r\n",
- reencodeBody'
- ]
- where
- (dthh, hh, dtb) = takeHeaders dt
- (ct, fromEncoding) = getMimeData hh
- fromEncoding' = transferToBody fromEncoding
- (message, multiPart, separator) = case ct of
- (ContentTypeHeader (MultiPartMime _) (ContentTypeParameters (Just sep) _ _)) -> (False, True, C8.pack sep)
- (ContentTypeHeader (MessageMime _) _ ) -> (True, False, "")
- _ -> (False, False, "")
- newEncoding :: TransferEncoding
- newEncoding = case (fromEncoding', toEncoding, multiPart) of
- (_, BBinaryEncoding, _) -> fromEncoding
- (BBinaryEncoding, B8BitEncoding, True) -> IdentityEncoding B7BitEncoding
- (_, B8BitEncoding, _) -> fromEncoding
- (_, B7BitEncoding, True) -> IdentityEncoding B7BitEncoding
- (BBinaryEncoding, B7BitEncoding, False) -> Base64EncodedBody
- (B8BitEncoding, B7BitEncoding, False) -> Base64EncodedBody
- _ -> fromEncoding
- newEncoding' = transferToBody newEncoding
- reencodeBody' :: ResourceData
- reencodeBody'
- | newEncoding == fromEncoding = dtb
- | message = reencodeBody newEncoding dtb
- | multiPart = concatParts separator . map (reencodeMessage newEncoding') . splitParts separator $ dtb
- | otherwise = reencodeBody newEncoding dtb
- reencodeBody :: TransferEncoding -> ResourceData -> ResourceData
- reencodeBody (IdentityEncoding _) dt = dt
- reencodeBody Base64EncodedBody dt = LBS.intercalate "\r\n" $ splitEach 76 $ B64.encode dt
- reencodeBody QPEncodedBody dt = dt
- splitEach :: Int -> LBS.ByteString -> [LBS.ByteString]
- splitEach n t
- | LBS.null t = []
- | otherwise = let (p, pp) = LBS.splitAt (fromIntegral n) t
- in p : splitEach n pp
- splitParts :: ByteString -> LBS.ByteString -> [LBS.ByteString]
- splitParts sep dt = map manageChunk chunks
- where
- sep' = BS.concat ["\r\n--", sep]
- chunks = LSearch.split sep' dt
- manageChunk = LBS.dropWhile (== P.asW8 '-') . LBS.dropWhile A.isHorizontalSpace . LBS.dropWhile A.isEndOfLine
- concatParts :: ByteString -> [LBS.ByteString] -> LBS.ByteString
- concatParts sep = joinChunks (LBS.fromStrict sep)
- where
- joinChunks _ [] = ""
- joinChunks _ [c] = c
- joinChunks sep' [c1, c2] = LBS.concat [c1, "\r\n--", sep', "--\r\n", c2]
- joinChunks sep' (c:cc2) = LBS.concat [c, "\r\n--", sep', "\r\n", joinChunks sep' cc2]
- replaceEncodingHeader :: TransferEncoding -> [ByteString] -> LBS.ByteString
- replaceEncodingHeader enc = LBS.concat . map LBS.fromStrict . echoAndReplace
- where
- h = transferEncodingHeaderName
- echoAndReplace [] = []
- echoAndReplace (l:ll) = if isPrefixCI h l
- then BS.concat [C8.pack h, ": ", C8.pack . show $ enc, "\r\n"] :
- dropWhile (\x -> BS.null x || (W.isSpace . BS.head $ x)) ll
- else l : echoAndReplace ll
-
- isPrefixCI :: String -> ByteString -> Bool
- isPrefixCI [] _ = True
- isPrefixCI (c:cc) bs = case BS.uncons bs of
- Nothing -> False
- Just (h, t) -> (C.toUpper . C.chr . fromIntegral $ h) == C.toUpper c && isPrefixCI cc t
|