Metadata.hs 8.9 KB

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