Skip to content

Conversation

@lichaofan2008
Copy link

@lichaofan2008 lichaofan2008 commented Nov 27, 2025

fix the tips of unable to open file.

Log: fix the tips of unable to open file.
Bug: https://pms.uniontech.com/bug-view-271335.html

画板
v20 BUG 分支合一到v25主线
Task: https://pms.uniontech.com/task-view-383477.html

Summary by Sourcery

Handle permission-controlled image files and update user-facing error messages when opening files fails.

Bug Fixes:

  • Differentiate between permission errors and damaged files when failing to load images.

Enhancements:

  • Query the system FileArmor service to detect whether a file path is under permission control before attempting to load images.

fix the tips of unable to open file.

Log: fix the tips of unable to open file.
Bug: https://pms.uniontech.com/bug-view-271335.html

画板
v20 BUG 分支合一到v25主线
Task: https://pms.uniontech.com/task-view-383477.html
@sourcery-ai
Copy link

sourcery-ai bot commented Nov 27, 2025

Reviewer's Guide

Adds a permission-checking pathControl helper that queries FileArmor over D-Bus before loading images, and wires it into loadImage to report a more accurate error when the file cannot be opened due to permissions, including updating the Chinese translation resources.

Sequence diagram for loadImage with FileArmor permission check

sequenceDiagram
    participant Caller
    participant FileHander
    participant QStandardPaths
    participant QDBusInterface as FileArmorProxy
    participant QDBusMessage as DBusReply

    Caller->>FileHander: loadImage(file)
    activate FileHander
    FileHander->>FileHander: checkFileBeforeLoad(file, false)
    FileHander->>FileHander: toLegalFile(file)
    FileHander->>FileHander: pathControl(legalPath)
    activate FileHander
    FileHander->>QDBusInterface: construct FileArmorProxy
    alt DocumentsLocation
        FileHander->>QStandardPaths: standardLocations(DocumentsLocation)
        QStandardPaths-->>FileHander: docLocations
        FileHander->>FileArmorProxy: call(GetApps, docPath)
        FileArmorProxy-->>FileHander: DBusReply
    end
    alt PicturesLocation
        FileHander->>QStandardPaths: standardLocations(PicturesLocation)
        QStandardPaths-->>FileHander: picLocations
        FileHander->>FileArmorProxy: call(GetApps, picPath)
        FileArmorProxy-->>FileHander: DBusReply
    end
    FileHander->>DBusReply: type()
    DBusReply-->>FileHander: ReplyMessage
    FileHander->>DBusReply: arguments()
    DBusReply-->>FileHander: appList
    FileHander->>QStandardPaths: findExecutable(deepin-draw)
    QStandardPaths-->>FileHander: strApp
    FileHander-->>Caller: true if appList contains strApp
    deactivate FileHander

    alt pathControl returns true
        FileHander->>FileHander: setError(EFileNotExist, No permissions to open it)
        FileHander-->>Caller: null QImage
    else pathControl returns false
        FileHander->>FileHander: loadImage_helper(legalPath, this)
        FileHander-->>Caller: QImage (may be valid or null)
        alt QImage is null
            FileHander->>FileHander: setError(EDamagedImageFile, Damaged file, unable to open it)
        end
    end
    deactivate FileHander
Loading

Class diagram for updated FileHander permission handling

classDiagram
    class FileHander {
        +static bool isLegalFile(QString file)
        +static QString toLegalFile(QString file)
        +static bool pathControl(QString sPath)
        +PageContext* loadDdf(QString file)
        +bool saveToDdf(PageContext* context, QString file)
        +QImage loadImage(QString file)
    }

    class QDBusInterface {
        +QDBusInterface(QString service, QString path, QString interface, QDBusConnection connection)
        +bool isValid()
        +QDBusMessage call(QString method, QString arg1)
    }

    class QDBusMessage {
        +int type()
        +QList~QVariant~ arguments()
    }

    class QStandardPaths {
        +static QStringList standardLocations(int type)
        +static QString findExecutable(QString executableName)
    }

    class QImage {
        +bool isNull()
    }

    class FileArmorService {
        +QDBusMessage GetApps(QString path)
    }

    FileHander ..> QDBusInterface : uses in pathControl
    FileHander ..> QDBusMessage : uses in pathControl
    FileHander ..> QStandardPaths : uses in pathControl
    FileHander ..> QImage : uses in loadImage
    QDBusInterface ..> QDBusMessage : returns
    FileArmorService <.. QDBusInterface : D-Bus proxy
Loading

File-Level Changes

Change Details Files
Introduce a static pathControl helper that queries FileArmor over D-Bus to determine whether the target path is permission-controlled for deepin-draw.
  • Create a QDBusInterface to com.deepin.FileArmor1 on the system bus and call GetApps() when the path is under the standard Documents or Pictures locations.
  • Capture the D-Bus reply and extract the returned application list when the call succeeds.
  • Resolve the deepin-draw binary path and return true if it is listed in the FileArmor response, indicating access is controlled.
src/service/filehander.cpp
src/service/filehander.h
Use pathControl in image loading to distinguish permission errors from damaged files and surface a specific error message.
  • Call pathControl on the normalized image path before checking for image load failure.
  • When pathControl reports control and the image is null, set an EFileNotExist error with a 'No permissions to open it' message and log an appropriate warning.
  • Fall back to the existing damaged-file handling when permissions are not the cause but the image fails to load.
src/service/filehander.cpp
Add a localized user-facing string for the new no-permissions error.
  • Introduce a new translatable source string 'No permissions to open it'.
  • Provide the Simplified Chinese translation '没有权限打开'.
  • Associate the translation entry with filehander.cpp so that it is picked up by the UI where the error is shown.
translations/deepin-draw_zh_CN.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • In pathControl, you assume the DBus reply contains at least one argument and directly call reply.arguments().takeFirst().toStringList(); consider checking that the message type is ReplyMessage and that arguments().size() > 0 before accessing to avoid runtime issues if the service returns an unexpected payload.
  • The duplicated logic for handling Documents and Pictures paths in pathControl (calling standardLocations, picking the first entry, then doing an identical startsWith and GetApps call) could be simplified by iterating over the relevant QStandardPaths::StandardLocation values to reduce duplication and make future changes easier.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `pathControl`, you assume the DBus reply contains at least one argument and directly call `reply.arguments().takeFirst().toStringList()`; consider checking that the message type is `ReplyMessage` and that `arguments().size() > 0` before accessing to avoid runtime issues if the service returns an unexpected payload.
- The duplicated logic for handling Documents and Pictures paths in `pathControl` (calling `standardLocations`, picking the first entry, then doing an identical `startsWith` and `GetApps` call) could be simplified by iterating over the relevant `QStandardPaths::StandardLocation` values to reduce duplication and make future changes easier.

## Individual Comments

### Comment 1
<location> `src/service/filehander.cpp:687-694` </location>
<code_context>
+        QStringList tmpDocLocation = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation);
+        if (tmpDocLocation.size() > 0) {
+            QString docPath = tmpDocLocation.first();
+            if (sPath.startsWith(docPath)) {
+                reply = iface.call("GetApps", docPath);
+            }
+        }
+        QStringList tmpPicLocation = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation);
+        if (tmpPicLocation.size() > 0) {
+            QString picPath = tmpPicLocation.first();
+            if (sPath.startsWith(picPath)) {
+                reply = iface.call("GetApps", picPath);
+            }
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Using `startsWith` for path matching can incorrectly classify similarly-prefixed directories as protected.

Because `sPath.startsWith(docPath)` / `sPath.startsWith(picPath)` also matches paths that only share a prefix (e.g. `/home/user/Documents2/...`), permissions could be applied to directories outside the intended Documents/Pictures folders.

To avoid this, normalize both paths and enforce a directory boundary, e.g.:
- Ensure `docPath`/`picPath` end with a path separator before the `startsWith` check, or
- Canonicalize and compare directories via `QDir` (e.g. using `QDir(docPath)` and a helper like `isRelativeTo` if available).

This prevents false positives on similarly named directories.

Suggested implementation:

```cpp
        QStringList tmpDocLocation = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation);
        if (tmpDocLocation.size() > 0) {
            QString docPath = QDir::cleanPath(tmpDocLocation.first());
            QString normalizedPath = QDir::cleanPath(sPath);

            QString docPathWithSep = docPath;
            if (!docPathWithSep.endsWith(QDir::separator())) {
                docPathWithSep += QDir::separator();
            }

            if (normalizedPath == docPath || normalizedPath.startsWith(docPathWithSep)) {
                reply = iface.call("GetApps", docPath);
            }
        }

        QStringList tmpPicLocation = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation);
        if (tmpPicLocation.size() > 0) {
            QString picPath = QDir::cleanPath(tmpPicLocation.first());
            QString normalizedPath = QDir::cleanPath(sPath);

            QString picPathWithSep = picPath;
            if (!picPathWithSep.endsWith(QDir::separator())) {
                picPathWithSep += QDir::separator();
            }

            if (normalizedPath == picPath || normalizedPath.startsWith(picPathWithSep)) {
                reply = iface.call("GetApps", picPath);
            }

```

1. At the top of `src/service/filehander.cpp`, ensure you include the QDir header:
   - Add `#include <QDir>` alongside the other Qt includes if it is not already present.
2. If `pathControl` is performance-sensitive and called very frequently, consider factoring the normalization of `sPath` out so it is computed once per function call rather than once per location block.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@deepin-ci-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: lichaofan2008

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@deepin-ci-robot
Copy link

deepin pr auto review

我来对这个代码diff进行审查,主要关注语法逻辑、代码质量、性能和安全性方面:

  1. 语法逻辑:
  • 代码语法正确,符合Qt/C++规范
  • pathControl函数中的逻辑判断顺序合理,先检查DBus接口有效性,再处理路径判断
  1. 代码质量:
  • pathControl函数中的代码存在重复,对Documents和Pictures目录的处理逻辑几乎相同,建议抽取为通用函数
  • 函数注释不够详细,应该说明返回值的含义和具体的使用场景
  • 变量命名可以更规范,如tmpDocLocation可以改为documentLocations
  1. 性能优化:
  • QStandardPaths::standardLocations()被多次调用,可以缓存结果
  • pathControl函数中有重复的DBus调用,可以考虑合并
  1. 安全性:
  • pathControl函数中的路径比较使用startsWith(),可能存在路径遍历风险,建议使用更严格的路径验证
  • 没有对DBus调用的返回值进行充分的错误处理

改进建议:

bool FileHander::pathControl(const QString &sPath)
{
    static const QStringList standardPaths = {
        QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation).first(),
        QStandardPaths::standardLocations(QStandardPaths::PicturesLocation).first()
    };

    QDBusInterface iface("com.deepin.FileArmor1", "/com/deepin/FileArmor1", 
                         "com.deepin.FileArmor1", QDBusConnection::systemBus());
    if (!iface.isValid()) {
        return false;
    }

    // 检查路径是否在受控目录下
    QString matchedPath;
    for (const QString &standardPath : standardPaths) {
        if (sPath.startsWith(standardPath + "/")) {  // 添加'/'确保完整目录匹配
            matchedPath = standardPath;
            break;
        }
    }

    if (matchedPath.isEmpty()) {
        return false;
    }

    // 执行DBus调用并处理返回值
    QDBusMessage reply = iface.call("GetApps", matchedPath);
    if (reply.type() != QDBusMessage::ReplyMessage) {
        qWarning() << "DBus call failed:" << reply.errorMessage();
        return false;
    }

    // 检查应用权限
    const QString deepinDrawPath = QStandardPaths::findExecutable("deepin-draw");
    if (deepinDrawPath.isEmpty()) {
        return false;
    }

    const QStringList allowedApps = reply.arguments().takeFirst().toStringList();
    return allowedApps.contains(deepinDrawPath);
}

其他改进建议:

  1. 在loadImage函数中,错误处理逻辑需要调整:
if (img.isNull()) {
    if (pathControl(legalPath)) {
        qWarning() << "Failed to load image: No permissions";
        d_pri()->setError(EFileNotExist, tr("No permissions to open it"));
    } else {
        qWarning() << "Failed to load image, file may be damaged";
        d_pri()->setError(EDamagedImageFile, tr("Damaged file, unable to open it"));
    }
}
  1. 建议添加更多的错误日志,便于调试和问题追踪

  2. 考虑将DBus接口相关的配置定义为常量,便于维护

  3. 建议添加单元测试来验证pathControl函数的各种边界情况

  4. 考虑使用QDir的canonicalPath()来处理路径,避免符号链接等问题

这些改进可以提高代码的可维护性、安全性和性能。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants