c2pa-rs

A fork of https://github.com/contentauth/c2pa-rs/
git clone git://archive.git.mtrnord.blog/mtrnords-photography-manager/c2pa-rs.git
Log | Files | Refs | README

asset_io.rs (9933B)


      1 // Copyright 2022 Adobe. All rights reserved.
      2 // This file is licensed to you under the Apache License,
      3 // Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
      4 // or the MIT license (http://opensource.org/licenses/MIT),
      5 // at your option.
      6 
      7 // Unless required by applicable law or agreed to in writing,
      8 // this software is distributed on an "AS IS" BASIS, WITHOUT
      9 // WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
     10 // implied. See the LICENSE-MIT and LICENSE-APACHE files for the
     11 // specific language governing permissions and limitations under
     12 // each license.
     13 
     14 use std::{
     15     fmt,
     16     io::{Cursor, Read, Seek, Write},
     17     path::Path,
     18 };
     19 
     20 use tempfile::NamedTempFile;
     21 
     22 use crate::{assertions::BoxMap, error::Result};
     23 
     24 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
     25 pub enum HashBlockObjectType {
     26     Cai,
     27     Xmp,
     28     Other,
     29 }
     30 
     31 impl fmt::Display for HashBlockObjectType {
     32     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
     33         write!(f, "{self:?}")
     34     }
     35 }
     36 #[derive(Debug)]
     37 pub struct HashObjectPositions {
     38     pub offset: usize, // offset from beginning of file to the beginning of object
     39     pub length: usize, // length of object
     40     pub htype: HashBlockObjectType, // type of hash block object
     41 }
     42 
     43 pub trait CAIRead: Read + Seek + Send {}
     44 
     45 impl<T> CAIRead for T where T: Read + Seek + Send {}
     46 
     47 impl From<String> for Box<dyn CAIRead> {
     48     fn from(val: String) -> Self {
     49         Box::new(Cursor::new(val))
     50     }
     51 }
     52 
     53 // Helper struct to create a concrete type for CAIRead when
     54 // that is required.  For example a function defined like this
     55 //  pub fn read<T>(&self, reader: &mut T) cannot currently accept
     56 // a CAIRead trait because it is not Sized (bound to a object).
     57 // This will likely change in a future version of Rust.
     58 pub(crate) struct CAIReadWrapper<'a> {
     59     pub reader: &'a mut dyn CAIRead,
     60 }
     61 
     62 impl Read for CAIReadWrapper<'_> {
     63     fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
     64         self.reader.read(buf)
     65     }
     66 }
     67 
     68 impl Seek for CAIReadWrapper<'_> {
     69     fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
     70         self.reader.seek(pos)
     71     }
     72 }
     73 
     74 pub trait CAIReadWrite: CAIRead + Write {}
     75 
     76 impl<T> CAIReadWrite for T where T: CAIRead + Write {}
     77 
     78 // Helper struct to create a concrete type for CAIReadWrite when
     79 // that is required. For example a function defined like this
     80 //  pub fn write<T>(&self, writer: &mut T) cannot currently accept
     81 // a CAIReadWrite trait because it is not Sized (bound to a object).
     82 // This will likely change in a future version of Rust.
     83 // go away in future revisions of Rust.
     84 pub(crate) struct CAIReadWriteWrapper<'a> {
     85     pub reader_writer: &'a mut dyn CAIReadWrite,
     86 }
     87 
     88 impl Read for CAIReadWriteWrapper<'_> {
     89     fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
     90         self.reader_writer.read(buf)
     91     }
     92 }
     93 
     94 impl Write for CAIReadWriteWrapper<'_> {
     95     fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
     96         self.reader_writer.write(buf)
     97     }
     98 
     99     fn flush(&mut self) -> std::io::Result<()> {
    100         self.reader_writer.flush()
    101     }
    102 }
    103 
    104 impl Seek for CAIReadWriteWrapper<'_> {
    105     fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
    106         self.reader_writer.seek(pos)
    107     }
    108 }
    109 
    110 /// CAIReader trait to insure CAILoader method support both Read & Seek
    111 // Interface for in memory CAI reading
    112 pub trait CAIReader: Sync + Send {
    113     // Return entire CAI block as Vec<u8>
    114     fn read_cai(&self, asset_reader: &mut dyn CAIRead) -> Result<Vec<u8>>;
    115 
    116     // Get XMP block
    117     fn read_xmp(&self, asset_reader: &mut dyn CAIRead) -> Option<String>;
    118 }
    119 
    120 pub trait CAIWriter: Sync + Send {
    121     // Writes store_bytes into output_steam using input_stream as the source asset
    122     fn write_cai(
    123         &self,
    124         input_stream: &mut dyn CAIRead,
    125         output_stream: &mut dyn CAIReadWrite,
    126         store_bytes: &[u8],
    127     ) -> Result<()>;
    128 
    129     // Finds location where the C2PA manifests will be placed in the asset specified by input_stream
    130     fn get_object_locations_from_stream(
    131         &self,
    132         input_stream: &mut dyn CAIRead,
    133     ) -> Result<Vec<HashObjectPositions>>;
    134 
    135     // Remove entire C2PA manifest store from asset
    136     fn remove_cai_store_from_stream(
    137         &self,
    138         input_stream: &mut dyn CAIRead,
    139         output_stream: &mut dyn CAIReadWrite,
    140     ) -> Result<()>;
    141 }
    142 
    143 pub trait AssetIO: Sync + Send {
    144     // Create instance of AssetIO handler.  The extension type is passed in so
    145     // that format specific customizations can be used during manifest embedding
    146     fn new(asset_type: &str) -> Self
    147     where
    148         Self: Sized;
    149 
    150     // Return AssetIO handler for this asset type
    151     fn get_handler(&self, asset_type: &str) -> Box<dyn AssetIO>;
    152 
    153     // Return streaming reader for this asset type
    154     fn get_reader(&self) -> &dyn CAIReader;
    155 
    156     // Return streaming writer if available
    157     fn get_writer(&self, _asset_type: &str) -> Option<Box<dyn CAIWriter>> {
    158         None
    159     }
    160 
    161     // Return entire CAI block as Vec<u8>
    162     #[allow(dead_code)]
    163     fn read_cai_store(&self, asset_path: &Path) -> Result<Vec<u8>>;
    164 
    165     // Write the CAI block to an asset
    166     fn save_cai_store(&self, asset_path: &Path, store_bytes: &[u8]) -> Result<()>;
    167 
    168     /// List of standard object offsets
    169     /// If the offsets exist return the start of those locations other it should
    170     /// return the calculated location of when it should start.  There may still be a
    171     /// length if the format contains extra header information for example.
    172     #[allow(dead_code)] // this here for wasm builds to pass clippy  (todo: remove)
    173     fn get_object_locations(&self, asset_path: &Path) -> Result<Vec<HashObjectPositions>>;
    174 
    175     // Remove entire C2PA manifest store from asset
    176     #[allow(dead_code)] // this here for wasm builds to pass clippy  (todo: remove)
    177     fn remove_cai_store(&self, asset_path: &Path) -> Result<()>;
    178 
    179     // List of supported extensions and mime types
    180     fn supported_types(&self) -> &[&str];
    181 
    182     /// OPTIONAL INTERFACES
    183 
    184     // Returns [`AssetPatch`] trait if this I/O handler supports patching.
    185     #[allow(dead_code)] // this here for wasm builds to pass clippy  (todo: remove)
    186     fn asset_patch_ref(&self) -> Option<&dyn AssetPatch> {
    187         None
    188     }
    189 
    190     // Returns [`RemoteRefEmbed`] trait if this I/O handler supports remote reference embedding.
    191     fn remote_ref_writer_ref(&self) -> Option<&dyn RemoteRefEmbed> {
    192         None
    193     }
    194 
    195     // Returns [`AssetBoxHash`] trait if this I/O handler supports box hashing.
    196     fn asset_box_hash_ref(&self) -> Option<&dyn AssetBoxHash> {
    197         None
    198     }
    199 
    200     // Returns [`ComposedManifestRefEmbed`] trait if this I/O handler supports composed data.
    201     fn composed_data_ref(&self) -> Option<&dyn ComposedManifestRef> {
    202         None
    203     }
    204 }
    205 
    206 // `AssetPatch` optimizes output generation for asset_io handlers that
    207 // are able to patch blocks of data without changing any other data. The
    208 // resultant file must still be a valid asset. This saves having to rewrite
    209 // assets since only the patched bytes are modified.
    210 pub trait AssetPatch {
    211     // Patches an existing manifest store with new manifest store.
    212     // Only existing manifest stores of the same size may be patched
    213     // since any other changes will invalidate asset hashes.
    214     #[allow(dead_code)] // this here for wasm builds to pass clippy  (todo: remove)
    215     fn patch_cai_store(&self, asset_path: &Path, store_bytes: &[u8]) -> Result<()>;
    216 }
    217 
    218 // `AssetBoxHash` provides interfaces needed to support C2PA BoxHash functionality.
    219 //  This trait is only implemented for supported types
    220 pub trait AssetBoxHash {
    221     // Returns Vec containing all BoxMap level objects in the asset in the order
    222     // they occur in the asset.  The hashes do not need to be calculated, only the
    223     // name and the positional information.  The list should be flat with each BoxMap
    224     // representing a single entry.
    225     fn get_box_map(&self, input_stream: &mut dyn CAIRead) -> Result<Vec<BoxMap>>;
    226 }
    227 
    228 // Type of remote reference to embed.  Some of the listed
    229 // emums are for future uses and experiments.
    230 #[allow(dead_code)]
    231 pub enum RemoteRefEmbedType {
    232     Xmp(String),
    233     StegoS(String),
    234     StegoB(Vec<u8>),
    235     Watermark(String),
    236 }
    237 
    238 // `RemoteRefEmbed` is used to embed remote references to external manifests.  The
    239 // technique used to embed a reference varies bases on the type of embedding.  Not
    240 // all embedding choices need be supported.
    241 pub trait RemoteRefEmbed {
    242     // Embed RemoteRefEmbedType into the asset
    243     #[allow(dead_code)] // this here for wasm builds to pass clippy  (todo: remove)
    244     fn embed_reference(&self, asset_path: &Path, embed_ref: RemoteRefEmbedType) -> Result<()>;
    245     // Embed RemoteRefEmbedType into the asset stream
    246     fn embed_reference_to_stream(
    247         &self,
    248         source_stream: &mut dyn CAIRead,
    249         output_stream: &mut dyn CAIReadWrite,
    250         embed_ref: RemoteRefEmbedType,
    251     ) -> Result<()>;
    252 }
    253 
    254 /// `ComposedManifestRefEmbed` is used to generate a C2PA manifest.  The
    255 /// returned `Vec<u8>` contains data preformatted to be directly compatible
    256 /// with the type specified in `format`.  
    257 pub trait ComposedManifestRef {
    258     // Return entire CAI block as Vec<u8>
    259     fn compose_manifest(&self, manifest_data: &[u8], format: &str) -> Result<Vec<u8>>;
    260 }
    261 
    262 /// Utility function to rename or copy a temp file to a permanent location.
    263 ///
    264 /// If the rename is not possible, due to cross volume references & etc, it will copy instead.
    265 pub fn rename_or_copy<P>(temp_file: NamedTempFile, asset_path: P) -> Result<()>
    266 where
    267     P: AsRef<Path>,
    268 {
    269     // clear temp flag for Windows
    270     let (_, path) = temp_file
    271         .keep()
    272         .map_err(|e| crate::Error::OtherError(Box::new(e)))?;
    273 
    274     std::fs::rename(&path, asset_path.as_ref())
    275         // if rename fails, try to copy in case we are on different volumes
    276         .or_else(|_| std::fs::copy(&path, asset_path).and(Ok(())))
    277         .map_err(crate::Error::IoError)
    278 }