Как создать изображение из контента MIME?

Я пытаюсь написать небольшое консольное приложение, используя C# на вершине.NET Core 2.2 Framework.

Консольное приложение сделает HTTP-запрос к внешнему API для получения нескольких изображений. Я могу сделать запрос на сервер и получить ответ. Однако сервер отвечает многокомпонентным ответом, используя сообщения 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);
}

2 ответа

Решение

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);
}

MIME-кодирование сложное, и обработка байтов, отправляемых сервером как строки, уже является ошибкой. Разделение на новые строки создаст еще больше проблем. Двоичный означает, что каждое значение между 0x00 и 0xff является действительным. Но Unicode и ASCII имеют разные диапазоны допустимых байтов, и особенно их преобразование проблематично. Внутренний строковый класс.NET интерпретирует каждый символ как два байта. В момент запуска HttpContent.ReadAsStringAsync он пытается интерпретировать каждый отдельный байт, полученный от сервера, как двухбайтовый символ Unicode. Я уверен, что вы не сможете оправиться от этой потери данных.

  • Используйте hex-редактор, такой как HxD, чтобы сравнить хорошую копию изображения с той, которую вы пишете из своего приложения, и найдите различия. По крайней мере, если вы хотите придерживаться своего собственного кода. Но я уверен, что вам все равно нужно будет перейти от манипуляции со строками к операциям Stream.
  • Используйте уже созданную библиотеку MIME для разбора. Одним из примеров является MimeKit. Это значительно сократит ваше время разработки.

В качестве ссылки вот как должны выглядеть первые 10 байтов JPG:

FF D8 FF E0 00 10 4A 46 49 46      ÿØÿà..JFIF
Другие вопросы по тегам