Prawidłowo sformatuj treść danych wieloczęściowych / formularzy

Wprowadzenietło

Piszę skrypt do przesyłania rzeczy, w tym plików za pomocąmultipart/form-data typ zawartości zdefiniowany wRFC 2388. Na dłuższą metę staram się udostępnić prosty skrypt Pythonaładowanie pakietów binarnych dla github, co wiąże się z przesyłaniem danych w formie do Amazon S3.

Związane z

To pytanie już zapytał, jak to zrobić, ale jak dotąd nie ma akceptowanej odpowiedzi ibardziej przydatne z dwóch odpowiedzi, na które obecnie wskazujete przepisy które z kolei budują całą wiadomość ręcznie. Jestem trochę zaniepokojony tym podejściem, szczególnie w odniesieniu do zestawów znaków i treści binarnych.

Jest równieżto pytanie, z jegoobecnie najwyżej punktowana odpowiedź sugerującMultipartPostHandler moduł. Ale to nie różni się zbytnio od przepisów, o których wspomniałem, i dlatego moje obawy dotyczą również tego.

ObawyZawartość binarna

RFC 2388 Sekcja 4.3 jednoznacznie stwierdza, że ​​zawartość ma być 7-bitowa, chyba że zostanie zadeklarowana inaczej, a zatemContent-Transfer-Encoding nagłówek może być wymagane. Czy to oznacza, że ​​musiałbym kodować pliki binarne Base64? Albo by to zrobiłContent-Transfer-Encoding: 8bit wystarczy dla dowolnych plików? Czy powinien to przeczytać?Content-Transfer-Encoding: binary?

Zestaw znaków dla pól nagłówka

Pola nagłówka ogólnie, afilename w szczególności nagłówek, domyślnie są tylko ASCII. Chciałbym, aby moja metoda mogła również przekazywać nazwy plików innych niż ASCII. Wiem, że dla mojej obecnej aplikacji do przesyłania rzeczy na github prawdopodobnie nie będę tego potrzebował, ponieważ nazwa pliku jest podana w osobnym polu. Ale chciałbym, aby mój kod był wielokrotnego użytku, więc wolę kodować parametr nazwy pliku w zgodny sposób.RFC 2388 Sekcja 4.4 doradza format wprowadzony wRFC 2231, np.filename*=utf-8''t%C3%A4st.txt.

Moje podejścieKorzystanie z bibliotek Pythona

Tak jakmultipart/form-data jest zasadniczo typem MIME, pomyślałem, że powinno być możliwe użycieemail pakiet ze standardowych bibliotek Pythona, aby skomponować mój post. Szczególnie skomplikowana obsługa pól nagłówków innych niż ASCII jest czymś, co chciałbym przekazać.

Dotychczasowa praca

Napisałem więc następujący kod:

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

Wynik wygląda tak:

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

Wydaje się, że dobrze radzi sobie z nagłówkami. Zawartość pliku binarnego zostanie zakodowana w oparciu o base64, co może być możliwe do uniknięcia, ale powinno działać wystarczająco dobrze. Martwią mnie pola tekstowe między nimi. Są również zakodowane w oparciu o base64. Myślę, że zgodnie ze standardem powinno to działać wystarczająco dobrze, ale wolałbym mieć tam prosty tekst, na wypadek, gdyby jakiś głupi framework musiał radzić sobie z danymi na poziomie pośrednim i nie wie o danych zakodowanych w Base64.

pytaniaCzy mogę używać 8-bitowych danych dla moich pól tekstowych i nadal odpowiadam specyfikacji?Czy mogę uzyskać pakiet e-mail do serializacji moich pól tekstowych jako 8-bitowych danych bez dodatkowego kodowania?Jeśli muszę trzymać się kodowania 7-bitowego, czy mogę uzyskać implementację do cytowania dla tych części tekstu, w których kodowanie jest krótsze niż base64?Czy mogę również uniknąć kodowania base64 dla zawartości plików binarnych?Jeśli mogę tego uniknąć, powinienem napisaćContent-Transfer-Encoding tak jak8bit lub jakbinary?Gdybym musiał sam serializować ciało, jak mógłbym użyćemail.header pakiet samodzielnie formatować tylko wartości nagłówka? (email.utils.encode_rfc2231 robi to.)Czy jest jakaś implementacja, która już zrobiła wszystko, co próbuję zrobić?

Pytania te są bardzo ściśle powiązane i można je podsumować jako„Jak byś to wdrożył”. W wielu przypadkach udzielenie odpowiedzi na jedno pytanie odpowiada lub zastępuje jedno. Mam więc nadzieję, że zgadzasz się, że jeden post dla wszystkich jest odpowiedni.

questionAnswers(2)

yourAnswerToTheQuestion