Mime.hs 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  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 Text.StringConvert as SC
  16. import qualified Data.Attoparsec.ByteString.Char8.Extras as P
  17. import qualified Data.Attoparsec.ByteString.Char8 as A
  18. import qualified Data.Char as C
  19. import Data.ByteString (ByteString)
  20. import Data.Word8 (Word8)
  21. --import qualified Data.Word8 as W
  22. import qualified Data.ByteString as BS
  23. import qualified Data.ByteString.Char8 as C8
  24. import qualified Data.ByteString.Lazy as LBS
  25. import qualified Data.ByteString.Base64.Lazy as B64
  26. import qualified Data.ByteString.Lazy.Search as LSearch
  27. --import qualified Data.ByteString.Search as SSearch
  28. --import Data.Default.Class
  29. {- |
  30. Required encoding for the given data.
  31. Will evaluate the entire data, thus using a lot of memory.
  32. 7/8 bit mime is available for data where all lines are shorter then 1000 bytes.
  33. 7 bit mime is available ofr that where no byte is larger than 128 or equal 0.
  34. othersiwe data requires binarymime.
  35. -}
  36. requiredEncoding :: ResourceData -> BodyEncoding
  37. requiredEncoding dt = case requiredFeatures 0 $ LBS.unpack dt of
  38. (False, False) -> B7BitEncoding
  39. (False, True) -> B8BitEncoding
  40. (True, _) -> BBinaryEncoding
  41. where
  42. requiredFeatures :: Int -> [Word8] -> (Bool, Bool)
  43. requiredFeatures _ [] = (False, False)
  44. requiredFeatures len (c:cc) = let
  45. nlen = if c == (fromIntegral . C.ord $ '\n') then 0 else len+1
  46. (ll, ee) = requiredFeatures nlen cc
  47. in (len >= 1000 || ll, c == 0 || c >= 128 || ee)
  48. reencodeMessage :: BodyEncoding -> ResourceData -> ResourceData
  49. reencodeMessage toEncoding dt = LBS.concat [
  50. if newEncoding == fromEncoding then LBS.fromStrict $ originalData hh else replaceEncodingHeader newEncoding hh,
  51. "\r\n",
  52. reencodeBody'
  53. ]
  54. where
  55. (hh, dtb) = takeHeaders dt
  56. (ct, fromEncoding) = getMimeData . public $ hh
  57. fromEncoding' = transferToBody fromEncoding
  58. (message, multiPart, separator) = case ct of
  59. (ContentTypeHeader (MultiPartMime _) (ContentTypeParameters (Just sep) _ _)) -> (False, True, C8.pack sep)
  60. (ContentTypeHeader (MessageMime _) _ ) -> (True, False, "")
  61. _ -> (False, False, "")
  62. newEncoding :: TransferEncoding
  63. newEncoding = case (fromEncoding', toEncoding, multiPart) of
  64. (_, BBinaryEncoding, _) -> fromEncoding
  65. (BBinaryEncoding, B8BitEncoding, True) -> IdentityEncoding B7BitEncoding
  66. (_, B8BitEncoding, _) -> fromEncoding
  67. (_, B7BitEncoding, True) -> IdentityEncoding B7BitEncoding
  68. (BBinaryEncoding, B7BitEncoding, False) -> Base64EncodedBody
  69. (B8BitEncoding, B7BitEncoding, False) -> Base64EncodedBody
  70. _ -> fromEncoding
  71. newEncoding' = transferToBody newEncoding
  72. reencodeBody' :: ResourceData
  73. reencodeBody'
  74. | newEncoding == fromEncoding = dtb
  75. | message = reencodeBody newEncoding dtb
  76. | multiPart = concatParts separator . map (reencodeMessage newEncoding') . splitParts separator $ dtb
  77. | otherwise = reencodeBody newEncoding dtb
  78. reencodeBody :: TransferEncoding -> ResourceData -> ResourceData
  79. reencodeBody (IdentityEncoding _) dt = dt
  80. reencodeBody Base64EncodedBody dt = LBS.intercalate "\r\n" $ splitEach 76 $ B64.encode dt
  81. reencodeBody QPEncodedBody dt = dt
  82. splitEach :: Int -> LBS.ByteString -> [LBS.ByteString]
  83. splitEach n t
  84. | LBS.null t = []
  85. | otherwise = let (p, pp) = LBS.splitAt (fromIntegral n) t
  86. in p : splitEach n pp
  87. splitParts :: ByteString -> LBS.ByteString -> [LBS.ByteString]
  88. splitParts sep dt = map manageChunk chunks
  89. where
  90. sep' = BS.concat ["\r\n--", sep]
  91. chunks = LSearch.split sep' dt
  92. manageChunk = LBS.dropWhile (== P.asW8 '-') . LBS.dropWhile A.isHorizontalSpace . LBS.dropWhile A.isEndOfLine
  93. concatParts :: ByteString -> [LBS.ByteString] -> LBS.ByteString
  94. concatParts sep = joinChunks (LBS.fromStrict sep)
  95. where
  96. joinChunks _ [] = ""
  97. joinChunks _ [c] = c
  98. joinChunks sep' [c1, c2] = LBS.concat [c1, "\r\n--", sep', "--\r\n", c2]
  99. joinChunks sep' (c:cc2) = LBS.concat [c, "\r\n--", sep', "\r\n", joinChunks sep' cc2]
  100. replaceEncodingHeader :: TransferEncoding -> PlainHeaders -> LBS.ByteString
  101. replaceEncodingHeader enc hh = LBS.fromStrict . originalData $ hh{public = map replaceHeader . public $ hh}
  102. where
  103. k = transferEncodingHeaderName
  104. replaceHeader h = if hasKey k h
  105. then Header{key=k, value=show enc,
  106. bare = BS.concat [SC.s k, ": ", SC.s . show $ enc, "\r\n"]}
  107. else h