c2pa_html/lib.rs
1// Copyright 2026 WritersLogic. All rights reserved.
2// Licensed under the Apache License, Version 2.0 or the MIT license,
3// at your option.
4
5//! C2PA manifest embedding, referencing, and hard binding for HTML documents.
6//!
7//! Implements the *Embedding Manifests into HTML* section of the C2PA Technical
8//! Specification: a C2PA Manifest Store carried inline as the Base64 content of
9//! a `<script type="application/c2pa">` element, or referenced externally by a
10//! `<link rel="c2pa-manifest">` element, both in the document `head`.
11//!
12//! # Scope
13//!
14//! - **Association** ([`document`]): discover, embed, reference, and remove.
15//! - **Hard binding** ([`hardbinding`]): the exact `c2pa.hash.data` coverage,
16//! with compute and verify.
17//!
18//! Signature verification, certificate trust, assertion validation, and
19//! resolution of an external manifest URI are not implemented here.
20//!
21//! # Bytes, not text
22//!
23//! The specification directs a validator to treat the document "as a series of
24//! bytes (vs. text)", so every entry point takes `&[u8]` and returns `Vec<u8>`.
25//! A document in a legacy ASCII-compatible encoding scans correctly, and byte
26//! offsets mean what the hard binding says they mean.
27//!
28//! # Zero dependencies, no features
29//!
30//! Discovery, embedding, Base64, SHA-2, and the binding algorithm are all
31//! in-crate. There is nothing to enable and nothing to pull in: the dependency
32//! list is empty in every configuration.
33//!
34//! Hashing still goes through [`hardbinding::Hasher`], with [`hardbinding::Sha2`]
35//! as the built-in implementation. A caller with a reason to substitute — an
36//! accelerated digest for a large asset, or one the host runtime already
37//! provides — passes their own.
38//!
39//! # Examples
40//!
41//! Reference an external Manifest Store, which the specification prefers:
42//!
43//! ```
44//! use c2pa_html::document;
45//!
46//! let page = b"<html>\n<head>\n <title>Example</title>\n</head>\n<body></body>\n</html>";
47//! let out = document::embed_reference(page, "https://fabrikam.example/m.c2pa").unwrap();
48//!
49//! let manifest = document::extract(&out).unwrap();
50//! assert_eq!(manifest.href(), Some("https://fabrikam.example/m.c2pa"));
51//! ```
52//!
53//! Or carry the Manifest Store inline:
54//!
55//! ```
56//! use c2pa_html::document;
57//!
58//! let page = b"<html>\n<head>\n <title>Example</title>\n</head>\n<body></body>\n</html>";
59//! let out = document::embed(page, b"manifest-store-bytes").unwrap();
60//!
61//! assert_eq!(
62//! document::extract(&out).unwrap().store(),
63//! Some(&b"manifest-store-bytes"[..])
64//! );
65//! // Removing the manifest restores the document byte for byte.
66//! assert_eq!(document::remove(&out).unwrap(), page);
67//! ```
68//!
69//! Bind the document:
70//!
71//! ```
72//! use c2pa_html::document;
73//! use c2pa_html::hardbinding::{compute_data_hash, verify_data_hash, Algorithm, Sha2};
74//!
75//! let page = b"<html>\n<head>\n <title>Example</title>\n</head>\n<body></body>\n</html>";
76//! let out = document::embed(page, b"manifest-store-bytes").unwrap();
77//!
78//! let binding = compute_data_hash(&out, Algorithm::Sha256, &Sha2).unwrap();
79//! assert!(verify_data_hash(&out, &binding, &Sha2).is_ok());
80//! ```
81//!
82//! # Relationship to the text bindings
83//!
84//! HTML is a file format, so its binding hashes stored bytes with no
85//! normalization, and re-serializing the document invalidates it by design. The
86//! text methods differ deliberately: A.9 structured text also hashes raw bytes
87//! but has no element to exclude, and A.8 unstructured text normalizes to NFC
88//! because clipboard-portable text may arrive in any normalization form. See
89//! [`c2pa-structured-text`] and [`c2pa-unstructured-text`].
90//!
91//! [`c2pa-structured-text`]: https://crates.io/crates/c2pa-structured-text
92//! [`c2pa-unstructured-text`]: https://crates.io/crates/c2pa-unstructured-text
93
94#![forbid(unsafe_code)]
95#![warn(missing_debug_implementations)]
96
97mod base64;
98mod scan;
99mod sha2;
100
101#[cfg(target_arch = "wasm32")]
102mod wasm;
103
104#[cfg(all(feature = "python", not(target_arch = "wasm32")))]
105mod python;
106
107pub mod document;
108pub mod error;
109pub mod hardbinding;
110
111pub use document::{embed, embed_reference, extract, locate_all, remove, Manifest};
112pub use error::Error;