c2pa_io.rs (5561B)
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::{fs::File, path::Path}; 15 16 use crate::{ 17 asset_io::{ 18 AssetIO, CAIRead, CAIReadWrite, CAIReader, CAIWriter, ComposedManifestRef, 19 HashBlockObjectType, HashObjectPositions, 20 }, 21 error::{Error, Result}, 22 }; 23 24 static SUPPORTED_TYPES: [&str; 3] = [ 25 "c2pa", 26 "application/c2pa", 27 "application/x-c2pa-manifest-store", 28 ]; 29 30 /// Supports working with ".c2pa" files containing only manifest store data 31 pub struct C2paIO {} 32 33 impl CAIReader for C2paIO { 34 fn read_cai(&self, asset_reader: &mut dyn CAIRead) -> Result<Vec<u8>> { 35 let mut cai_data = Vec::new(); 36 // read the whole file 37 asset_reader.read_to_end(&mut cai_data)?; 38 Ok(cai_data) 39 } 40 41 // C2PA files have no xmp data 42 fn read_xmp(&self, _asset_reader: &mut dyn CAIRead) -> Option<String> { 43 None 44 } 45 } 46 47 impl CAIWriter for C2paIO { 48 fn write_cai( 49 &self, 50 _input_stream: &mut dyn CAIRead, 51 output_stream: &mut dyn CAIReadWrite, 52 store_bytes: &[u8], 53 ) -> Result<()> { 54 // just write the store bytes and ingore the input stream 55 output_stream.write_all(store_bytes)?; 56 Ok(()) 57 } 58 59 fn get_object_locations_from_stream( 60 &self, 61 __input_stream: &mut dyn CAIRead, 62 ) -> Result<Vec<HashObjectPositions>> { 63 // there is no data to hash 64 Ok(vec![]) 65 } 66 67 fn remove_cai_store_from_stream( 68 &self, 69 _input_stream: &mut dyn CAIRead, 70 _output_stream: &mut dyn CAIReadWrite, 71 ) -> Result<()> { 72 // nothing to do here, just return Ok 73 Ok(()) 74 } 75 } 76 77 impl AssetIO for C2paIO { 78 fn read_cai_store(&self, asset_path: &Path) -> Result<Vec<u8>> { 79 let mut f = File::open(asset_path)?; 80 self.read_cai(&mut f) 81 } 82 83 fn save_cai_store(&self, asset_path: &std::path::Path, store_bytes: &[u8]) -> Result<()> { 84 // just save the data in a file 85 std::fs::write(asset_path, store_bytes) 86 .map_err(|_err| Error::BadParam("C2PA write error".to_owned()))?; 87 88 Ok(()) 89 } 90 91 fn get_object_locations( 92 &self, 93 _asset_path: &std::path::Path, 94 ) -> Result<Vec<HashObjectPositions>> { 95 let hop = HashObjectPositions { 96 offset: 0, 97 length: 0, 98 htype: HashBlockObjectType::Cai, 99 }; 100 101 Ok(vec![hop]) 102 } 103 104 fn remove_cai_store(&self, _asset_path: &Path) -> Result<()> { 105 Ok(()) 106 } 107 108 fn new(_asset_type: &str) -> Self 109 where 110 Self: Sized, 111 { 112 C2paIO {} 113 } 114 115 fn get_handler(&self, asset_type: &str) -> Box<dyn AssetIO> { 116 Box::new(C2paIO::new(asset_type)) 117 } 118 119 fn get_reader(&self) -> &dyn CAIReader { 120 self 121 } 122 123 fn get_writer(&self, asset_type: &str) -> Option<Box<dyn CAIWriter>> { 124 Some(Box::new(C2paIO::new(asset_type))) 125 } 126 127 fn supported_types(&self) -> &[&str] { 128 &SUPPORTED_TYPES 129 } 130 131 fn composed_data_ref(&self) -> Option<&dyn ComposedManifestRef> { 132 Some(self) 133 } 134 } 135 136 impl ComposedManifestRef for C2paIO { 137 // Return entire CAI block as Vec<u8> 138 fn compose_manifest(&self, manifest_data: &[u8], _format: &str) -> Result<Vec<u8>> { 139 Ok(manifest_data.to_vec()) 140 } 141 } 142 143 #[cfg(test)] 144 #[cfg(feature = "file_io")] 145 pub mod tests { 146 #![allow(clippy::expect_used)] 147 #![allow(clippy::unwrap_used)] 148 149 use tempfile::tempdir; 150 151 use super::{AssetIO, C2paIO, CAIReader, CAIWriter}; 152 use crate::{ 153 status_tracker::OneShotStatusTracker, 154 store::Store, 155 utils::test::{fixture_path, temp_dir_path, temp_signer}, 156 }; 157 158 #[test] 159 fn c2pa_io_parse() { 160 let path = fixture_path("C.jpg"); 161 162 let temp_dir = tempdir().expect("temp dir"); 163 let temp_path = temp_dir_path(&temp_dir, "test.c2pa"); 164 165 let c2pa_io = C2paIO {}; 166 let manifest = crate::jumbf_io::load_jumbf_from_file(&path).expect("read_cai_store"); 167 c2pa_io 168 .save_cai_store(&temp_path, &manifest) 169 .expect("save cai store"); 170 171 let store = Store::load_from_asset(&temp_path, false, &mut OneShotStatusTracker::new()) 172 .expect("loading store"); 173 174 let signer = temp_signer(); 175 176 let manifest2 = store.to_jumbf(signer.as_ref()).expect("to_jumbf"); 177 assert_eq!(&manifest, &manifest2); 178 } 179 180 #[test] 181 #[cfg(feature = "file_io")] 182 fn c2pa_stream_io() { 183 use std::io::{empty, Cursor}; 184 let path = fixture_path("C.jpg"); 185 186 let c2pa_io = C2paIO {}; 187 let manifest = crate::jumbf_io::load_jumbf_from_file(&path).expect("load_jumbf_from_file"); 188 let mut output_stream = Cursor::new(Vec::new()); 189 c2pa_io 190 .write_cai(&mut empty(), &mut output_stream, &manifest) 191 .expect("write_cai"); 192 193 output_stream.set_position(0); 194 let manifest2 = c2pa_io.read_cai(&mut output_stream).expect("read_cai"); 195 196 assert_eq!(&manifest, &manifest2); 197 } 198 }