Skip to content

fix(webui): support PPT preview and shell open in server mode#1679

Merged
kaizhou-lab merged 3 commits intomainfrom
zynx/fix/server-mode-ppt-preview
Mar 24, 2026
Merged

fix(webui): support PPT preview and shell open in server mode#1679
kaizhou-lab merged 3 commits intomainfrom
zynx/fix/server-mode-ppt-preview

Conversation

@piorpua
Copy link
Copy Markdown
Contributor

@piorpua piorpua commented Mar 24, 2026

Summary

  • Add shellBridgeStandalone using Node.js child_process so "使用系统默认应用打开" works in server (non-Electron) mode
  • Add /api/ppt-proxy/:port reverse proxy route with Location rewriting, X-Frame-Options: SAMEORIGIN override, and a navigation guard script injected into HTML responses to prevent the preview page from navigating outside the proxy path
  • Update PptViewer to use <iframe> with proxy URL in server mode, <WebviewHost> only in Electron mode

Test plan

  • Server mode: open a .pptx file — preview loads correctly in the iframe
  • Server mode: click "使用系统默认应用打开" — file opens in the system default app
  • Electron mode: PPT preview still works via <WebviewHost> (no regression)
  • Electron mode: "使用系统默认应用打开" still works (no regression)

zynx added 2 commits March 24, 2026 19:11
- Add shellBridgeStandalone using Node.js child_process (open/xdg-open/cmd)
  so "open with system app" works in server (non-Electron) mode
- Register shellBridgeStandalone in initBridgeStandalone
- Export isActivePreviewPort and stopAllWatchSessions from pptPreviewBridge
- Add /api/ppt-proxy/:port reverse proxy route in apiRoutes:
  - Validates port against active sessions (SSRF prevention)
  - Rewrites Location headers (absolute and relative) through the proxy
  - Overrides X-Frame-Options to SAMEORIGIN so iframe can load
  - Injects a navigation guard script into HTML responses to prevent
    officecli page JS from navigating the iframe outside the proxy path
- Update PptViewer to use <iframe> in server mode with proxy URL,
  and <WebviewHost> only in Electron mode
@piorpua
Copy link
Copy Markdown
Contributor Author

piorpua commented Mar 24, 2026

Code Review:fix(webui): support PPT preview and shell open in server mode (#1679)

变更概述

本 PR 为 server(非 Electron)模式新增了两项能力:(1) shellBridgeStandalone 通过 Node.js child_process 实现 shell 操作(打开文件/外链/文件夹);(2) /api/ppt-proxy/:port 反向代理路由将 officecli watch server 的 PPT 预览通过 Web 服务器暴露给浏览器 iframe。渲染层 PptViewer 根据运行环境分别使用 <webview>(Electron)或 <iframe>(server 模式)。变更涉及 src/process/bridge/src/process/webserver/routes/apiRoutes.tssrc/renderer/ 共 5 个文件。


方案评估

结论:✅ 方案合理

整体方案思路正确:通过 port 白名单校验防止 SSRF、用 Location 重写保证代理透明性、注入 guard script 限制 iframe 导航逸出——三层防护设计合理。shellBridgeStandalone 使用 execFile(而非 exec)避免了 shell 注入,是正确选择。方案与项目已有的 process/renderer 分层架构完全一致。注入 guard script 采用模板字符串拼接 port(已验证为整数),不存在 XSS 风险。主要待完善点为:proxy 请求缺少超时保护、非 HTML 流式响应路径缺少 proxyRes 错误处理。


问题清单

🟠 HIGH — 非 HTML 流式响应路径缺少 proxyRes 错误处理

文件src/process/webserver/routes/apiRoutes.ts,第 538–542 行

问题代码

} else {
  res.removeHeader('X-Frame-Options');
  res.writeHead(statusCode, responseHeaders);
  proxyRes.pipe(res, { end: true });
}

问题说明proxyRes 是一个 IncomingMessage 可读流。Node.js 中 pipe() 不会自动将 source 的 error 事件转发给 destination;如果 proxyRes 在 pipe 过程中触发 error(例如 officecli server 中途崩溃、连接被重置),且没有注册 error 监听器,该错误将成为 未处理的 EventEmitter error,在 Node.js 中会导致进程崩溃。对比同文件 HTML 路径(第 535–537 行)已经正确处理了 proxyRes.on('error', ...),非 HTML 路径遗漏了这一保护。

修复建议

} else {
  res.removeHeader('X-Frame-Options');
  res.writeHead(statusCode, responseHeaders);
  proxyRes.on('error', () => {
    // headers already sent via writeHead — can't change status, just destroy
    res.destroy();
  });
  proxyRes.pipe(res, { end: true });
}

🟡 MEDIUM — proxy 请求缺少超时配置,请求可能永久挂起

文件src/process/webserver/routes/apiRoutes.ts,第 467–473 行

问题代码

const options: http.RequestOptions = {
  hostname: '127.0.0.1',
  port,
  path: subPath + query,
  method: req.method,
  headers: proxyHeaders,
};

问题说明:未设置 timeout 字段。如果 officecli watch server 在接收请求后迟迟不返回响应(如处理大型 PPT 卡住),Express 请求会一直挂起,最终耗尽服务器连接资源。proxyReq.on('error') 只处理连接建立失败的情况,无法覆盖"连接成功但响应超时"的场景。

修复建议

const PROXY_TIMEOUT_MS = 30_000;

const options: http.RequestOptions = {
  hostname: '127.0.0.1',
  port,
  path: subPath + query,
  method: req.method,
  headers: proxyHeaders,
  timeout: PROXY_TIMEOUT_MS,
};

// 在创建请求后追加:
proxyReq.on('timeout', () => {
  proxyReq.destroy();
  if (!res.headersSent) res.status(504).json({ message: 'PPT preview proxy timeout' });
});

🔵 LOW — guard script 中 location.href getter 返回相对路径,可能破坏 officecli 页面代码

文件src/process/webserver/routes/apiRoutes.ts,第 515 行

问题代码

try{Object.defineProperty(location,'href',{get:function(){return location.pathname+location.search+location.hash;},set:function(v){_a(rw(v));},configurable:true});}catch(e){}

问题说明:覆盖 location.href 的 getter 使其返回 pathname + search + hash(相对路径,如 /api/ppt-proxy/55555/index.html),而非浏览器标准的完整 URL(如 http://app-host/api/ppt-proxy/55555/index.html)。officecli 页面若读取 window.location.href 做 URL 构造(例如 new URL(path, location.href)),将因缺少 origin 而报错或得到错误结果。guard script 的核心目标是拦截导航,setter 拦截已足够——getter 覆盖是多余且有副作用的。

修复建议:移除 getter 覆盖,仅保留 setter 以拦截直接赋值导航:

try{Object.defineProperty(location,'href',{set:function(v){_a(rw(v));},configurable:true});}catch(e){}

🔵 LOW — isActivePreviewPort 无测试覆盖

文件src/process/bridge/pptPreviewBridge.ts,第 277–284 行

问题说明:新导出的 isActivePreviewPort 函数是 proxy 路由 SSRF 防护的核心逻辑,但现有 tests/unit/pptPreviewBridge.test.ts 中未包含对此函数的任何测试用例(包括"有效端口返回 true"、"已终止 session 返回 false"、"不存在端口返回 false"等场景)。按 testing skill 要求,新增功能必须有对应测试。

修复建议:在 pptPreviewBridge.test.ts 中补充:

describe('isActivePreviewPort', () => {
  it('returns true for an active session port', async () => { ... });
  it('returns false for an aborted session port', async () => { ... });
  it('returns false for an unknown port', () => {
    expect(isActivePreviewPort(9999)).toBe(false);
  });
});

🔵 LOW — shellBridgeStandalone.ts 无测试覆盖

文件src/process/bridge/shellBridgeStandalone.ts

问题说明:新文件没有对应的单元测试。runOpen 的三平台分支逻辑(win32/darwin/linux)和各 IPC provider 注册均未被测试。

修复建议:新建 tests/unit/shellBridgeStandalone.test.ts,mock execFileipcBridge.shell.*,验证三个平台下 runOpen 调用的命令和参数正确。


汇总

# 严重级别 文件 问题
1 🟠 HIGH apiRoutes.ts:538 非 HTML pipe 路径缺少 proxyRes 错误处理
2 🟡 MEDIUM apiRoutes.ts:467 proxy 请求无超时,可能永久挂起
3 🔵 LOW apiRoutes.ts:515 location.href getter 覆盖返回相对路径
4 🔵 LOW pptPreviewBridge.ts:277 isActivePreviewPort 缺少测试
5 🔵 LOW shellBridgeStandalone.ts 新文件缺少单元测试

结论

⚠️ 有条件批准 — 存在 1 个 HIGH 问题(非 HTML pipe 路径的进程崩溃风险)需处理后合并;MEDIUM 超时问题和其余 LOW 问题建议同步修复。


本报告由本地 pr-review skill 生成,包含完整项目上下文,无截断限制。

- Add proxyRes error handler on non-HTML pipe path to prevent uncaught
  EventEmitter errors from crashing the process
- Add 30s timeout to proxy requests with 504 response on timeout
- Remove location.href getter override in guard script to avoid breaking
  pages that rely on window.location.href returning a full URL
- Add isActivePreviewPort tests: active port, exited process, stopped session
- Add shellBridgeStandalone unit tests covering all three platforms and
  error handling

Review follow-up for #1679
@piorpua
Copy link
Copy Markdown
Contributor Author

piorpua commented Mar 24, 2026

PR Fix 验证报告

原始 PR: #1679
修复方式: 直接推送到 zynx/fix/server-mode-ppt-preview

# 严重级别 文件 问题 修复方式 状态
1 🟠 HIGH apiRoutes.ts:538 非 HTML pipe 路径缺少 proxyRes 错误处理,未处理的 EventEmitter error 可能导致进程崩溃 proxyRes.pipe() 前添加 proxyRes.on('error', () => res.destroy()) ✅ 已修复
2 🟡 MEDIUM apiRoutes.ts:467 proxy 请求无超时配置,连接成功但响应挂起时无法处理 添加 PROXY_TIMEOUT_MS = 30_000 到 options,并注册 proxyReq.on('timeout', ...) 返回 504 ✅ 已修复
3 🔵 LOW apiRoutes.ts:515 location.href getter 覆盖返回相对路径,破坏依赖完整 URL 的页面代码 移除 getter,仅保留 setter 拦截直接赋值导航 ✅ 已修复
4 🔵 LOW pptPreviewBridge.ts:277 isActivePreviewPort 缺少测试覆盖 pptPreviewBridge.test.ts 中新增 4 个测试用例:未知端口、活跃端口、进程退出后、session 停止后 ✅ 已修复
5 🔵 LOW shellBridgeStandalone.ts 新文件缺少单元测试 新建 shellBridgeStandalone.test.ts,覆盖三平台分支(darwin/win32/linux)、IPC provider 注册和错误处理 ✅ 已修复

总结: ✅ 已修复 5 个 | ❌ 未能修复 0 个 | ⏭️ 跳过 0 个

@kaizhou-lab kaizhou-lab merged commit 3b243ab into main Mar 24, 2026
13 of 17 checks passed
@piorpua piorpua deleted the zynx/fix/server-mode-ppt-preview branch March 24, 2026 11:56
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.

2 participants