Metadata.hs 8.8 KB

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