]> gitweb.michael.orlitzky.com - dead/halcyon.git/blob - src/Mail.hs
Make the email date header RFC822-compliant.
[dead/halcyon.git] / src / Mail.hs
1 -- |Email functions and data types.
2
3 module Mail
4 where
5
6 import Control.Concurrent
7 import Control.Concurrent.MVar
8 import Control.Exception (evaluate)
9 import Data.List (intercalate)
10 import System.Exit
11 import System.Process
12 import System.Time (CalendarTime(..), ClockTime, getClockTime, Month, toCalendarTime)
13 import System.IO
14
15 type Header = String
16
17 -- |A crude model of an RFC821 email message.
18 data Message = Message { headers :: [Header],
19 subject :: String,
20 body :: String,
21 from :: String,
22 to :: String }
23 deriving (Eq)
24
25 -- |Showing a message will print it in roughly RFC-compliant
26 -- form. This form is sufficient for handing the message off to
27 -- sendmail.
28 instance Show Message where
29 show m =
30 concat [ if (length (headers m) == 0) then "" else (intercalate "\n" (headers m)) ++ "\n",
31 "Subject: " ++ (subject m) ++ "\n",
32 "From: " ++ (from m) ++ "\n",
33 "To: " ++ (to m) ++ "\n",
34 "\n",
35 (body m) ]
36
37
38
39 -- |Pad a string on the left with zeros until the entire string has
40 -- length n.
41 pad_left :: String -> Int -> String
42 pad_left str n
43 | n < (length str) = str
44 | otherwise = (replicate num_zeros '0') ++ str
45 where num_zeros = n - (length str)
46
47
48
49 -- |Formats a month name according to RFC822.
50 format_month :: Month -> String
51 format_month month = take 3 (show month)
52
53
54 -- |Takes an offset from UTC (in seconds) and returns the four-digit
55 -- offset as a 'String' in +hhmm format.
56 format_timezone :: Int -> String
57 format_timezone seconds =
58 sign ++ pad_hh ++ pad_mm
59 where
60 seconds_norm = abs seconds
61 hh = seconds_norm `div` 3600
62 mm = (seconds_norm - (hh*3600)) `div` 60
63 pad_hh = pad_left (show hh) 2
64 pad_mm = pad_left (show mm) 2
65 sign = if seconds < 0 then "-" else "+"
66
67
68 -- |Takes a 'ClockTime' as an argument, and formats it as an RFC822 Date header.
69 --
70 -- See,
71 --
72 -- <http://cr.yp.to/immhf/date.html>
73 --
74 -- for information.
75 format_clocktime :: ClockTime -> IO String
76 format_clocktime ct = do
77 caltime <- (toCalendarTime ct)
78
79 let days = pad_left (show (ctDay caltime)) 2
80 let month = format_month (ctMonth caltime)
81 let year = show $ ctYear caltime
82 let hours = pad_left (show (ctHour caltime)) 2
83 let minutes = pad_left (show (ctMin caltime)) 2
84 let seconds = pad_left (show (ctSec caltime)) 2
85 let timezone = format_timezone (ctTZ caltime)
86
87 return $ concat [(show $ ctWDay caltime) ++ ", ",
88 days ++ " ",
89 month ++ " ",
90 year ++ " ",
91 hours ++ ":",
92 minutes ++ ":",
93 seconds ++ " ",
94 timezone]
95
96
97 -- |Constructs an RFC822 Date header with the current date/time.
98 construct_date_header :: IO String
99 construct_date_header = do
100 date <- getClockTime
101 format_clocktime date
102
103
104
105 -- |Takes a message as an argument, and passes it to the system's
106 -- sendmail binary.
107 sendmail :: Message -> IO (String, String, ExitCode)
108 sendmail message = do
109 let sendmail_args = ["-f",
110 (from message)]
111
112 (inh, outh, errh, ph) <-
113 runInteractiveProcess "/usr/bin/sendmail" sendmail_args Nothing Nothing
114
115 outm <- newEmptyMVar
116 outs <- hGetContents outh
117
118 errm <- newEmptyMVar
119 errs <- hGetContents errh
120
121 forkIO $ hPutStr inh (show message) >> hClose inh
122 forkIO $ evaluate (length outs) >> putMVar outm ()
123 forkIO $ evaluate (length errs) >> putMVar errm ()
124
125 readMVar outm
126 readMVar errm
127
128 ec <- waitForProcess ph
129 return (outs, errs, ec)
130
131
132 -- |The 'sendmail' function returns a three-tuple of its outputs,
133 -- errors, and exit codes. This function pretty-prints one of those
134 -- three-tuples.
135 print_sendmail_result :: (String, String, ExitCode) -> IO ()
136 print_sendmail_result (outs, errs, ec) = do
137 case ec of
138 ExitSuccess -> return ()
139 _ -> putStrLn $ concat ["Output: " ++ outs,
140 "\nErrors: " ++ errs,
141 "\nExit Code: " ++ (show ec)]