Metadata.hs 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. {-# LANGUAGE OverloadedStrings #-}
  2. {-# LANGUAGE TemplateHaskell #-}
  3. module Walrus.Backend.Metadata where
  4. import Data.ByteString (ByteString)
  5. import Data.Time.Clock (UTCTime)
  6. import SMTP.Address
  7. import SMTP.Account
  8. import qualified SMTP.Mime as Mime
  9. import SMTP.Response
  10. import Data.Text.IsText
  11. import Data.Time.ISO8601
  12. import Data.IP
  13. import Data.Default.Class
  14. import Control.Applicative
  15. import Control.Lens
  16. import Text.Read (readMaybe)
  17. import Data.Attoparsec.ByteString.Char8 (Parser)
  18. import qualified Data.Attoparsec.ByteString.Char8 as A
  19. import Data.Attoparsec.ByteString.Char8.Extras
  20. import qualified Data.ByteString as BS
  21. import qualified Data.List as List
  22. data BackendAction = DATA deriving (Show, Read, Eq, Ord, Bounded, Enum)
  23. data ClientIdentity = ClientIdentity {_clientIp :: IP, _clientPort :: Int} deriving (Show, Read, Ord, Eq)
  24. data MaybeMetadata = MaybeMetadata {_mclientId :: Maybe ClientIdentity, _mclientName :: Maybe ByteString,
  25. _mmailFrom :: Maybe Account, _mrcptTo :: [Address], _mrcptFailed :: [(Address, Response)],
  26. _mauth :: Maybe ByteString, _mrecvDate :: Maybe UTCTime, _mbodyEnc :: Mime.BodyEncoding,
  27. _msmtpUtf8 :: Bool, _maction :: Maybe BackendAction, _munrecognized :: [ByteString]
  28. } deriving (Show, Ord, Eq)
  29. data Metadata = Metadata {_clientId :: ClientIdentity, _clientName :: ByteString,
  30. _mailFrom :: Account, _rcptTo :: [Address], _rcptFailed :: [(Address, Response)],
  31. _auth :: Maybe ByteString, _recvDate :: UTCTime, _bodyEnc :: Mime.BodyEncoding,
  32. _smtpUtf8 :: Bool, _action :: BackendAction, _unrecognized :: [ByteString]
  33. } deriving (Show, Ord, Eq)
  34. makeLenses ''ClientIdentity
  35. makeLenses ''MaybeMetadata
  36. makeLenses ''Metadata
  37. instance Default MaybeMetadata where
  38. def = MaybeMetadata Nothing Nothing Nothing [] [] Nothing Nothing Mime.B7BitEncoding False Nothing []
  39. metadataForClient :: IP -> Int -> MaybeMetadata
  40. metadataForClient c p = def & mclientId .~ (Just (ClientIdentity c p))
  41. resetMetadata :: MaybeMetadata -> MaybeMetadata
  42. resetMetadata d = def & mclientId .~ d^.mclientId & mclientName .~ d^.mclientName
  43. strictMetadata :: MaybeMetadata -> Maybe Metadata
  44. strictMetadata m = do
  45. act <- m^.maction
  46. cid <- m^.mclientId
  47. cnm <- m^.mclientName
  48. rfm <- m^.mmailFrom
  49. let rto = m^.mrcptTo
  50. rfail = m^.mrcptFailed
  51. usr = m^.mauth
  52. rcv <- m^.mrecvDate
  53. let enc = m^.mbodyEnc
  54. utf = m^.msmtpUtf8
  55. unrq = m^.munrecognized
  56. return $ Metadata cid cnm rfm rto rfail usr rcv enc utf act unrq
  57. renderMetadata :: Metadata -> ByteString
  58. renderMetadata m = BS.concat serialize
  59. where
  60. serialize :: [ByteString]
  61. serialize = let
  62. act = m^.action
  63. cid = m^.clientId
  64. cnm = m^.clientName
  65. rfm = m^.mailFrom
  66. rto = m^.rcptTo
  67. rfail = m^.rcptFailed
  68. usr = m^.auth
  69. rcv = m^.recvDate
  70. enc = m^.bodyEnc
  71. utf = m^.smtpUtf8
  72. usrStr = case usr of
  73. Nothing -> []
  74. Just u -> ["Auth-User: ", u, "\r\n"]
  75. toStr = List.concat $ map (\x -> ["To: ", renderMetadataAddress $ x, "\r\n"]) rto
  76. failStr = List.concat $ map (\(a, r) -> ["Failed: ", renderMetadataAddress a, "; ", renderResponse r, "\r\n"]) rfail
  77. unrec = m^.unrecognized
  78. h = [
  79. "Action: ", show act, "\r\n",
  80. "Client-Ip: ", show $ cid^.clientIp, "\r\n",
  81. "Client-Port: ", show $ cid^.clientPort, "\r\n",
  82. "Client-Name: ", fromText cnm, "\r\n",
  83. "Return-Path: ", fromText . normalAccountName $ rfm, "\r\n",
  84. "Recv-Date: ", formatISO8601 $ rcv, "\r\n",
  85. "Body-Encoding: ", show $ enc, "\r\n",
  86. "SMTP-UTF8: ", if utf then "Yes" else "No", "\r\n"
  87. ] :: [String]
  88. in map fromText h ++ toStr ++ failStr ++ usrStr ++ unrec
  89. parseMetadata :: A.Parser Metadata
  90. parseMetadata = do
  91. (m', h', p') <- parserFold parseField (def, Nothing, Nothing)
  92. A.endOfLine
  93. let i = do
  94. h <- h'
  95. p <- p'
  96. return $ ClientIdentity h p
  97. m = set mclientId i m'
  98. case strictMetadata m of
  99. Just s -> return s
  100. Nothing -> failParser
  101. where
  102. parseField :: Parser ((MaybeMetadata, Maybe IP, Maybe Int) -> (MaybeMetadata, Maybe IP, Maybe Int))
  103. parseField = do
  104. skipHorizontalSpace
  105. A.choice [
  106. do
  107. act <- hdr "Action" parseEnumCI
  108. return $ \(m, ip, p) -> (set maction (Just act) m, ip, p),
  109. do
  110. ip <- hdr "Client-Ip" parseRead
  111. return $ \(m, _, p) -> (m, ip, p),
  112. do
  113. p <- hdr "Client-Port" parseRead
  114. return $ \(m, ip, _) -> (m, ip, p),
  115. do
  116. nm <- hdr "Client-Name" bsval
  117. return $ \(m, ip, p) -> (set mclientName (Just nm) m, ip, p),
  118. do
  119. frm <- hdr "Return-Path" parseAccountVal
  120. return $ \(m, ip, p) -> (set mmailFrom (Just frm) m, ip, p),
  121. do
  122. rtp <- hdr "To" parseAddressingVal
  123. return $ \(m, ip, p) -> let
  124. crtp = m^.mrcptTo
  125. in (set mrcptTo (rtp:crtp) m, ip, p),
  126. do
  127. rfl <- hdr "Failed" (parseAddressingReason)
  128. return $ \(m, ip, p) -> let
  129. fld = m^.mrcptFailed
  130. in (set mrcptFailed (rfl:fld) m, ip, p),
  131. do
  132. recv <- hdr "Recv-Date" parseISO8601Val
  133. return $ \(m, ip, p) -> (set mrecvDate (Just recv) m, ip, p),
  134. do
  135. enc <- hdr "Body-Encoding" parseEncodingVal
  136. return $ \(m, ip, p) -> (set mbodyEnc enc m, ip, p),
  137. do
  138. utf <- hdr "SMTP-UTF8" parseBoolVal
  139. return $ \(m, ip, p) -> (set msmtpUtf8 utf m, ip, p),
  140. do
  141. usr <- hdr "Auth-User" bsval
  142. return $ \(m, ip, p) -> (set mauth (Just usr) m, ip, p),
  143. do
  144. u <- entireHdr
  145. return $ \(m, ip, p) -> let
  146. uu = m^.munrecognized
  147. in (set munrecognized (u:uu) m, ip, p)
  148. ]
  149. entireHdr :: Parser ByteString
  150. entireHdr = do
  151. A.satisfy (not . A.isEndOfLine . asW8)
  152. t <- A.takeTill (A.isEndOfLine . asW8)
  153. A.endOfLine
  154. l <- takeLines
  155. return $ BS.concat [t, "\r\n", l]
  156. takeLines :: Parser ByteString
  157. takeLines = do
  158. c' <- A.peekChar
  159. case c' of
  160. Nothing -> return ""
  161. Just c -> if isCHorizontalSpace c
  162. then do
  163. l <- A.takeTill (A.isEndOfLine . asW8)
  164. A.endOfLine
  165. ll <- takeLines
  166. return $ BS.concat [l, "\r\n", ll]
  167. else return ""
  168. hdr :: ByteString -> Parser a -> Parser a
  169. hdr pt f = do
  170. skipHorizontalSpace
  171. A.stringCI pt
  172. skipHorizontalSpace
  173. A.char ':'
  174. skipHorizontalSpace
  175. r <- f
  176. skipHorizontalSpace
  177. A.endOfLine
  178. return r
  179. bsval :: Parser ByteString
  180. bsval = do
  181. v <- A.takeTill (A.isEndOfLine . asW8)
  182. A.endOfLine
  183. c' <- A.peekChar
  184. case c' of
  185. Nothing -> return v
  186. Just c -> if isCHorizontalSpace c
  187. then do
  188. v' <- bsval
  189. return $ BS.concat [v, " ", v']
  190. else return v
  191. parseRead :: Read a => Parser a
  192. parseRead = do
  193. v <- bsval
  194. case readMaybe . fromText $ v of
  195. Nothing -> failParser
  196. Just i -> return i
  197. parseVal :: Parser a -> Parser a
  198. parseVal p = do
  199. v <- bsval
  200. case A.parseOnly p v of
  201. Left _ -> failParser
  202. Right v' -> return v'
  203. parseAccountVal = parseVal parseAccount
  204. parseAddressingVal = parseVal parseMetadataAddress
  205. parseEncodingVal = parseVal Mime.parseBodyEncoding
  206. parseBoolVal = parseVal parseMetadataBool
  207. parseISO8601Val = do
  208. v <- bsval
  209. case parseISO8601 . fromText $ v of
  210. Nothing -> failParser
  211. Just t -> return t
  212. parseMetadataBool :: Parser Bool
  213. parseMetadataBool = do
  214. A.choice [
  215. A.stringCI "YES" *> return True,
  216. A.stringCI "NO" *> return False
  217. ]
  218. parseAddressingReason :: Parser (Address, Response)
  219. parseAddressingReason = do
  220. a <- parseMetadataAddress
  221. skipHorizontalSpace
  222. A.char ';'
  223. skipHorizontalSpace
  224. r <- parseResponse
  225. return (a, r)