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
5 changes: 5 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@ this package adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

## [Unreleased]

- Let `/Prev` and `/XRefStm` be ordinary PDF dictionary keys: they mark an unsupported
incremental update only as trailer keys, so an outline item's `/Prev` no longer makes the
whole document unreadable (#427).
- 允许 `/Prev` 与 `/XRefStm` 作为普通 PDF 字典键:仅当它们出现在 trailer 中才表示不支持的增量更新,大纲项里的 `/Prev` 不再使整份文档被判不可读(#427)。

- Add current-scope `ops_status` diagnostics and public `diagnose(ctx, agent)` inspection; report integration, host/runtime, presentation and external provider readiness without executing or activating capabilities (#440).
- 新增当前作用域 `ops_status` 与公开 `diagnose(ctx, agent)`,区分接入、宿主/运行时、展示及外部提供者状态,不执行或激活能力(#440)。
- Expose opt-in Native capability providers with original names/contracts, explicit preset allow/startup/disable controls, per-instance selection invalidation and provider-owned authorization; reuse scoped discovery publication without requiring the OPS worker for external-only sessions (#440).
Expand Down
5 changes: 4 additions & 1 deletion docs/native-kernel.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,10 @@ xref table, an exact final startxref/EOF footer, unique dictionary keys and obje
identities, and directly declared stream lengths. Every active xref offset must point
exactly at its object header. Objects cannot overlap; gaps must contain only whitespace
or comments. This deliberately rejects incremental updates, encryption, object/xref
streams, recovery scans and ambiguous duplicate data, including decoded forbidden names.
streams, recovery scans and ambiguous duplicate data. `/Encrypt`, `/ObjStm` and `/XRef`
are refused wherever they appear, decoded escapes included, while `/Prev` and `/XRefStm` are
refused only as trailer keys — the one place they denote an earlier xref section — so a
dictionary that merely names them, such as an outline item's previous sibling, stays readable.

Bounds are checked before indexed allocation: 16 MiB per input, 10,000 xref entries,
100,000 metadata values, depth 64, 4,096 keys per dictionary, 256 bytes per name/token,
Expand Down
6 changes: 4 additions & 2 deletions docs/原生内核.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,10 @@ FILE_CHANGED 始终使所请求页不完整。引用页生成的 source 类型
内部 PDF 容器解析器仅接受受限的 PDF 1.0–1.7 子集:单个传统 xref 表、
准确的末尾 startxref/EOF、唯一字典键及对象标识,以及直接整数形式的流长度。
每个有效 xref 偏移必须准确指向对象头;对象不可重叠,间隙只能包含空白或注释。
增量更新、加密、对象流/xref 流、恢复扫描及重复歧义数据均被拒绝,名称转义后
仍会检查不支持的结构名称。
增量更新、加密、对象流/xref 流、恢复扫描及重复歧义数据均被拒绝。
`/Encrypt`、`/ObjStm` 与 `/XRef` 无论出现在哪里都会被拒绝,名称转义后也不例外;
`/Prev` 与 `/XRefStm` 仅作为 trailer 键时才表示较早的 xref 区段并被拒绝,
因此仅使用这些名称的普通字典(如大纲项的前一同级引用)仍可读取。

在索引分配前实施边界:输入 16 MiB、xref 条目 10,000、元数据值 100,000、
嵌套深度 64、每字典 4,096 个键、名称/词元 256 字节、字符串及编码/解码流
Expand Down
10 changes: 10 additions & 0 deletions native/crates/ops-documents/src/pdf_container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,16 @@ pub fn parse(data: &[u8]) -> Result<Document<'_>> {
}
let trailer = lexer.value(0)?;
let trailer = trailer.dict()?;
// Only a trailer key means "this file has an earlier xref section"; the same
// names inside an object dictionary are ordinary keys (outline items carry
// `/Prev` for their previous sibling).
for key in [b"Prev".as_slice(), b"XRefStm"] {
if trailer.contains_key(key) {
return Err(unsupported(
"Encrypted, incremental, object-stream and xref-stream PDFs are unsupported",
));
}
}
let size = get(trailer, b"Size")?.integer()?;
if size == 0
|| size > MAX_OBJECTS
Expand Down
28 changes: 27 additions & 1 deletion native/crates/ops-documents/src/pdf_container_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,40 @@ fn duplicate_keys_and_escaped_unsupported_names_are_rejected() {
] {
rejected(&fixture(&[value]), "DOCUMENT_PARSE_ERROR");
}
for name in ["Encrypt", "Pr#65v", "XRefStm", "ObjStm", "XRef"] {
for name in ["Encrypt", "Enc#72ypt", "ObjStm", "XRef"] {
rejected(
&fixture(&[format!("<< /{name} null >>").as_bytes()]),
"UNSUPPORTED_DOCUMENT",
);
}
}
#[test]
fn outline_prev_is_an_ordinary_key_while_a_trailer_prev_stays_unsupported() {
// An outline item names its previous sibling with /Prev, which is not an
// incremental-update marker; refusing the token made the whole document
// unreadable (#427).
let pdf = fixture(&[
b"<< /Type /Catalog /Pages 2 0 R /Outlines 3 0 R >>",
b"<< /Type /Pages /Kids [] /Count 0 >>",
b"<< /Type /Outlines /First 4 0 R /Last 5 0 R /Count 2 >>",
b"<< /Title (First) /Parent 3 0 R /Next 5 0 R >>",
b"<< /Title (Second) /Parent 3 0 R /Prev 4 0 R /XRefStm 4 0 R >>",
]);
let document = parse(&pdf).unwrap();
let second = document.object((5, 0)).unwrap().value.dict().unwrap();
assert!(matches!(get(second, b"Prev").unwrap(), Value::Ref(4, 0)));
// The same names still refuse a file that really has an earlier xref section.
for key in [b"/Prev".as_slice(), b"/Pr#65v", b"/XRefStm"] {
let mut trailer = b"/Root 1 0 R ".to_vec();
trailer.extend_from_slice(key);
trailer.extend_from_slice(b" 9 0 R >>");
rejected(
&replace(&pdf, b"/Root 1 0 R >>", &trailer),
"UNSUPPORTED_DOCUMENT",
);
}
}
#[test]
fn offset_alias_overlap_and_unindexed_data_are_never_recovered() {
let pdf = fixture(&[b"<<>>", b"(value)"]);
let document = parse(&pdf).unwrap();
Expand Down
12 changes: 8 additions & 4 deletions native/crates/ops-documents/src/pdf_syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,13 @@ impl<'a> Lexer<'a> {
}
Err(bad("Unclosed PDF literal string"))
}
/// A name is a token, not a policy decision.
///
/// `/Prev` and `/XRefStm` only describe an incremental update while they are
/// trailer keys, and an outline item legitimately carries `/Prev` as its
/// previous sibling. Those two are therefore refused where a trailer is read
/// (`pdf_container::parse`), not here; `/Encrypt`, `/ObjStm` and `/XRef`
/// stay refused at the token because no supported structure can hold them.
fn name(&mut self) -> Result<Vec<u8>> {
self.at += 1;
let mut name = Vec::new();
Expand All @@ -213,10 +220,7 @@ impl<'a> Lexer<'a> {
return Err(limit("PDF name exceeds 256 bytes"));
}
}
if matches!(
name.as_slice(),
b"Encrypt" | b"Prev" | b"XRefStm" | b"ObjStm" | b"XRef"
) {
if matches!(name.as_slice(), b"Encrypt" | b"ObjStm" | b"XRef") {
return Err(unsupported(
"Encrypted, incremental, object-stream and xref-stream PDFs are unsupported",
));
Expand Down
Loading