Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions sdk/src/asset_handlers/bmff_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1640,6 +1640,15 @@ impl AssetIO for BmffIO {
fn supported_types(&self) -> &[&str] {
&SUPPORTED_TYPES
}

fn supports_stream(&self, stream: &mut dyn CAIRead) -> Result<bool> {
stream.rewind()?;

let mut header = [0u8; 12];
stream.read_exact(&mut header)?;

Ok(header[4..8] == *b"ftyp")
}
}

impl CAIWriter for BmffIO {
Expand Down
16 changes: 14 additions & 2 deletions sdk/src/asset_handlers/c2pa_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ use serde_bytes::ByteBuf;
use crate::{
assertions::{BoxMap, C2PA_BOXHASH},
asset_io::{
AssetBoxHash, AssetIO, CAIRead, CAIReadWrite, CAIReader, CAIWriter, ComposedManifestRef,
HashBlockObjectType, HashObjectPositions,
AssetBoxHash, AssetIO, CAIRead, CAIReadWrapper, CAIReadWrite, CAIReader, CAIWriter,
ComposedManifestRef, HashBlockObjectType, HashObjectPositions,
},
error::{Error, Result},
jumbf::boxes::{self, BoxReader, Cai},
};

static SUPPORTED_TYPES: [&str; 3] = [
Expand All @@ -35,6 +36,8 @@ pub struct C2paIO {}

impl CAIReader for C2paIO {
fn read_cai(&self, asset_reader: &mut dyn CAIRead) -> Result<Vec<u8>> {
asset_reader.rewind()?;

let mut cai_data = Vec::new();
// read the whole file
asset_reader.read_to_end(&mut cai_data)?;
Expand Down Expand Up @@ -131,6 +134,15 @@ impl AssetIO for C2paIO {
&SUPPORTED_TYPES
}

fn supports_stream(&self, stream: &mut dyn CAIRead) -> Result<bool> {
stream.rewind()?;

let super_box = BoxReader::read_super_box(&mut CAIReadWrapper { reader: stream })?;
let cai_block = Cai::from(super_box);

Ok(cai_block.desc_box().uuid() == boxes::CAI_BLOCK_UUID)
}

fn composed_data_ref(&self) -> Option<&dyn ComposedManifestRef> {
Some(self)
}
Expand Down
9 changes: 9 additions & 0 deletions sdk/src/asset_handlers/gif_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,15 @@ impl AssetIO for GifIO {
fn supported_types(&self) -> &[&str] {
&["gif", "image/gif"]
}

fn supports_stream(&self, stream: &mut dyn CAIRead) -> Result<bool> {
stream.rewind()?;

let mut header = [0u8; 6];
stream.read_exact(&mut header)?;

Ok(header == *b"GIF87a" || header == *b"GIF89a")
}
}

impl GifIO {
Expand Down
9 changes: 9 additions & 0 deletions sdk/src/asset_handlers/jpeg_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,15 @@ impl AssetIO for JpegIO {
fn supported_types(&self) -> &[&str] {
&SUPPORTED_TYPES
}

fn supports_stream(&self, stream: &mut dyn CAIRead) -> Result<bool> {
stream.rewind()?;

let mut header = [0u8; 3];
stream.read_exact(&mut header)?;

Ok(header == [0xff, 0xd8, 0xff])
}
}

impl RemoteRefEmbed for JpegIO {
Expand Down
12 changes: 12 additions & 0 deletions sdk/src/asset_handlers/mp3_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,18 @@ impl AssetIO for Mp3IO {
fn supported_types(&self) -> &[&str] {
&SUPPORTED_TYPES
}

fn supports_stream(&self, stream: &mut dyn CAIRead) -> Result<bool> {
stream.rewind()?;

let mut header = [0u8; 3];
stream.read_exact(&mut header)?;

Ok(
(header[0] == 0x49 && header[1] == 0x44 && header[2] == 0x33)
|| (header[0] == 0xff && header[1] == 0xfb),
)
}
}

impl CAIWriter for Mp3IO {
Expand Down
12 changes: 10 additions & 2 deletions sdk/src/asset_handlers/pdf_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ use std::{fs::File, path::Path};
use crate::{
asset_handlers::pdf::{C2paPdf, Pdf},
asset_io::{AssetIO, CAIRead, CAIReader, CAIWriter, ComposedManifestRef, HashObjectPositions},
Error,
Error::{JumbfNotFound, NotImplemented, PdfReadError},
Error::{self, JumbfNotFound, NotImplemented, PdfReadError},
};

static SUPPORTED_TYPES: [&str; 2] = ["pdf", "application/pdf"];
Expand Down Expand Up @@ -109,6 +108,15 @@ impl AssetIO for PdfIO {
&SUPPORTED_TYPES
}

fn supports_stream(&self, stream: &mut dyn CAIRead) -> crate::Result<bool> {
stream.rewind()?;

let mut header = [0u8; 5];
stream.read_exact(&mut header)?;

Ok(header == *b"%PDF-")
}

fn composed_data_ref(&self) -> Option<&dyn ComposedManifestRef> {
Some(self)
}
Expand Down
10 changes: 10 additions & 0 deletions sdk/src/asset_handlers/png_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ fn read_string(asset_reader: &mut dyn CAIRead, max_read: u32) -> Result<String>

Ok(String::from_utf8_lossy(&s).to_string())
}

pub struct PngIO {}

impl CAIReader for PngIO {
Expand Down Expand Up @@ -569,6 +570,15 @@ impl AssetIO for PngIO {
fn supported_types(&self) -> &[&str] {
&SUPPORTED_TYPES
}

fn supports_stream(&self, stream: &mut dyn CAIRead) -> Result<bool> {
stream.rewind()?;

let mut header = [0u8; 8];
stream.read_exact(&mut header)?;

Ok(header == [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])
}
}

fn get_xmp_insertion_point(asset_reader: &mut dyn CAIRead) -> Option<(u64, u32)> {
Expand Down
10 changes: 10 additions & 0 deletions sdk/src/asset_handlers/riff_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,16 @@ impl AssetIO for RiffIO {
fn supported_types(&self) -> &[&str] {
&SUPPORTED_TYPES
}

fn supports_stream(&self, stream: &mut dyn CAIRead) -> Result<bool> {
stream.rewind()?;

let mut header = [0u8; 4];
stream.read_exact(&mut header)?;

// TODO: if we want to detect the subtype we can do extra parsing on the next few bytes
Ok(header == *b"RIFF")
}
}

impl CAIWriter for RiffIO {
Expand Down
9 changes: 9 additions & 0 deletions sdk/src/asset_handlers/svg_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,15 @@ impl AssetIO for SvgIO {
fn supported_types(&self) -> &[&str] {
&SUPPORTED_TYPES
}

fn supports_stream(&self, stream: &mut dyn CAIRead) -> Result<bool> {
stream.rewind()?;

let mut bytes = [0u8; 4096];
let len = stream.read(&mut bytes)?;

Ok(bytes[..len].windows(4).any(|pattern| pattern == b"<svg"))
}
}

// create manifest entry
Expand Down
19 changes: 19 additions & 0 deletions sdk/src/asset_handlers/tiff_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1332,6 +1332,7 @@ where

asset_reader.read_to_vec(xmp_ifd_entry.value_count).ok()
}

pub struct TiffIO {}

impl CAIReader for TiffIO {
Expand Down Expand Up @@ -1426,6 +1427,24 @@ impl AssetIO for TiffIO {
fn supported_types(&self) -> &[&str] {
&SUPPORTED_TYPES
}

fn supports_stream(&self, stream: &mut dyn CAIRead) -> Result<bool> {
stream.rewind()?;

let mut header = [0u8; 4];
stream.read_exact(&mut header)?;

Ok(
// Little-endian TIFF
header == [0x49, 0x49, 0x2A, 0x00]
// Big-endian TIFF
|| header == [0x4D, 0x4D, 0x00, 0x2A]
// Little-endian BigTIFF
|| header == [0x49, 0x49, 0x2B, 0x00]
// Big-endian BigTIFF
|| header == [0x4D, 0x4D, 0x00, 0x2B],
)
}
}

impl CAIWriter for TiffIO {
Expand Down
10 changes: 9 additions & 1 deletion sdk/src/asset_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,14 @@ pub trait AssetIO: Sync + Send {
// List of supported extensions and mime types
fn supported_types(&self) -> &[&str];

/// Returns whether the asset handler supports parsing the specified stream.
///
/// It's recommended to default to true if unimplemented for a particular format.
fn supports_stream(&self, stream: &mut dyn CAIRead) -> Result<bool> {
let _stream = stream;
Ok(true)
}

// OPTIONAL INTERFACES

// Returns [`AssetPatch`] trait if this I/O handler supports patching.
Expand Down Expand Up @@ -260,7 +268,7 @@ pub trait RemoteRefEmbed {

/// `ComposedManifestRefEmbed` is used to generate a C2PA manifest. The
/// returned `Vec<u8>` contains data preformatted to be directly compatible
/// with the type specified in `format`.
/// with the type specified in `format`.
pub trait ComposedManifestRef {
// Return entire CAI block as Vec<u8>
fn compose_manifest(&self, manifest_data: &[u8], format: &str) -> Result<Vec<u8>>;
Expand Down
10 changes: 10 additions & 0 deletions sdk/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,16 @@ pub enum Error {
#[error("thumbnail format {0} is unsupported")]
UnsupportedThumbnailFormat(String),

#[error("the specified stream is not of format {format}")]
InvalidFormat { format: String },

#[error("failed to check format {format}")]
FormatCheckFailed {
format: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},

#[error("`trust.signer_info` is missing from settings")]
MissingSignerSettings,

Expand Down
37 changes: 35 additions & 2 deletions sdk/src/jumbf_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,16 +100,41 @@ pub(crate) fn is_bmff_format(asset_type: &str) -> bool {
bmff_io.supported_types().contains(&asset_type)
}

/// Checks whether the stream matches the specified asset type.
///
/// This function may return the following errors:
/// - [Error::InvalidFormat] if the stream doesn't match the input format.
/// - [Error::FormatCheckFailed] if there was an error during the format check.
/// - [Error::UnsupportedType] if the format does not have a corresponding parser.
pub fn check_stream_supported(asset_type: &str, stream: &mut dyn CAIRead) -> Result<()> {
match get_assetio_handler(asset_type) {
Some(asset_handler) => match asset_handler.supports_stream(stream) {
Err(err) => Err(Error::FormatCheckFailed {
format: asset_type.to_owned(),
source: Some(Box::new(err)),
}),
Ok(false) => Err(Error::InvalidFormat {
format: asset_type.to_owned(),
}),
Ok(true) => Ok(()),
},
None => Err(Error::UnsupportedType),
}
}

/// Return jumbf block from in memory asset
#[allow(dead_code)]
pub fn load_jumbf_from_memory(asset_type: &str, data: &[u8]) -> Result<Vec<u8>> {
let mut buf_reader = Cursor::new(data);

check_stream_supported(asset_type, &mut buf_reader)?;
load_jumbf_from_stream(asset_type, &mut buf_reader)
}

/// Return jumbf block from stream asset
pub fn load_jumbf_from_stream(asset_type: &str, input_stream: &mut dyn CAIRead) -> Result<Vec<u8>> {
check_stream_supported(asset_type, input_stream)?;

let cai_block = match get_cailoader_handler(asset_type) {
Some(asset_handler) => asset_handler.read_cai(input_stream)?,
None => return Err(Error::UnsupportedType),
Expand All @@ -127,6 +152,8 @@ pub fn save_jumbf_to_stream(
output_stream: &mut dyn CAIReadWrite,
store_bytes: &[u8],
) -> Result<()> {
check_stream_supported(asset_type, input_stream)?;

match get_caiwriter_handler(asset_type) {
Some(asset_handler) => asset_handler.write_cai(input_stream, output_stream, store_bytes),
None => Err(Error::UnsupportedType),
Expand All @@ -136,6 +163,8 @@ pub fn save_jumbf_to_stream(
/// writes the jumbf data in store_bytes into an asset in data and returns the newly created asset
pub fn save_jumbf_to_memory(asset_type: &str, data: &[u8], store_bytes: &[u8]) -> Result<Vec<u8>> {
let mut input_stream = Cursor::new(data);
check_stream_supported(asset_type, &mut input_stream)?;

let output_vec: Vec<u8> = Vec::with_capacity(data.len() + store_bytes.len() + 1024);
let mut output_stream = Cursor::new(output_vec);

Expand Down Expand Up @@ -508,8 +537,12 @@ pub mod tests {
.unwrap();
removed.set_position(0);
let result = load_jumbf_from_stream(asset_type, &mut removed);
if (asset_type != "wav")
&& (asset_type != "avi" && asset_type != "mp3" && asset_type != "webp")
// For c2pa this is expected to return `Error::InvalidFormat`.
if asset_type != "wav"
&& asset_type != "avi"
&& asset_type != "mp3"
&& asset_type != "webp"
&& asset_type != "c2pa"
{
assert!(matches!(&result.err().unwrap(), Error::JumbfNotFound));
}
Expand Down