当我使用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
}
...
或其他东西会很好,但它看起来并不支持它。
答案 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'