use crate::{
asset_io::{ObjectLocations, ObjectType, ReadSeek},
crypto::base64,
error::{Error, Result},
utils::io_utils::{stream_len, ReaderUtils},
};
pub(crate) fn hash_positions(
full_len: usize,
region_start: usize,
region_len: usize,
) -> Vec<ObjectLocations> {
let region_end = region_start + region_len;
vec![
ObjectLocations {
offset: region_start as u64,
length: region_len as u64,
htype: ObjectType::C2pa,
},
ObjectLocations {
offset: 0,
length: region_start as u64,
htype: ObjectType::Other,
},
ObjectLocations {
offset: region_end as u64,
length: full_len.saturating_sub(region_end) as u64,
htype: ObjectType::Other,
},
]
}
pub(crate) fn read_text_stream(mut reader: &mut dyn ReadSeek) -> Result<String> {
reader.rewind()?;
let len = stream_len(reader)?;
let bytes = reader.read_to_vec(len)?;
String::from_utf8(bytes)
.map_err(|_| Error::InvalidAsset("text asset is not valid UTF-8".to_string()))
}
pub(crate) const BEGIN_DELIMITER: &str = "-----BEGIN C2PA MANIFEST-----";
pub(crate) const END_DELIMITER: &str = "-----END C2PA MANIFEST-----";
pub(crate) const DATA_URI_PREFIX: &str = "data:application/c2pa;base64,";
pub(crate) fn encode_data_uri(store_bytes: &[u8]) -> String {
format!("{DATA_URI_PREFIX}{}", base64::encode(store_bytes))
}
pub(crate) enum ManifestReference {
Embedded(Vec<u8>),
External,
}
pub(crate) fn parse_manifest_reference(reference: &str) -> Result<ManifestReference> {
let reference = reference.trim();
match reference.strip_prefix(DATA_URI_PREFIX) {
Some(b64) => {
let bytes = base64::decode(b64.trim())
.map_err(|_| Error::InvalidAsset("invalid base64 in c2pa data URI".into()))?;
Ok(ManifestReference::Embedded(bytes))
}
None => Ok(ManifestReference::External),
}
}
pub(crate) fn is_manifest_reference(reference: &str) -> bool {
let reference = reference.trim();
if reference.starts_with(DATA_URI_PREFIX) {
return true;
}
if reference.is_empty() || reference.contains(char::is_whitespace) {
return false;
}
match reference.split_once(':') {
Some((scheme, rest)) => {
!rest.is_empty()
&& scheme.starts_with(|c: char| c.is_ascii_alphabetic())
&& scheme
.chars()
.all(|c| c.is_ascii_alphanumeric() || matches!(c, '+' | '-' | '.'))
}
None => false,
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::panic)]
#![allow(clippy::unwrap_used)]
use super::*;
#[test]
fn data_uri_round_trip_and_external() {
let store = b"\x00\x01\x02manifest";
let uri = encode_data_uri(store);
assert!(uri.starts_with(DATA_URI_PREFIX));
match parse_manifest_reference(&uri).unwrap() {
ManifestReference::Embedded(b) => assert_eq!(b, store),
ManifestReference::External => panic!("expected embedded"),
}
match parse_manifest_reference("https://example.com/a.c2pa").unwrap() {
ManifestReference::External => {}
ManifestReference::Embedded(_) => panic!("expected external"),
}
}
}