Metadata.hs 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  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 Encoding
  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.Maybe (isJust)
  18. import Data.Attoparsec.ByteString.Char8 (Parser)
  19. import qualified Data.Attoparsec.ByteString.Char8 as A
  20. import SMTP.Parser.ParserTools
  21. import qualified Data.ByteString as BS
  22. import qualified Data.List as List
  23. data ClientIdentity = ClientIdentity {_clientIp :: IP, _clientPort :: Int} deriving (Show, Read, Ord, Eq)
  24. data Metadata = Metadata {_clientId :: Maybe ClientIdentity, _clientName :: Maybe ByteString,
  25. _mailFrom :: Maybe Account, _rcptTo :: [Address], _rcptFailed :: [(Address, Response)],
  26. _auth :: Maybe ByteString, _recvDate :: Maybe UTCTime, _bodyEnc :: Mime.BodyEncoding,
  27. _smtpUtf8 :: Bool, _unrecognized :: [ByteString]
  28. } deriving (Read, Show, Ord, Eq)
  29. makeLenses ''ClientIdentity
  30. makeLenses ''Metadata
  31. instance Default Metadata where
  32. def = Metadata Nothing Nothing Nothing [] [] Nothing Nothing Mime.B7BitEncoding False []
  33. metadataForClient :: IP -> Int -> Metadata
  34. metadataForClient c p = def & clientId .~ (Just (ClientIdentity c p))
  35. resetMetadata :: Metadata -> Metadata
  36. resetMetadata d = def & clientId .~ d^.clientId & clientName .~ d^.clientName
  37. renderMetadata :: Metadata -> Maybe ByteString
  38. renderMetadata m = BS.concat <$> serialize
  39. where
  40. serialize :: Maybe [ByteString]
  41. serialize = do
  42. cid <- m^.clientId
  43. cnm <- m^.clientName
  44. rfm <- m^.mailFrom
  45. let rto = m^.rcptTo
  46. rfail = m^.rcptFailed
  47. usr = m^.auth
  48. rcv <- m^.recvDate
  49. let enc = m^.bodyEnc
  50. utf = m^.smtpUtf8
  51. usrStr = case usr of
  52. Nothing -> []
  53. Just u -> ["Auth-User: ", u, "\r\n"]
  54. let toStr = List.concat $ map (\x -> ["To: ", renderMetadataAddress x, "\r\n"]) rto
  55. failStr = List.concat $ map (\(a, r) -> ["Failed: ", renderMetadataAddress a, "; ", renderResponse r, "\r\n"]) rfail
  56. let unrec = m^.unrecognized
  57. return $ [
  58. "Client-Ip: ", utf8bs . show $ cid^.clientIp, "\r\n",
  59. "Client-Port: ", utf8bs . show $ cid^.clientPort, "\r\n",
  60. "Client-Name: ", cnm, "\r\n",
  61. "Return-Path: ", normalAccountName $ rfm, "\r\n",
  62. "Recv-Date: ", utf8bs . formatISO8601 $ rcv, "\r\n",
  63. "Body-Encoding: ", utf8bs . show $ enc, "\r\n",
  64. "SMTP-UTF8: ", if utf then "Yes" else "No", "\r\n"
  65. ] ++ toStr ++ failStr ++ usrStr ++ unrec
  66. isMetadataComplete :: Metadata -> Bool
  67. isMetadataComplete = isJust . renderMetadata
  68. parseMetadata :: A.Parser Metadata
  69. parseMetadata = do
  70. (m', h', p') <- parseFold parseField (def, Nothing, Nothing)
  71. let i = do
  72. h <- h'
  73. p <- p'
  74. return $ ClientIdentity h p
  75. m = set clientId i m'
  76. if isMetadataComplete m
  77. then return m
  78. else failParser
  79. where
  80. parseField :: Parser ((Metadata, Maybe IP, Maybe Int) -> (Metadata, Maybe IP, Maybe Int))
  81. parseField = do
  82. skipHorizontalSpace
  83. A.choice [
  84. do
  85. ip <- hdr "Client-Ip" parseRead
  86. return $ \(m, _, p) -> (m, ip, p),
  87. do
  88. p <- hdr "Client-Port" parseRead
  89. return $ \(m, ip, _) -> (m, ip, p),
  90. do
  91. nm <- hdr "Client-Name" bsval
  92. return $ \(m, ip, p) -> (set clientName (Just nm) m, ip, p),
  93. do
  94. frm <- hdr "Return-Path" parseAccountVal
  95. return $ \(m, ip, p) -> (set mailFrom (Just frm) m, ip, p),
  96. do
  97. rtp <- hdr "To" parseAddressingVal
  98. return $ \(m, ip, p) -> let
  99. crtp = m^.rcptTo
  100. in (set rcptTo (rtp:crtp) m, ip, p),
  101. do
  102. rfl <- hdr "Failed" (parseAddressingReason)
  103. return $ \(m, ip, p) -> let
  104. fld = m^.rcptFailed
  105. in (set rcptFailed (rfl:fld) m, ip, p),
  106. do
  107. recv <- hdr "Recv-Date" parseISO8601Val
  108. return $ \(m, ip, p) -> (set recvDate (Just recv) m, ip, p),
  109. do
  110. enc <- hdr "Body-Encoding" parseEncodingVal
  111. return $ \(m, ip, p) -> (set bodyEnc enc m, ip, p),
  112. do
  113. utf <- hdr "SMTP-UTF8" parseBoolVal
  114. return $ \(m, ip, p) -> (set smtpUtf8 utf m, ip, p),
  115. do
  116. usr <- hdr "Auth-User" bsval
  117. return $ \(m, ip, p) -> (set auth (Just usr) m, ip, p),
  118. do
  119. u <- entireHdr
  120. return $ \(m, ip, p) -> let
  121. uu = m^.unrecognized
  122. in (set unrecognized (u:uu) m, ip, p)
  123. ]
  124. entireHdr :: Parser ByteString
  125. entireHdr = do
  126. t <- A.takeTill (A.isEndOfLine . asW8)
  127. A.endOfLine
  128. l <- takeLines
  129. return $ BS.concat [t, "\r\n", l]
  130. takeLines :: Parser ByteString
  131. takeLines = do
  132. c' <- A.peekChar
  133. case c' of
  134. Nothing -> return ""
  135. Just c -> if isCHorizontalSpace c
  136. then do
  137. l <- A.takeTill (A.isEndOfLine . asW8)
  138. A.endOfLine
  139. ll <- takeLines
  140. return $ BS.concat [l, "\r\n", ll]
  141. else return ""
  142. hdr :: ByteString -> Parser a -> Parser a
  143. hdr pt f = do
  144. skipHorizontalSpace
  145. A.stringCI pt
  146. skipHorizontalSpace
  147. A.char ':'
  148. skipHorizontalSpace
  149. r <- f
  150. skipHorizontalSpace
  151. A.endOfLine
  152. return r
  153. bsval :: Parser ByteString
  154. bsval = do
  155. v <- A.takeTill (A.isEndOfLine . asW8)
  156. A.endOfLine
  157. c' <- A.peekChar
  158. case c' of
  159. Nothing -> return v
  160. Just c -> if isCHorizontalSpace c
  161. then do
  162. v' <- bsval
  163. return $ BS.concat [v, " ", v']
  164. else return v
  165. parseRead :: Read a => Parser a
  166. parseRead = do
  167. v <- bsval
  168. case readMaybe . bsutf8 $ v of
  169. Nothing -> failParser
  170. Just i -> return i
  171. parseVal :: Parser a -> Parser a
  172. parseVal p = do
  173. v <- bsval
  174. case A.parseOnly p v of
  175. Left _ -> failParser
  176. Right v' -> return v'
  177. parseAccountVal = parseVal parseAccount
  178. parseAddressingVal = parseVal parseMetadataAddress
  179. parseEncodingVal = parseVal Mime.parseBodyEncoding
  180. parseBoolVal = parseVal parseMetadataBool
  181. parseISO8601Val = do
  182. v <- bsval
  183. case parseISO8601 . bsutf8 $ v of
  184. Nothing -> failParser
  185. Just t -> return t
  186. parseMetadataBool :: Parser Bool
  187. parseMetadataBool = do
  188. A.choice [
  189. A.stringCI "YES" *> return True,
  190. A.stringCI "NO" *> return False
  191. ]
  192. parseAddressingReason :: Parser (Address, Response)
  193. parseAddressingReason = do
  194. a <- parseMetadataAddress
  195. skipHorizontalSpace
  196. A.char ';'
  197. skipHorizontalSpace
  198. r <- parseResponse
  199. return (a, r)