Skip to content

fix(archive): tar.7z use QProcess pipeline instead of shell script#370

Merged
deepin-bot[bot] merged 1 commit intolinuxdeepin:release/eaglefrom
LiHua000:release/eagle
Mar 13, 2026
Merged

fix(archive): tar.7z use QProcess pipeline instead of shell script#370
deepin-bot[bot] merged 1 commit intolinuxdeepin:release/eaglefrom
LiHua000:release/eagle

Conversation

@LiHua000
Copy link
Contributor

@LiHua000 LiHua000 commented Mar 13, 2026

  • Replace temp script with two QProcess (tar | 7z) to avoid command injection and special chars in password breaking the command
  • Add CliProperties::getTar7zTarArgs/getTar7z7zArgs for safe argv
  • Pass 7z password as single "-p"+password to avoid stdin prompt freeze
  • Add killTar7zPipelineIfActive() and writeToProcess guard in pipeline
  • cli7zplugin: call killTar7zPipelineIfActive() in killProcess()

Log: 修复 tar.7z 使用脚本导致的命令注入与密码特殊字符问题,改为双 QProcess 管道并避免加密时卡死

Bug:https://pms.uniontech.com/bug-view-343119.html

Summary by Sourcery

Replace tar.7z compression via temporary shell script with a safer QProcess-based tar→7z pipeline and adjust process handling accordingly.

New Features:

  • Add dedicated CliProperties helpers to build tar and 7z argument lists for tar.7z pipelines without shell concatenation.

Bug Fixes:

  • Eliminate command injection and password special-character issues in tar.7z compression by avoiding shell-constructed command strings.
  • Prevent UI freezes during encrypted tar.7z creation by passing the 7z password as a single argv parameter instead of relying on stdin prompts.
  • Ensure tar.7z pipeline processes are correctly terminated when cancellation is requested or errors occur.

Enhancements:

  • Refine process and stdout handling to support tar.7z two-process pipelines while reusing existing progress parsing and output logic.

- Replace temp script with two QProcess (tar | 7z) to avoid command
  injection and special chars in password breaking the command
- Add CliProperties::getTar7zTarArgs/getTar7z7zArgs for safe argv
- Pass 7z password as single "-p"+password to avoid stdin prompt freeze
- Add killTar7zPipelineIfActive() and writeToProcess guard in pipeline
- cli7zplugin: call killTar7zPipelineIfActive() in killProcess()

Log: 修复 tar.7z 使用脚本导致的命令注入与密码特殊字符问题,改为双 QProcess 管道并避免加密时卡死

Bug:https://pms.uniontech.com/bug-view-343119.html
@sourcery-ai
Copy link

sourcery-ai bot commented Mar 13, 2026

Reviewer's Guide

Refactors tar.7z creation to replace the temporary shell script with a safer two-QProcess tar | 7z pipeline, introduces dedicated argument builders for tar/7z, hardens password handling, and adds specific lifecycle and kill logic for the new pipeline while adapting progress/output parsing accordingly.

Sequence diagram for new tar_7z QProcess pipeline in addFiles

sequenceDiagram
    actor User
    participant Cli7zPlugin
    participant CliInterface
    participant CliProperties
    participant QProcessTar as QProcess_tar
    participant QProcess7z as QProcess_7z

    User->>Cli7zPlugin: requestAddFiles(options.bTar_7z = true)
    Cli7zPlugin->>CliInterface: addFiles(files, options, password)

    activate CliInterface
    CliInterface->>CliInterface: m_isTar7z = true
    CliInterface->>CliInterface: m_filesSize = options.qTotalSize
    CliInterface->>CliProperties: getTar7zTarArgs(fileList, renameList)
    CliProperties-->>CliInterface: tarArgs(QStringList)
    CliInterface->>CliProperties: getTar7z7zArgs(archivePath, password, options.bHeaderEncryption, options.iCompressionLevel, options.strCompressionMethod, options.strEncryptionMethod, options.iVolumeSize)
    CliProperties-->>CliInterface: sevenZArgs(QStringList)

    CliInterface->>CliInterface: tarPath = findExecutable(tar)
    CliInterface->>CliInterface: sevenZPath = findExecutable(addProgram)
    CliInterface->>CliInterface: create m_tarProcess, m_tar7z_7z
    CliInterface->>QProcessTar: setStandardOutputProcess(m_tar7z_7z)
    CliInterface->>QProcess7z: setProcessChannelMode(MergedChannels)
    CliInterface->>CliInterface: connect readyReadStandardOutput -> readStdout
    CliInterface->>CliInterface: connect finished -> processFinished

    CliInterface->>QProcessTar: start(tarPath, tarArgs)
    QProcessTar-->>CliInterface: started
    CliInterface->>CliInterface: waitForStarted(5000)
    alt tar started
        CliInterface->>QProcess7z: start(sevenZPath, sevenZArgs)
        CliInterface->>CliInterface: m_processId = m_tar7z_7z->processId()
        CliInterface->>CliInterface: m_childProcessId = [m_tarProcess->processId()]
        CliInterface->>CliInterface: connect errorOccurred -> lambda(error)
    else tar failed
        CliInterface->>CliInterface: ret = false
        CliInterface->>CliInterface: deleteProcess()
    end

    loop compressionProgress
        QProcess7z-->>CliInterface: stdout (progress, messages)
        CliInterface->>CliInterface: readStdout(handleAll)
        CliInterface->>CliInterface: handleProgress(line)
        CliInterface-->>Cli7zPlugin: signalprogress(percentage)
        CliInterface-->>Cli7zPlugin: signalCurFileName(name)
    end

    QProcess7z-->>CliInterface: finished(exitCode, exitStatus)
    CliInterface->>CliInterface: processFinished(exitCode, exitStatus)
    CliInterface-->>Cli7zPlugin: emitFinished
    Cli7zPlugin-->>User: addFiles result
    deactivate CliInterface
Loading

Sequence diagram for killProcess with tar_7z pipeline

sequenceDiagram
    actor User
    participant Cli7zPlugin
    participant CliInterface
    participant QProcessTar as QProcess_tar
    participant QProcess7z as QProcess_7z

    User->>Cli7zPlugin: cancelOperation()
    Cli7zPlugin->>Cli7zPlugin: killProcess(emitFinished)
    Cli7zPlugin->>CliInterface: killTar7zPipelineIfActive()

    alt tar_7z pipeline active
        CliInterface->>CliInterface: m_tar7z_7z != nullptr
        opt tar still running
            CliInterface->>QProcessTar: kill()
        end
        opt 7z still running
            CliInterface->>QProcess7z: kill()
        end
        CliInterface->>CliInterface: m_isProcessKilled = true
        CliInterface-->>Cli7zPlugin: return true
        Cli7zPlugin-->>User: cancellationDone
    else no tar_7z pipeline
        CliInterface-->>Cli7zPlugin: return false
        Cli7zPlugin->>Cli7zPlugin: fallback kill using m_process
        Cli7zPlugin-->>User: cancellationDone
    end
Loading

Updated class diagram for CliInterface, CliProperties, and Cli7zPlugin

classDiagram
    class CliProperties {
        +QStringList addArgs(archive, files, password, headerEncryption, compressionLevel, compressionMethod, encryptionMethod, volumeSize, isTar7z, globalWorkDir, renameList)
        +QStringList getTar7zTarArgs(files, renameList)
        +QStringList getTar7z7zArgs(archive, password, headerEncryption, compressionLevel, compressionMethod, encryptionMethod, volumeSize)
        +QStringList commentArgs(archive, commentfile)
        +QStringList deleteArgs(archive, files, password)
        +QStringList extractArgs(archive, files, preservePaths, password)
        -QString m_progressarg
        -QString substituteCompressionLevelSwitch(level)
        -QString substituteCompressionMethodSwitch(method)
        -QString substituteEncryptionMethodSwitch(method)
        -QString substituteMultiVolumeSwitch(volumeSize)
    }

    class CliInterface {
        <<abstract>>
        +PluginFinishType addFiles(files, options, password)
        +bool runProcess(programName, args)
        +virtual void killProcess(emitFinished) = 0
        +bool killTar7zPipelineIfActive()
        -void deleteProcess()
        -void handleProgress(line)
        -void writeToProcess(data)
        -void readStdout(handleAll)
        -void processFinished(exitCode, exitStatus)
        -CliProperties* m_cliProps
        -KPtyProcess* m_process
        -QProcess* m_tarProcess
        -QProcess* m_tar7z_7z
        -QVector~qint64~ m_childProcessId
        -qint64 m_filesSize
        -bool m_isTar7z
        -bool m_isProcessKilled
        -QByteArray m_stdOutData
    }

    class Cli7zPlugin {
        +bool isOpenFileFailed(line)
        +void killProcess(emitFinished)
        -CliInterface* m_interface
    }

    CliInterface --> CliProperties : uses
    Cli7zPlugin ..|> CliInterface : implements via composition

    class QProcess {
        +void start(program, arguments)
        +bool waitForStarted(msecs)
        +void setStandardOutputProcess(process)
        +void setProcessChannelMode(mode)
        +qint64 processId()
        +ProcessState state()
        +void kill()
        +void waitForFinished(msecs)
    }

    CliInterface --> QProcess : manages

    class KPtyProcess {
        +QIODevice* pty()
        +QStringList program()
        +qint64 processId()
        +void write(data)
    }

    CliInterface --> KPtyProcess : manages existing process
Loading

File-Level Changes

Change Details Files
Replace temp shell script approach for tar.7z with a QProcess-based tar 7z pipeline and integrate it into addFiles and process lifecycle.
  • In addFiles, detect tar.7z mode and build separate tar and 7z argument lists via CliProperties helpers instead of writing a temporary bash script.
  • Locate tar and 7z executables with QStandardPaths::findExecutable and start two QProcess instances connected via setStandardOutputProcess.
  • Wire 7z process signals (readyReadStandardOutput, finished, errorOccurred) to existing handlers and track process IDs for monitoring.
  • Avoid waitForStarted on the 7z process to prevent UI deadlock when 7z waits for stdin, and ensure failure paths clean up via deleteProcess.
  • Adjust post-compression handling to wait for the tar.7z 7z process when moving the temporary archive.
Add dedicated tar.7z argument builders that construct argv lists safely without shell concatenation, including robust password and option handling for 7z.
  • Remove the previous isTar7z command string construction in addArgs that manually escaped characters and returned a single shell pipeline string.
  • Introduce getTar7zTarArgs to build tar arguments (including rename transforms and -C path handling) as a pure QStringList suitable for QProcess.
  • Introduce getTar7z7zArgs to build 7z arguments, including archive path, compression/encryption options, multi-volume, progress arg, and password handling via a single "-p" + password argument.
  • Ensure argument builders filter out empty strings and no longer rely on shell metacharacter escaping.
3rdparty/interface/archiveinterface/cliproperties.cpp
3rdparty/interface/archiveinterface/cliproperties.h
Introduce explicit management and safe termination of the tar.7z pipeline processes, and integrate this into plugin kill logic.
  • Add killTar7zPipelineIfActive to stop tar and 7z processes if running, set m_isProcessKilled, and return whether a pipeline was active.
  • Extend deleteProcess to recognize and clean up the tar.7z pipeline, blocking signals, killing with timeouts, and deleting both QProcess instances.
  • Expose killTar7zPipelineIfActive in CliInterface header and invoke it from Cli7zPlugin::killProcess before handling the generic m_process path.
  • Remove now-unused script path tracking from CliInterface, since temp scripts are no longer created.
3rdparty/interface/archiveinterface/cliinterface.cpp
3rdparty/interface/archiveinterface/cliinterface.h
3rdparty/cli7zplugin/cli7zplugin.cpp
Adapt stdout reading, progress parsing, and stdin writing to support outputs coming from the new tar.7z 7z process and avoid invalid stdin usage.
  • Update writeToProcess to early-return when tar.7z pipeline is active (stdin belongs to tar, not 7z) and to guard against null m_process.
  • Generalize handleProgress to treat either m_tar7z_7z or a normal 7z process as 7z output, and remove special-case parsing logic for the old tempScript-based tar.7z progress format.
  • Refactor readStdout to select between m_tar7z_7z and m_process for reading, guard against null, and treat tar.7z pipeline output like 7z add output for incomplete lines without newline.
  • Protect unrar-specific handling with an m_process null check to avoid dereferencing when only the pipeline is active.
3rdparty/interface/archiveinterface/cliinterface.cpp

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

@deepin-ci-robot
Copy link

deepin pr auto review

这份代码改动主要针对 tar.7z 格式的压缩流程进行了重构,从之前的 Shell 脚本拼接方式改为使用两个 QProcess 建立管道的方式。这是一个很好的改进方向,能够避免 Shell 转义字符问题和密码安全风险。

以下是对代码的详细审查意见:

1. 语法与逻辑审查

1.1 内存管理风险 (严重)

cliinterface.cppaddFiles 函数中:

m_tar7z_7z = new QProcess();
m_tarProcess = new QProcess();

问题:这里直接使用了裸指针 new。如果在 start 之后、deleteProcess 被调用之前发生异常或提前返回,这两个对象可能会泄漏。
建议:使用 QScopedPointerstd::unique_ptr 来管理这两个进程对象的生命周期。或者确保在所有退出路径(包括错误返回)上都调用了 deleteProcess()。目前的代码在 if (!ret) 分支中调用了 deleteProcess(),这是一个好的开始,但最好能从根本上杜绝泄漏。

1.2 进程启动顺序与状态检查

m_tarProcess->start(tarPath, tarArgs);
if (m_tarProcess->waitForStarted(5000)) {
    m_tar7z_7z->start(sevenZPath, sevenZArgs);
    // ...
    ret = m_tar7z_7z->state() == QProcess::Starting || m_tar7z_7z->state() == QProcess::Running;
    // ...
}

问题:逻辑基本正确,但 waitForStarted(5000) 是阻塞等待。虽然 5 秒通常足够,但在极端情况下(如系统负载极高),可能导致主线程卡顿。
建议:虽然 QProcess 管道连接必须在 start 之前建立,但可以考虑使用异步方式处理启动失败的情况,或者至少在日志中记录启动超时的事件,方便排查问题。

1.3 管道断开处理

connect(m_tar7z_7z, &QProcess::errorOccurred, this, [this](QProcess::ProcessError) {
    if (m_tar7z_7z && m_tar7z_7z->state() == QProcess::NotRunning) {
        processFinished(-1, QProcess::CrashExit);
    }
});

问题:只连接了 7z 进程的 errorOccurred。如果 tar 进程崩溃或写入失败,m_tar7z_7z (7z) 可能会因为读到 EOF 而正常退出,这会被误判为正常结束。
建议:也应该监听 m_tarProcesserrorOccurredfinished 信号。如果 tar 进程异常退出,应当主动 kill 掉 7z 进程并报错,防止产生损坏的压缩包。

2. 代码质量审查

2.1 代码复用与清晰度

CliProperties::getTar7zTarArgsCliProperties::getTar7z7zArgs 将命令参数构建逻辑分离得很好,比之前在 addArgs 中通过字符串拼接要清晰得多,且类型安全。

2.2 注释

新增的注释(如 // 压缩tar.7z:用两个 QProcess 管道...)非常有帮助,解释了改动的意图。

3. 代码性能审查

3.1 字符串操作

getTar7zTarArgs 中:

for (QString file : files) {
    // ...
    if (file.endsWith(QLatin1Char('/'))) {
        file.chop(1);
    }
    // ...
}

问题:这里对文件列表进行了遍历和字符串操作。
建议:这是必要的逻辑,性能影响可忽略。但建议使用 const QString &file 避免不必要的拷贝(虽然现代编译器通常会优化这一点)。

4. 代码安全审查

4.1 密码处理 (优秀)

if (!password.isEmpty()) {
    // 单参数 -p+密码,避免 7z 再从 stdin 提示输入导致管道卡死
    args << (QStringLiteral("-p") + password);
}

评价:这是本次改动最大的安全亮点。之前的 Shell 脚本方式容易导致密码泄露到进程列表或环境变量中,或者被 Shell 历史记录捕获。现在直接作为参数传递给 7z,虽然仍可能被 ps 命令看到,但避免了 Shell 层面的转义注入风险,且解决了管道阻塞问题。

4.2 临时文件处理

旧代码生成了临时脚本文件 tempScript_...sh 并在结束后删除。新代码完全移除了临时文件,这减少了磁盘 I/O 和权限问题,也避免了残留脚本文件的安全隐患。

5. 具体改进建议

  1. 完善 CliInterface::deleteProcess
    deleteProcess 中,当处理 tar.7z 管道时,先 kill 7z (m_tar7z_7z) 还是先 kill tar (m_tarProcess) 是有讲究的。
    当前代码:

    if (m_tar7z_7z->state() != QProcess::NotRunning) {
        m_tar7z_7z->kill();
    }
    // ...
    if (m_tarProcess->state() != QProcess::NotRunning) {
        m_tarProcess->kill();
    }

    建议:通常应该先 kill 写入端(tar),这样 7z 会读到 EOF 并尝试完成当前压缩块。如果先 kill 7z,tar 可能会因为管道断裂(SIGPIPE)而崩溃写入错误日志。虽然最终结果都是停止,但顺序不同可能导致日志输出不同。建议先杀 tar,稍作延迟后再杀 7z,或者直接杀掉 tar 后等待 7z 自动退出。

  2. readStdout 中的逻辑

    QProcess *outProcess = m_tar7z_7z ? m_tar7z_7z : m_process;

    这个三元运算符用得很好,统一了读取逻辑。

  3. handleProgress 中的逻辑
    移除了针对 tempScript 的解析逻辑是正确的,因为现在直接读取 7z 的标准输出,不再需要解析 tar 的原始输出。

总结

这次改动显著提高了代码的安全性和健壮性,消除了 Shell 脚本带来的注入风险和转义字符噩梦。
主要风险点在于进程生命周期管理(裸指针和异常退出路径)以及管道断裂时的错误处理。如果能将裸指针改为智能指针,并加强对 tar 进程异常退出的监听,代码质量将更上一层楼。

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 - I've found 1 issue, and left some high level feedback:

  • In CliProperties::getTar7z7zArgs the headerEncryption parameter is currently unused even though callers (e.g. CliInterface::addFiles) pass options.bHeaderEncryption; either incorporate this flag into the generated 7z arguments or remove the parameter to avoid confusion and divergence from previous behavior.
  • Cli7zPlugin::killProcess returns early after killTar7zPipelineIfActive() without calling deleteProcess(), so m_tarProcess/m_tar7z_7z and their signal connections are not cleaned up there; consider centralizing process teardown (including deleting these QProcess instances and disconnecting signals) to prevent leaks and unexpected callbacks after kill.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `CliProperties::getTar7z7zArgs` the `headerEncryption` parameter is currently unused even though callers (e.g. `CliInterface::addFiles`) pass `options.bHeaderEncryption`; either incorporate this flag into the generated 7z arguments or remove the parameter to avoid confusion and divergence from previous behavior.
- `Cli7zPlugin::killProcess` returns early after `killTar7zPipelineIfActive()` without calling `deleteProcess()`, so `m_tarProcess`/`m_tar7z_7z` and their signal connections are not cleaned up there; consider centralizing process teardown (including deleting these QProcess instances and disconnecting signals) to prevent leaks and unexpected callbacks after kill.

## Individual Comments

### Comment 1
<location path="3rdparty/interface/archiveinterface/cliproperties.cpp" line_range="119-121" />
<code_context>
+    return args;
+}
+
+QStringList CliProperties::getTar7z7zArgs(const QString &archive, const QString &password, bool headerEncryption,
+                                         int compressionLevel, const QString &compressionMethod,
+                                         const QString &encryptionMethod, int volumeSize)
+{
+    QStringList args;
</code_context>
<issue_to_address>
**issue (bug_risk):** Header-encryption flag is ignored in tar.7z pipeline, potentially changing behavior vs non-tar.7z paths.

`headerEncryption` is accepted but never used here. In the previous flow, `substitutePasswordSwitch(password, headerEncryption)` applied the appropriate 7z switches (e.g. `-mhe=on`). In the tar.7z pipeline that behavior is lost, so headers might not be encrypted when requested. If header encryption is required for tar.7z, this method should add the appropriate 7z switch based on `headerEncryption` to match the non-tar.7z behavior.
</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.

Comment on lines +119 to +121
QStringList CliProperties::getTar7z7zArgs(const QString &archive, const QString &password, bool headerEncryption,
int compressionLevel, const QString &compressionMethod,
const QString &encryptionMethod, int volumeSize)
Copy link

Choose a reason for hiding this comment

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

issue (bug_risk): Header-encryption flag is ignored in tar.7z pipeline, potentially changing behavior vs non-tar.7z paths.

headerEncryption is accepted but never used here. In the previous flow, substitutePasswordSwitch(password, headerEncryption) applied the appropriate 7z switches (e.g. -mhe=on). In the tar.7z pipeline that behavior is lost, so headers might not be encrypted when requested. If header encryption is required for tar.7z, this method should add the appropriate 7z switch based on headerEncryption to match the non-tar.7z behavior.

@deepin-ci-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: LiHua000, max-lvs

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

@LiHua000
Copy link
Contributor Author

/merge

@deepin-bot deepin-bot bot merged commit 0cea7c5 into linuxdeepin:release/eagle Mar 13, 2026
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.

3 participants