-- © 2001, 2002 Peter Thiemann module WASH.Mail.MIME where -- RFC 2045 -- RFC 2046 import Char import IO import Monad import Random import qualified WASH.Utility.Base64 as Base64 import qualified WASH.Mail.ContentDisposition as CD import qualified WASH.Utility.QuotedPrintable as QuotedPrintable import WASH.Mail.HeaderField import qualified WASH.Utility.RFC2279 as RFC2279 -- UTF-8 -- -------------------------------------------------------------------- textDOC subty docLines = DOC { mediatype= "text", subtype= subty, textLines= docLines, parameters= [], filename= "", -- obsolete contentDisposition = CD.Inline [], messageData="", parts=[] } binaryDOC ty subty bindata = DOC { mediatype= ty, subtype= subty, messageData= bindata, textLines= [], parameters= [], filename= "", -- obsolete contentDisposition = CD.Attachment [], parts=[] } multipartDOC subty subdocs = DOC { mediatype= "multipart", subtype= subty, messageData= "", textLines= [], parameters= [], filename= "", -- obsolete contentDisposition = CD.None, parts= subdocs } data DOC = DOC { mediatype :: String, -- type subtype :: String, -- subtype parameters :: [KV], -- ^ parameters of the media type filename :: String, -- ^ suggested filename, OBSOLETE contentDisposition :: CD.ContentDisposition, -- ^ sets Content-Disposition, e.g. for suggesting a filename -- depending on mediatype only one of the following is relevant: messageData :: String, -- ^ data, only relevant for binary data textLines :: [String], -- ^ lines, only relevant for text mediatypes parts :: [DOC] -- ^ data, only relevant for multipart mediatypes } recommend_cte h doc = case mediatype doc of "text" -> case sendMode h of SevenBit -> "quoted-printable" EightBit -> "8bit" "multipart" -> "7bit" _ -> case sendMode h of SevenBit -> "base64" EightBit -> "8bit" inventBoundary = inventKey 10 (init Base64.alphabet_list) where inventKey len chars = do g <- getStdGen let candidate = take len $ map (chars !!) $ randomRs (0, length chars - 1) g return ("=_" ++ candidate ++ "=_") -- see RFC 2045, 6.7 for reasoning about this choice of boundary string data SendMode = EightBit | SevenBit data SendControl = SendControl { sendH :: Handle, sendMode :: SendMode } smtpSendControl = SendControl { sendH = stdout, sendMode = SevenBit } httpSendControl = SendControl { sendH = stdout, sendMode = EightBit } instance Send DOC where hSend h doc = let cte = recommend_cte h doc in do boundary <- inventBoundary let extraParameter | mediatype doc == "multipart" = [KV ("boundary", '\"':boundary++"\"")] | mediatype doc == "text" = [KV ("charset", "utf-8")] | otherwise = [] hSend h (makeContentType (mediatype doc) (subtype doc) (extraParameter ++ parameters doc)) hSend h (makeContentTransferEncoding cte) when (CD.hasContentDisposition $ contentDisposition doc) $ hSend h (makeContentDisposition (CD.toString (contentDisposition doc))) -- hSend h (makeContentDisposition (filename doc)) hSend h CRLF case mediatype doc of "text" -> hSendText h doc "multipart" -> hSendMultipart h boundary doc _ -> hSendBinary h doc hSendText h doc = case sendMode h of EightBit -> hPutStr hdl str SevenBit -> hPutStr hdl (QuotedPrintable.encode str) where hdl = sendH h str = RFC2279.encode $ flat (textLines doc) flat [] = [] flat (xs:xss) = xs ++ "\r\n" ++ flat xss hSendBinary h doc = case sendMode h of SevenBit -> hPutStr (sendH h) (Base64.encode (messageData doc)) EightBit -> hPutStr (sendH h) (messageData doc) hSendMultipart h boundary doc = do -- may send a preamble for non-MIME-able MUAs at this point sendParts (parts doc) where hdl = sendH h sendParts [] = do hPutStr hdl "--" hPutStr hdl boundary hPutStr hdl "--" hSend h CRLF sendParts (doc:docs) = do hPutStr hdl "--" hPutStr hdl boundary hSend h CRLF hSend h doc sendParts docs data CRLF = CRLF instance Send CRLF where hSend h CRLF = hPutStr (sendH h) "\n" data Mail = Mail { to :: [String], subject :: String, cc :: [String], bcc :: [String], headers :: [Header], contents :: DOC } simpleMail recipients subj doc = Mail { to= recipients, subject= subj, cc=[], bcc=[], headers=[], contents=doc } class Send m where send :: m -> IO () hSend :: SendControl -> m -> IO () send = hSend smtpSendControl instance Send Header where hSend h header = hPutStr (sendH h) (show header) instance Send Mail where hSend h mail = do hSend h (makeTO (to mail)) hSend h (makeSubject (subject mail)) hSend h (makeCC (cc mail)) hSend h (makeBCC (bcc mail)) hSend h mimeHeader hSend h identHeader sequence (map (hSend h) (headers mail)) hSend h (contents mail)