c2pa_structured_text/
embed.rs1const BEGIN: &str = "-----BEGIN C2PA MANIFEST-----";
2const END: &str = "-----END C2PA MANIFEST-----";
3const B64_CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
4
5pub enum ManifestRef<'a> {
6 Url(&'a str),
7 Embedded(&'a [u8]),
8}
9
10pub fn embed_manifest(
11 text: &str,
12 manifest: ManifestRef<'_>,
13 comment_prefix: &str,
14 comment_suffix: Option<&str>,
15) -> String {
16 let reference = match manifest {
17 ManifestRef::Url(url) => url.to_string(),
18 ManifestRef::Embedded(bytes) => {
19 format!("data:application/c2pa;base64,{}", base64_encode(bytes))
20 }
21 };
22
23 let suffix = comment_suffix.unwrap_or("");
24 let manifest_line = format!(
25 "{comment_prefix} {BEGIN} {reference} {END} {suffix}"
26 ).trim_end().to_string();
27
28 format!("{manifest_line}\n{text}")
29}
30
31fn base64_encode(input: &[u8]) -> String {
32 let mut out = Vec::with_capacity((input.len() + 2) / 3 * 4);
33 for chunk in input.chunks(3) {
34 let b0 = chunk[0] as u32;
35 let b1 = if chunk.len() > 1 { chunk[1] as u32 } else { 0 };
36 let b2 = if chunk.len() > 2 { chunk[2] as u32 } else { 0 };
37 let triple = (b0 << 16) | (b1 << 8) | b2;
38 out.push(B64_CHARS[((triple >> 18) & 0x3F) as usize]);
39 out.push(B64_CHARS[((triple >> 12) & 0x3F) as usize]);
40 if chunk.len() > 1 {
41 out.push(B64_CHARS[((triple >> 6) & 0x3F) as usize]);
42 } else {
43 out.push(b'=');
44 }
45 if chunk.len() > 2 {
46 out.push(B64_CHARS[(triple & 0x3F) as usize]);
47 } else {
48 out.push(b'=');
49 }
50 }
51 String::from_utf8(out).unwrap()
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57
58 #[test]
59 fn embed_url_python() {
60 let text = "print('hello')\n";
61 let result = embed_manifest(text, ManifestRef::Url("https://example.com/m.c2pa"), "#", None);
62 assert!(result.starts_with("# -----BEGIN C2PA MANIFEST-----"));
63 assert!(result.contains("https://example.com/m.c2pa"));
64 assert!(result.contains("-----END C2PA MANIFEST-----"));
65 assert!(result.ends_with("print('hello')\n"));
66 }
67
68 #[test]
69 fn embed_url_css() {
70 let result = embed_manifest("body {}", ManifestRef::Url("https://example.com/m.c2pa"), "/*", Some("*/"));
71 assert!(result.starts_with("/* -----BEGIN C2PA MANIFEST-----"));
72 assert!(result.contains("-----END C2PA MANIFEST----- */"));
73 }
74
75 #[test]
76 fn embed_data_uri() {
77 let bytes = b"test manifest";
78 let result = embed_manifest("content", ManifestRef::Embedded(bytes), "#", None);
79 assert!(result.contains("data:application/c2pa;base64,"));
80 }
81
82 #[test]
83 fn base64_known_vectors() {
84 assert_eq!(base64_encode(b""), "");
85 assert_eq!(base64_encode(b"f"), "Zg==");
86 assert_eq!(base64_encode(b"fo"), "Zm8=");
87 assert_eq!(base64_encode(b"foo"), "Zm9v");
88 assert_eq!(base64_encode(b"foob"), "Zm9vYg==");
89 assert_eq!(base64_encode(b"fooba"), "Zm9vYmE=");
90 assert_eq!(base64_encode(b"foobar"), "Zm9vYmFy");
91 }
92}