解析MIME邮件,提取二进制附件和文本转换

时间:2015-01-08 21:16:41

标签: haskell

我开始使用mime来解析电子邮件并提取附件。 我做的任何事情,当我把它写入磁盘时,二进制附件总是被破坏了。 然后我意识到,由于一些奇怪的原因,当消息被解析为数据类型时,所有base64附件都已被解码。那是我的问题开始的时候。

如果是图像,则不起作用。 我做的第一件事是将提取的Text附件转换为带有TE.encodeUtf8的ByteString。 没运气。我尝试了所有Text.Encoding函数将Text转换为ByteString - 没有任何效果。 然后由于一些愚蠢的原因我将提取的文本转换/编码回base64,然后我再次从base64解码它,这次它工作。为什么呢?

因此,如果我将提取的附件编码为base64并对其进行解码,则可以正常工作。 B.writeFile "tmp/test.jpg" $ B.pack $ decode $ encodeRawString True $ T.unpack attachment 为什么?为什么简单的Text to ByteString编码不起作用,但上面的愚蠢呢?

最终,我更多地玩了它,并且与Data.ByteString.Char8这样的B.writeFile "tmp/test.jpg" $ BC.pack $ T.unpack attachment一起使用时达到了这一点import Codec.MIME.Parse import Codec.MIME.Type import Data.Maybe import Data.Text (Text, unpack, strip) import qualified Data.Text as T (null) import Data.Text.Encoding (encodeUtf8) import Data.ByteString (ByteString) data Attachment = Attachment { attName :: Text , attSize :: Int , attBody :: Text } deriving (Show) genAttach :: Text -> [Attachment] genAttach m = let prs v = if isAttach v then [Just (mkAttach v)] else case mime_val_content v of Single c -> if T.null c then [Nothing] else prs $ parseMIMEMessage c Multi vs -> concatMap prs vs in let atts = filter isJust $ prs $ parseMIMEMessage m in if null atts then [] else map fromJust atts isAttach :: MIMEValue -> Bool isAttach mv = maybe False check $ mime_val_disp mv where check d = if (dispType d) == DispAttachment then True else False mkAttach :: MIMEValue -> Attachment mkAttach v = let prms = dispParams $ fromJust $ mime_val_disp v Single cont = mime_val_content v name = check . filter isFn where isFn (Filename _) = True isFn _ = False check = maybe "" (\(Filename n) -> n) . listToMaybe size = check . filter isSz where isSz (Size _) = True isSz _ = False check = maybe "" (\(Size n) -> n) . listToMaybe in Attachment { attName = name prms , attSize = let s = size prms in if T.null s then 0 else read $ unpack s , attBody = cont } 所以我仍然需要将Text转换为String,然后将String转换为ByteString.Char8,然后它才能正常工作,并且我会得到未损坏的图像。

可以请有人解释这一切。为什么这种痛苦与二进制图像附件? 为什么我不能将base64解码后的Text转换为ByteString?我错过了什么?

谢谢。

更新

这是根据请求提取附件的代码。 我认为它与文本编码/解码无关。

{{1}}

2 个答案:

答案 0 :(得分:3)

请注意,mime包选择使用Text值表示二进制内容。派生相应ByteString的方法是对文本进行latin1编码。在这种情况下,保证文本字符串中的所有代码点都在0 - 255范围内。

使用以下内容创建文件:

Content-Type: image/gif
Content-Transfer-Encoding: base64

R0lGODlhAQABAIABAP8AAP///yH5BAEAAAEALAAAAAABAAEAAAICRAEAOw==

这是http://commons.wikimedia.org/wiki/File:1x1.GIF

的1x1红色GIF图像的base64编码

以下是一些使用parseMIMEMessage重新创建此文件的代码。

import Codec.MIME.Parse
import Codec.MIME.Type

import qualified Data.Text as T
import qualified Data.Text.IO as TIO
import qualified Data.ByteString.Char8 as BS
import System.IO

test1 path = do
  msg <- TIO.readFile path
  let mval = parseMIMEMessage msg
      Single img = mime_val_content mval
  withBinaryFile "out-io" WriteMode $ \h -> do
    hSetEncoding h latin1
    TIO.hPutStr h img

test2 path = do
  msg <- TIO.readFile path
  let mval = parseMIMEMessage msg
      Single img = mime_val_content mval
      bytes =   BS.pack $ T.unpack img
  BS.writeFile "out-bs" bytes

test2中,latin1编码由BS.pack . T.unpack完成。

答案 1 :(得分:0)

mime包始终使用Text,但根据其base64 MIME撤消邮件部分正文的任何​​quoted-printable(或Content-Encoding)编码标题字段。

这意味着生成的消息部分的主体是Text类型但是(除非MIME类型是text/*)这不是一个合适的类型,因为正文应该是一个序列 bytes ,而不是字符。它使用的字符是Unicode代码点00FF,它们具有明显的字节映射,但它们不是同一个东西。 (此外,如果MIME类型 text/*charset不是us-asciiiso8859-1,那么我认为mime将会出现问题内容。)

我怀疑发生的事情是您正在将Text写入您使用Data.Text.IO.writeFile或类似的磁盘,该磁盘使用您环境中指定的字符编码将字符转换为字节。许多常见字符编码将字符00映射到7F到字节007F,但几乎没有将剩余字符80映射到FF到它们的Data.Bytestring.Char8相应的字节。在如今的许多系统中,环境的编码是UTF8,它甚至不会将这些字符映射到单个字节。 (文件大小是否与您的预期不同?)

为了正确写出来,您需要先将这些字符转换为正确的字节。最简单的方法是使用String模块中的函数,这些函数旨在以这种方式混淆字节和字符。但它们适用于Text s而不是Text s,因此您必须解压缩并重新包装所有内容。

我不确定你是如何设法对你的80值进行base-64编码的,因为base-64编码也对字节而不是字符进行操作。无论你做了什么,你必须设法将字符FF直接映射到{ "plugins": [ "transform-class-properties", "babel-root-slash-import" ] } 到相应的字节上,而不是以任何方式对它们进行编码。

如果您想了解更多信息,请参阅此处有一篇好文章:https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/