Formatar corretamente o corpo de dados de várias partes / formulário
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.
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.
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
?
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
.
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.
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.