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

svg_io.rs (26278B)


      1 // Copyright 2023 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     fs::{File, OpenOptions},
     16     io::{BufReader, Cursor, Seek, SeekFrom, Write},
     17     path::Path,
     18 };
     19 
     20 use conv::ValueFrom;
     21 use fast_xml::{
     22     events::{BytesText, Event},
     23     Reader, Writer,
     24 };
     25 use tempfile::Builder;
     26 
     27 use crate::{
     28     asset_io::{
     29         rename_or_copy,
     30         AssetIO,
     31         AssetPatch,
     32         CAIRead,
     33         CAIReadWrite,
     34         CAIReader,
     35         CAIWriter, //RemoteRefEmbedType,
     36         HashBlockObjectType,
     37         //HashBlockObjectType,
     38         HashObjectPositions,
     39         RemoteRefEmbed,
     40     },
     41     error::{Error, Result},
     42     utils::base64,
     43 };
     44 
     45 static SUPPORTED_TYPES: [&str; 8] = [
     46     "svg",
     47     "application/svg+xml",
     48     "xhtml",
     49     "xml",
     50     "application/xhtml+xml",
     51     "application/xml",
     52     "image/svg+xml",
     53     "text/xml",
     54 ];
     55 
     56 const SVG: &str = "svg";
     57 const METADATA: &str = "metadata";
     58 const MANIFEST: &str = "c2pa:manifest";
     59 const MANIFEST_NS: &str = "xmlns:c2pa";
     60 const MANIFEST_NS_VAL: &str = "http://c2pa.org/manifest";
     61 
     62 pub struct SvgIO {}
     63 
     64 impl CAIReader for SvgIO {
     65     fn read_cai(&self, reader: &mut dyn CAIRead) -> Result<Vec<u8>> {
     66         let (decoded_manifest_opt, _detected_tag_location, _insertion_point) =
     67             detect_manifest_location(reader)?;
     68 
     69         match decoded_manifest_opt {
     70             Some(decoded_manifest) => {
     71                 if !decoded_manifest.is_empty() {
     72                     Ok(decoded_manifest)
     73                 } else {
     74                     Err(Error::JumbfNotFound)
     75                 }
     76             }
     77             None => Err(Error::JumbfNotFound),
     78         }
     79     }
     80 
     81     // Get XMP block
     82     fn read_xmp(&self, _asset_reader: &mut dyn CAIRead) -> Option<String> {
     83         None
     84     }
     85 }
     86 
     87 impl AssetIO for SvgIO {
     88     fn new(_asset_type: &str) -> Self {
     89         SvgIO {}
     90     }
     91 
     92     fn get_handler(&self, asset_type: &str) -> Box<dyn AssetIO> {
     93         Box::new(SvgIO::new(asset_type))
     94     }
     95 
     96     fn get_reader(&self) -> &dyn CAIReader {
     97         self
     98     }
     99 
    100     fn asset_patch_ref(&self) -> Option<&dyn AssetPatch> {
    101         Some(self)
    102     }
    103 
    104     fn get_writer(&self, asset_type: &str) -> Option<Box<dyn CAIWriter>> {
    105         Some(Box::new(SvgIO::new(asset_type)))
    106     }
    107 
    108     fn read_cai_store(&self, asset_path: &Path) -> Result<Vec<u8>> {
    109         let mut f = File::open(asset_path)?;
    110         self.read_cai(&mut f)
    111     }
    112 
    113     fn save_cai_store(&self, asset_path: &std::path::Path, store_bytes: &[u8]) -> Result<()> {
    114         let mut input_stream = std::fs::OpenOptions::new()
    115             .read(true)
    116             .open(asset_path)
    117             .map_err(Error::IoError)?;
    118 
    119         let mut temp_file = Builder::new()
    120             .prefix("c2pa_temp")
    121             .rand_bytes(5)
    122             .tempfile()?;
    123 
    124         self.write_cai(&mut input_stream, &mut temp_file, store_bytes)?;
    125 
    126         // copy temp file to asset
    127         rename_or_copy(temp_file, asset_path)
    128     }
    129 
    130     fn get_object_locations(
    131         &self,
    132         asset_path: &std::path::Path,
    133     ) -> Result<Vec<HashObjectPositions>> {
    134         let mut input_stream =
    135             std::fs::File::open(asset_path).map_err(|_err| Error::EmbeddingError)?;
    136 
    137         self.get_object_locations_from_stream(&mut input_stream)
    138     }
    139 
    140     fn remove_cai_store(&self, asset_path: &Path) -> Result<()> {
    141         let mut input_file = File::open(asset_path)?;
    142 
    143         let mut temp_file = Builder::new()
    144             .prefix("c2pa_temp")
    145             .rand_bytes(5)
    146             .tempfile()?;
    147 
    148         self.remove_cai_store_from_stream(&mut input_file, &mut temp_file)?;
    149 
    150         // copy temp file to asset
    151         rename_or_copy(temp_file, asset_path)
    152     }
    153 
    154     fn remote_ref_writer_ref(&self) -> Option<&dyn RemoteRefEmbed> {
    155         None
    156     }
    157 
    158     fn supported_types(&self) -> &[&str] {
    159         &SUPPORTED_TYPES
    160     }
    161 }
    162 
    163 // create manifest entry
    164 fn create_manifest_tag(data: &[u8], with_meta: bool) -> Result<Vec<u8>> {
    165     let mut output: Vec<u8> = Vec::with_capacity(data.len() + 256);
    166     let mut writer = Writer::new(Cursor::new(output));
    167 
    168     let encoded = base64::encode(data);
    169 
    170     if with_meta {
    171         writer
    172             .create_element(METADATA)
    173             .write_inner_content(|writer| {
    174                 writer
    175                     .create_element(MANIFEST)
    176                     .with_attribute((MANIFEST_NS, MANIFEST_NS_VAL))
    177                     .write_text_content(BytesText::from_plain_str(&encoded))?;
    178                 Ok(())
    179             })
    180             .map_err(|_e| Error::XmlWriteError)?;
    181     } else {
    182         writer
    183             .create_element(MANIFEST)
    184             .with_attribute((MANIFEST_NS, MANIFEST_NS_VAL))
    185             .write_text_content(BytesText::from_plain_str(&encoded))
    186             .map_err(|_e| Error::XmlWriteError)?;
    187     }
    188 
    189     output = writer.into_inner().into_inner();
    190 
    191     Ok(output)
    192 }
    193 
    194 enum DetectedTagsDepth {
    195     Metadata,
    196     Manifest,
    197     Empty,
    198 }
    199 
    200 // returns tuple of found manifest, where in the XML hierarchy the manifest needs to go, and the manifest insertion point
    201 fn detect_manifest_location(
    202     input_stream: &mut dyn CAIRead,
    203 ) -> Result<(Option<Vec<u8>>, DetectedTagsDepth, usize)> {
    204     input_stream.rewind()?;
    205 
    206     let mut buf = Vec::new();
    207 
    208     let buf_reader = BufReader::new(input_stream);
    209 
    210     let mut xml_reader = Reader::from_reader(buf_reader);
    211 
    212     let mut xml_path: Vec<String> = Vec::new();
    213 
    214     let mut detected_level = DetectedTagsDepth::Empty;
    215     let mut insertion_point = 0;
    216 
    217     let mut output: Option<Vec<u8>> = None;
    218 
    219     loop {
    220         match xml_reader.read_event(&mut buf) {
    221             Ok(Event::Start(ref e)) => {
    222                 let name = String::from_utf8_lossy(e.name()).into_owned();
    223                 xml_path.push(name);
    224 
    225                 if xml_path.len() == 2 && xml_path[0] == SVG && xml_path[1] == METADATA {
    226                     detected_level = DetectedTagsDepth::Metadata;
    227                     insertion_point = xml_reader.buffer_position();
    228                 }
    229 
    230                 if xml_path.len() == 3
    231                     && xml_path[0] == SVG
    232                     && xml_path[1] == METADATA
    233                     && xml_path[2] == MANIFEST
    234                 {
    235                     detected_level = DetectedTagsDepth::Manifest;
    236                     insertion_point = xml_reader.buffer_position();
    237 
    238                     let mut temp_buf = Vec::new();
    239                     let s = xml_reader
    240                         .read_text(e.name(), &mut temp_buf)
    241                         .map_err(|_e| {
    242                             Error::InvalidAsset("XML manifest tag invalid content".to_string())
    243                         })?;
    244 
    245                     output = Some(base64::decode(&s).map_err(|_e| {
    246                         dbg!(_e);
    247                         Error::InvalidAsset("XML bad base64 encoding".to_string())
    248                     })?);
    249                 }
    250 
    251                 if xml_path.len() == 1 && xml_path[0] == SVG {
    252                     detected_level = DetectedTagsDepth::Empty;
    253                     insertion_point = xml_reader.buffer_position();
    254                 }
    255             }
    256             Ok(Event::End(_)) => {
    257                 let _p = xml_path.pop();
    258             }
    259             Ok(Event::Eof) => break,
    260             Err(_) => return Err(Error::InvalidAsset("XML invalid".to_string())),
    261             _ => (),
    262         }
    263     }
    264 
    265     Ok((output, detected_level, insertion_point))
    266 }
    267 
    268 fn add_required_segs_to_stream(
    269     input_stream: &mut dyn CAIRead,
    270     output_stream: &mut dyn CAIReadWrite,
    271 ) -> Result<()> {
    272     let (encoded_manifest_opt, _detected_tag_location, _insertion_point) =
    273         detect_manifest_location(input_stream)?;
    274 
    275     let need_manifest = if let Some(encoded_manifest) = encoded_manifest_opt {
    276         encoded_manifest.is_empty()
    277     } else {
    278         true
    279     };
    280 
    281     if need_manifest {
    282         // add some data
    283         let data: &str = "placeholder manifest";
    284 
    285         let svg = SvgIO::new("svg");
    286         let svg_writer = svg.get_writer("svg").ok_or(Error::UnsupportedType)?;
    287 
    288         svg_writer.write_cai(input_stream, output_stream, data.as_bytes())?;
    289     } else {
    290         // just clone
    291         input_stream.rewind()?;
    292         output_stream.rewind()?;
    293         std::io::copy(input_stream, output_stream)?;
    294     }
    295 
    296     Ok(())
    297 }
    298 
    299 impl CAIWriter for SvgIO {
    300     fn write_cai(
    301         &self,
    302         input_stream: &mut dyn CAIRead,
    303         output_stream: &mut dyn CAIReadWrite,
    304         store_bytes: &[u8],
    305     ) -> Result<()> {
    306         input_stream.rewind()?;
    307         let (_encoded_manifest, detected_tag_location, _insertion_point) =
    308             detect_manifest_location(input_stream)?;
    309 
    310         input_stream.rewind()?;
    311         let buf_reader = BufReader::new(input_stream);
    312         let mut reader = Reader::from_reader(buf_reader);
    313 
    314         output_stream.rewind()?;
    315         let mut writer = Writer::new(output_stream);
    316 
    317         let mut buf = Vec::new();
    318         let mut xml_path: Vec<String> = Vec::new();
    319 
    320         match detected_tag_location {
    321             DetectedTagsDepth::Metadata => {
    322                 // add manifest case
    323                 let manifest_data = create_manifest_tag(store_bytes, false)?;
    324 
    325                 loop {
    326                     match reader.read_event(&mut buf) {
    327                         Ok(Event::Start(e)) => {
    328                             let name = String::from_utf8_lossy(e.name()).into_owned();
    329                             xml_path.push(name);
    330 
    331                             // writes the event to the writer
    332                             writer
    333                                 .write_event(Event::Start(e))
    334                                 .map_err(|_e| Error::XmlWriteError)?;
    335 
    336                             // add manifest data
    337                             if xml_path.len() == 2 && xml_path[0] == SVG && xml_path[1] == METADATA
    338                             {
    339                                 writer
    340                                     .write(&manifest_data)
    341                                     .map_err(|_e| Error::XmlWriteError)?;
    342                             }
    343                         }
    344                         Ok(Event::Eof) => break,
    345                         Ok(Event::End(e)) => {
    346                             let _p = xml_path.pop();
    347                             writer
    348                                 .write_event(Event::End(e))
    349                                 .map_err(|_e| Error::XmlWriteError)?;
    350                         }
    351                         Ok(e) => writer.write_event(&e).map_err(|_e| Error::XmlWriteError)?,
    352                         Err(_e) => return Err(Error::InvalidAsset("XML invalid".to_string())),
    353                     }
    354                     buf.clear();
    355                 }
    356             }
    357             DetectedTagsDepth::Manifest => {
    358                 // replace manifest case
    359                 let encoded = base64::encode(store_bytes);
    360 
    361                 loop {
    362                     match reader.read_event(&mut buf) {
    363                         Ok(Event::Start(e)) => {
    364                             let name = String::from_utf8_lossy(e.name()).into_owned();
    365                             xml_path.push(name);
    366 
    367                             // writes the event to the writer
    368                             writer
    369                                 .write_event(Event::Start(e))
    370                                 .map_err(|_e| Error::XmlWriteError)?;
    371                         }
    372                         Ok(Event::Text(e)) => {
    373                             // add manifest data
    374                             if xml_path.len() == 3
    375                                 && xml_path[0] == SVG
    376                                 && xml_path[1] == METADATA
    377                                 && xml_path[2] == MANIFEST
    378                             {
    379                                 writer
    380                                     .write(encoded.as_bytes())
    381                                     .map_err(|_e| Error::XmlWriteError)?;
    382                             } else {
    383                                 writer
    384                                     .write_event(Event::Text(e))
    385                                     .map_err(|_e| Error::XmlWriteError)?; // pass Event through
    386                             }
    387                         }
    388                         Ok(Event::Eof) => break,
    389                         Ok(Event::End(e)) => {
    390                             let _p = xml_path.pop();
    391                             writer
    392                                 .write_event(Event::End(e))
    393                                 .map_err(|_e| Error::XmlWriteError)?;
    394                         }
    395                         Ok(e) => writer.write_event(&e).map_err(|_e| Error::XmlWriteError)?,
    396                         Err(_e) => return Err(Error::InvalidAsset("XML invalid".to_string())),
    397                     }
    398                     buf.clear();
    399                 }
    400             }
    401             DetectedTagsDepth::Empty => {
    402                 //add metadata & manifest case
    403                 let manifest_data = create_manifest_tag(store_bytes, true)?;
    404 
    405                 loop {
    406                     match reader.read_event(&mut buf) {
    407                         Ok(Event::Start(e)) => {
    408                             let name = String::from_utf8_lossy(e.name()).into_owned();
    409                             xml_path.push(name);
    410 
    411                             // writes the event to the writer
    412                             writer
    413                                 .write_event(Event::Start(e))
    414                                 .map_err(|_e| Error::XmlWriteError)?;
    415 
    416                             // add manifest data
    417                             if xml_path.len() == 1 && xml_path[0] == SVG {
    418                                 writer
    419                                     .write(&manifest_data)
    420                                     .map_err(|_e| Error::XmlWriteError)?;
    421                             }
    422                         }
    423                         Ok(Event::Eof) => break,
    424                         Ok(Event::End(e)) => {
    425                             let _p = xml_path.pop();
    426                             writer
    427                                 .write_event(Event::End(e))
    428                                 .map_err(|_e| Error::XmlWriteError)?;
    429                         }
    430                         Ok(e) => writer.write_event(&e).map_err(|_e| Error::XmlWriteError)?,
    431                         Err(_e) => return Err(Error::InvalidAsset("XML invalid".to_string())),
    432                     }
    433                     buf.clear();
    434                 }
    435             }
    436         }
    437 
    438         Ok(())
    439     }
    440 
    441     fn get_object_locations_from_stream(
    442         &self,
    443         input_stream: &mut dyn CAIRead,
    444     ) -> Result<Vec<HashObjectPositions>> {
    445         let output: Vec<u8> = Vec::new();
    446         let mut output_stream = Cursor::new(output);
    447 
    448         add_required_segs_to_stream(input_stream, &mut output_stream)?;
    449 
    450         let mut positions: Vec<HashObjectPositions> = Vec::new();
    451 
    452         let (decoded_manifest_opt, _detected_tag_location, manifest_pos) =
    453             detect_manifest_location(&mut output_stream)?;
    454 
    455         let decoded_manifest = decoded_manifest_opt.ok_or(Error::JumbfNotFound)?;
    456         let encoded_manifest_len = base64::encode(&decoded_manifest).len();
    457 
    458         positions.push(HashObjectPositions {
    459             offset: manifest_pos,
    460             length: encoded_manifest_len,
    461             htype: HashBlockObjectType::Cai,
    462         });
    463 
    464         // add hash of chunks before cai
    465         positions.push(HashObjectPositions {
    466             offset: 0,
    467             length: manifest_pos,
    468             htype: HashBlockObjectType::Other,
    469         });
    470 
    471         // add position from cai to end
    472         let end = manifest_pos + encoded_manifest_len;
    473         let length = usize::value_from(input_stream.seek(SeekFrom::End(0))?)
    474             .map_err(|_err| Error::InvalidAsset("value out of range".to_string()))?
    475             - end;
    476         positions.push(HashObjectPositions {
    477             offset: end,
    478             length,
    479             htype: HashBlockObjectType::Other,
    480         });
    481 
    482         Ok(positions)
    483     }
    484 
    485     fn remove_cai_store_from_stream(
    486         &self,
    487         input_stream: &mut dyn CAIRead,
    488         output_stream: &mut dyn CAIReadWrite,
    489     ) -> Result<()> {
    490         let buf_reader = BufReader::new(input_stream);
    491         let mut reader = Reader::from_reader(buf_reader);
    492 
    493         output_stream.rewind()?;
    494         let mut writer = Writer::new(output_stream);
    495 
    496         let mut buf = Vec::new();
    497         let mut xml_path: Vec<String> = Vec::new();
    498 
    499         loop {
    500             match reader.read_event(&mut buf) {
    501                 Ok(Event::Start(e)) => {
    502                     let name = String::from_utf8_lossy(e.name()).into_owned();
    503                     xml_path.push(name);
    504 
    505                     if xml_path.len() == 3
    506                         && xml_path[0] == SVG
    507                         && xml_path[1] == METADATA
    508                         && xml_path[2] == MANIFEST
    509                     {
    510                         // skip the manifest
    511                         continue;
    512                     } else {
    513                         writer
    514                             .write_event(Event::Start(e))
    515                             .map_err(|_e| Error::XmlWriteError)?; // pass Event through
    516                     }
    517                 }
    518                 Ok(Event::Text(e)) => {
    519                     if xml_path.len() == 3
    520                         && xml_path[0] == SVG
    521                         && xml_path[1] == METADATA
    522                         && xml_path[2] == MANIFEST
    523                     {
    524                         // skip the manifest
    525                         continue;
    526                     } else {
    527                         writer
    528                             .write_event(Event::Text(e))
    529                             .map_err(|_e| Error::XmlWriteError)?; // pass Event through
    530                     }
    531                 }
    532                 Ok(Event::Eof) => break,
    533                 Ok(Event::End(e)) => {
    534                     if xml_path.len() == 3
    535                         && xml_path[0] == SVG
    536                         && xml_path[1] == METADATA
    537                         && xml_path[2] == MANIFEST
    538                     {
    539                         // skip the manifest
    540                         let _p = xml_path.pop();
    541                         continue;
    542                     } else {
    543                         let _p = xml_path.pop();
    544                         writer
    545                             .write_event(Event::End(e))
    546                             .map_err(|_e| Error::XmlWriteError)?; // pass Event through
    547                     }
    548                 }
    549                 Ok(e) => writer.write_event(&e).map_err(|_e| Error::XmlWriteError)?,
    550                 Err(_e) => return Err(Error::InvalidAsset("XML invalid".to_string())),
    551             }
    552             buf.clear();
    553         }
    554 
    555         Ok(())
    556     }
    557 }
    558 
    559 impl AssetPatch for SvgIO {
    560     fn patch_cai_store(&self, asset_path: &std::path::Path, store_bytes: &[u8]) -> Result<()> {
    561         let mut input_file = OpenOptions::new()
    562             .write(true)
    563             .read(true)
    564             .create(false)
    565             .open(asset_path)?;
    566 
    567         let (asset_manifest_opt, _detected_tag_location, insertion_point) =
    568             detect_manifest_location(&mut input_file)?;
    569         let encoded_store_bytes = base64::encode(store_bytes);
    570 
    571         if let Some(manifest_bytes) = asset_manifest_opt {
    572             // base 64 encode
    573             let encoded_manifest_bytes = base64::encode(&manifest_bytes);
    574             // can patch if encoded lengths are ==
    575             if encoded_store_bytes.len() == encoded_manifest_bytes.len() {
    576                 input_file.seek(SeekFrom::Start(insertion_point as u64))?;
    577                 input_file.write_all(encoded_store_bytes.as_bytes())?;
    578                 Ok(())
    579             } else {
    580                 Err(Error::InvalidAsset(
    581                     "patch_cai_store store size mismatch.".to_string(),
    582                 ))
    583             }
    584         } else {
    585             Err(Error::InvalidAsset(
    586                 "patch_cai_store store size mismatch.".to_string(),
    587             ))
    588         }
    589     }
    590 }
    591 
    592 #[cfg(test)]
    593 pub mod tests {
    594     #![allow(clippy::expect_used)]
    595     #![allow(clippy::panic)]
    596     #![allow(clippy::unwrap_used)]
    597 
    598     use std::io::Read;
    599 
    600     use tempfile::tempdir;
    601 
    602     use super::*;
    603     use crate::utils::{
    604         hash_utils::vec_compare,
    605         test::{fixture_path, temp_dir_path},
    606     };
    607 
    608     #[test]
    609     fn test_write_svg_no_meta() {
    610         let more_data = "some more test data".as_bytes();
    611         let source = fixture_path("sample1.svg");
    612 
    613         let mut success = false;
    614         if let Ok(temp_dir) = tempdir() {
    615             let output = temp_dir_path(&temp_dir, "sample1.svg");
    616 
    617             if let Ok(_size) = std::fs::copy(source, &output) {
    618                 let svg_io = SvgIO::new("svg");
    619 
    620                 if let Ok(()) = svg_io.save_cai_store(&output, more_data) {
    621                     if let Ok(read_test_data) = svg_io.read_cai_store(&output) {
    622                         assert!(vec_compare(more_data, &read_test_data));
    623                         success = true;
    624                     }
    625                 }
    626             }
    627         }
    628         assert!(success)
    629     }
    630 
    631     #[test]
    632     fn test_write_svg_with_meta() {
    633         let more_data = "some more test data".as_bytes();
    634         let source = fixture_path("sample2.svg");
    635 
    636         let mut success = false;
    637         if let Ok(temp_dir) = tempdir() {
    638             let output = temp_dir_path(&temp_dir, "sample2.svg");
    639 
    640             if let Ok(_size) = std::fs::copy(source, &output) {
    641                 let svg_io = SvgIO::new("svg");
    642 
    643                 if let Ok(()) = svg_io.save_cai_store(&output, more_data) {
    644                     if let Ok(read_test_data) = svg_io.read_cai_store(&output) {
    645                         assert!(vec_compare(more_data, &read_test_data));
    646                         success = true;
    647                     }
    648                 }
    649             }
    650         }
    651         assert!(success)
    652     }
    653 
    654     #[test]
    655     fn test_write_svg_with_manifest() {
    656         let more_data = "some more test data into existing manifest".as_bytes();
    657         let source = fixture_path("sample3.svg");
    658 
    659         let mut success = false;
    660         if let Ok(temp_dir) = tempdir() {
    661             let output = temp_dir_path(&temp_dir, "sample3.svg");
    662 
    663             if let Ok(_size) = std::fs::copy(source, &output) {
    664                 let svg_io = SvgIO::new("svg");
    665 
    666                 if let Ok(()) = svg_io.save_cai_store(&output, more_data) {
    667                     if let Ok(read_test_data) = svg_io.read_cai_store(&output) {
    668                         assert!(vec_compare(more_data, &read_test_data));
    669                         success = true;
    670                     }
    671                 }
    672             }
    673         }
    674         assert!(success)
    675     }
    676 
    677     #[test]
    678     fn test_patch_write_svg() {
    679         let test_data = "some test data".as_bytes();
    680         let source = fixture_path("sample1.svg");
    681 
    682         let mut success = false;
    683         if let Ok(temp_dir) = tempdir() {
    684             let output = temp_dir_path(&temp_dir, "sample1.svg");
    685 
    686             if let Ok(_size) = std::fs::copy(source, &output) {
    687                 let svg_io = SvgIO::new("svg");
    688 
    689                 if let Ok(()) = svg_io.save_cai_store(&output, test_data) {
    690                     if let Ok(source_data) = svg_io.read_cai_store(&output) {
    691                         // create replacement data of same size
    692                         let mut new_data = vec![0u8; source_data.len()];
    693                         new_data[..test_data.len()].copy_from_slice(test_data);
    694                         svg_io.patch_cai_store(&output, &new_data).unwrap();
    695 
    696                         let replaced = svg_io.read_cai_store(&output).unwrap();
    697 
    698                         assert_eq!(new_data, replaced);
    699 
    700                         success = true;
    701                     }
    702                 }
    703             }
    704         }
    705         assert!(success)
    706     }
    707 
    708     #[test]
    709     fn test_remove_c2pa() {
    710         let source = fixture_path("sample4.svg");
    711 
    712         let temp_dir = tempdir().unwrap();
    713         let output = temp_dir_path(&temp_dir, "sample4.svg");
    714 
    715         std::fs::copy(source, &output).unwrap();
    716         let svg_io = SvgIO::new("svg");
    717 
    718         svg_io.remove_cai_store(&output).unwrap();
    719 
    720         // read back in asset, JumbfNotFound is expected since it was removed
    721         match svg_io.read_cai_store(&output) {
    722             Err(Error::JumbfNotFound) => (),
    723             _ => unreachable!(),
    724         }
    725     }
    726 
    727     #[test]
    728     fn test_get_object_location() {
    729         let more_data = "some more test data into existing manifest".as_bytes();
    730         let source = fixture_path("sample1.svg");
    731 
    732         let mut success = false;
    733         if let Ok(temp_dir) = tempdir() {
    734             let output = temp_dir_path(&temp_dir, "sample1.svg");
    735 
    736             if let Ok(_size) = std::fs::copy(source, &output) {
    737                 let svg_io = SvgIO::new("svg");
    738 
    739                 if let Ok(()) = svg_io.save_cai_store(&output, more_data) {
    740                     if let Ok(locations) = svg_io.get_object_locations(&output) {
    741                         for op in locations {
    742                             if op.htype == HashBlockObjectType::Cai {
    743                                 let mut of = File::open(&output).unwrap();
    744 
    745                                 let mut manifests_buf: Vec<u8> = vec![0u8; op.length];
    746                                 of.seek(SeekFrom::Start(op.offset as u64)).unwrap();
    747                                 of.read_exact(manifests_buf.as_mut_slice()).unwrap();
    748                                 let buf_str = std::str::from_utf8(&manifests_buf).unwrap();
    749                                 let decoded_data = base64::decode(buf_str).unwrap();
    750                                 if vec_compare(more_data, &decoded_data) {
    751                                     success = true;
    752                                 }
    753                             }
    754                         }
    755                     }
    756                 }
    757             }
    758         }
    759         assert!(success)
    760     }
    761 }