【问题标题】:How to create a image from MIME content?如何从 MIME 内容创建图像?
【发布时间】:2019-06-21 16:57:27
【问题描述】:

我正在尝试在 .NET Core 2.2 框架的顶部使用 C# 编写一个小型控制台应用程序。

控制台应用程序将向外部 API 发出 HTTP 请求以获取多个图像。我能够向服务器发出请求并获得响应。但是,服务器使用 MIMI 消息以多部分响应进行响应。

我能够解析请求并获取每条消息的 MIME 正文。但是,我无法弄清楚如何从正文内容中创建文件。

这里是原始 MIMI 消息如何开头的示例

我尝试将正文作为字符串写入文件,但没有成功

string body = GetMimeBody(message);
File.WriteAllText("image_from_string" + MimeTypeMap.GetExtension(contentType), bytes);

我也尝试像这样将字符串转换为byte[],但还是不行

byte[] bytes = Encoding.ASCII.GetBytes(body);
File.WriteAllBytes("image_from_ascii_bytes" + MimeTypeMap.GetExtension(contentType), bytes);

byte[] bytes = Encoding.Default.GetBytes(body);
File.WriteAllBytes("image_from_default_bytes" + MimeTypeMap.GetExtension(contentType), bytes);


byte[] bytes = Encoding.UTF8.GetBytes(body);
File.WriteAllBytes("image_from_utf8_bytes" + MimeTypeMap.GetExtension(contentType), bytes);

“不工作”是指图像无法正确打开。照片查看器说“图像似乎已损坏或损坏。”

我怎样才能正确地从消息中制作出好的图像?

更新

这里是解析部分的代码

var responseContentType = response.Content.Headers.GetValues("Content-Type").FirstOrDefault();
string splitter = string.Format("--{0}", GetBoundary(responseContentType));
string content = await response.Content.ReadAsStringAsync();
var messages = content.Split(splitter, StringSplitOptions.RemoveEmptyEntries);

foreach (var message in messages)
{
    var mimiParts = message.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);
    if (mimiParts.Length == 0)
    {
        continue;
    }

    string contentId = Str.GetValue("Content-ID", mimiParts, ':');
    string objectId = Str.GetValue("Object-ID", mimiParts, ':');
    string contentType = Str.GetValue("Content-Type", mimiParts, ':');

    if (string.IsNullOrWhiteSpace(contentId) || string.IsNullOrWhiteSpace(objectId) || string.IsNullOrWhiteSpace(contentType))
    {
        continue;
    }

    string body = mimiParts[mimiParts.Length - 1];

    var filename = string.Format("{0}_{1}{2}", contentId, objectId, MimeTypeMap.GetExtension(contentType));

    var decoded = System.Net.WebUtility.HtmlDecode(data);
    File.WriteAllText("image_from_html_decoded_bytes" + filename, decoded);
}

这里是解析消息的方法

public class Str
{
    public static string GetValue(string startWith, string[] lines, char splitter = '=')
    {
        foreach (var line in lines)
        {
            var value = line.Trim();

            if (!value.StartsWith(startWith, StringComparison.CurrentCultureIgnoreCase) || !line.Contains(splitter))
            {
                continue;
            }

            return value.Split(splitter)[1].Trim();
        }

        return string.Empty;
    }
}

这是显示mimiParts 变量内容的屏幕截图

更新 2

根据下面的反馈,我尝试使用MimeKit 包,而不是尝试自己解析响应。以下是我尝试使用响应的方式。但是,我仍然遇到与上述相同的错误。写入图像文件时,出现图像损坏错误。

var responseContentType = response.Content.Headers.GetValues("Content-Type").FirstOrDefault();

if (!ContentType.TryParse(responseContentType, out ContentType documentContentType))
{
    return;
}

var stream = await response.Content.ReadAsStreamAsync();

MimeEntity entity = MimeEntity.Load(documentContentType, stream);
Multipart messages = entity as Multipart;

if (messages == null)
{
    throw new Exception("Unable to cast entity to Multipart");
}

foreach (MimeEntity message in messages)
{
    string contentId = message.Headers["Content-ID"];
    string objectId = message.Headers["Object-ID"];
    string contentType = message.Headers["Content-Type"];

    if (string.IsNullOrWhiteSpace(contentId) || string.IsNullOrWhiteSpace(objectId) || string.IsNullOrWhiteSpace(contentType))
    {
        continue;
    }

    var filename = string.Format("{0}_{1}{2}", contentId, objectId, MimeTypeMap.GetExtension(contentType));

    message.WriteTo(filename);
}

【问题讨论】:

  • 试试:Convert.FromBase64String(string)
  • @jdweng 的结果也一样。
  • @MikeA 那是不可能的。传递实际上不是 Base64 的字符串将引发异常。所以要么它已经是二进制的,那么结果就是一个异常,或者它是 Base64 编码的,而不是将它传递给 Convert.FromBase64String 会给你一个新的,不同的二进制字符串。
  • 请提供GetMimeBody()的实现
  • 我用关于如何提取正文的代码更新问题

标签: c# mime mime-message


【解决方案1】:

MimeEntity.WriteTo (file) 将不幸包含导致损坏错误的 MIME 标头。

您需要做的是将 MimeEntity 转换为 MimePart,然后使用 MimePart.Content.DecodeTo (stream) 保存 解码的 内容:

var responseContentType = response.Content.Headers.GetValues("Content-Type").FirstOrDefault();

if (!ContentType.TryParse(responseContentType, out ContentType documentContentType))
{
    return;
}

var stream = await response.Content.ReadAsStreamAsync();

MimeEntity entity = MimeEntity.Load(documentContentType, stream);
Multipart multipart = entity as Multipart;

if (multipart == null)
{
    throw new Exception("Unable to cast entity to Multipart");
}

foreach (MimePart part in multipart.OfType<MimePart> ())
{
    string contentType = part.ContentType.MimeType;
    string contentId = part.ContentId;
    string objectId = part.Headers["Object-ID"];

    if (string.IsNullOrWhiteSpace(contentId) || string.IsNullOrWhiteSpace(objectId) || string.IsNullOrWhiteSpace(contentType))
    {
        continue;
    }

    var filename = string.Format("{0}_{1}{2}", contentId, objectId, MimeTypeMap.GetExtension(contentType));

    using (var output = File.Create (filename))
        part.Content.DecodeTo (output);
}

【讨论】:

  • 好吧,你知道什么。 MimeKit 的制造商就其功能提供建议。如果这不是最好的支持,我不知道是什么。
  • 我尽量关注 StackOverflow 并在看到 MimeKit/MailKit 问题时回答它们 :-)
  • @jstedfast 我想知道是否有办法将解码后的内容写入不同的流(即 MemoyStream),以便我可以在以后委派写入文件的过程。我创建了一个 FileObject 模型类 (gist.github.com/CrestApps/ee07bc1a76766bbbbded5c1fe304ba5d),然后我将您的代码更改为 var file = new FileObject(); file.Content = new MemoryStream(); message.Content.DecodeTo(file.Content);,但是当我尝试将 file.Content 写入文件时,图像会损坏。如何从 MimePart 生成内存流,以便委派写入过程?
  • 记得在将文件写入文件之前回退文件。内容流,否则文件将为空(也称为“损坏”)。
  • 太棒了!就是这样。
【解决方案2】:

MIME 编码很难,将服务器发送的字节视为字符串已经是一个错误。在换行符处拆分它会产生更多问题。二进制表示 0x00 和 0xff 之间的每个值都是有效的。但是 Unicode 和 ASCII 有不同的有效字节范围,尤其是转换它们是有问题的。 .NET 内部字符串类将每个字符解释为两个字节。 HttpContent.ReadAsStringAsync 运行的那一刻,它会尝试将从服务器接收到的每个单个字节解释为两个字节的 Unicode 字符。我很确定您将无法从数据丢失中恢复。

  • 使用像 HxD 这样的十六进制编辑器将图像的良好副本与您从应用程序中写出的图像进行比较,并寻找差异。至少如果您想坚持使用自己的代码。但我确信您仍然需要从字符串操作切换到流操作。
  • 使用已创建的 MIME 解析库。一个例子是MimeKit。这将大大缩短您的开发时间。

作为参考,以下是 JPG 的前 10 个字节的样子:

FF D8 FF E0 00 10 4A 46 49 46      ÿØÿà..JFIF

【讨论】:

  • 知道如何遍历 MimeEntity 孩子吗?我使用ContentType.Parse() 来获取内容类型。现在,我创建了这样的MimeEntity var stream = await response.Content.ReadAsStreamAsync(); MimeEntity messages = MimeEntity.Load(documentContentType, stream);,但是我如何循环遍历子项又名消息?
  • @MikeA MimeMessage 是来自服务器的整个响应。它具有 Attachments 和 BodyParts 之类的属性,这就是您的内容应该存在的地方。每个条目都应该是抽象 MimeEntity 的特化。虽然我无权访问您要解析的数据,所以我只能推测。只需在调试器中检查 MimeKit 返回的对象。
  • 这个例子就是我正在做的。我已经查看了调试器。我看到一个名为“children”的私有属性,其中包含我要查找的所有对象,但不确定如何公开访问。
  • @MikeA 具有私有属性“children”的对象的类型(类)是什么?
猜你喜欢
  • 2017-04-29
  • 1970-01-01
  • 2011-09-07
  • 2021-05-12
  • 2012-09-27
  • 1970-01-01
  • 2019-05-05
  • 2016-08-09
  • 2010-12-11
相关资源
最近更新 更多