From 6954f707adaa34e2fdc6df78b6bcce79e35279ee Mon Sep 17 00:00:00 2001 From: Michael Orlitzky Date: Fri, 15 Oct 2010 13:01:02 -0400 Subject: [PATCH] Make the email date header RFC822-compliant. --- src/Mail.hs | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++- src/Main.hs | 5 ++-- 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/src/Mail.hs b/src/Mail.hs index ba605f9..3304d76 100644 --- a/src/Mail.hs +++ b/src/Mail.hs @@ -9,6 +9,7 @@ import Control.Exception (evaluate) import Data.List (intercalate) import System.Exit import System.Process +import System.Time (CalendarTime(..), ClockTime, getClockTime, Month, toCalendarTime) import System.IO type Header = String @@ -24,7 +25,7 @@ data Message = Message { headers :: [Header], -- |Showing a message will print it in roughly RFC-compliant -- form. This form is sufficient for handing the message off to -- sendmail. -instance Show Message where +instance Show Message where show m = concat [ if (length (headers m) == 0) then "" else (intercalate "\n" (headers m)) ++ "\n", "Subject: " ++ (subject m) ++ "\n", @@ -34,6 +35,73 @@ instance Show Message where (body m) ] + +-- |Pad a string on the left with zeros until the entire string has +-- length n. +pad_left :: String -> Int -> String +pad_left str n + | n < (length str) = str + | otherwise = (replicate num_zeros '0') ++ str + where num_zeros = n - (length str) + + + +-- |Formats a month name according to RFC822. +format_month :: Month -> String +format_month month = take 3 (show month) + + +-- |Takes an offset from UTC (in seconds) and returns the four-digit +-- offset as a 'String' in +hhmm format. +format_timezone :: Int -> String +format_timezone seconds = + sign ++ pad_hh ++ pad_mm + where + seconds_norm = abs seconds + hh = seconds_norm `div` 3600 + mm = (seconds_norm - (hh*3600)) `div` 60 + pad_hh = pad_left (show hh) 2 + pad_mm = pad_left (show mm) 2 + sign = if seconds < 0 then "-" else "+" + + +-- |Takes a 'ClockTime' as an argument, and formats it as an RFC822 Date header. +-- +-- See, +-- +-- +-- +-- for information. +format_clocktime :: ClockTime -> IO String +format_clocktime ct = do + caltime <- (toCalendarTime ct) + + let days = pad_left (show (ctDay caltime)) 2 + let month = format_month (ctMonth caltime) + let year = show $ ctYear caltime + let hours = pad_left (show (ctHour caltime)) 2 + let minutes = pad_left (show (ctMin caltime)) 2 + let seconds = pad_left (show (ctSec caltime)) 2 + let timezone = format_timezone (ctTZ caltime) + + return $ concat [(show $ ctWDay caltime) ++ ", ", + days ++ " ", + month ++ " ", + year ++ " ", + hours ++ ":", + minutes ++ ":", + seconds ++ " ", + timezone] + + +-- |Constructs an RFC822 Date header with the current date/time. +construct_date_header :: IO String +construct_date_header = do + date <- getClockTime + format_clocktime date + + + -- |Takes a message as an argument, and passes it to the system's -- sendmail binary. sendmail :: Message -> IO (String, String, ExitCode) diff --git a/src/Main.hs b/src/Main.hs index 53c3f90..7025b4b 100644 --- a/src/Main.hs +++ b/src/Main.hs @@ -4,7 +4,6 @@ where import Control.Concurrent (forkIO, threadDelay) import Control.Monad (forever, when) import System.Exit (ExitCode(..), exitWith) -import System.Time (getClockTime) import System.IO (hPutStrLn, stderr) import CommandLine @@ -66,8 +65,8 @@ recurse username latest_status_id maybe_message = do recurse username new_latest_status_id maybe_message return () Just message -> do - date <- getClockTime - let messages = map (message_from_status message (show date)) new_statuses + date_header <- construct_date_header + let messages = map (message_from_status message (date_header)) new_statuses sendmail_results <- mapM sendmail messages mapM print_sendmail_result sendmail_results recurse username new_latest_status_id maybe_message -- 2.44.2