Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
5 changes: 5 additions & 0 deletions sdk/src/asset_handlers/c2pa_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ impl AssetIO for C2paIO {
&SUPPORTED_TYPES
}

fn supports_stream(&self, _stream: &mut dyn CAIRead) -> Result<bool> {
// TODO: complex
Ok(true)
}

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
11 changes: 10 additions & 1 deletion sdk/src/asset_handlers/jpeg_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
use std::{
collections::HashMap,
fs::File,
io::{BufReader, Cursor, Write},
io::{BufReader, Cursor, SeekFrom, Write},
path::*,
};

Expand Down 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
4 changes: 4 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,10 @@ impl AssetIO for Mp3IO {
fn supported_types(&self) -> &[&str] {
&SUPPORTED_TYPES
}
fn supports_stream(&self, _stream: &mut dyn CAIRead) -> Result<bool> {
// TODO: complex
Ok(true)
}
}

impl CAIWriter for Mp3IO {
Expand Down
14 changes: 11 additions & 3 deletions sdk/src/asset_handlers/pdf_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,12 @@
// specific language governing permissions and limitations under
// each license.

use std::{fs::File, path::Path};
use std::{fs::File, io::SeekFrom, 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
5 changes: 5 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,11 @@ impl AssetIO for SvgIO {
fn supported_types(&self) -> &[&str] {
&SUPPORTED_TYPES
}

fn supports_stream(&self, _stream: &mut dyn CAIRead) -> Result<bool> {
// TODO: complex
Ok(true)
}
}

// 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
3 changes: 3 additions & 0 deletions sdk/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,9 @@ pub enum Error {
#[error("thumbnail format {0} is unsupported")]
UnsupportedThumbnailFormat(String),

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

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

Expand Down
22 changes: 22 additions & 0 deletions sdk/src/jumbf_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,16 +100,34 @@ 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, or otherwise returns
/// [Error::IncorrectFormat].
pub fn check_stream_supported(asset_type: &str, stream: &mut dyn CAIRead) -> Result<()> {
match get_assetio_handler(asset_type) {
Some(asset_handler) => {
if !asset_handler.supports_stream(stream)? {
Err(Error::IncorrectFormat(asset_type.to_owned()))
} else {
Ok(())
}
}
None => Err(Error::IncorrectFormat(asset_type.to_owned())),
}
}

/// 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 +145,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 +156,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
4 changes: 2 additions & 2 deletions sdk/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5797,11 +5797,11 @@ pub mod tests {
&mut report,
&Settings::default(),
);
assert!(matches!(result, Err(Error::UnsupportedType)));
assert!(matches!(result, Err(Error::IncorrectFormat(_))));
println!("Error report: {report:?}");
assert!(!report.logged_items().is_empty());

assert!(report.has_error(Error::UnsupportedType));
assert!(report.has_error(Error::IncorrectFormat(format.to_owned())));
}

#[test]
Expand Down