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

reader.rs (9121B)


      1 // Copyright 2024 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 //! The Reader provides a way to read a manifest store from an asset.
     15 //! It also performs validation on the manifest store.
     16 
     17 #[cfg(feature = "file_io")]
     18 use std::fs::{read, File};
     19 use std::io::{Read, Seek, Write};
     20 
     21 use async_generic::async_generic;
     22 
     23 #[cfg(feature = "file_io")]
     24 use crate::error::Error;
     25 use crate::{
     26     claim::ClaimAssetData, error::Result, manifest_store::ManifestStore,
     27     settings::get_settings_value, status_tracker::DetailedStatusTracker, store::Store,
     28     validation_status::ValidationStatus, Manifest, ManifestStoreReport,
     29 };
     30 
     31 /// A reader for the manifest store.
     32 pub struct Reader {
     33     pub(crate) manifest_store: ManifestStore,
     34 }
     35 
     36 impl Reader {
     37     /// Create a manifest store Reader from a stream.
     38     /// # Arguments
     39     /// * `format` - The format of the stream.
     40     /// * `stream` - The stream to read from.
     41     /// # Returns
     42     /// A reader for the manifest store.
     43     /// # Errors
     44     /// If the stream is not a valid manifest store.
     45     /// validation status should be checked for non severe errors
     46     /// # Example
     47     /// ```no_run
     48     /// use std::io::Cursor;
     49     ///
     50     /// use c2pa::Reader;
     51     /// let mut stream = Cursor::new(include_bytes!("../tests/fixtures/CA.jpg"));
     52     /// let reader = Reader::from_stream("image/jpeg", stream).unwrap();
     53     /// println!("{}", reader.json());
     54     /// ```
     55     #[async_generic()]
     56     pub fn from_stream(format: &str, mut stream: impl Read + Seek + Send) -> Result<Reader> {
     57         let verify = get_settings_value::<bool>("verify.verify_after_reading")?; // defaults to true
     58         let reader = if _sync {
     59             ManifestStore::from_stream(format, &mut stream, verify)
     60         } else {
     61             ManifestStore::from_stream_async(format, &mut stream, verify).await
     62         }?;
     63         Ok(Reader {
     64             manifest_store: reader,
     65         })
     66     }
     67 
     68     #[cfg(feature = "file_io")]
     69     /// Create a manifest store Reader from a file.
     70     /// # Arguments
     71     /// * `path` - The path to the file.
     72     /// # Returns
     73     /// A reader for the manifest store.
     74     /// # Errors
     75     /// If the file is not a valid manifest store.
     76     /// validation status should be checked for non severe errors.
     77     /// # Example
     78     /// ```no_run
     79     /// use c2pa::Reader;
     80     /// let reader = Reader::from_file("path/to/file.jpg").unwrap();
     81     /// ```
     82     /// # Note
     83     /// If the file does not have a manifest store, the function will check for a sidecar manifest
     84     /// with the same name and a .c2pa extension.
     85     pub fn from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Reader> {
     86         let path = path.as_ref();
     87         let format = crate::format_from_path(path).ok_or(crate::Error::UnsupportedType)?;
     88         let mut file = File::open(path)?;
     89         let result = Self::from_stream(&format, &mut file);
     90         if let Err(Error::JumbfNotFound) = result {
     91             // if not embedded or cloud, check for sidecar first and load if it exists
     92             let potential_sidecar_path = path.with_extension("c2pa");
     93             if potential_sidecar_path.exists() {
     94                 let manifest_data = read(potential_sidecar_path)?;
     95                 return Self::from_manifest_data_and_stream(&manifest_data, &format, &mut file);
     96             }
     97         }
     98         result
     99     }
    100 
    101     /// Create a manifest store [`Reader`]` from a JSON string.
    102     /// # Arguments
    103     /// * `json` - A Json String containing a manifest store definition.
    104     /// # Returns
    105     /// A [`Reader`]` for the manifest store.
    106     /// # Note
    107     /// This should only be used for testing
    108     /// Any referenced resources will not be available
    109     pub fn from_json(json: &str) -> Result<Reader> {
    110         let manifest_store = serde_json::from_str(json)?;
    111         Ok(Reader { manifest_store })
    112     }
    113 
    114     /// Create a manifest store [`Reader`] from existing c2pa_data and a stream
    115     /// You can use this to validate a remote manifest or a sidecar manifest
    116     /// # Arguments
    117     /// * `c2pa_data` - The c2pa data (a manifest store in JUMBF format)
    118     /// * `format` - The format of the stream
    119     /// * `stream` - The stream to verify the store against
    120     /// # Returns
    121     /// A [`Reader`] for the manifest store
    122     /// # Errors
    123     /// If the c2pa_data is not valid, or severe errors occur in validation
    124     /// validation status should be checked for non severe errors
    125     #[async_generic()]
    126     pub fn from_manifest_data_and_stream(
    127         c2pa_data: &[u8],
    128         format: &str,
    129         mut stream: impl Read + Seek + Send,
    130     ) -> Result<Reader> {
    131         let mut validation_log = DetailedStatusTracker::new();
    132 
    133         // first we convert the JUMBF into a usable store
    134         let store = Store::from_jumbf(c2pa_data, &mut validation_log)?;
    135 
    136         if _sync {
    137             Store::verify_store(
    138                 &store,
    139                 &mut ClaimAssetData::Stream(&mut stream, format),
    140                 &mut validation_log,
    141             )?;
    142         } else {
    143             Store::verify_store_async(
    144                 &store,
    145                 &mut ClaimAssetData::Stream(&mut stream, format),
    146                 &mut validation_log,
    147             )
    148             .await?;
    149         }
    150 
    151         Ok(Reader {
    152             manifest_store: ManifestStore::from_store(store, &validation_log),
    153         })
    154     }
    155 
    156     /// Get the manifest store as a JSON string
    157     pub fn json(&self) -> String {
    158         self.manifest_store.to_string()
    159     }
    160 
    161     /// Get the [`ValidationStatus`] array of the manifest store if it exists.
    162     ///
    163     /// This validation report only includes error statuses on applied to the active manifest.
    164     /// And error statuses for ingredients that are not already reported by the ingredient status.
    165     /// The uri field can be used to identify the associated manifest.
    166     /// # Example
    167     /// ```no_run
    168     /// use c2pa::Reader;
    169     /// let stream = std::io::Cursor::new(include_bytes!("../tests/fixtures/CA.jpg"));
    170     /// let reader = Reader::from_stream("image/jpeg", stream).unwrap();
    171     /// let status = reader.validation_status();
    172     /// ```
    173     /// # Note
    174     /// The validation status should be checked for validation errors.
    175     pub fn validation_status(&self) -> Option<&[ValidationStatus]> {
    176         self.manifest_store.validation_status()
    177     }
    178 
    179     /// Return the active [`Manifest`] if it exists.
    180     pub fn active_manifest(&self) -> Option<&Manifest> {
    181         self.manifest_store.get_active()
    182     }
    183 
    184     /// Return the active [`Manifest`] label if one exists.
    185     pub fn active_label(&self) -> Option<&str> {
    186         self.manifest_store.active_label()
    187     }
    188 
    189     /// Returns an iterator over [`Manifest`][Manifest]s.
    190     pub fn iter_manifests(&self) -> impl Iterator<Item = &Manifest> + '_ {
    191         self.manifest_store.manifests().values()
    192     }
    193 
    194     /// Return a [`Manifest`] for a given label if it exists.
    195     /// # Arguments
    196     /// * `label` - The label of the requested [`Manifest`]
    197     pub fn get_manifest(&self, label: &str) -> Option<&Manifest> {
    198         self.manifest_store.get(label)
    199     }
    200 
    201     /// Write a resource identified by URI to the given stream.
    202     /// # Arguments
    203     /// * `uri` - The URI of the resource to write (from an identifier field).
    204     /// * `stream` - The stream to write to.
    205     /// # Returns
    206     /// The number of bytes written.
    207     /// # Errors
    208     /// If the resource does not exist.
    209     /// # Example
    210     /// ```no_run
    211     /// use c2pa::Reader;
    212     /// let stream = std::io::Cursor::new(Vec::new());
    213     /// let reader = Reader::from_file("path/to/file.jpg").unwrap();
    214     /// let manifest = reader.active_manifest().unwrap();
    215     /// let uri = &manifest.thumbnail_ref().unwrap().identifier;
    216     /// let bytes_written = reader.resource_to_stream(uri, stream).unwrap();
    217     /// ```
    218     pub fn resource_to_stream(
    219         &self,
    220         uri: &str,
    221         mut stream: impl Write + Read + Seek + Send,
    222     ) -> Result<usize> {
    223         self.manifest_store
    224             .get_resource(uri, &mut stream)
    225             .map(|size| size as usize)
    226     }
    227 }
    228 
    229 impl Default for Reader {
    230     fn default() -> Self {
    231         Self {
    232             manifest_store: ManifestStore::new(),
    233         }
    234     }
    235 }
    236 
    237 impl std::fmt::Display for Reader {
    238     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    239         f.write_str(self.json().as_str())
    240     }
    241 }
    242 
    243 impl std::fmt::Debug for Reader {
    244     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    245         let report = ManifestStoreReport::from_store(self.manifest_store.store())
    246             .map_err(|_| std::fmt::Error)?;
    247         f.write_str(&report.to_string())
    248     }
    249 }