如果未指定 "filename=",cgi.FieldStorage 和 multipart/form-data 会尝试将二进制文件解码为 UTF-8
cgi.FieldStorage with multipart/form-data tries to decode binary file as UTF-8 if "filename=" not specified
当我使用 cgi.FieldStorage
解析 multipart/form-data
请求(或使用 cgi.FieldStorage
的任何 Web 框架,如 Pyramid)时,我无法处理来自某些不支持的客户端的文件上传在部件的 Content-Disposition
header.
中提供 filename=file.ext
如果缺少 filename=
选项,FieldStorage()
会尝试将文件内容解码为 UTF-8 和 return 字符串。显然许多文件是二进制文件而不是 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
header 替换为具有文件名的文件名:
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
之类的东西就好了,但它看起来不支持那个。
I have trouble processing file uploads from certain clients which don't provide a filename=file.ext in the part's Content-Disposition header.
filename= 参数实际上是服务器端确定某个部分代表文件上传的唯一方法。如果客户端省略了这个参数,它并不是真正发送一个文件上传,而是一个纯文本表单域。在这样的字段中发送任意二进制数据在技术上仍然是合法的,但是包括 Python cgi
在内的许多服务器环境会被它混淆。
It would be nice if you could provide encoding=None or something
如果您将 errors
设置为 surrogateescape
,您至少可以从解码的字符中恢复原始字节。
我最终使用更简单的 FieldStorage
子类解决了这个问题,所以我将其发布在这里作为答案。您可以覆盖 __init__
并将文件名添加到 Content-Disposition
header,而不是覆盖 .filename
属性,使其成为 returns 的 属性如果没有为该输入提供一个文件名:
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'
当我使用 cgi.FieldStorage
解析 multipart/form-data
请求(或使用 cgi.FieldStorage
的任何 Web 框架,如 Pyramid)时,我无法处理来自某些不支持的客户端的文件上传在部件的 Content-Disposition
header.
filename=file.ext
如果缺少 filename=
选项,FieldStorage()
会尝试将文件内容解码为 UTF-8 和 return 字符串。显然许多文件是二进制文件而不是 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
header 替换为具有文件名的文件名:
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
之类的东西就好了,但它看起来不支持那个。
I have trouble processing file uploads from certain clients which don't provide a filename=file.ext in the part's Content-Disposition header.
filename= 参数实际上是服务器端确定某个部分代表文件上传的唯一方法。如果客户端省略了这个参数,它并不是真正发送一个文件上传,而是一个纯文本表单域。在这样的字段中发送任意二进制数据在技术上仍然是合法的,但是包括 Python cgi
在内的许多服务器环境会被它混淆。
It would be nice if you could provide encoding=None or something
如果您将 errors
设置为 surrogateescape
,您至少可以从解码的字符中恢复原始字节。
我最终使用更简单的 FieldStorage
子类解决了这个问题,所以我将其发布在这里作为答案。您可以覆盖 __init__
并将文件名添加到 Content-Disposition
header,而不是覆盖 .filename
属性,使其成为 returns 的 属性如果没有为该输入提供一个文件名:
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'