Metadata.hs 7.4 KB

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