使用Turbogears进行文件上传2

时间:2010-03-03 21:54:36

标签: python turbogears2

我一直在尝试使用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 个答案:

答案 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支持LocalFileStorageMongoDBGridFSStorage和亚马逊的S3Storage。并且,至少对于存储在本地和S3中的文件,fileid将由uuid.uuid1()生成。

答案 1 :(得分:1)

我不太了解Turbogears以及它是否可以提供任何东西以避免以下情况,但在我看来,这段代码充满了危险。恶意用户可能会覆盖(或创建)Turbogears python进程具有写访问权限的任何文件。

如果asset_dirname/tmpfile.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类型检测。

无论如何,应通过记录标识或类似名称为要保存的文件赋予唯一的名称,并将其保存在应用目录所有者(是常规用户)或其他某些存储服务的控制下,作为上述仓库等。

这是跨语言良好系统设计的问题:)。