Skip to content

Conversation

@lichaofan2008
Copy link

@lichaofan2008 lichaofan2008 commented Dec 1, 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 paths before loading and adjust user-facing error messages accordingly.

Bug Fixes:

  • Show a specific 'No permissions to open it' error when attempting to open images in permission-controlled document or picture paths instead of misreporting them as damaged files.

Enhancements:

  • Introduce a path permission control helper that queries the FileArmor D-Bus service to detect restricted document and picture locations before image loading.

@sourcery-ai
Copy link

sourcery-ai bot commented Dec 1, 2025

Reviewer's Guide

Adds permission-control awareness when loading images so that files blocked by FileArmor produce a clear 'no permission' error instead of a generic damaged-file error, including a new helper for permission checks and updated translations.

Sequence diagram for updated image loading with permission control

sequenceDiagram
    actor User
    participant FileHander
    participant FileArmor1

    User->>FileHander: loadImage(file)
    FileHander->>FileHander: checkFileBeforeLoad(file, false)
    FileHander->>FileHander: legalPath = toLegalFile(file)
    FileHander->>FileHander: img = loadImage_helper(legalPath, this)
    FileHander->>FileHander: pathControl(legalPath)
    activate FileHander
    FileHander->>FileArmor1: GetApps(docPath_or_picPath)
    FileArmor1-->>FileHander: QDBusMessage reply_with_app_list
    FileHander-->>FileHander: isBlocked = app_list_contains_deepin_draw
    deactivate FileHander

    alt File_blocked_by_FileArmor
        FileHander->>FileHander: setError(EFileNotExist, No_permissions_to_open_it)
        FileHander-->>User: return_empty_QImage()
    else File_not_blocked_and_image_invalid
        FileHander->>FileHander: img.isNull()
        FileHander->>FileHander: setError(EDamagedImageFile, Damaged_file_unable_to_open_it)
        FileHander-->>User: return_empty_QImage()
    else File_not_blocked_and_image_valid
        FileHander-->>User: return_QImage(img)
    end
Loading

Class diagram for FileHander with new pathControl method

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

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

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

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

    FileHander ..> QDBusInterface : uses
    FileHander ..> QDBusMessage : uses
    FileHander ..> QStandardPaths : uses
Loading

File-Level Changes

Change Details Files
Introduce a static helper to check whether a given path is controlled/blocked by FileArmor and use it when loading images.
  • Add FileHander::pathControl static method to query com.deepin.FileArmor1 over the system D-Bus for apps blocked for the Documents and Pictures standard locations.
  • Cache standard document and picture locations using QStandardPaths and compare the input path prefix to decide which directory to query.
  • Inspect the D-Bus reply, get the blocked application list, and return true when deepin-draw is among the blocked apps, otherwise false.
src/service/filehander.cpp
src/service/filehander.h
Adjust image loading logic to distinguish permission errors from damaged files and set appropriate error messages.
  • Call pathControl before interpreting a null QImage as a damaged file.
  • If pathControl reports that the path is controlled, log a permission error, set EFileNotExist with a 'No permissions to open it' message, and return an empty QImage.
  • If the image is still null and no permission issue is detected, keep the existing damaged-file handling but return early after setting the error.
src/service/filehander.cpp
Add a localized error string for the no-permission case in the Chinese translations.
  • Introduce the 'No permissions to open it' source string with its Simplified Chinese translation.
  • Align translation locations with the updated filehander.cpp line numbers.
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 only check the first entry from DocumentsLocation and PicturesLocation; if multiple locations are configured (e.g., multiple mounted dirs), consider iterating all returned paths to avoid missing controlled files.
  • The name and comment for pathControl suggest it returns whether the path is under access control, but the implementation returns true only when access is denied for deepin-draw; consider renaming or clarifying the comment to reflect that it specifically checks for denied permission for this app.
  • Because pathControl performs a synchronous D-Bus call on every image load for applicable paths, consider caching the result per directory or file to avoid repeated blocking calls in hot paths like loadImage.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `pathControl`, you only check the first entry from `DocumentsLocation` and `PicturesLocation`; if multiple locations are configured (e.g., multiple mounted dirs), consider iterating all returned paths to avoid missing controlled files.
- The name and comment for `pathControl` suggest it returns whether the path is under access control, but the implementation returns true only when access is denied for deepin-draw; consider renaming or clarifying the comment to reflect that it specifically checks for denied permission for this app.
- Because `pathControl` performs a synchronous D-Bus call on every image load for applicable paths, consider caching the result per directory or file to avoid repeated blocking calls in hot paths like `loadImage`.

## Individual Comments

### Comment 1
<location> `src/service/filehander.cpp:691-696` </location>
<code_context>
+        return false;
+    }
+
+    if (tmpDocLocation.size() > 0) {
+        QString docPath = tmpDocLocation.first();
+        if (sPath.startsWith(docPath)) {
+            reply = iface.call("GetApps", docPath);
+        } else {
+            if (tmpPicLocation.size() > 0) {
+                QString picPath = tmpPicLocation.first();
+                if (sPath.startsWith(picPath)) {
</code_context>

<issue_to_address>
**issue (bug_risk):** Picture location is only checked when a documents location exists, which looks unintended.

Because the `tmpPicLocation` branch is nested inside the `tmpDocLocation.size() > 0` check, picture paths are never evaluated when there are no document locations. If Documents and Pictures should be checked independently, consider restructuring so each is handled in its own `if` block, e.g.:

```cpp
if (!tmpDocLocation.isEmpty()) {
    const QString &docPath = tmpDocLocation.first();
    if (sPath.startsWith(docPath)) {
        reply = iface.call("GetApps", docPath);
    }
}

if (!reply.isValid() && !tmpPicLocation.isEmpty()) {
    const QString &picPath = tmpPicLocation.first();
    if (sPath.startsWith(picPath)) {
        reply = iface.call("GetApps", picPath);
    }
}
```
</issue_to_address>

### Comment 2
<location> `src/service/filehander.cpp:705-706` </location>
<code_context>
+
+    if (reply.type() == QDBusMessage::ReplyMessage) {
+        QList<QString> lValue = reply.arguments().takeFirst().toStringList();
+        QString strApp = QStandardPaths::findExecutable("deepin-draw");
+        // 此路径对画板进行了权限禁用
+        if (lValue.contains(strApp)) {
+            return true;
+        }
</code_context>

<issue_to_address>
**suggestion:** Handling of an empty executable path from `findExecutable` is unclear and may lead to accidental matches.

`QStandardPaths::findExecutable` returns an empty string when the binary is not found. In that case, `lValue.contains(strApp)` becomes `lValue.contains("")`, which could match an empty entry in the D-Bus reply. Please guard against this, e.g.:

```cpp
QString strApp = QStandardPaths::findExecutable("deepin-draw");
if (!strApp.isEmpty() && lValue.contains(strApp)) {
    return true;
}
```

This ensures we only treat the path as controlled when the executable is actually resolvable.

```suggestion
    if (reply.type() == QDBusMessage::ReplyMessage) {
        QList<QString> lValue = reply.arguments().takeFirst().toStringList();
        QString strApp = QStandardPaths::findExecutable("deepin-draw");
        // 此路径对画板进行了权限禁用
        if (!strApp.isEmpty() && lValue.contains(strApp)) {
            return true;
        }
```
</issue_to_address>

### Comment 3
<location> `src/service/filehander.cpp:780-781` </location>
<code_context>
         QImage img = loadImage_helper(legalPath, this);
-        if (img.isNull()) {
+        if (pathControl(legalPath)){
+            qWarning() << "Failed to load image: No permissions";
+            d_pri()->setError(EFileNotExist, tr("No permissions to open it"));
+            return QImage();
+        } else if (img.isNull()) {
</code_context>

<issue_to_address>
**issue (bug_risk):** Using `EFileNotExist` for a permission-denied case may be misleading for callers.

This branch reports a permission error using `EFileNotExist` and the message "No permissions to open it". If callers distinguish between missing files and permission failures, this can cause incorrect behavior (e.g., recreating a file instead of prompting for permission changes). Prefer a dedicated permission error (e.g. `EPermissionDenied`), or confirm that all existing callers truly treat this as equivalent to "file not found" and won't mis-handle it.
</issue_to_address>

### Comment 4
<location> `src/service/filehander.cpp:679` </location>
<code_context>
     return result;
 }

+bool FileHander::pathControl(const QString &sPath)
+{
+    static QStringList tmpDocLocation = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation);
</code_context>

<issue_to_address>
**issue (complexity):** Consider flattening the branching in `pathControl` and checking permissions before actually loading the image in `loadImage` to simplify control flow and make the logic easier to follow.

You can keep the behavior but reduce complexity and make the flow clearer with a small refactor of `pathControl` and a tiny reorder in `loadImage`.

### 1. Flatten `pathControl` branching

The current nesting:

```cpp
if (tmpDocLocation.size() > 0) {
    QString docPath = tmpDocLocation.first();
    if (sPath.startsWith(docPath)) {
        reply = iface.call("GetApps", docPath);
    } else {
        if (tmpPicLocation.size() > 0) {
            QString picPath = tmpPicLocation.first();
            if (sPath.startsWith(picPath)) {
                reply = iface.call("GetApps", picPath);
            }
        }
    }
}
```

can be flattened without changing behavior:

```cpp
bool FileHander::pathControl(const QString &sPath)
{
    static const QStringList tmpDocLocation =
        QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation);
    static const QStringList tmpPicLocation =
        QStandardPaths::standardLocations(QStandardPaths::PicturesLocation);

    QDBusInterface iface("com.deepin.FileArmor1",
                         "/com/deepin/FileArmor1",
                         "com.deepin.FileArmor1",
                         QDBusConnection::systemBus());
    if (!iface.isValid()) {
        qWarning() << "Failed to connect to D-Bus interface";
        return false;
    }

    QString targetPath;
    if (!tmpDocLocation.isEmpty() && sPath.startsWith(tmpDocLocation.first())) {
        targetPath = tmpDocLocation.first();
    } else if (!tmpPicLocation.isEmpty() && sPath.startsWith(tmpPicLocation.first())) {
        targetPath = tmpPicLocation.first();
    }

    if (targetPath.isEmpty())
        return false;

    QDBusMessage reply = iface.call("GetApps", targetPath);
    if (reply.type() != QDBusMessage::ReplyMessage)
        return false;

    const QStringList apps = reply.arguments().takeFirst().toStringList();
    const QString drawPath = QStandardPaths::findExecutable("deepin-draw");
    // 此路径对画板进行了权限禁用
    return apps.contains(drawPath);
}
```

Changes:
- Flatter control flow (no deep nesting).
- Early-return on empty `targetPath` and invalid replies.
- Same semantics: `true` only when the reply is valid and contains `deepin-draw`.

### 2. Check permissions before loading in `loadImage`

Currently you load the image before checking permissions, which complicates reasoning:

```cpp
auto legalPath = toLegalFile(file);
QImage img = loadImage_helper(legalPath, this);
if (pathControl(legalPath)) {
    qWarning() << "Failed to load image: No permissions";
    d_pri()->setError(EFileNotExist, tr("No permissions to open it"));
    return QImage();
} else if (img.isNull()) {
    ...
}
```

You can make the flow simpler and avoid doing work when access is forbidden:

```cpp
QImage FileHander::loadImage(const QString &file)
{
    qDebug() << "Loading image file:" << file;
    d_pri()->unsetError();

    if (!checkFileBeforeLoad(file, false))
        return QImage();

    const auto legalPath = toLegalFile(file);

    if (pathControl(legalPath)) {
        qWarning() << "Failed to load image: No permissions";
        d_pri()->setError(EFileNotExist, tr("No permissions to open it"));
        return QImage();
    }

    QImage img = loadImage_helper(legalPath, this);
    if (img.isNull()) {
        qWarning() << "Failed to load image, file may be damaged";
        d_pri()->setError(EDamagedImageFile, tr("Damaged file, unable to open it"));
        return QImage();
    }

#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
    img = convertToSRgbColorSpace(img);
#else
    img = img.convertToFormat(QImage::Format_ARGB32);
#endif
    return img;
}
```

This keeps all observable behavior intact (same errors, same return values) but:
- Makes the permission check explicit and early.
- Clarifies the meaning of the `pathControl` result in the context of `loadImage`.
</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.

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
@deepin-ci-robot
Copy link

deepin pr auto review

我来对这段代码进行审查,主要从语法逻辑、代码质量、性能和安全性几个方面分析:

  1. 语法逻辑:
  • 代码整体语法正确,符合C++和Qt的语法规范
  • pathControl函数的逻辑流程清晰,错误处理完整
  1. 代码质量:
  • 函数命名pathControl不够直观,建议改为hasPathRestriction或isPathRestricted
  • tmpLocation变量名不够清晰,建议改为allowedLocations
  • D-Bus接口的调用缺少错误处理,应该检查iface.isValid()之外的错误情况
  • 代码注释不够充分,特别是D-Bus接口调用的部分
  1. 代码性能:
  • 每次调用pathControl都会重新获取标准路径和创建D-Bus接口,建议将这些操作缓存起来
  • 可以考虑使用QDir::canonicalPath()来规范化路径比较,避免路径遍历攻击
  1. 代码安全:
  • pathControl函数中的路径比较使用startsWith()可能存在安全问题,建议:
    • 使用QDir::canonicalPath()规范化路径
    • 确保比较时包含路径分隔符,避免误判(如/tmp/test vs /tmp/test1)
  • D-Bus调用没有进行权限验证
  • 没有对输入路径进行合法性检查,可能存在路径遍历风险

建议的改进方案:

bool FileHander::pathControl(const QString &sPath)
{
    if (sPath.isEmpty()) {
        return false;
    }

    // 规范化路径,防止路径遍历攻击
    QString canonicalPath = QDir(sPath).canonicalPath();
    if (canonicalPath.isEmpty()) {
        qWarning() << "Invalid path:" << sPath;
        return false;
    }

    // 缓存标准路径
    static const QStringList allowedLocations = []() {
        QStringList locations;
        locations << QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation);
        locations << QStandardPaths::standardLocations(QStandardPaths::PicturesLocation);
        if (locations.empty()) {
            qWarning() << "Failed to get standard locations";
        }
        return locations;
    }();

    if (allowedLocations.empty()) {
        return false;
    }

    // 检查路径是否在允许的目录中
    bool isInAllowedLocation = false;
    QString matchedLocation;
    for (const QString &location : allowedLocations) {
        QString canonicalLocation = QDir(location).canonicalPath();
        if (canonicalPath.startsWith(canonicalLocation + "/")) {
            isInAllowedLocation = true;
            matchedLocation = canonicalLocation;
            break;
        }
    }

    if (!isInAllowedLocation) {
        return false;
    }

    // 缓存D-Bus接口
    static const QDBusInterface iface("com.deepin.FileArmor1", 
                                    "/com/deepin/FileArmor1", 
                                    "com.deepin.FileArmor1", 
                                    QDBusConnection::systemBus());
    
    if (!iface.isValid()) {
        qWarning() << "Failed to connect to D-Bus interface";
        return false;
    }

    // 调用D-Bus接口
    QDBusMessage reply = iface.call("GetApps", matchedLocation);
    if (reply.type() != QDBusMessage::ReplyMessage) {
        qWarning() << "D-Bus call failed:" << reply.errorMessage();
        return false;
    }

    QList<QString> lValue = reply.arguments().takeFirst().toStringList();
    qDebug() << "App list:" << lValue;
    
    QString strApp = QStandardPaths::findExecutable("deepin-draw");
    if (!strApp.isEmpty() && lValue.contains(strApp)) {
        qWarning() << "Permission denied for app:" << strApp;
        return true;
    }

    return false;
}

这个改进版本:

  1. 增加了路径规范化处理,防止路径遍历攻击
  2. 使用静态变量缓存标准路径和D-Bus接口,提高性能
  3. 改进了路径比较逻辑,确保准确匹配
  4. 增加了更多的错误处理和日志记录
  5. 使用更严格的路径比较(添加了"/"分隔符)
  6. 增加了D-Bus调用的错误处理

这些改进使代码更安全、更高效,同时保持了原有的功能。

@deepin-ci-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: lichaofan2008, lzwind

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

@lichaofan2008
Copy link
Author

/forcemerge

@deepin-bot
Copy link
Contributor

deepin-bot bot commented Dec 2, 2025

This pr force merged! (status: unstable)

@deepin-bot deepin-bot bot merged commit a94713c into linuxdeepin:develop/snipe Dec 2, 2025
15 of 16 checks passed
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.

4 participants