Skip signed files or remove C2PA metadata
This commit is contained in:
parent
910e779b09
commit
8270c6a034
6 changed files with 63 additions and 1 deletions
|
|
@ -14,6 +14,7 @@ pub enum PngError {
|
|||
ChunkMissing(&'static str),
|
||||
InvalidDepthForType(BitDepth, ColorType),
|
||||
IncorrectDataLength(usize, usize),
|
||||
C2PAMetadataPreventsChanges,
|
||||
Other(Box<str>),
|
||||
}
|
||||
|
||||
|
|
@ -41,6 +42,9 @@ impl fmt::Display for PngError {
|
|||
"Data length {} does not match the expected length {}",
|
||||
l1, l2
|
||||
),
|
||||
PngError::C2PAMetadataPreventsChanges => f.write_str(
|
||||
"The image contains C2PA manifest that would be invalidated by any file changes",
|
||||
),
|
||||
PngError::Other(ref s) => f.write_str(s),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,6 +75,8 @@ pub struct Chunk {
|
|||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum StripChunks {
|
||||
/// None
|
||||
///
|
||||
/// ...except caBX chunk if it contains a C2PA.org signature.
|
||||
None,
|
||||
/// Remove specific chunks
|
||||
Strip(IndexSet<[u8; 4]>),
|
||||
|
|
@ -111,6 +113,36 @@ pub struct RawChunk<'a> {
|
|||
pub data: &'a [u8],
|
||||
}
|
||||
|
||||
impl RawChunk<'_> {
|
||||
// Is it a chunk for C2PA/CAI JUMBF metadata
|
||||
pub(crate) fn is_c2pa(&self) -> bool {
|
||||
if self.name == *b"caBX" {
|
||||
if let Some((b"jumb", data)) = parse_jumbf_box(self.data) {
|
||||
if let Some((b"jumd", data)) = parse_jumbf_box(data) {
|
||||
if data.get(..4) == Some(b"c2pa") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_jumbf_box(data: &[u8]) -> Option<(&[u8], &[u8])> {
|
||||
if data.len() < 8 {
|
||||
return None;
|
||||
}
|
||||
let (len, rest) = data.split_at(4);
|
||||
let len = u32::from_be_bytes(len.try_into().unwrap()) as usize;
|
||||
if len < 8 || len > data.len() {
|
||||
return None;
|
||||
}
|
||||
let (box_name, data) = rest.split_at(4);
|
||||
let data = data.get(..len - 8)?;
|
||||
Some((box_name, data))
|
||||
}
|
||||
|
||||
pub fn parse_next_chunk<'a>(
|
||||
byte_data: &'a [u8],
|
||||
byte_offset: &mut usize,
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ use clap::ArgMatches;
|
|||
mod cli;
|
||||
use indexmap::IndexSet;
|
||||
use log::{error, warn, Level, LevelFilter};
|
||||
use oxipng::{Deflaters, InFile, Options, OutFile, RowFilter, StripChunks};
|
||||
use oxipng::{Deflaters, InFile, Options, OutFile, PngError, RowFilter, StripChunks};
|
||||
use rayon::prelude::*;
|
||||
|
||||
use crate::cli::DISPLAY_CHUNKS;
|
||||
|
|
@ -88,6 +88,10 @@ fn main() -> ExitCode {
|
|||
// PNG files, and return an error for them.
|
||||
// We don't really want to return an error code for those files.
|
||||
Ok(_) => OptimizationResult::Ok,
|
||||
Err(e @ PngError::C2PAMetadataPreventsChanges) => {
|
||||
warn!("{input}: {e}");
|
||||
OptimizationResult::Skipped
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{input}: {e}");
|
||||
OptimizationResult::Failed
|
||||
|
|
|
|||
|
|
@ -114,6 +114,15 @@ impl PngData {
|
|||
}
|
||||
_ => {
|
||||
if opts.strip.keep(&chunk.name) {
|
||||
if chunk.is_c2pa() {
|
||||
// StripChunks::None is the default value, so to keep optimizing by default,
|
||||
// interpret it as stripping the C2PA metadata.
|
||||
// The C2PA metadata is invalidated if the file changes, so it shouldn't be kept.
|
||||
if opts.strip == StripChunks::None {
|
||||
continue;
|
||||
}
|
||||
return Err(PngError::C2PAMetadataPreventsChanges);
|
||||
}
|
||||
aux_chunks.push(Chunk {
|
||||
name: chunk.name,
|
||||
data: chunk.data.to_owned(),
|
||||
|
|
|
|||
BIN
tests/files/c2pa-signed.png
Normal file
BIN
tests/files/c2pa-signed.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
13
tests/lib.rs
13
tests/lib.rs
|
|
@ -42,6 +42,19 @@ fn optimize() {
|
|||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skip_c2pa() {
|
||||
let result = oxipng::optimize(
|
||||
&"tests/files/c2pa-signed.png".into(),
|
||||
&OutFile::None,
|
||||
&Options {
|
||||
strip: StripChunks::Keep(indexset! {*b"caBX"}),
|
||||
..Options::default()
|
||||
},
|
||||
);
|
||||
assert!(matches!(result, Err(PngError::C2PAMetadataPreventsChanges)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn optimize_corrupted() {
|
||||
let result = oxipng::optimize(
|
||||
|
|
|
|||
Loading…
Reference in a new issue