我一直在尝试使用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()
答案 0 :(得分:2)
我只是希望有人来到这里寻找答案,要知道Allesandro Molina伟大的图书馆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的GridFSStorage
和亚马逊的S3Storage
。并且,至少对于存储在本地和S3中的文件,fileid
将由uuid.uuid1()
生成。
答案 1 :(得分:1)
我不太了解Turbogears以及它是否可以提供任何东西以避免以下情况,但在我看来,这段代码充满了危险。恶意用户可能会覆盖(或创建)Turbogears python进程具有写访问权限的任何文件。
如果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
中提供的用户提供的文件名。
答案 2 :(得分:1)
@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方法将用于整理文件名...
答案 3 :(得分:0)
turbogears不仅仅是带有额外设施的挂架吗?你可以在那里查看帮助:
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())
答案 4 :(得分:0)
Werkzeug有一个很好的辅助函数来保护名为secure_filename的文件名。我想你可以采用和使用它。
答案 5 :(得分:-1)
关于操作方法,我将给出已经给出的好的答案。
这是我关于存储文件命名的2便士。
实际上使用原始名称保存文件可能会导致漏洞。 如果可以的话,我唯一使用原始名称的目的就是提示mime类型检测。
无论如何,应通过记录标识或类似名称为要保存的文件赋予唯一的名称,并将其保存在应用目录所有者(是常规用户)或其他某些存储服务的控制下,作为上述仓库等。
这是跨语言良好系统设计的问题:)。