Mime.hs 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. {-# LANGUAGE OverloadedStrings #-}
  2. module Data.SMTP.Mime (
  3. module Data.SMTP.Types.Mime,
  4. module Data.SMTP.Parser.Mime,
  5. getMimeData,
  6. reencodeMessage,
  7. requiredEncoding,
  8. splitEach
  9. ) where
  10. import Data.SMTP.Types.Mime
  11. import Data.SMTP.Parser.Mime
  12. import Data.SMTP.Types.Resource
  13. import Data.SMTP.Parser.Resource
  14. import Text.StringConvert
  15. import qualified Data.Attoparsec.ByteString.Char8.Extras as P
  16. import qualified Data.Attoparsec.ByteString.Char8 as A
  17. import qualified Data.Char as C
  18. import Data.ByteString (ByteString)
  19. import Data.Word8 (Word8)
  20. import qualified Data.Word8 as W
  21. import qualified Data.ByteString as BS
  22. import qualified Data.ByteString.Char8 as C8
  23. import qualified Data.ByteString.Lazy as LBS
  24. import qualified Data.ByteString.Base64.Lazy as B64
  25. import qualified Data.ByteString.Lazy.Search as LSearch
  26. --import qualified Data.ByteString.Search as SSearch
  27. import Data.Default.Class
  28. getMimeData :: [Header] -> (ContentTypeHeader, TransferEncoding)
  29. getMimeData hh = (ct, te)
  30. where
  31. ct = case getHeader hh contentTypeHeaderName of
  32. Nothing -> def
  33. Just (Header (_, cth)) -> case A.parseOnly parseContentType (s cth) of
  34. Left _ -> def
  35. Right (ctv, ctp) -> ContentTypeHeader ctv ctp
  36. te = case getHeader hh transferEncodingHeaderName of
  37. Nothing -> def
  38. Just (Header (_, teh)) -> case A.parseOnly parseTransferEncoding (C8.pack teh) of
  39. Left _ -> def
  40. Right t -> t
  41. {- |
  42. Required encoding for the given data.
  43. Will evaluate the entire data, thus using a lot of memory.
  44. 7/8 bit mime is available for data where all lines are shorter then 1000 bytes.
  45. 7 bit mime is available ofr that where no byte is larger than 128 or equal 0.
  46. othersiwe data requires binarymime.
  47. -}
  48. requiredEncoding :: ResourceData -> BodyEncoding
  49. requiredEncoding dt = case requiredFeatures 0 $ LBS.unpack dt of
  50. (False, False) -> B7BitEncoding
  51. (False, True) -> B8BitEncoding
  52. (True, _) -> BBinaryEncoding
  53. where
  54. requiredFeatures :: Int -> [Word8] -> (Bool, Bool)
  55. requiredFeatures _ [] = (False, False)
  56. requiredFeatures len (c:cc) = let
  57. nlen = if c == (fromIntegral . C.ord $ '\n') then 0 else len+1
  58. (ll, ee) = requiredFeatures nlen cc
  59. in (len >= 1000 || ll, c == 0 || c >= 128 || ee)
  60. reencodeMessage :: BodyEncoding -> ResourceData -> ResourceData
  61. reencodeMessage toEncoding dt = LBS.concat [
  62. if newEncoding == fromEncoding then LBS.fromStrict $ BS.concat dthh else replaceEncodingHeader newEncoding dthh,
  63. "\r\n",
  64. reencodeBody'
  65. ]
  66. where
  67. (dthh, hh, dtb) = takeHeaders dt
  68. (ct, fromEncoding) = getMimeData hh
  69. fromEncoding' = transferToBody fromEncoding
  70. (message, multiPart, separator) = case ct of
  71. (ContentTypeHeader (MultiPartMime _) (ContentTypeParameters (Just sep) _ _)) -> (False, True, C8.pack sep)
  72. (ContentTypeHeader (MessageMime _) _ ) -> (True, False, "")
  73. _ -> (False, False, "")
  74. newEncoding :: TransferEncoding
  75. newEncoding = case (fromEncoding', toEncoding, multiPart) of
  76. (_, BBinaryEncoding, _) -> fromEncoding
  77. (BBinaryEncoding, B8BitEncoding, True) -> IdentityEncoding B7BitEncoding
  78. (_, B8BitEncoding, _) -> fromEncoding
  79. (_, B7BitEncoding, True) -> IdentityEncoding B7BitEncoding
  80. (BBinaryEncoding, B7BitEncoding, False) -> Base64EncodedBody
  81. (B8BitEncoding, B7BitEncoding, False) -> Base64EncodedBody
  82. _ -> fromEncoding
  83. newEncoding' = transferToBody newEncoding
  84. reencodeBody' :: ResourceData
  85. reencodeBody'
  86. | newEncoding == fromEncoding = dtb
  87. | message = reencodeBody newEncoding dtb
  88. | multiPart = concatParts separator . map (reencodeMessage newEncoding') . splitParts separator $ dtb
  89. | otherwise = reencodeBody newEncoding dtb
  90. reencodeBody :: TransferEncoding -> ResourceData -> ResourceData
  91. reencodeBody (IdentityEncoding _) dt = dt
  92. reencodeBody Base64EncodedBody dt = LBS.intercalate "\r\n" $ splitEach 76 $ B64.encode dt
  93. reencodeBody QPEncodedBody dt = dt
  94. splitEach :: Int -> LBS.ByteString -> [LBS.ByteString]
  95. splitEach n t
  96. | LBS.null t = []
  97. | otherwise = let (p, pp) = LBS.splitAt (fromIntegral n) t
  98. in p : splitEach n pp
  99. splitParts :: ByteString -> LBS.ByteString -> [LBS.ByteString]
  100. splitParts sep dt = map manageChunk chunks
  101. where
  102. sep' = BS.concat ["\r\n--", sep]
  103. chunks = LSearch.split sep' dt
  104. manageChunk = LBS.dropWhile (== P.asW8 '-') . LBS.dropWhile A.isHorizontalSpace . LBS.dropWhile A.isEndOfLine
  105. concatParts :: ByteString -> [LBS.ByteString] -> LBS.ByteString
  106. concatParts sep = joinChunks (LBS.fromStrict sep)
  107. where
  108. joinChunks _ [] = ""
  109. joinChunks _ [c] = c
  110. joinChunks sep' [c1, c2] = LBS.concat [c1, "\r\n--", sep', "--\r\n", c2]
  111. joinChunks sep' (c:cc2) = LBS.concat [c, "\r\n--", sep', "\r\n", joinChunks sep' cc2]
  112. replaceEncodingHeader :: TransferEncoding -> [ByteString] -> LBS.ByteString
  113. replaceEncodingHeader enc = LBS.concat . map LBS.fromStrict . echoAndReplace
  114. where
  115. h = transferEncodingHeaderName
  116. echoAndReplace [] = []
  117. echoAndReplace (l:ll) = if isPrefixCI h l
  118. then BS.concat [C8.pack h, ": ", C8.pack . show $ enc, "\r\n"] :
  119. dropWhile (\x -> BS.null x || (W.isSpace . BS.head $ x)) ll
  120. else l : echoAndReplace ll
  121. isPrefixCI :: String -> ByteString -> Bool
  122. isPrefixCI [] _ = True
  123. isPrefixCI (c:cc) bs = case BS.uncons bs of
  124. Nothing -> False
  125. Just (h, t) -> (C.toUpper . C.chr . fromIntegral $ h) == C.toUpper c && isPrefixCI cc t