mime-string-0.5: src/Codec/MIME/String/Flatten.hs
module Codec.MIME.String.Flatten
(flatten, Attachments, Attachment)
where
import qualified Codec.Binary.Base64.String as Base64 (encode)
import qualified Codec.MIME.String.QuotedPrintable as QP (encode)
import Codec.MIME.String.Headers
import Codec.MIME.String.Internal.Utils
import Codec.MIME.String.Types
{-
XXX For message IDs:
import Network.BSD (getHostName)
import System.Locale (defaultTimeLocale)
import System.Random (randomIO)
import System.Time (getClockTime, toCalendarTime, formatCalendarTime)
-}
type Attachments = [Attachment]
type Attachment = (String, FilePath, Maybe ContentType)
-- XXX This should be rewritten in a more compositional way!
flatten :: Headers -> String -> Maybe String -> Attachments -> IO String
flatten headers body maybeHtmlBody attachments
= do -- XXX Could add one of one isn't already found? msgid <- mk_msgid
let -- XXX This (\n) could be prettier - check llengths of bits
alternativeBoundary = "=:A"
alternativePartStart = "\n--" ++ alternativeBoundary ++ "\n"
alternativePartsEnd = "\n--" ++ alternativeBoundary ++ "--\n"
mixedBoundary = "=:M"
mixedPartStart = "\n--" ++ mixedBoundary ++ "\n"
mixedPartsEnd = "\n--" ++ mixedBoundary ++ "--\n"
common_headers = unlines (concatMap h_raw_header headers)
++ "MIME-Version: 1.0\n"
text_content_headers = unlines [
"Content-type: text/plain; charset=utf-8",
"Content-transfer-encoding: quoted-printable",
"Content-Disposition: inline"]
html_content_headers = unlines [
"Content-type: text/html; charset=utf-8",
"Content-transfer-encoding: quoted-printable",
"Content-Disposition: inline"]
-- This is overly paranoid
safe_char c = isAsciiAlphaNum c || (c `elem` " .-_")
mime_attachment (a, fn, mct)
= let -- XXX showParam and ct should do some sanity checking
-- of the values they are passed
showParam (Parameter k v) = "; " ++ k ++ "=\"" ++ v ++ "\""
ct = case mct of
Just (ContentType x y ps) ->
"Content-type: " ++ x ++ "/" ++ y
++ concatMap showParam ps ++ "\n"
Nothing -> "Content-type: application/octet-stream\n"
in mixedPartStart
++ ct
++ "Content-transfer-encoding: base64\n"
++ "Content-Disposition: attachment; filename=\""
++ (case reverse $ filter safe_char
$ takeWhile ('/' /=)
$ reverse fn of
[] -> "unknown"
fn' -> fn')
++ "\"\n"
++ "\n"
++ Base64.encode a
msg = if single_part
then case maybeHtmlBody of
Nothing ->
common_headers
++ text_content_headers
++ "\n"
++ QP.encode (my_lines body)
Just htmlBody ->
common_headers
++ "Content-type: multipart/alternative; boundary=\""
++ alternativeBoundary ++ "\"\n"
++ "\n"
++ "This is a multi-part message in MIME format.\n"
++ alternativePartStart
++ text_content_headers
++ "\n"
++ QP.encode (my_lines body)
++ alternativePartStart
++ html_content_headers
++ "\n"
++ QP.encode (my_lines htmlBody)
++ alternativePartsEnd
else case maybeHtmlBody of
Nothing ->
common_headers
++ "Content-type: multipart/mixed; boundary=\""
++ mixedBoundary ++ "\"\n"
++ "\n"
++ "This is a multi-part message in MIME format.\n"
++ mixedPartStart
++ text_content_headers
++ "\n"
++ QP.encode (my_lines body)
++ concatMap mime_attachment attachments
++ mixedPartsEnd
Just htmlBody ->
common_headers
++ "Content-type: multipart/mixed; boundary=\""
++ mixedBoundary ++ "\"\n"
++ "\n"
++ "This is a multi-part message in MIME format.\n"
++ mixedPartStart
-- Start of the body
++ "Content-type: multipart/alternative; boundary=\""
++ alternativeBoundary ++ "\"\n"
++ "\n"
++ alternativePartStart
++ text_content_headers
++ "\n"
++ QP.encode (my_lines body)
++ alternativePartStart
++ html_content_headers
++ "\n"
++ QP.encode (my_lines htmlBody)
++ alternativePartsEnd
-- End of the body
++ concatMap mime_attachment attachments
++ mixedPartsEnd
return msg
where single_part = null attachments
-- XXX The IO calls used to use liftIOErr. We could use something similar
-- but require we are called in a MonadError m?
-- XXX We should possibly be including a program name
{-
mk_msgid :: IO String
mk_msgid
= do hostname <- getHostName
clock_time <- getClockTime
calendar_time <- toCalendarTime clock_time
r <- randomIO
let timestamp = formatCalendarTime defaultTimeLocale
"%Y%m%d%H%M%S" calendar_time
lhs = concat $ intersperse "."
[timestamp, show (r :: Int)]
return (lhs ++ "@" ++ hostname)
-}