Formatar corretamente o corpo de dados de várias partes / formulário

Introduçãofundo

Eu estou escrevendo um script para fazer upload de coisas, incluindo arquivos usando omultipart/form-data tipo de conteúdo definido emRFC 2388. No longo prazo, estou tentando fornecer um script Python simples para fazeruploads de pacotes binários para o github, que envolve o envio de dados semelhantes a formulários para o Amazon S3.

Relacionado

Essa questão já perguntou sobre como fazer isso, mas é sem uma resposta aceita até agora, eo mais útil das duas respostas que atualmente aponta paraestas receitas que, por sua vez, constrói toda a mensagem manualmente. Estou um pouco preocupado com esta abordagem, particularmente no que diz respeito a charsets e conteúdo binário.

Há tambémessa questão, com suaatualmente a resposta de maior pontuação sugerindo oMultipartPostHandler módulo. Mas isso não é muito diferente das receitas que mencionei e, portanto, minhas preocupações também se aplicam.

PreocupaçõesConteúdo binário

RFC 2388 Seção 4.3 afirma explicitamente que o conteúdo deve ser de 7 bits, a menos que seja declarado de outra forma e, portanto,Content-Transfer-Encoding cabeçalho pode ser necessário. Isso significa que eu teria que codificar em Base64 o conteúdo do arquivo binário? Ou seriaContent-Transfer-Encoding: 8bit ser suficiente para arquivos arbitrários? Ou deveria lerContent-Transfer-Encoding: binary?

Charset para campos de cabeçalho

Campos de cabeçalho em geral efilename campo de cabeçalho em particular, são ASCII somente por padrão. Eu gostaria que meu método fosse capaz de passar nomes de arquivos não-ASCII também. Eu sei que para minha aplicação atual de fazer upload de material para o github, provavelmente não precisarei disso, pois o nome do arquivo é dado em um campo separado. Mas eu gostaria que meu código fosse reutilizável, então eu prefiro codificar o parâmetro de nome de arquivo de uma forma conforme.RFC 2388 Seção 4.4 aconselha o formato introduzido noRFC 2231, por exemplo.filename*=utf-8''t%C3%A4st.txt.

Minha abordagemUsando bibliotecas python

Comomultipart/form-data é essencialmente um tipo MIME, eu pensei que deveria ser possível usar oemail pacote das bibliotecas python padrão para compor o meu post. A manipulação complicada de campos de cabeçalho não-ASCII em particular é algo que eu gostaria de delegar.

Trabalho até agora

Então eu escrevi o seguinte código:

#!/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())

O resultado é assim:

--===============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==--

Parece lidar razoavelmente bem com cabeçalhos. O conteúdo do arquivo binário receberá codificação base64, que pode ser evitável, mas que deve funcionar bem o suficiente. O que me preocupa são os campos de texto entre eles. Eles também são codificados em base64. Eu acho que de acordo com o padrão, isso deve funcionar bem o suficiente, mas eu prefiro ter texto simples lá, apenas no caso de algum framework burro ter que lidar com os dados em um nível intermediário e não saber sobre os dados codificados em Base64.

QuestõesPosso usar dados de 8 bits para meus campos de texto e ainda estar em conformidade com a especificação?Posso obter o pacote de email para serializar meus campos de texto como dados de 8 bits sem codificação extra?Se eu tiver que manter uma codificação de 7 bits, posso fazer com que a implementação use imprimível entre as partes de texto em que essa codificação é menor que a base64?Posso evitar a codificação de base64 para conteúdo de arquivo binário também?Se eu puder evitar, devo escrever oContent-Transfer-Encoding Como8bit ou comobinary?Se eu tivesse que serializar o corpo sozinho, como eu poderia usar oemail.header pacote por conta própria para apenas formatar valores de cabeçalho? (email.utils.encode_rfc2231 faz isso.)Existe alguma implementação que já fez tudo o que estou tentando fazer?

Estas questões estão intimamente relacionadas e podem ser resumidas como"Como você implementaria isso". Em muitos casos, responder a uma pergunta responde ou anula outra. Então, eu espero que você concorde que um único post para todos eles seja apropriado.

questionAnswers(2)

yourAnswerToTheQuestion