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