Python 如果“filename=”未指定,则带有多部分/表单数据的cgi.FieldStorage会尝试将二进制文件解码为UTF-8
当我使用cgi.FieldStorage解析多部分/表单数据请求或任何web框架(如使用cgi.FieldStorage的Pyramid)时,我很难处理从某些客户端上传的文件,这些客户端在部件的内容处理头中没有提供filename=file.ext 如果缺少filename=选项,FieldStorage将尝试将文件内容解码为UTF-8并返回字符串。很明显,许多文件是二进制文件,而不是UTF-8,因此会给出虚假的结果 例如:Python 如果“filename=”未指定,则带有多部分/表单数据的cgi.FieldStorage会尝试将二进制文件解码为UTF-8,python,python-3.x,file-upload,cgi,multipartform-data,Python,Python 3.x,File Upload,Cgi,Multipartform Data,当我使用cgi.FieldStorage解析多部分/表单数据请求或任何web框架(如使用cgi.FieldStorage的Pyramid)时,我很难处理从某些客户端上传的文件,这些客户端在部件的内容处理头中没有提供filename=file.ext 如果缺少filename=选项,FieldStorage将尝试将文件内容解码为UTF-8并返回字符串。很明显,许多文件是二进制文件,而不是UTF-8,因此会给出虚假的结果 例如: >>> import cgi >>>
>>> 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=选项,但我目前正在处理一个不包含该选项的客户端,根据规范,省略文件名似乎是有效的
目前,我正在使用一种相当黑客的解决方法,将FieldStorage子类化,并将相关的内容处置头替换为具有以下文件名的头:
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不要将其解码为UTF-8?如果您可以提供encoding=None或其他什么,那就太好了,但它看起来不支持这一点
我在处理从某些客户端上传的文件时遇到问题,这些客户端在部件的内容处理头中没有提供filename=file.ext
filename=参数实际上是服务器端确定部件表示文件上载的唯一方法。如果客户机省略了这个参数,它实际上并不是发送文件上传,而是发送一个纯文本表单字段。在这样一个字段中发送任意二进制数据在技术上仍然是合法的,但包括Python cgi在内的许多服务器环境都会被它弄糊涂
如果您能提供encoding=None或其他什么,那就太好了
如果将错误设置为“代理场景”,则至少可以从解码的字符中恢复原始字节。我最终使用了一个更简单的FieldStorage子类来解决这个问题,因此我将其作为一个答案发布在这里。您不必重写_init_uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
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的回答所指出的,您可以使用代理场景错误处理程序,然后将其编码回字节。这有点迂回,但也可能是最简单的解决方法:
>>> 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'
设置errors=subrogateScape,然后执行string.encode'utf-8','subrogateScape'是一个很好的hack,谢谢!