module Codec.MIME.String.Flatten (flatten) where
import qualified Codec.Binary.Base64.String as Base64 (encode)
import Codec.MIME.String.Date (FullDate, show_full_date, show_mbox_full_date)
import Codec.MIME.String.EncodedWord (base64_encode)
import qualified Codec.MIME.String.QuotedPrintable as QP (encode)
import Codec.MIME.String.Internal.Utils
import Data.List (intersperse)
import Network.BSD (getHostName)
import System.Locale (defaultTimeLocale)
import System.Random (randomIO)
import System.Time (getClockTime, toCalendarTime, formatCalendarTime)
flatten :: String -> String -> String -> FullDate
-> String -> [(String, FilePath)]
-> IO String
flatten from_name from_email subject date body attachments
= do msgid <- mk_msgid
let
from_full = encode_name from_name ++ "\n <" ++ from_email ++ ">"
boundary = "=:"
part_start = "\n--" ++ boundary ++ "\n"
parts_end = "\n--" ++ boundary ++ "--\n"
text_content = unlines [
"Content-type: text/plain; charset=utf-8",
"Content-transfer-encoding: quoted-printable",
"Content-Disposition: inline"]
common_headers = unlines [
"From " ++ from_email ++ " " ++ show_mbox_full_date date,
"Date: " ++ show_full_date date,
"Message-ID: " ++ msgid,
"From: " ++ from_full,
"Subject: " ++ mk_subject subject,
"MIME-Version: 1.0"]
safe_char c = isAsciiAlphaNum c || (c `elem` " .-_")
mime_attachment (a, fn)
= part_start
++ "Content-type: application/octet-stream\n"
++ "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 common_headers
++ text_content
++ "\n"
++ QP.encode (my_lines body)
else common_headers
++ "Content-type: multipart/mixed; boundary=\""
++ boundary ++ "\"\n"
++ "\n"
++ "This is a multi-part message in MIME format.\n"
++ part_start
++ text_content
++ "\n"
++ QP.encode (my_lines body)
++ concatMap mime_attachment attachments
++ parts_end
return msg
where single_part = null attachments
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)
mk_subject :: String -> String
mk_subject subject
= if can_send_clear subject &&
length_at_most (76 length "Subject: ") subject
then subject
else encode_string (length "Subject: ") subject
can_send_clear :: String -> Bool
can_send_clear = all isAsciiPrint
length_at_most :: Int -> [a] -> Bool
length_at_most _ [] = True
length_at_most 0 _ = False
length_at_most i (_:xs) = length_at_most (i1) xs
encode_name :: String -> String
encode_name rn = encode_string (length "From: ") rn
encode_string :: Int -> String -> String
encode_string n s
= concat $ intersperse "\n " $ map (base64_encode "utf-8") (splits n' s)
where n' = ((76 n length "=?utf-8?B??=") * 3) `div` 4