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

ВведениеФон

Я пишу скрипт для загрузки материалов, включая файлы, используя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)

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