Formato correcto multipart / form-cuerpo de datos

IntroducciónFondo

Estoy escribiendo un guión para cargar cosas, incluidos archivos que utilizan elmultipart/form-data tipo de contenido definido enRFC 2388. A largo plazo, estoy tratando de proporcionar un script Python simple para hacercargas de paquetes binarios para github, lo que implica enviar datos similares a formularios a Amazon S3.

Relacionado

Esta pregunta ya ha preguntado acerca de cómo hacer esto, pero no tiene una respuesta aceptada hasta ahora, ycuanto más útil De las dos respuestas que actualmente tiene puntos paraestas recetas que a su vez construye todo el mensaje manualmente. Estoy algo preocupado por este enfoque, particularmente con respecto a los conjuntos de caracteres y el contenido binario.

También hayesta pregunta, con suActualmente la respuesta con la puntuación más alta sugiriendo laMultipartPostHandler módulo. Pero eso no es muy diferente de las recetas que mencioné, y por lo tanto mis preocupaciones también se aplican a eso.

PreocupacionesContenido binario

RFC 2388 Sección 4.3 declara explícitamente que se espera que el contenido sea de 7 bits a menos que se declare lo contrario, y por lo tanto unContent-Transfer-Encoding encabezamiento podría ser requerido ¿Eso significa que tendría que codificar en base64 el contenido de un archivo binario? O lo haríaContent-Transfer-Encoding: 8bit ¿Será suficiente para archivos arbitrarios? ¿O debería leer eso?Content-Transfer-Encoding: binary?

Conjunto de caracteres para campos de encabezado

Los campos de encabezado en general, yfilename el campo de encabezado en particular, son ASCII solo por defecto. Me gustaría que mi método también pueda pasar nombres de archivos que no sean ASCII. Sé que para mi aplicación actual de cargar cosas para github, probablemente no lo necesitaré, ya que el nombre del archivo aparece en un campo separado. Pero me gustaría que mi código fuera reutilizable, por lo que prefiero codificar el parámetro de nombre de archivo de forma conforme.RFC 2388 Sección 4.4 Se aconseja el formato introducido en.RFC 2231, p.ej.filename*=utf-8''t%C3%A4st.txt.

Mi acercamientoUsando bibliotecas de python

Comomultipart/form-data es esencialmente un tipo MIME, pensé que debería ser posible utilizar elemail paquete de las bibliotecas estándar de python para componer mi post. El manejo bastante complicado de los campos de encabezado no ASCII en particular es algo que me gustaría delegar.

Trabajar hasta ahora

Así que escribí el siguiente 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())

El resultado se ve así:

--===============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 manejar bastante bien los encabezados. El contenido del archivo binario se codificará en base64, lo que puede evitarse pero debería funcionar lo suficientemente bien. Lo que me preocupa son los campos de texto intermedios. También están codificados en base64. Creo que de acuerdo con el estándar, esto debería funcionar lo suficientemente bien, pero preferiría tener texto sin formato allí, en caso de que algún marco estúpido tenga que lidiar con los datos en un nivel intermedio y no conozca los datos codificados en Base64.

Preguntas¿Puedo usar datos de 8 bits para mis campos de texto y seguir cumpliendo con las especificaciones?¿Puedo obtener el paquete de correo electrónico para serializar mis campos de texto como datos de 8 bits sin codificación adicional?Si tengo que atenerme a una codificación de 7 bits, ¿puedo hacer que la implementación use la parte imprimible entre comillas para aquellas partes de texto donde esa codificación sea más corta que la base64?¿Puedo evitar también la codificación base64 para el contenido de archivos binarios?Si puedo evitarlo, ¿debo escribir elContent-Transfer-Encoding como8bit o comobinary?Si tuviera que serializar el cuerpo yo mismo, ¿cómo podría usar elemail.header paquete por sí solo para formatear los valores de encabezado? (email.utils.encode_rfc2231 Haz esto.)¿Hay alguna implementación que ya hizo todo lo que estoy tratando de hacer?

Estas preguntas están estrechamente relacionadas y podrían resumirse como"¿Cómo implementarías esto?". En muchos casos, responder una pregunta responde u obsoleta a otra. Así que espero que estén de acuerdo en que una sola publicación para todos ellos es apropiada.

Respuestas a la pregunta(2)

Su respuesta a la pregunta