使用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()
6 个回答
我对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
。
我想告诉那些来这里寻找答案的人,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()
生成。
@mhawke - 你说得对,这个问题需要处理。具体要看你对文件的使用情况。如果文件名冲突不重要,比如你只关心某些数据的最新版本,那就没什么问题;或者如果文件名本身不重要,只在乎文件的内容,那也可以,但这样做还是不太好。
你可以在一个临时目录里使用一个有名字的临时文件,然后在验证完后再把文件移动到最终的位置。或者你可以像下面这样检查文件名是否已经存在:
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方法可以用来整理文件名,让它更整洁...