Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 48 additions & 14 deletions packages/renderers/email/README.en.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# @file-viewer/renderer-email

Standalone email renderer package for Flyfish File Viewer. It handles `.eml`, `.msg`, and `.mbox` previews, body/header switching, attachment download, and nested attachment previews.
Browser-native `.eml`, Outlook `.msg`, and `.mbox` preview with body/header switching, attachment download and nested attachment preview. No server conversion or runtime CDN is used.

## Usage

```ts
import FileViewer from '@file-viewer/vue3'
import { FileViewer } from '@file-viewer/vue3'
import { emailRenderer } from '@file-viewer/renderer-email'

const options = {
Expand All @@ -14,27 +14,61 @@ const options = {
}
```

You can also compose it with other renderers:
Pass a real `File` named `message.msg`, or a URL with the original filename. The existing email route selects the MSG parser lazily; EML/MBOX do not import the MSG decoder.

For attachment previews, compose the corresponding PDF, image, Word and other renderers. The host supplies the standard `renderNestedBuffer` callback. Embedded Outlook messages are extracted as `.msg`, not renamed to text; original attachment bytes are retained for download.

### Rich-text Outlook messages

The full preset already includes the Word renderer and RTF capability:

```ts
import allRenderers from '@file-viewer/preset-all'
const options = { preset: allRenderers }
```

For a selective integration, install `@file-viewer/renderer-word` and `@file-viewer/capability-rtf` alongside the email renderer:

```ts
import { emailRenderer } from '@file-viewer/renderer-email'
import { pdfRenderer } from '@file-viewer/renderer-pdf'
import { archiveRenderer } from '@file-viewer/renderer-archive'
import { wordRenderer } from '@file-viewer/renderer-word'
import '@file-viewer/capability-rtf'

const options = {
builtinRenderers: 'none',
renderers: [pdfRenderer, archiveRenderer, emailRenderer],
renderers: [emailRenderer, wordRenderer],
}
```

## Capabilities
Compressed RTF is decompressed locally. HTML-encapsulated RTF can supply an HTML body; ordinary rich RTF uses the existing RTF.js capability through the nested renderer contract. An email-only installation retains readable plain text with a notice when rich rendering is unavailable. Email RTF always blocks external resources and links, even when the host opts into them for other document types.

## Outlook MSG coverage

- Parses `.eml` and `.mbox` with `postal-mime`.
- Parses Outlook `.msg` with `@kenjiuno/msgreader`.
- Supports HTML body, plain-text body, and raw header switching. HTML email is rendered read-only inside a sandbox iframe.
- Supports attachment download and nested preview through `renderNestedBuffer` when the host viewer provides it.
- Does not depend on any online service or public CDN, making it suitable for intranet attachment centers, ticket email archives, and customer communication review.
- Unicode and ANSI properties; Outlook codepages, Unicode `bodyHtml` and binary `html`, BOM and HTML charset handling.
- Subject, From, on-behalf-of Sender, separate To/Cc/Bcc, submission/delivery time, original transport headers and a clearly labeled metadata fallback for drafts.
- HTML, plain text and RTF body switching; tables, authored styles and local CID/Content-Location raster images.
- MIME-aware attachment names, lazy binary extraction, downloads and nested MSG preview. Unknown sizes are shown as unknown until extraction, not as zero bytes.
- Body uses the available height until an attachment opens. Closing an attachment restores body space and keyboard focus. Pending views and owned object URLs are cleaned up on replacement, cancellation and unmount.
- Simplified Chinese, English, Japanese and German notices follow the viewer locale. Narrow-host layout and light/dark presentation are supported.

EML/MBOX remain on `postal-mime`; MBOX still previews its first message and reports the count rather than implementing a mailbox browser.

## Privacy, limits and compatibility

Email HTML is read-only in an empty-sandbox iframe. Sanitization and a restrictive CSP block scripts, forms, embedded documents, local paths, external stylesheets, fonts, tracking images and remote resource requests. Only owned local image resources and raster image data URLs are allowed. External hyperlinks are not activated. This applies to EML/MBOX as well as MSG: emails that relied on remote images will no longer fetch them automatically. Normal origins use blob URLs; opaque-origin WebViews use bounded local data URLs for inline images.

Limits: 128 MiB MSG source/individual attachment, 32 MiB HTML or decompressed RTF, 32 MiB eagerly extracted inline images, 1,024 attachments and 4,096 recipients per message, 32,768 CFB directory entries and 24 storage levels. HTML additionally bounds node count and serialized/inline expansion. Malformed, cyclic, truncated or excessive input fails explicitly. These are defensive bounds, not a claim of a complete adversarial parser audit.

S/MIME/IRM decryption and signature verification are **not** implemented. Protected/signed message classes are identified with a notice; only available content is displayed. MSG contacts, tasks, appointments, OLE activation and every Outlook-specific MAPI property are not complete Outlook replacements. RTF fidelity is limited by the installed RTF capability. No claim of pixel-identical Outlook rendering is made without a matching real-file comparison.

## Verification

```sh
pnpm --filter @file-viewer/renderer-email verify:email
pnpm exec playwright install chromium
pnpm --filter @file-viewer/renderer-email verify:msg:browser
```

## Migration Note
The existing root email regression gate invokes `verify:email`, preserving the EML #232 checks and adding the MSG tests. `test/msg.test.mjs` includes actual installed-MsgReader tests over generated CFB v3/v4, ANSI, Unicode, RTF and nested-message fixtures. Those integration cases are required by default; `MSG_UNIT_ONLY=1` is only an explicitly reported offline subset.

The core package no longer bundles the email renderer and no longer installs `postal-mime` / `@kenjiuno/msgreader` directly. Install this renderer explicitly, or use `@file-viewer/preset-all`, when email preview is required.
The browser gate uses the emitted production renderer and real Chromium with explicit Reader/RTF host API fixtures. It covers layout, sanitization, downloads, races and disposal, **not** installed-parser integration or RTF.js visual parity. It writes screenshots and a JSON report to `output/msg-browser/`. See [fixture provenance](test/fixtures/README.md).
64 changes: 49 additions & 15 deletions packages/renderers/email/README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# @file-viewer/renderer-email

Flyfish File Viewer 的独立邮件 renderer 包。它负责 `.eml`、`.msg`、`.mbox` 邮件预览、正文/头信息切换、附件下载和附件嵌套预览
浏览器原生预览 `.eml`、Outlook `.msg`、`.mbox`,支持正文/邮件头切换、附件下载和嵌套附件预览,不依赖服务端转换或运行时 CDN

## 用法
## 接入

```ts
import FileViewer from '@file-viewer/vue3'
import { FileViewer } from '@file-viewer/vue3'
import { emailRenderer } from '@file-viewer/renderer-email'

const options = {
Expand All @@ -14,27 +14,61 @@ const options = {
}
```

也可以与其他 renderer 组合:
传入带真实 `.msg` 文件名的 `File`,或保留原文件名的 URL。沿用现有 email 路由,仅在命中 MSG 时加载其解析模块,EML/MBOX 不加载 MSG 解析器。

附件预览需要组合相应的 PDF、图片、Word 等 renderer,由宿主提供标准 `renderNestedBuffer`。内嵌 Outlook 邮件按真实 `.msg` 提取和预览,不伪装成文本;下载保留附件原始字节。

### Outlook 富文本正文

Full preset 已包含 Word renderer 和 RTF 能力:

```ts
import allRenderers from '@file-viewer/preset-all'
const options = { preset: allRenderers }
```

按需接入时,同时安装 `@file-viewer/renderer-word` 和 `@file-viewer/capability-rtf`:

```ts
import { emailRenderer } from '@file-viewer/renderer-email'
import { pdfRenderer } from '@file-viewer/renderer-pdf'
import { archiveRenderer } from '@file-viewer/renderer-archive'
import { wordRenderer } from '@file-viewer/renderer-word'
import '@file-viewer/capability-rtf'

const options = {
builtinRenderers: 'none',
renderers: [pdfRenderer, archiveRenderer, emailRenderer],
renderers: [emailRenderer, wordRenderer],
}
```

## 能力边界
本地解压压缩 RTF;其中封装的 HTML 可用于正文预览,普通富文本通过嵌套 renderer 复用现有 RTF.js 能力。仅安装 email renderer 时仍可阅读提取的纯文本,并提示富文本能力未安装。邮件 RTF 始终禁止外部资源和链接,不继承宿主针对其他文档开启的外链权限。

## MSG 能力

- `.eml` 和 `.mbox` 使用 `postal-mime` 解析邮件头、HTML 正文、纯文本正文和附件。
- `.msg` 使用 `@kenjiuno/msgreader` 解析 Outlook MSG 文件。
- 支持 HTML / 正文 / 头信息切换,HTML 邮件在 sandbox iframe 中只读展示。
- 支持附件下载;宿主 viewer 提供 `renderNestedBuffer` 时,附件会继续复用 PDF、Office、图片、代码等现有 renderer。
- 不绑定任何在线服务或公共 CDN,适合内网附件中心、工单邮件归档和客户来信查看。
- Unicode/ANSI 属性、Outlook 代码页、Unicode `bodyHtml` 与二进制 `html`、BOM 和 HTML charset 解码。
- 主题、发件人、代发 Sender、独立的 To/Cc/Bcc、提交/投递时间、原始邮件头;草稿缺少邮件头时显示明确标识的元数据摘要。
- HTML、纯文本、RTF 正文切换,保留表格、作者样式和本地 CID/Content-Location 栅格图片。
- MIME、附件安全文件名、惰性二进制提取、原字节下载和内嵌 MSG 预览;未知大小在提取前显示未知,不误报为零字节。
- 未打开附件时正文占满可用高度;关闭附件恢复正文空间和键盘焦点;切换、取消、卸载会清理过期预览和资源 URL。
- 提示支持简体中文、英语、日语、德语,跟随 viewer locale;适配窄容器和明暗主题。

EML/MBOX 继续使用 `postal-mime`。MBOX 仍预览第一封邮件并提示邮件数量,本次不新增邮箱列表浏览器。

## 隐私、限制与兼容性

HTML 正文位于空 sandbox iframe 内,通过净化和严格 CSP 禁止脚本、表单操作、嵌入文档、本地路径、外部样式、字体、追踪图片及远程资源请求。只允许本地持有的图片资源和栅格图片 data URL,外部超链接不激活。此规则同时作用于 EML/MBOX,因此历史上依赖远程图片的邮件不再自动联网加载。普通来源使用 Blob URL;不透明来源的 WebView 使用有界的本地 data URL 展示内嵌图片。

安全上限:MSG 原文件/单个附件 128 MiB,HTML 或解压后的 RTF 32 MiB,提前解码的内嵌图片合计 32 MiB,每封邮件最多 1,024 个附件、4,096 个收件人,CFB 最多 32,768 个目录项、24 层存储。HTML 另有节点数量和序列化/内嵌资源膨胀上限。损坏、循环、截断或超限输入明确失败;这些防御性限制不代表完成了全面对抗性解析器审计。

**不实现 S/MIME/IRM 解密或签名验证。** 对受保护/签名邮件类型显示提示,仅展示可获得的内容。联系人、任务、日程、OLE 激活和全部 Outlook 专有 MAPI 属性不属于完整 Outlook 替代范围。富文本还原度受已安装 RTF 能力限制,未经真实文件对照不承诺与 Outlook 像素一致。

## 验证

```sh
pnpm --filter @file-viewer/renderer-email verify:email
pnpm exec playwright install chromium
pnpm --filter @file-viewer/renderer-email verify:msg:browser
```

## 迁移说明
根目录原有邮件回归入口会调用 `verify:email`,保留 EML #232 检查并加入 MSG 测试。`test/msg.test.mjs` 默认使用真实安装的 MsgReader,覆盖生成的 CFB v3/v4、ANSI、Unicode、RTF 和嵌套邮件。`MSG_UNIT_ONLY=1` 仅用于明确标注的离线子集,不能替代集成验收。

`@file-viewer/core` 已不再内置 email renderer,也不再直接安装 `postal-mime` / `@kenjiuno/msgreader`。需要邮件预览时,请显式安装本包,或直接使用 `@file-viewer/preset-all` 聚合能力
浏览器测试使用生产 renderer 编译结果、真实 Chromium,以及明确声明的 Reader/RTF 宿主 API 测试替身,验证布局、安全隔离、下载、竞态和清理;不代表真实解析器集成或 RTF.js 视觉还原验收。截图和 JSON 结果输出到 `output/msg-browser/`。参见[样本来源说明](test/fixtures/README.md)
5 changes: 4 additions & 1 deletion packages/renderers/email/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@
"scripts": {
"build": "tsc -b tsconfig.json",
"type-check": "tsc -b tsconfig.json",
"verify:github-232": "pnpm --filter @file-viewer/core build && pnpm build && node scripts/verify-github-232.mjs"
"verify:email": "pnpm --filter @file-viewer/core build && pnpm build && node scripts/verify-github-232.mjs && node --test test/msg.test.mjs",
"verify:github-232": "pnpm verify:email",
"verify:msg": "pnpm --filter @file-viewer/core build && pnpm build && node --test test/msg.test.mjs",
"verify:msg:browser": "pnpm --filter @file-viewer/core build && pnpm build && node scripts/verify-msg-browser.mjs"
},
"dependencies": {
"@file-viewer/core": "workspace:3.1.1",
Expand Down
Loading
Loading