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 }