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