正确格式化multipart / form

介绍

背景

我正在编写一个脚本来使用RFC 2388中定义的multipart/form-data内容类型来上传包括文件的文件。从长远来看,我试图提供一个简单的Python脚本来为github上传二进制包,涉及向Amazon S3发送表单式数据。

有关

这个问题已经问到了如何做到这一点,但是到目前为止还没有被接受的答案,并且目前有两个答案中更有用的是指向这些食谱,而这些食谱又手动地构建了整个消息。 我对这种方法有些担心,特别是关于字符集和二进制内容。

还有一个问题,目前得分最高的答案是建议MultipartPostHandler模块。 但是这与我提到的食谱没有什么不同,因此我的担心也适用于此。

关注

二进制内容

RFC 2388第4.3节明确规定,除非另有声明,否则内容预期为7位,因此可能需要Content-Transfer-Encoding标头。 这是否意味着我必须对Base64编码二进制文件内容进行编码? 或者Content-Transfer-Encoding: 8bit是否足够用于任意文件? 或者应该读取Content-Transfer-Encoding: binary

标题字段的字符集

一般情况下,标题字段和filename标题字段仅在默认情况下为ASCII。 我希望我的方法能够传递非ASCII文件名。 我知道,对于我目前正在上传github的应用程序的应用程序,我可能不需要这样做,因为文件名是在单独的字段中给出的。 但是我希望我的代码是可重用的,所以我宁愿以合适的方式编码文件名参数。 RFC 2388第4.4节建议RFC 2231中引入的格式,例如filename*=utf-8''t%C3%A4st.txt

我的方法

使用python库

由于multipart/form-data本质上是一种MIME类型,我认为应该可以使用标准python库中的email包来撰写我的文章。 特别是非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, 'rn')
        b.write(b'rn')
        b = b.getvalue()
        pos = b.find(b'rnrn')
        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短的文本部分使用quoted printable?
  • 我是否也可以避免对二进制文件内容进行base64编码?
  • 如果我可以避免它,我应该将Content-Transfer-Encoding编写为8bit还是binary
  • 如果我必须自己序列化主体,那么我怎么才能使用email.header包来自己设置标题值的格式呢? ( email.utils.encode_rfc2231这样做。)
  • 是否有一些实现已经完成了我所要做的一切?
  • 这些问题密切相关,可以概括为“你将如何实现这一点” 。 在很多情况下,回答一个问题或者回答或者废弃另一个问题。 所以我希望你们同意,对于他们来说,一个单一的职位是适当的。


    这是一个占位符答案,描述了我在等待某些权威性输入时所做的一些问题。 如果证明这种方法在至少一项设计决策中是错误的或不适合的,我会很乐意接受不同的答案。

    以下是我现在根据自己的喜好使用此代码的代码。 我做了以下决定:

    我可以为我的文本字段使用8位数据,并仍符合规范吗?

    我决定这样做。 至少对于这个应用程序来说,它确实有效。

    我可以让电子邮件包将我的文本字段序列化为8位数据而无需额外编码吗?

    我发现没有办法,所以我正在做我自己的序列化,就像我看到的所有其他食谱一样。

    我是否也可以避免对二进制文件内容进行base64编码?

    简单地用二进制发送文件内容似乎工作得很好,至少在我的单一应用程序中。

    如果我可以避免它,我应该将Content-Transfer-Encoding编写为8位还是二进制?

    正如RFC 2045第2.8节所述, 8bit数据受CRLF对之间998个八位字节的行长度限制,我认为binary是更一般的,因此这里的描述更合适。

    如果我必须自己序列化主体,那么我怎么才能使用email.header包来自己设置标题值的格式呢?

    如已经编辑到我的问题中, email.utils.encode_rfc2231对此非常有用。 我尝试首先使用ascii进行编码,但在非ASCII数据或双引号字符串内禁止的ASCII字符的情况下使用该方法。

    是否有一些实现已经完成了我所要做的一切?

    不是我所知道的。 尽管如此,其他实现也被邀请采用我的代码。


    编辑:

    感谢这个评论,我现在意识到RFC 2231的头文件并未得到普遍接受:目前的HTML 5草案禁止使用它。 它也被视为在野外造成问题。 但是,由于POST标题并不总是与特定的HTML文档相对应(例如,考虑Web API),所以我不确定我是否也会相信该草案。 也许正确的做法是按照RFC 5987第4.2节的建议给出编码和非编码名称。 但是该RFC用于HTTP头部,而多部分/表单数据头部在技术上是HTTP本体。 因此,该RFC不适用,并且我不知道任何明确允许(甚至鼓励)同时使用这两种表单的多部分/表单数据的RFC。


    您可能希望查看使用POST从Python脚本问题发送文件,该问题指向正在成为http最常用的Python库的Requests库。 如果你不能找到所有需要的功能并决定自己实现它,我鼓励你将它贡献给这个项目。

    链接地址: http://www.djcxy.com/p/22211.html

    上一篇: Properly format multipart/form

    下一篇: Post Method + WinHttpRequest + multipart/form