具有multipart / form-data的cgi.FieldStorage尝试将二进制文件解码为UTF-8,如果" filename ="未标明

时间:2017-02-13 20:51:04

标签: python python-3.x file-upload cgi multipartform-data

当我使用cgi.FieldStorage解析multipart/form-data请求(或任何使用cgi.FieldStorage的金字塔等网络框架)时,我无法处理来自某些客户的文件上传问题在部分的filename=file.ext标题中提供Content-Disposition

如果缺少filename=选项,FieldStorage()会尝试将文件内容解码为UTF-8并返回一个字符串。显然很多文件都是二进制文件而不是UTF-8,因此会产生伪造的结果。

例如:

>>> import cgi
>>> import io
>>> body = (b'--KQNTvuH-itP09uVKjjZiegh7\r\n' +
...         b'Content-Disposition: form-data; name=payload\r\n\r\n' +
...         b'\xff\xd8\xff\xe0\x00\x10JFIF')
>>> env = {
...     'REQUEST_METHOD': 'POST',
...     'CONTENT_TYPE': 'multipart/form-data; boundary=KQNTvuH-itP09uVKjjZiegh7',
...     'CONTENT_LENGTH': len(body),
... }
>>> fs = cgi.FieldStorage(fp=io.BytesIO(body), environ=env)
>>> (fs['payload'].filename, fs['payload'].file.read())
(None, '����\x00\x10JFIF')

浏览器和大多数 HTTP库确实包含用于文件上传的filename=选项,但我目前正在处理一个不存在的客户端(并且省略了根据规范,filename似乎确实有效。

目前,我通过继承FieldStorage并使用具有文件名的Content-Disposition标头取代相关的import cgi import os class FileFieldStorage(cgi.FieldStorage): """To use, subclass FileFieldStorage and override _file_fields with a tuple of the names of the file field(s). You can also override _file_name with the filename to add. """ _file_fields = () _file_name = 'file_name' def __init__(self, fp=None, headers=None, outerboundary=b'', environ=os.environ, keep_blank_values=0, strict_parsing=0, limit=None, encoding='utf-8', errors='replace'): if self._file_fields and headers and headers.get('content-disposition'): content_disposition = headers['content-disposition'] key, pdict = cgi.parse_header(content_disposition) if (key == 'form-data' and pdict.get('name') in self._file_fields and 'filename' not in pdict): del headers['content-disposition'] quoted_file_name = self._file_name.replace('"', '\\"') headers['content-disposition'] = '{}; filename="{}"'.format( content_disposition, quoted_file_name) super().__init__(fp=fp, headers=headers, outerboundary=outerboundary, environ=environ, keep_blank_values=keep_blank_values, strict_parsing=strict_parsing, limit=limit, encoding=encoding, errors=errors) 标头,使用了非常糟糕的解决方法:

body

在我的第一次测试中使用env>>> class TestFieldStorage(FileFieldStorage): ... _file_fields = ('payload',) >>> fs = TestFieldStorage(fp=io.BytesIO(body), environ=env) >>> (fs['payload'].filename, fs['payload'].file.read()) ('file_name', b'\xff\xd8\xff\xe0\x00\x10JFIF') ,现在可以使用:

FieldStorage

有没有办法避免这种黑客并告诉encoding=None不要解码为UTF-8?如果你能提供... 10 = CONTENT 10 { table = tt_content select { where = colPos=0 orderBy = sorting selectFields = image,header,bodytext languageField = sys_language_uid } wrap = <items>|</items> renderObj = COA renderObj { wrap = <item>|</item> 10 = TEXT 10 { wrap = <title>|</title> field=header } 20 = TEXT 20 { wrap = <text><![CDATA[ | ]]></text> field = bodytext parseFunc = < lib.parseFunc_RTE } ... 或其他东西会很好,但它看起来并不支持它。

2 个答案:

答案 0 :(得分:1)

  

我在处理来自某些客户端的文件上传时遇到问题,这些客户端没有在部件的Content-Disposition标头中提供filename = file.ext。

filename =参数实际上是服务器端可以确定部件代表文件上载的唯一方式。如果客户端省略了此参数,则它实际上不是发送文件上载,而是发送纯文本表单字段。在这样的字段中发送任意二进制数据在技术上仍然是合法的,但包括Python cgi在内的许多服务器环境都会被它混淆。

  

如果您能提供encoding = None或

,那就太好了

如果将errors设置为surrogateescape,您至少可以从已解码的字符中恢复原始字节。

答案 1 :(得分:0)

我最后使用一个更简单的FieldStorage子类解决了这个问题,因此我将其作为答案发布在此处。您可以将__init__属性覆盖为一个返回文件名的属性,而不是覆盖Content-Disposition并向.filename标头添加文件名,如果没有提供该文件名输入:

class MyFieldStorage(cgi.FieldStorage):
    @property
    def filename(self):
        if self._original_filename is not None:
            return self._original_filename
        elif self.name == 'payload':
            return 'file_name'
        else:
            return None

    @filename.setter
    def filename(self, value):
        self._original_filename = value

此外,正如@ bobince的回答所指出的那样,您可以使用surrogateescape错误处理程序,然后将其编码回字节。它有点迂回,但也可能是最简单的解决方法:

>>> fs = cgi.FieldStorage(fp=io.BytesIO(body), environ=env, errors='surrogateescape')
>>> fs['payload'].file.read().encode('utf-8', 'surrogateescape')
b'\xff\xd8\xff\xe0\x00\x10JFIF'