PyQt“受保护”文件对话框

问题描述 投票:0回答:1

我在基于 Linux 的系统上使用 PYQt 5; 目前,我需要让用户插入 USB 笔驱动器并将一些文件保存到其中,或从中打开一些文件(即软件升级或配置)。 显示一个文件对话框,我可以在其中保存或选择文件似乎是一个简单的任务。

但另外我希望用户仅浏览 USB 笔驱动器(即 /mnt/myUSB/..) 前进和后退,但不要返回系统目录并执行一些可能有害的操作。

有人这样做过吗?

执行此操作会在 /mnt 中打开对话框,但不会阻止用户浏览回 /usr/local/bin 或类似的“危险”路径。

    dialog = QtWidgets.QFileDialog(self) 
    dialog.setDirectory('/mnt')
    dialog.setFileMode(QtWidgets.QFileDialog.AnyFile) 
    dialog.exec_()
linux pyqt filedialog
1个回答
0
投票

可以通过使用 QFileDialog 的一些已知功能和一些未记录的方面来实现。

为此有两个主要要求:

  1. 设置一个代理模型,允许过滤视图可访问的路径;
  2. 使用非本机文件对话框,因为这是在显示文件系统内容的视图中使用代理模型的唯一方法;

第一个要求是通过使用

setProxyModel()
和实现
filterAcceptsRow()
的 QSortFilterProxyModel 子类来完成:

class LimitedAccessProxy(QSortFilterProxyModel):
    def __init__(self, basePath):
        super().__init__()
        self.base = basePath

    def lessThan(self, left, right):
        # required to keep the original order
        return left.row() < right.row()

    def filterAcceptsRow(self, row, parent):
        if row < 0:
            return super().filterAcceptsRow(row, parent)
        path = self.sourceModel().filePath(self.sourceModel().index(row, 0, parent))
        return path.startswith(self.base) or self.base.startswith(path)

这将确保仅显示“base”目录内的路径。

self.base.startswith(path)
部分还确保所有父目录都可供视图使用,这是访问基本路径本身所必需的:如果任何父目录被过滤掉,则永远不会到达基本路径。 使用方法如下:

dialog = QFileDialog() dialog.setFileMode(QFileDialog.AnyFile) dialog.setOption(QFileDialog.DontUseNativeDialog) dialog.setDirectory('/tmp') dialog.setProxyModel(LimitedAccessProxy('/tmp')) dialog.open()

现在,这并不能完全阻止用户在目录结构中“向上”移动。在您的具体情况下,这可能不是一个大问题,因为 
/tmp

的父目录是根目录,它不向标准用户提供写访问权限。 但是,如果目标目录是另一个

可写
目录的子目录,这可能是一个问题。

为了限制该访问,我们可以在当前目录发生更改时进行检查,并防止用户永远导航父目录。为此,我们:

当前路径为“基础”路径时禁用“向上”按钮;

每当用户能够浏览不允许的目录时,就在历史记录中“后退”(并禁用“前进”按钮);
  • 禁用“查找”组合中引用不需要的路径的项目;
  • 覆盖
  • accept()
  • 函数,这样它就不会无意中接受在文件名字段中手动输入的
  • entered
  • 路径;
    
    class RestrictedFileDialog(QFileDialog):
        def __init__(self, path, parent=None):
            super().__init__(parent)
            self.setFileMode(QFileDialog.AnyFile)
            self.setOption(QFileDialog.DontUseNativeDialog)
            self.setDirectory(path)
            if path != QDir.rootPath():
                self.path = path.rstrip(QDir.separator())
                self.proxy = LimitedAccessProxy(self.path)
                self.setProxyModel(self.proxy)
                self.lookInCombo = self.findChild(QComboBox, 'lookInCombo')
                self.backButton = self.findChild(QToolButton, 'backButton')
                self.forwardButton = self.findChild(QToolButton, 'forwardButton')
                self.toParentButton = self.findChild(QToolButton, 'toParentButton')
    
                self.directoryEntered.connect(self._checkDirectory)
                self.backButton.clicked.connect(self._historyFix)
                self.forwardButton.clicked.connect(self._historyFix)
                self.lookInCombo.model().rowsInserted.connect(self._checkLookInCombo)
    
        def _checkLookInCombo(self):
            model = self.lookInCombo.model()
            for r in range(model.rowCount()):
                index = model.index(r, 0)
                url = index.data(Qt.UserRole + 1)
                if isinstance(url, QUrl):
                    path = url.toLocalFile()
                    if not path or not path.startswith(self.path):
                        model.itemFromIndex(index).setEnabled(False)
    
        def _historyFix(self):
            self._checkDirectory(self.directory().absolutePath())
    
        def _checkDirectory(self, path):
            isSub = path.startswith(self.path + QDir.separator())
            self.toParentButton.setEnabled(isSub)
            if not isSub and path != self.path:
                self.backButton.click()
                self.forwardButton.setEnabled(False)
    
        def accept(self):
            files = self.selectedFiles()
            if files:
                info = QFileInfo(files[0])
                if info.absoluteFilePath().startswith(self.path):
                    super().accept()
    
    
    app = QApplication(sys.argv)
    dialog = RestrictedFileDialog('/tmp/coldcase')
    dialog.open()
    sys.exit(app.exec_())
    
        
© www.soinside.com 2019 - 2024. All rights reserved.