Загрузка файлов с помощью Turbogears 2

Я пытался разработать «передовой» способ управления загрузкой файлов с помощью Turbogears 2 и до сих пор не нашел никаких примеров. Я нашел способ загрузить файл, но не уверен, насколько он надежен.

Кроме того, как лучше получить имя загруженного файла?

    file = request.POST['file']
    permanent_file = open(os.path.join(asset_dirname,
        file.filename.lstrip(os.sep)), 'w')
    shutil.copyfileobj(file.file, permanent_file)
    file.file.close()
    this_file = self.request.params["file"].filename 
    permanent_file.close()

Итак, если я правильно понимаю, может ли что-то подобное избежать основной проблемы «именования»? id = UUID.

    file = request.POST['file']
    permanent_file = open(os.path.join(asset_dirname,
        id.lstrip(os.sep)), 'w')
    shutil.copyfileobj(file.file, permanent_file)
    file.file.close()
    this_file = file.filename
    permanent_file.close()

person William Chambers    schedule 03.03.2010    source источник
comment
Да, использование uuid1().hex или uuid4().hex решит проблему именования и большинство проблем с безопасностью. Вам не нужно вызывать lstrip() для uuid (не используйте id в качестве имени переменной - он маскирует встроенную id()). Итак, используйте uuid, чтобы сгенерировать уникальное имя и скопировать загруженные данные в файл с таким именем в вашем каталоге загрузки. Если вам нужно сохранить имя файла, указанное пользователем, сохраните его как метаданные, возможно, в своей базе данных. Вы столкнетесь с дополнительными проблемами безопасности, некоторые из которых описаны здесь: scanit.be/uploads/php-file-upload.pdf   -  person mhawke    schedule 08.03.2010
comment
Да, спасибо за помощь, mhawke. Отличное чтение. Из любопытства, есть ли реальная причина использовать uuid4 (). Hex вместо uuid4 ()? Первый генерирует несколько более удобный для человека UUID для использования в URL-адресах и т.п. (Я думаю о создании двух UUID, один для URL-адреса загрузки, а второй для фактического имени файла / идентификатора.   -  person William Chambers    schedule 08.03.2010
comment
За исключением длины имени файла, uuid4().hex и str(uuid4()) более или менее одинаковы. Я не вижу никаких преимуществ в наличии независимых UUID для URL-адреса и имени файла, поскольку теперь вам понадобится дополнительный слой для сопоставления UUID в URL-адресе с фактическим файлом.   -  person mhawke    schedule 09.03.2010


Ответы (6)


@mhawke - вы правы, вы должны справиться с этим - зависит от того, что вы делаете с файлом, если не имеет значения, есть ли конфликт имен, например, вы заботитесь только о последней версии некоторых данных, тогда, вероятно, нет проблема, или если имя файла на самом деле не важно, только его содержимое, но это все еще плохая практика.

Вы можете использовать именованный временный файл в каталоге tmp, а затем переместить файл после проверки в его окончательное расположение. Или вы можете проверить, что имя файла еще не существует, например:

file.name = slugify(myfile.filename)
name, ext = os.path.splitext(file.name)
while os.path.exists(os.path.join(permanent_store, file.name)):
    name += '_'
    file.name = name + ext

raw_file = os.path.join(permanent_store, file.name)

Метод slugify будет использоваться для очистки имени файла ...

person Ross    schedule 05.03.2010
comment
Теперь мы переходим к условиям гонки. Между проверкой, существует ли уже файл и его фактическим созданием, будет промежуток времени. Что, если 2 пользователя одновременно загрузят файл с одинаковым именем? os.path.exists() может вернуть False в обоих случаях, тогда, когда файлы будут созданы, один файл перезапишет другой. - person mhawke; 08.03.2010

Я просто хочу, чтобы все, кто приходит сюда в поисках ответов, знали, что отличная библиотека Аллесандро Молины Depot представляет собой лучший ответ на этот вопрос.

Он решает как проблемы с именованием, так и с копированием, и он прекрасно впишется в ваше приложение TurboGears. Вы можете использовать его с MongoDB GridFS, как в этом примере:

from depot.manager import DepotManager

# Configure a *default* depot to store files on MongoDB GridFS
DepotManager.configure('default', {
    'depot.backend': 'depot.io.gridfs.GridFSStorage',
    'depot.mongouri': 'mongodb://localhost/db'
})

depot = DepotManager.get()

# Save the file and get the fileid
fileid = depot.create(open('/tmp/file.png'))

# Get the file back
stored_file = depot.get(fileid)
print stored_file.filename
print stored_file.content_type

или вы можете легко создать поля вложений в своих моделях SQLAlchemy, например:

from depot.fields.sqlalchemy import UploadedFileField

class Document(Base):
    __tablename__ = 'document'

    uid = Column(Integer, autoincrement=True, primary_key=True)
    name = Column(Unicode(16), unique=True)

    content = Column(UploadedFileField)

… А затем сохранить документы с прикрепленными файлами (источником может быть файл или байты) становится так же просто, как:

doc = Document(name=u'Foo', content=open('/tmp/document.xls'))
DBSession.add(doc)

Depot поддерживает как LocalFileStorage, MongoDB, _ 5_ и S3Storage от Amazon. И, по крайней мере, для файлов, хранящихся локально и в S3, fileid будет сгенерирован uuid.uuid1().

person Martin Thorsen Ranang    schedule 20.04.2015

Я мало знаю о Turbogears и о том, может ли он что-нибудь дать, чтобы избежать следующего, но мне кажется, что этот код чреват опасностью. Злоумышленник может перезаписать (или создать) любой файл, к которому процесс Python Turbogears имеет доступ для записи.

Что, если asset_dirname равно /tmp, содержимое file.filename равно ../../../../../../../etc/passwd, а содержимое файла root::0:0:root:/root:/bin/bash? В среде UNIX этот код (ожидающие разрешения) откроет файл /tmp/../../../../../../../etc/passwd в режиме усечения, а затем скопирует в него содержимое загруженного файла, эффективно перезаписав файл паролей вашей системы и указав пользователя root без пароля. Предположительно, есть неприятные вещи, которые можно сделать и с машиной Windows.

Хорошо, это крайний пример, который требует, чтобы python работал как root (никто этого не делает, не так ли?). Даже если python работает как пользователь с низким уровнем привилегий, ранее загруженные файлы могут быть перезаписаны по желанию.

Подводя итог, не доверяйте вводу пользователя, в данном случае указанному пользователем имени файла, доступному в file.filename.

person mhawke    schedule 04.03.2010
comment
Да, я понимаю, что это рискованная система. Я не совсем уверен в том, как python обычно обрабатывает файлы (переход с фона PHP, поэтому это немного сложное изменение, хотя мне нравится python). Вот почему я надеюсь, что кто-то, кто разработал лучшую систему загрузки Turbogears, узнает и выручайте. И спасибо, это полезный первый шаг. :) - person William Chambers; 04.03.2010
comment
Я думаю, что turbogears предоставляет набор полезных элементов управления, называемых виджетами Tosca, и что есть виджет для загрузки файлов. Я бы посоветовал изучить это, так как вы, надеюсь, в конечном итоге получите что-то более стандартное (по сравнению с турбогенераторами), которое, как мы надеемся, позволит избежать тех видов уязвимостей, которые я указал в вашем примере. - person mhawke; 04.03.2010
comment
Большое спасибо за эту идею. Похоже, что у Tosca Widgets действительно есть поле «файловое поле». Их документы несколько сбивают с толку, но я пока это нашел. toscawidgets.org/documentation/tw.forms/modules/fields/ - person William Chambers; 04.03.2010

Разве турбоагрегаты не являются просто пилонами с дополнительными принадлежностями? Вы можете проверить справку там:

http://wiki.pylonshq.com/display/pylonsdocs/Form+Handling#file-uploads

Тем не менее, это все еще содержит потенциальную брешь в безопасности, о которой упоминал Мхок:

os.path.join(permanent_store, myfile.filename.lstrip(os.sep))

То же, что и выше, если имя файла каким-то образом было ../../../../../etc/passwd, вы можете заменить этот файл ...

Таким образом, вы можете просто получить фактическое имя файла следующим образом:

os.path.join(permanent_store, myfile.filename.split(os.sep).pop())
person Ross    schedule 04.03.2010
comment
С os.path.join(permanent_store,myfile.filename.split(os.sep).pop()) все еще существует проблема доверия данным, предоставленным пользователем. Что, если пользователи загружают файлы с одинаковым базовым именем? Если базовое имя противоречит друг другу, предварительно загруженные файлы все равно будут перезаписаны. - person mhawke; 05.03.2010
comment
Итак, можно ли было бы удалить все косые черты из имени файла? - person William Chambers; 05.03.2010
comment
Ну, pop просто использует имя файла и игнорирует путь и предотвращает уязвимость безопасности при доступе за пределами вашего временного местоположения. - person Ross; 05.03.2010

Werkzeug имеет очень хорошую вспомогательную функцию для защиты имен файлов, которая называется secure_filename < / а>. Я думаю, вы можете принять и использовать его.

person Mengu    schedule 08.03.2010

о том, как идти, я бы согласился с уже данными хорошими ответами.

вот мои 2 пенса об именах сохраненных файлов.

действительно, сохранение файла с исходным именем может вызвать уязвимость. единственное использование оригинального имени, если оно вообще используется, - это намек на определение типа mime.

в любом случае файлам, которые нужно сохранить, должны быть присвоены уникальные имена, идентичность записи или что-то подобное, и они должны храниться в месте под контролем владельца каталога приложения, который является обычным пользователем, или в какой-либо другой службе хранения, как упомянуто выше депо и т. д.

это вопрос кросс-языковой разработки хорошей системы :).

person alex    schedule 12.09.2019