Metadata.hs 8.7 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 Text.StringConvert
  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.concatMap (\x -> ["To: ", renderMetadataAddress x, "\r\n"]) rto
  92. failStr = List.concatMap (\(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: ", s cnm, "\r\n",
  99. "Return-Path: ", s . 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 s 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 sm -> return sm
  118. Nothing -> fail "missing required fields"
  119. where
  120. parseField :: Parser ((MaybeMetadata, Maybe IP, Maybe Int) -> (MaybeMetadata, Maybe IP, Maybe Int))
  121. parseField = A.choice [
  122. do
  123. act <- hdr "Action" parseEnumCI
  124. return $ \(m, ip, p) -> (set maction (Just act) m, ip, p),
  125. do
  126. ip <- hdr "Client-Ip" parseRead
  127. return $ \(m, _, p) -> (m, Just ip, p),
  128. do
  129. p <- hdr "Client-Port" parseRead
  130. return $ \(m, ip, _) -> (m, ip, Just p),
  131. do
  132. nm <- hdr "Client-Name" (A.takeTill A.isSpace)
  133. return $ \(m, ip, p) -> (set mclientName (Just nm) m, ip, p),
  134. do
  135. frm <- hdr "Return-Path" parseAccount
  136. return $ \(m, ip, p) -> (set mmailFrom (Just frm) m, ip, p),
  137. do
  138. rtp <- hdr "To" parseAddress
  139. return $ \(m, ip, p) -> let
  140. crtp = m^.mrcptTo
  141. in (set mrcptTo (rtp:crtp) m, ip, p),
  142. do
  143. rfl <- hdr "Failed" parseAddressingReason
  144. return $ \(m, ip, p) -> let
  145. fld = m^.mrcptFailed
  146. in (set mrcptFailed (rfl:fld) m, ip, p),
  147. do
  148. recv <- hdr "Recv-Date" parseISO8601Val
  149. return $ \(m, ip, p) -> (set mrecvDate (Just recv) m, ip, p),
  150. do
  151. enc <- hdr "Body-Encoding" Mime.parseBodyEncoding
  152. return $ \(m, ip, p) -> (set mbodyEnc enc m, ip, p),
  153. do
  154. utf <- hdr "SMTP-UTF8" parseMetadataBool
  155. return $ \(m, ip, p) -> (set msmtpUtf8 utf m, ip, p),
  156. do
  157. usr <- hdr "Auth-User" A.takeByteString
  158. return $ \(m, ip, p) -> (set mauth (Just usr) m, ip, p),
  159. do
  160. sz <- hdr "Data-Size" A.decimal
  161. return $ \(m, ip, p) -> (set mdataSize (Just sz) m, ip, p),
  162. do
  163. u <- entireHdr
  164. return $ \(m, ip, p) -> let
  165. uu = m^.munrecognized
  166. in (set munrecognized (u:uu) m, ip, p)
  167. ]
  168. entireHdr :: Parser ByteString
  169. entireHdr = do
  170. a <- A.satisfy (not . A.isEndOfLine . asW8)
  171. t <- A.takeTill (A.isEndOfLine . asW8)
  172. A.endOfLine
  173. l <- takeLines
  174. return $ BS.concat [C8.cons a t, "\r\n", l]
  175. takeLines :: Parser ByteString
  176. takeLines = do
  177. c' <- A.peekChar
  178. case c' of
  179. Nothing -> return ""
  180. Just c -> if isCHorizontalSpace c
  181. then do
  182. l <- A.takeTill (A.isEndOfLine . asW8)
  183. A.endOfLine
  184. ll <- takeLines
  185. return $ BS.concat [l, "\r\n", ll]
  186. else return ""
  187. hdr :: ByteString -> Parser a -> Parser a
  188. hdr pt f = do
  189. skipHorizontalSpace
  190. A.stringCI pt
  191. skipHorizontalSpace
  192. A.char ':'
  193. skipHorizontalSpace
  194. t <- bsval
  195. r <- case A.parseOnly f t of
  196. Left _ -> fail $ "failed parsing value of " ++ s pt
  197. Right v -> return v
  198. skipHorizontalSpace
  199. return r
  200. bsval :: Parser ByteString
  201. bsval = do
  202. ll <- entireHdr
  203. let (b, _) = BS.spanEnd (\x -> x == asW8 '\r' || x == asW8 '\n') ll
  204. return b
  205. parseRead :: Read a => Parser a
  206. parseRead = do
  207. v <- A.takeTill A.isSpace
  208. case readMaybe . s $ v of
  209. Nothing -> fail "failed parsing value"
  210. Just i -> return i
  211. parseISO8601Val = do
  212. v <- A.takeTill A.isSpace
  213. case parseISO8601 . s $ v of
  214. Nothing -> fail "failed parsing ISO8601 date"
  215. Just t -> return t
  216. parseMetadataBool :: Parser Bool
  217. parseMetadataBool = 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)