Правильно отформатируйте multipart / form-data body

ВступлениеBackgroundI»

я пишу сценарий для загрузки материалов, включая файлы, используяmultipart/form-data тип контента, определенный вRFC 2388, В конце концов яя пытаюсь предоставить простой скрипт на Python, чтобы сделатьзагрузка бинарных пакетов для github, который включает в себя отправку подобных данных в Amazon S3.

связанные с

Этот вопрос уже спрашивал о том, как это сделать, но пока без ответа, итем полезнее из двух ответов он в настоящее время имеетэти рецепты которые в свою очередь строят все сообщение вручную. Я несколько обеспокоен этим подходом, особенно в отношении кодировок и двоичного содержимого.

Существует такжеэтот вопрос, с этимина данный момент самый результативный ответ предлагаяMultipartPostHandler модуль. Но это не сильно отличается от рецептов, которые я упомянул, и поэтому мои опасения применимы и к этому.

ОбеспокоенностьДвоичный контент

RFC 2388, раздел 4.3 прямо указывает, что контент должен быть 7-битным, если не указано иное, и, следовательно,Content-Transfer-Encoding заголовок может потребоваться Значит ли это, что яДолжен ли Base64-кодировать содержимое двоичного файла? Или будетContent-Transfer-Encoding: 8bit быть достаточным для произвольных файлов? Или это следует прочитать?Content-Transfer-Encoding: binary

Charset для полей заголовка

Поля заголовка в целом иfilename Поле заголовка, в частности, ASCII только по умолчанию. Я'Мне бы хотелось, чтобы мой метод также мог передавать имена файлов не-ASCII. Я знаю, что для моего текущего приложения загрузки материала для github, я, вероятно, выигралЭто необходимо, поскольку имя файла указывается в отдельном поле. Но я'Мне бы хотелось, чтобы мой код можно было использовать повторно, поэтому яЯ лучше закодирую параметр имени файла соответствующим образом.RFC 2388, раздел 4.4 советует формат введен вRFC 2231например, ,filename*=utf-8''t%C3%A4st.txt

Мой подходИспользование библиотек Python

Какmultipart/form-data по сути MIME-тип, я подумал, что должно быть возможно использоватьemail пакет из стандартных библиотек python для написания моего поста. В частности, довольно сложная обработка полей заголовка не ASCIIЯ хотел бы делегировать.

Работай пока

Поэтому я написал следующий код:

#!/usr/bin/python3.2

import email.charset
import email.generator
import email.header
import email.mime.application
import email.mime.multipart
import email.mime.text
import io
import sys

class FormData(email.mime.multipart.MIMEMultipart):

    def __init__(self):
        email.mime.multipart.MIMEMultipart.__init__(self, 'form-data')

    def setText(self, name, value):
        part = email.mime.text.MIMEText(value, _charset='utf-8')
        part.add_header('Content-Disposition', 'form-data', name=name)
        self.attach(part)
        return part

    def setFile(self, name, value, filename, mimetype=None):
        part = email.mime.application.MIMEApplication(value)
        part.add_header('Content-Disposition', 'form-data',
                        name=name, filename=filename)
        if mimetype is not None:
            part.set_type(mimetype)
        self.attach(part)
        return part

    def http_body(self):
        b = io.BytesIO()
        gen = email.generator.BytesGenerator(b, False, 0)
        gen.flatten(self, False, '\r\n')
        b.write(b'\r\n')
        b = b.getvalue()
        pos = b.find(b'\r\n\r\n')
        assert pos >= 0
        return b[pos + 4:]

fd = FormData()
fd.setText('foo', 'bar')
fd.setText('täst', 'Täst')
fd.setFile('file', b'abcdef'*50, 'Täst.txt')
sys.stdout.buffer.write(fd.http_body())

Результат выглядит так:

--===============6469538197104697019==
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Content-Disposition: form-data; name="foo"

YmFy

--===============6469538197104697019==
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Content-Disposition: form-data; name*=utf-8''t%C3%A4st

VMOkc3Q=

--===============6469538197104697019==
Content-Type: application/octet-stream
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Content-Disposition: form-data; name="file"; filename*=utf-8''T%C3%A4st.txt

YWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJj
ZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVm
YWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJj
ZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVm
YWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJjZGVmYWJj
ZGVmYWJjZGVmYWJjZGVm

--===============6469538197104697019==--

Кажется, он довольно хорошо обрабатывает заголовки. Содержимое двоичного файла будет закодировано в base64, чего можно избежать, но которое должно работать достаточно хорошо. Что меня беспокоит, так это текстовые поля между ними. Они также закодированы в base64. Я думаю, что в соответствии со стандартом, это должно работать достаточно хорошо, но яЯ предпочел бы иметь простой текст, на тот случай, если какая-то тупая структура должна иметь дело с данными на промежуточном уровне и не знать о данных, закодированных в Base64.

ВопросыМогу ли я использовать 8-битные данные для своих текстовых полей и при этом соответствовать спецификации?Могу ли я получить пакет электронной почты для сериализации моих текстовых полей как 8-битных данных без дополнительной кодировки?Если мне нужно придерживаться какой-то 7-битной кодировки, могу ли я заставить реализацию использовать цитируемую печатную форму для тех частей текста, где эта кодировка короче, чем base64?Можно ли избежать кодирования base64 для содержимого двоичных файлов?Если я могу избежать этого, я должен написатьContent-Transfer-Encoding как8bit или как?binaryЕсли бы мне пришлось самому сериализовать тело, как я мог бы использоватьemail.header пакет самостоятельно форматировать значения заголовка? (email.utils.encode_rfc2231 Является ли это.)Есть ли реализация, которая уже сделала все, что яя пытаюсь сделать?

Эти вопросы очень тесно связаны и могут быть обобщены как «Как бы вы это реализовали?, Во многих случаях ответ на один вопрос либо отвечает, либо устаревает на другой. Поэтому я надеюсь, что вы согласны, что один пост для всех из них подходит.

Ответы на вопрос(2)

Ваш ответ на вопрос