use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use pyo3::types::{PyBytes, PyDict};
use crate::document::Manifest;
use crate::hardbinding::{self, Algorithm, DataHash, Exclusion, Sha2};
fn map_err(e: crate::Error) -> PyErr {
match e.code() {
Some(code) => PyValueError::new_err(format!("{e} [{code}]")),
None => PyValueError::new_err(e.to_string()),
}
}
fn algorithm(alg: &str) -> PyResult<Algorithm> {
Algorithm::from_id(alg).map_err(map_err)
}
#[pyfunction]
fn embed<'py>(py: Python<'py>, html: &[u8], store: &[u8]) -> PyResult<Bound<'py, PyBytes>> {
let out = crate::document::embed(html, store).map_err(map_err)?;
Ok(PyBytes::new(py, &out))
}
#[pyfunction]
fn embed_reference<'py>(py: Python<'py>, html: &[u8], href: &str) -> PyResult<Bound<'py, PyBytes>> {
let out = crate::document::embed_reference(html, href).map_err(map_err)?;
Ok(PyBytes::new(py, &out))
}
#[pyfunction]
fn remove<'py>(py: Python<'py>, html: &[u8]) -> PyResult<Bound<'py, PyBytes>> {
let out = crate::document::remove(html).map_err(map_err)?;
Ok(PyBytes::new(py, &out))
}
#[pyfunction]
fn extract<'py>(py: Python<'py>, html: &[u8]) -> PyResult<Option<Bound<'py, PyDict>>> {
let manifest = match crate::document::extract(html) {
Ok(m) => m,
Err(crate::Error::NotFound) => return Ok(None),
Err(e) => return Err(map_err(e)),
};
let out = PyDict::new(py);
out.set_item("start", manifest.start())?;
out.set_item("length", manifest.length())?;
match &manifest {
Manifest::Embedded { store, .. } => {
out.set_item("kind", "embedded")?;
out.set_item("store", PyBytes::new(py, store))?;
}
Manifest::Referenced { href, .. } => {
out.set_item("kind", "referenced")?;
out.set_item("href", href.as_str())?;
}
}
Ok(Some(out))
}
#[pyfunction]
fn locate_all(html: &[u8]) -> Vec<(usize, usize)> {
crate::document::locate_all(html)
.into_iter()
.map(|r| (r.start, r.len()))
.collect()
}
fn data_hash_to_dict<'py>(py: Python<'py>, dh: &DataHash) -> PyResult<Bound<'py, PyDict>> {
let out = PyDict::new(py);
out.set_item("alg", dh.alg.as_str())?;
out.set_item("hash", PyBytes::new(py, &dh.hash))?;
out.set_item(
"exclusions",
dh.exclusions
.iter()
.map(|e| (e.start, e.length))
.collect::<Vec<_>>(),
)?;
Ok(out)
}
#[pyfunction]
#[pyo3(signature = (html, alg = "sha256"))]
fn compute_data_hash<'py>(py: Python<'py>, html: &[u8], alg: &str) -> PyResult<Bound<'py, PyDict>> {
let dh = hardbinding::compute_data_hash(html, algorithm(alg)?, &Sha2).map_err(map_err)?;
data_hash_to_dict(py, &dh)
}
#[pyfunction]
#[pyo3(signature = (html, hash, exclusions, alg = "sha256"))]
fn verify_data_hash(
html: &[u8],
hash: &[u8],
exclusions: Vec<(usize, usize)>,
alg: &str,
) -> PyResult<()> {
let dh = DataHash {
exclusions: exclusions
.into_iter()
.map(|(start, length)| Exclusion { start, length })
.collect(),
alg: algorithm(alg)?.id().to_string(),
hash: hash.to_vec(),
name: None,
};
hardbinding::verify_data_hash(html, &dh, &Sha2).map_err(map_err)
}
#[pyfunction]
#[pyo3(signature = (html, alg = "sha256"))]
fn inline_hash_before_embed<'py>(
py: Python<'py>,
html: &[u8],
alg: &str,
) -> PyResult<Bound<'py, PyBytes>> {
let digest = hardbinding::inline_hash_before_embed(html, algorithm(alg)?, &Sha2);
Ok(PyBytes::new(py, &digest))
}
#[pymodule]
fn c2pa_html(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(embed, m)?)?;
m.add_function(wrap_pyfunction!(embed_reference, m)?)?;
m.add_function(wrap_pyfunction!(extract, m)?)?;
m.add_function(wrap_pyfunction!(remove, m)?)?;
m.add_function(wrap_pyfunction!(locate_all, m)?)?;
m.add_function(wrap_pyfunction!(compute_data_hash, m)?)?;
m.add_function(wrap_pyfunction!(verify_data_hash, m)?)?;
m.add_function(wrap_pyfunction!(inline_hash_before_embed, m)?)?;
m.add("SCRIPT_TYPE", crate::document::SCRIPT_TYPE)?;
m.add("LINK_REL", crate::document::LINK_REL)?;
m.add("__version__", env!("CARGO_PKG_VERSION"))?;
Ok(())
}