介绍

背景

我正在编写一个脚本,使用RFC 2388中定义的multipart/form-data内容类型上传包括文件在内的内容。从长远来看,我试图提供一个简单的Python脚本来执行uploads of binary packages for github,该脚本涉及将类似表单的数据发送到Amazon S3。

有关的

This question已经询问如何执行此操作,但是到目前为止,尚无公认的答案。目前,这两个答案中的the more useful指向these recipes,而this question则手动构建了整个消息。我有点担心这种方法,特别是在字符集和二进制内容方面。

还有currently highest-scoring answer,其 MultipartPostHandler 表示RFC 2388 Section 4.3模块。但这与我提到的食谱没有太大不同,因此我的担忧也是如此。

顾虑

二进制内容

Content-Transfer-Encoding header明确声明除非另有声明,否则内容的内容预计为7位,因此可能需要RFC 2388 Section 4.4。这是否意味着我必须对Base64编码的二进制文件内容进行编码?还是Content-Transfer-Encoding: 8bit对于任意文件而言就足够了?还是应该阅读Content-Transfer-Encoding: binary

header 字段的字符集

通常, header 字段(尤其是filename header 字段)默认情况下仅是ASCII。我希望我的方法也能够传递非ASCII文件名。我知道对于我当前为github上载内容的应用程序,由于文件名是在单独的字段中给出的,因此我可能不需要它。但是我希望代码可以重用,所以我宁愿使用一致的方式对文件名参数进行编码。 RFC 2231建议在 filename*=utf-8''t%C3%A4st.txt package中引入的格式,例如multipart/form-data

我的方法

使用python库

由于email本质上是MIME类型,我认为应该可以使用标准python库中的 Content-Transfer-Encoding package撰写我的文章。我想委托(delegate)非ASCII header 字段进行相当复杂的处理。

到目前为止工作

所以我写了下面的代码:

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

结果看起来像这样:
--===============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==--

它似乎确实可以很好地处理 header 。二进制文件内容将被base64编码,这是可以避免的,但是应该可以正常工作。让我担心的是介于两者之间的文本字段。它们也是base64编码的。我认为,按照标准,这应该可以很好地工作,但是我宁愿在其中添加纯文本,以防万一某些笨拙的框架必须在中间级别处理数据并且不了解Base64编码数据。

问题
  • 我可以在文本字段中使用8位数据,但仍然符合规范吗?
  • 是否可以获取电子邮件包以将我的文本字段序列化为8位数据而无需额外编码?
  • 如果我必须坚持使用7位编码,那么我可以让实现对那些编码比base64短的文本部分使用带引号的可打印吗?
  • 我也可以避免对二进制文件内容进行base64编码吗?
  • 如果可以避免,应该将8bit写为binary还是email.header
  • 如果我必须自己序列化正文,我如何才能单独使用 email.utils.encode_rfc2231 来格式化标题值? (ojit_a执行此操作。)
  • 是否已经完成了我想做的所有实现?

  • 这些问题密切相关,可以概括为“您将如何实现” 。在许多情况下,回答一个问题会回答另一个问题或使另一个问题过时。因此,我希望您同意所有这些都适合发表一篇文章。

    最佳答案

    这是一个占位符答案,描述了我在等待一些问题的权威意见时所做的事情。如果它表明该方法在至少一个设计决策中是错误的或不合适的,我将很乐意接受不同的答案。

    Here是我目前根据自己的喜好使用的代码。
    我做出了以下决定:



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



    我找不到办法,所以我正在做自己的序列化,就像我在此看到的所有other recipes一样。



    简单地以二进制形式发送文件内容似乎已经足够好,至少在我的单个应用程序中是如此。



    正如RFC 2045 Section 2.8所述,在LFCR对之间8bit数据受998个八位位组的行长限制,我决定binary更加通用,因此在这里更合适的描述。



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



    不是我知道的。但是,邀请其他实现采用my code的想法。

    编辑:

    多亏了this comment,我现在知道RFC 2231对于 header 的使用并没有被普遍接受:HTML 5 forbids its use的当前草案。还可以看到cause problems in the wild。但是由于POST header 并不总是与特定的HTML文档相对应(例如,以Web API为例),因此我不确定在这方面我是否会信任该草案。 RFC 5987 Section 4.2建议,正确的方法可能是同时提供编码名称和未编码名称。但是RFC是用于HTTP header 的,而multipart/form-data header 在技术上是HTTP正文。因此该RFC不适用,我不知道有任何RFC明确允许(甚至鼓励)同时为multipart/form-data使用两种形式。

    10-06 05:27
    查看更多