Metadata.hs 8.5 KB

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