mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-02 00:36:46 +00:00
Revisions 2–4 of the standard security handler take the password in PDFDocEncoding (ISO 32000-2 §7.6.4.3.2, Algorithm 2). PdfCraft authenticates an encrypted file twice, and neither did that: - pdfcraft-crypt took Latin-1 and dropped every other character, so "ı" (0x9A) and "€" (0xA0) vanished and "ılık€" became "lk"; - the renderer (vendored hayro-syntax, "TODO: Convert to PDFDocEncoding") used the UTF-8 bytes, so any non-ASCII password failed, even "Çok güzel", which Latin-1 and PDFDocEncoding write alike; - the engine handed the renderer an owner-recovered user password as Latin-1 characters, which the renderer then read as UTF-8 again. Files that qpdf or Acrobat protected with such passwords could not be opened with the right password. - pdfcraft-crypt encodes R2–R4 passwords in PDFDocEncoding and, for a password with characters PDFDocEncoding lacks (ğ, ş, İ), also tries its UTF-8 bytes, which qpdf writes for those. The PDFDocEncoding table moves here from pdfcraft-cos (which depends on this crate) and cos uses it. - hayro-syntax (vendored patch 14) tries an R2–R4 password as given, then in PDFDocEncoding. - The engine passes a recovered user password to the renderer as UTF-8 when its bytes are UTF-8 and as PDFDocEncoding otherwise, so the renderer gets exactly those bytes back. Co-authored-by: Brandon Thomas <echelon@gmail.com>
54 lines
2.3 KiB
Rust
54 lines
2.3 KiB
Rust
//! pdfcraft-cos — the PDF object layer (L1, standalone with `pdfcraft-filters`).
|
||
//!
|
||
//! Parses the COS object graph lazily and tolerantly, keeps edits in a copy-on-write overlay
|
||
//! (cheap snapshots for undo), and writes documents back either incrementally (original bytes
|
||
//! untouched) or as a full, garbage-collected rewrite. The PDF object graph *is* PdfCraft's
|
||
//! document model (plan/architecture.md §5, ADR-0010).
|
||
//!
|
||
//! Done: every cross-reference form (tables, streams, hybrid `/XRefStm`), object streams, repair
|
||
//! by scanning, encryption on load and save (R2–R6, crypt filters, `/EFF`), incremental writing,
|
||
//! and full saves that pack objects into compressed object streams with a cross-reference stream.
|
||
//! Not yet: streaming very large files within a memory budget (`ByteSource`,
|
||
//! `core.lazy-loading`), and linearized output (`core.linearization`, M11). See the README.
|
||
|
||
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
|
||
|
||
mod bytes;
|
||
mod document;
|
||
mod object;
|
||
pub mod page_labels;
|
||
mod parser;
|
||
mod security;
|
||
mod writer;
|
||
|
||
pub use bytes::Bytes;
|
||
pub use document::{Document, Revision, SourceIdentity, XrefEntry};
|
||
pub use object::{Dict, MAX_DECODED, Name, ObjRef, Object, PdfString, Stream};
|
||
pub use parser::{Lexer, parse_indirect};
|
||
pub use pdfcraft_crypt::{Algorithm, Auth, Method as CryptMethod, NewEncryption, Permissions, SecurityHandler, pdfdoc_byte, pdfdoc_char};
|
||
pub use writer::{SaveOptions, pdf_date, serialize, write_full, write_incremental};
|
||
|
||
#[derive(Debug, thiserror::Error, Clone, PartialEq)]
|
||
pub enum CosError {
|
||
#[error("not a PDF file (no %PDF- header)")]
|
||
NotPdf,
|
||
#[error("syntax error at byte {offset}: {detail}")]
|
||
Syntax { offset: usize, detail: String },
|
||
#[error("object {0} is missing")]
|
||
MissingObject(u32),
|
||
#[error("object {0} is not a dictionary")]
|
||
NotADictionary(ObjRef),
|
||
#[error("stream data could not be decoded: {0}")]
|
||
Filter(String),
|
||
#[error("the document is protected by a password")]
|
||
NeedsPassword,
|
||
#[error("the password is incorrect")]
|
||
WrongPassword,
|
||
#[error("unsupported security: {0}")]
|
||
Security(String),
|
||
#[error("internal lock poisoned")]
|
||
Poisoned,
|
||
#[error("this document was opened read-only with a decode limit and cannot be saved")]
|
||
ReadOnlyLimit,
|
||
}
|