使用Turbogears 2进行文件上传

6 投票
6 回答
2372 浏览
提问于 2025-04-15 19:58

我一直在尝试找出在使用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 个回答

2

我对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

3

我想告诉那些来这里寻找答案的人,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 支持 LocalFileStorageMongoDBGridFSStorage,以及亚马逊的 S3Storage。而且,至少对于本地存储和 S3 中的文件,fileid 将由 uuid.uuid1() 生成。

2

@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方法可以用来整理文件名,让它更整洁...

撰写回答