#[cfg(feature = "file_io")]
use std::path::PathBuf;
use std::{
ffi::OsStr,
io::{Read, Seek, SeekFrom, Write},
path::Path,
};
#[allow(unused)] use tempfile::{tempdir, Builder, NamedTempFile, SpooledTempFile, TempDir};
use crate::{asset_io::rename_or_move, Error, Result};
#[allow(dead_code)]
pub(crate) fn patch_data_in_file(
source_path: &Path,
start_location: u64,
replace_len: u64,
data: &[u8],
) -> Result<()> {
let mut source = std::fs::File::open(source_path)?;
let mut dest = tempfile_builder("c2pa_temp")?;
patch_stream(&mut source, &mut dest, start_location, replace_len, data)?;
rename_or_move(dest, source_path)?;
Ok(())
}
#[allow(dead_code)]
pub(crate) fn insert_data_at<R: Read + Seek, W: Write>(
source: &mut R,
dest: &mut W,
location: u64,
data: &[u8],
) -> Result<()> {
source.rewind()?;
let mut before_handle = source.take(location);
std::io::copy(&mut before_handle, dest)?;
dest.write_all(data)?;
let source = before_handle.into_inner();
source.seek(SeekFrom::Start(location))?;
std::io::copy(source, dest)?;
Ok(())
}
#[allow(dead_code)]
pub(crate) fn patch_stream<R: Read + Seek + ?Sized, W: Write + ?Sized>(
source: &mut R,
dest: &mut W,
start_location: u64,
replace_len: u64,
data: &[u8],
) -> Result<()> {
source.rewind()?;
let source_len = stream_len(source)?;
if start_location + replace_len > source_len {
return Err(Error::BadParam("read past end of source stream".into()));
}
let mut before_handle = source.take(start_location);
std::io::copy(&mut before_handle, dest)?;
dest.write_all(data)?;
let source = before_handle.into_inner();
source.seek(SeekFrom::Start(start_location + replace_len))?;
std::io::copy(source, dest)?;
Ok(())
}
#[allow(dead_code)]
pub(crate) fn stream_len<R: Read + Seek + ?Sized>(reader: &mut R) -> Result<u64> {
let old_pos = reader.stream_position()?;
let len = reader.seek(SeekFrom::End(0))?;
if old_pos != len {
reader.seek(SeekFrom::Start(old_pos))?;
}
Ok(len)
}
#[cfg(target_arch = "wasm32")]
pub(crate) fn stream_with_fs_fallback(_threshold: usize) -> impl Read + Write + Seek {
std::io::Cursor::new(Vec::new())
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn stream_with_fs_fallback(threshold: usize) -> impl Read + Write + Seek {
SpooledTempFile::new(threshold * 1024 * 1024)
}
pub(crate) fn safe_vec<T: Clone>(item_cnt: u64, init_with: Option<T>) -> Result<Vec<T>> {
let num_items = usize::try_from(item_cnt)?;
let mut output: Vec<T> = Vec::new();
output
.try_reserve_exact(num_items)
.map_err(|_e| Error::InsufficientMemory)?;
if let Some(i) = init_with {
output.resize(num_items, i);
}
Ok(output)
}
pub trait ReaderUtils {
fn read_to_vec(&mut self, data_len: u64) -> Result<Vec<u8>>;
}
impl<R: Read + Seek> ReaderUtils for R {
fn read_to_vec(&mut self, data_len: u64) -> Result<Vec<u8>> {
let old_pos = self.stream_position()?;
let len = self.seek(SeekFrom::End(0))?;
if old_pos != len {
self.seek(SeekFrom::Start(old_pos))?;
}
if old_pos
.checked_add(data_len)
.ok_or(Error::BadParam("source stream read out of range".into()))?
> len
{
return Err(Error::BadParam("read past end of source stream".into()));
}
let mut output: Vec<u8> = safe_vec(data_len, None)?;
self.take(data_len).read_to_end(&mut output)?;
Ok(output)
}
}
pub(crate) fn tempfile_builder<T: AsRef<OsStr> + Sized>(prefix: T) -> Result<NamedTempFile> {
#[cfg(all(target_os = "wasi", target_env = "p1"))]
return Err(Error::NotImplemented(
"tempfile_builder requires wasip2 or later".to_string(),
));
#[cfg(all(target_os = "wasi", not(target_env = "p1")))]
return Builder::new()
.prefix(&prefix)
.rand_bytes(5)
.tempfile_in("/")
.map_err(Error::IoError);
#[cfg(not(target_os = "wasi"))]
return Builder::new()
.prefix(&prefix)
.rand_bytes(5)
.tempfile()
.map_err(Error::IoError);
}
#[allow(dead_code)] pub(crate) fn tempdirectory() -> Result<TempDir> {
#[cfg(target_os = "wasi")]
return TempDir::new_in("/").map_err(Error::IoError);
#[cfg(not(target_os = "wasi"))]
return tempdir().map_err(Error::IoError);
}
#[cfg(feature = "file_io")]
pub fn uri_to_path(uri: &str, manifest_label: Option<&str>) -> PathBuf {
let mut path_str = uri.replace(':', "_");
if let Some(stripped) = path_str.strip_prefix("self#jumbf=") {
path_str = stripped.to_owned();
} else {
return PathBuf::from(path_str);
}
let mut path = PathBuf::from(path_str);
if let Ok(stripped) = path.strip_prefix("/c2pa/") {
path = stripped.to_path_buf();
} else if let Some(manifest_label) = manifest_label {
let mut new_path = PathBuf::from(manifest_label.replace(':', "_"));
new_path.push(path);
path = new_path;
}
path
}
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used)]
#![allow(clippy::unwrap_used)]
use std::io::Cursor;
#[cfg(feature = "file_io")]
#[test]
fn test_uri_to_path() {
let uri = "self#jumbf=/c2pa/urn:uuid:b3386820-9994-4b58-926f-1c47b82504c4:contentauth/c2pa.assertions/c2pa.thumbnail.claim.jpeg";
let expected_path = "urn_uuid_b3386820-9994-4b58-926f-1c47b82504c4_contentauth/c2pa.assertions/c2pa.thumbnail.claim.jpeg";
assert_eq!(uri_to_path(uri, None), PathBuf::from(expected_path));
assert_eq!(
uri_to_path(expected_path, None),
PathBuf::from(expected_path)
);
let uri = "self#jumbf=c2pa.assertions/c2pa.thumbnail.claim";
let manifest_label = "test";
let expected_path = format!("{manifest_label}/c2pa.assertions/c2pa.thumbnail.claim");
assert_eq!(
uri_to_path(uri, Some(manifest_label)),
PathBuf::from(&expected_path)
);
assert_eq!(
uri_to_path(&expected_path, Some(manifest_label)),
PathBuf::from(expected_path)
);
let uri = "self#jumbf=c2pa.assertions/c2pa.thumbnail.claim";
let manifest_label_with_colon = "urn:uuid:test:label";
let expected_path_with_colon = "urn_uuid_test_label/c2pa.assertions/c2pa.thumbnail.claim";
assert_eq!(
uri_to_path(uri, Some(manifest_label_with_colon)),
PathBuf::from(expected_path_with_colon)
);
}
use super::*;
#[test]
fn test_patch_stream() {
let source = "this is a very very good test";
let mut output = Vec::new();
patch_stream(&mut Cursor::new(source.as_bytes()), &mut output, 10, 5, &[]).unwrap();
assert_eq!(&output, "this is a very good test".as_bytes());
let mut output = Vec::new();
patch_stream(
&mut Cursor::new(source.as_bytes()),
&mut output,
10,
14,
"so so".as_bytes(),
)
.unwrap();
assert_eq!(&output, "this is a so so test".as_bytes());
let mut output = Vec::new();
patch_stream(
&mut Cursor::new(source.as_bytes()),
&mut output,
10,
0,
"very ".as_bytes(),
)
.unwrap();
assert_eq!(&output, "this is a very very very good test".as_bytes());
let mut output = Vec::new();
patch_stream(
&mut Cursor::new(source.as_bytes()),
&mut output,
0,
29,
"all new data".as_bytes(),
)
.unwrap();
assert_eq!(&output, "all new data".as_bytes());
let mut output = Vec::new();
patch_stream(&mut Cursor::new(source.as_bytes()), &mut output, 0, 29, &[]).unwrap();
assert_eq!(&output, "".as_bytes());
let mut output = Vec::new();
assert!(patch_stream(
&mut Cursor::new(source.as_bytes()),
&mut output,
10,
29,
&[],
)
.is_err());
}
}