2017-02-13 38 views
0

Когда я использую cgi.FieldStorage для анализа запроса multipart/form-data (или любой веб-структуры, такой как Pyramid, которая использует cgi.FieldStorage) У меня есть проблема с загрузкой файлов с определенных клиентов, которые не содержат filename=file.ext в заголовке Content-Disposition.cgi.FieldStorage с multipart/form-data пытается декодировать двоичный файл как UTF-8, если «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 делает кажутся действительными в соответствии со спецификацией).

В настоящее время я использую довольно Hacky обходной путь подклассов 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 не декодировать, как UTF-8? Было бы неплохо, если бы вы могли предоставить encoding=None или что-то в этом роде, но это не похоже на то, что это поддерживает.

ответ

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' 
1

У меня возникли проблемы с загрузкой файлов с определенных клиентов, которые не предоставляют имя_файла = file.ext в заголовке Content-Disposition.

Параметр filename = фактически является единственным способом, с помощью которого сторона сервера может определить, что часть представляет загрузку файла. Если клиент опускает этот параметр, он не отправляет загрузку файла, а представляет собой текстовое поле. По-прежнему технически законно отправлять произвольные двоичные данные в таком поле, но многие серверные среды, включая Python cgi, будут смущены им.

Было бы хорошо, если бы вы не могли бы обеспечить кодирование = None или что-то

Если вы установите errors в surrogateescape вы, по крайней мере, быть в состоянии восстановить исходные байты из расшифрованных символов.

+0

Установка ошибок = 'surrogateescape' и затем делать' string.encode ('UTF-8', 'surrogateescape') 'хороший хак, спасибо! –