如何使用Python请求库和unicode文件名进行多部分上传?

时间:2015-02-02 16:32:49

标签: python unicode utf-8 python-requests

我尝试使用其REST API将文件上传到JIRA。上传是作为多部分POST完成的。只要文件名是ASCII,代码就可以正常工作。如果它们包含任何非ASCII字符,则JIRA会出现500错误。

我尝试通过在POST操作中对文件名进行编码来纠正错误,文件上传成功,但JIRA显示编码文件名的ASCII版本,而不是将文件名解码为原始UTF-8。

以下是Python代码的片段:

files = {'file': (urllib2.quote(zd_attachment['file_name'].encode('utf8')), open('/tmp/%s' % zd_attachment['file_name'], 'rb'), zd_attachment['content_type'])}
mp_header = {'X-Atlassian-Token': 'nocheck'}
r = requests.post("%s/rest/api/2/issue/%s/attachments" % (jira_url, issue_id), headers=mp_header, files=files, auth=jira_auth)

我使用错误的方法对文件名进行编码吗?为了正确处理Unicode文件名,我是否遗漏了其他内容?

3 个答案:

答案 0 :(得分:6)

Urllib3对非ASCII文件名(issuepull request)使用了错误的编码方法。解决方法是创建您自己的PreparedRequest或实现auth handler并在发送之前更改请求的正文。我在blog post中有一些关于此的例子。

答案 1 :(得分:3)

  

我使用错误的方法对文件名进行编码吗?

坏消息是,实际上还没有任何广泛接受的方法可以将非ASCII和其他麻烦的字符(如引号)编码到文件名中。 Content-Disposition; filename参数在网络上是不可靠的。

requests / urllib3努力做得很好。如果文件名包含非ASCII字符,它会尝试使用RFC 2231提出的针对MIME的疯狂filename*=标头参数方案对其进行编码(来源:urllib3.fields.format_header_param)。

事实上,这对于HTTP文件下载响应中的Content-Disposition标题是正确的(RFC5987),有些浏览器现在甚至支持,但表单提交不是由此覆盖。

服务器端没有任何东西支持这种形式的编码,所以我怀疑JIRA可能会看到一个没有filename的文件上传字段(因为它只有一个filename*)并将其玩具扔出婴儿车。

HTML5已经接管了multiple/form-data媒体类型的定义,现在不再使用这种编码方案:

  

用户代理不得使用RFC 2388建议的RFC 2231编码。

相反:

  

生成的multipart / form-data资源中包含的文件名(作为文件字段的一部分)必须使用上面选择的字符编码

所以你应该传入一个Unicode字符串作为文件名,urllib3应该将其编码为与表格其余部分相同的编码(例如UTF-8)。您不能将IMO报告为urllib3报告的错误。目前你可能不得不满足于仅使用ASCII的文件名。

除此之外:不幸的是,我们离这里的救赎还很远。除了传统的浏览器及其任意编码之外,HTML5还允许浏览器应用额外的任意不可恢复的修改,从而保持生活的美好和愚蠢:

  

如果需要,可以近似精确的名称(例如,可以从文件名中删除换行符,可以将引号更改为"%22",并且在所选字符编码中不可表达的字符可以由其他字符替换字符)。

答案 2 :(得分:0)

现在应该解决此问题。从v1.25.3开始,urllib3使用的是非ASCII文件名(merged PR)的HTML5编码。

我最近尝试使用请求库通过REST API将附件上传到Jira,并且文件名不再存在问题。

可以通过这种方式指定请求的files参数(确保进行URL编码的文件名):

files = {'file': (zd_attachment['file_name'], open('/tmp/%s' % zd_attachment['file_name'], 'rb'), zd_attachment['content_type'])}