Metadata.hs 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  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 Text.StringConvert
  12. import Data.Time.ISO8601
  13. import Data.IP
  14. import Data.Default.Class
  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.concatMap (\x -> ["To: ", renderMetadataAddress x, "\r\n"]) rto
  93. failStr = List.concatMap (\(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 sm -> return sm
  119. Nothing -> fail "missing required fields"
  120. where
  121. parseField :: Parser ((MaybeMetadata, Maybe IP, Maybe Int) -> (MaybeMetadata, Maybe IP, Maybe Int))
  122. parseField = 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 _ -> fail $ "failed parsing value of " ++ s pt
  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 -> fail "failed parsing value"
  211. Just i -> return i
  212. parseISO8601Val = do
  213. v <- A.takeTill A.isSpace
  214. case parseISO8601 . fromTextual $ v of
  215. Nothing -> fail "failed parsing ISO8601 date"
  216. Just t -> return t
  217. parseMetadataBool :: Parser Bool
  218. parseMetadataBool = A.choice [
  219. A.stringCI "YES" *> return True,
  220. A.stringCI "NO" *> return False
  221. ]
  222. parseAddressingReason :: Parser (Address, Response)
  223. parseAddressingReason = do
  224. a <- parseMetadataAddress
  225. skipHorizontalSpace
  226. A.char ';'
  227. skipHorizontalSpace
  228. r <- parseLineResponse
  229. return (a, r)