Metadata.hs 8.9 KB

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