{-| Add delivery headers to an email message. -} module DeliveryHeaders ( -- * Data types Addr(..) , MailTime -- * header utilities , isDate , isFrom , makeReceived , addHeaders -- * string utilities , toStr , rstrip -- * time utilities , getMailTime ) where import Control.Arrow (left) import Control.Applicative ( (<$>) ) import Data.ByteString.Char8 (unpack) import Data.Char (isSpace) import Data.List (dropWhileEnd) import Data.Maybe (fromJust, isJust) import Data.Monoid import Data.UnixTime (mailDateFormat, getUnixTime, formatUnixTime) import Text.Parsec import Text.ParserCombinators.Parsec.Rfc2822NS (Field(..), message, GenericMessage(..) ) -- | ... actually, any string at all is considered a valid -- "time" string we can use. newtype MailTime = MailTime String -- | just a newtype to distinguish address from other strings. newtype Addr = Addr { unAddr :: String } deriving (Show, Eq) toStr :: MailTime -> String toStr (MailTime str) = str -- | get the unix time getMailTime :: IO MailTime getMailTime = do time <- getUnixTime MailTime . unpack <$> formatUnixTime mailDateFormat time -- | is this a 'Date' field? isDate :: Field -> Bool isDate field = case field of Date _ -> True _ -> False -- | Is this a 'From' field? isFrom :: Field -> Bool isFrom field = case field of From _ -> True _ -> False -- | make up the "@Received:@" header, given a time, -- a possible "from" address, and a "to" address. makeReceived :: MailTime -> Maybe Addr -> Addr -> String makeReceived (MailTime timeStr) fromAddr toAddr = let toAddrStr = unAddr toAddr rec = ["Received: for " <> toAddrStr <> " with local (attomail)"] envelope = if isJust fromAddr then [" (envelope-from " <> unAddr (fromJust fromAddr) <> ")"] else [] end = ["; " <> timeStr <> "\r\n"] -- should end w/ crlf?? in concat $ rec ++ envelope ++ end -- | strip whitespace from right-hand end rstrip :: String -> String rstrip = dropWhileEnd isSpace -- | add minimal headers: a "@Received:@" header, a "@Date:@" -- header if we haven't already been given one, -- a "@From:@" field if we haven't already been given one. addHeaders :: MailTime -> String -> Maybe Addr -> Addr -> Either String String addHeaders time mesgText fromAddr toAddr = do (Message headers body) <- left show (parse message "stdin" mesgText) let received = [makeReceived time fromAddr toAddr] fromStr = case fromAddr of Nothing -> "" Just a -> unAddr a hasDate = any isDate headers hasFrom = any isFrom headers dateField = if hasDate then [] else ["Date: " <> toStr time <> "\r\n"] fromField = if hasFrom then [] else ["From: " <> fromStr <> "\r\n"] headBit = [rstrip (take (length mesgText - length body) mesgText) <> "\r\n"] newHead = concat $ received ++ headBit ++ dateField ++ fromField ++ ["\r\n"] return $ newHead ++ body