Skip to content

Commit 466bdae

Browse files
authoredFeb 9, 2025··
Treat most auxiliary chunk errors as benign. (#569)
1 parent 819d91e commit 466bdae

File tree

3 files changed

+113
-65
lines changed

3 files changed

+113
-65
lines changed
 

‎src/decoder/stream.rs

+110-62
Original file line numberDiff line numberDiff line change
@@ -975,7 +975,7 @@ impl StreamingDecoder {
975975

976976
fn parse_chunk(&mut self, type_str: ChunkType) -> Result<Decoded, DecodingError> {
977977
self.state = Some(State::new_u32(U32ValueKind::Crc(type_str)));
978-
let parse_result = match type_str {
978+
let mut parse_result = match type_str {
979979
IHDR => self.parse_ihdr(),
980980
chunk::sBIT => self.parse_sbit(),
981981
chunk::PLTE => self.parse_plte(),
@@ -998,8 +998,7 @@ impl StreamingDecoder {
998998
_ => Ok(Decoded::PartialChunk(type_str)),
999999
};
10001000

1001-
parse_result.map_err(|e| {
1002-
self.state = None;
1001+
parse_result = parse_result.map_err(|e| {
10031002
match e {
10041003
// `parse_chunk` is invoked after gathering **all** bytes of a chunk, so
10051004
// `UnexpectedEof` from something like `read_be` is permanent and indicates an
@@ -1012,7 +1011,37 @@ impl StreamingDecoder {
10121011
}
10131012
e => e,
10141013
}
1015-
})
1014+
});
1015+
1016+
// Ignore benign errors in some auxiliary chunks. `LimitsExceeded`, `Parameter`
1017+
// and other error kinds are *not* treated as benign. We only ignore errors in *some*
1018+
// auxiliary chunks (i.e. we don't use `chunk::is_critical`), because for chunks like
1019+
// `fcTL` or `fdAT` the fallback to the static/non-animated image has to be implemented
1020+
// *on top* of the `StreamingDecoder` API.
1021+
//
1022+
// TODO: Consider supporting a strict mode where even benign errors are reported up.
1023+
// See https://github.com/image-rs/image-png/pull/569#issuecomment-2642062285
1024+
if matches!(parse_result.as_ref(), Err(DecodingError::Format(_)))
1025+
&& matches!(
1026+
type_str,
1027+
chunk::cHRM
1028+
| chunk::gAMA
1029+
| chunk::iCCP
1030+
| chunk::pHYs
1031+
| chunk::sBIT
1032+
| chunk::sRGB
1033+
| chunk::tRNS
1034+
)
1035+
{
1036+
parse_result = Ok(Decoded::Nothing);
1037+
}
1038+
1039+
// Clear the parsing state to enforce that parsing can't continue after an error.
1040+
if parse_result.is_err() {
1041+
self.state = None;
1042+
}
1043+
1044+
parse_result
10161045
}
10171046

10181047
fn parse_fctl(&mut self) -> Result<Decoded, DecodingError> {
@@ -1113,74 +1142,69 @@ impl StreamingDecoder {
11131142
}
11141143

11151144
fn parse_sbit(&mut self) -> Result<Decoded, DecodingError> {
1116-
let mut parse = || {
1117-
let info = self.info.as_mut().unwrap();
1118-
if info.palette.is_some() {
1119-
return Err(DecodingError::Format(
1120-
FormatErrorInner::AfterPlte { kind: chunk::sBIT }.into(),
1121-
));
1122-
}
1145+
let info = self.info.as_mut().unwrap();
1146+
if info.palette.is_some() {
1147+
return Err(DecodingError::Format(
1148+
FormatErrorInner::AfterPlte { kind: chunk::sBIT }.into(),
1149+
));
1150+
}
11231151

1124-
if self.have_idat {
1125-
return Err(DecodingError::Format(
1126-
FormatErrorInner::AfterIdat { kind: chunk::sBIT }.into(),
1127-
));
1128-
}
1152+
if self.have_idat {
1153+
return Err(DecodingError::Format(
1154+
FormatErrorInner::AfterIdat { kind: chunk::sBIT }.into(),
1155+
));
1156+
}
11291157

1130-
if info.sbit.is_some() {
1131-
return Err(DecodingError::Format(
1132-
FormatErrorInner::DuplicateChunk { kind: chunk::sBIT }.into(),
1133-
));
1134-
}
1158+
if info.sbit.is_some() {
1159+
return Err(DecodingError::Format(
1160+
FormatErrorInner::DuplicateChunk { kind: chunk::sBIT }.into(),
1161+
));
1162+
}
11351163

1136-
let (color_type, bit_depth) = { (info.color_type, info.bit_depth) };
1137-
// The sample depth for color type 3 is fixed at eight bits.
1138-
let sample_depth = if color_type == ColorType::Indexed {
1139-
BitDepth::Eight
1140-
} else {
1141-
bit_depth
1142-
};
1143-
self.limits
1144-
.reserve_bytes(self.current_chunk.raw_bytes.len())?;
1145-
let vec = self.current_chunk.raw_bytes.clone();
1146-
let len = vec.len();
1164+
let (color_type, bit_depth) = { (info.color_type, info.bit_depth) };
1165+
// The sample depth for color type 3 is fixed at eight bits.
1166+
let sample_depth = if color_type == ColorType::Indexed {
1167+
BitDepth::Eight
1168+
} else {
1169+
bit_depth
1170+
};
1171+
self.limits
1172+
.reserve_bytes(self.current_chunk.raw_bytes.len())?;
1173+
let vec = self.current_chunk.raw_bytes.clone();
1174+
let len = vec.len();
11471175

1148-
// expected lenth of the chunk
1149-
let expected = match color_type {
1150-
ColorType::Grayscale => 1,
1151-
ColorType::Rgb | ColorType::Indexed => 3,
1152-
ColorType::GrayscaleAlpha => 2,
1153-
ColorType::Rgba => 4,
1154-
};
1176+
// expected lenth of the chunk
1177+
let expected = match color_type {
1178+
ColorType::Grayscale => 1,
1179+
ColorType::Rgb | ColorType::Indexed => 3,
1180+
ColorType::GrayscaleAlpha => 2,
1181+
ColorType::Rgba => 4,
1182+
};
1183+
1184+
// Check if the sbit chunk size is valid.
1185+
if expected != len {
1186+
return Err(DecodingError::Format(
1187+
FormatErrorInner::InvalidSbitChunkSize {
1188+
color_type,
1189+
expected,
1190+
len,
1191+
}
1192+
.into(),
1193+
));
1194+
}
11551195

1156-
// Check if the sbit chunk size is valid.
1157-
if expected != len {
1196+
for sbit in &vec {
1197+
if *sbit < 1 || *sbit > sample_depth as u8 {
11581198
return Err(DecodingError::Format(
1159-
FormatErrorInner::InvalidSbitChunkSize {
1160-
color_type,
1161-
expected,
1162-
len,
1199+
FormatErrorInner::InvalidSbit {
1200+
sample_depth,
1201+
sbit: *sbit,
11631202
}
11641203
.into(),
11651204
));
11661205
}
1167-
1168-
for sbit in &vec {
1169-
if *sbit < 1 || *sbit > sample_depth as u8 {
1170-
return Err(DecodingError::Format(
1171-
FormatErrorInner::InvalidSbit {
1172-
sample_depth,
1173-
sbit: *sbit,
1174-
}
1175-
.into(),
1176-
));
1177-
}
1178-
}
1179-
info.sbit = Some(Cow::Owned(vec));
1180-
Ok(Decoded::Nothing)
1181-
};
1182-
1183-
parse().ok();
1206+
}
1207+
info.sbit = Some(Cow::Owned(vec));
11841208
Ok(Decoded::Nothing)
11851209
}
11861210

@@ -2963,4 +2987,28 @@ mod tests {
29632987
&err,
29642988
);
29652989
}
2990+
2991+
#[test]
2992+
fn test_incorrect_trns_chunk_is_ignored() {
2993+
let png = {
2994+
let mut png = Vec::new();
2995+
write_png_sig(&mut png);
2996+
write_rgba8_ihdr_with_width(&mut png, 8);
2997+
write_chunk(&mut png, b"tRNS", &[12, 34, 56]);
2998+
write_chunk(
2999+
&mut png,
3000+
b"IDAT",
3001+
&generate_rgba8_with_width_and_height(8, 8),
3002+
);
3003+
write_iend(&mut png);
3004+
png
3005+
};
3006+
let decoder = Decoder::new(png.as_slice());
3007+
let mut reader = decoder.read_info().unwrap();
3008+
let mut buf = vec![0; reader.output_buffer_size()];
3009+
assert!(reader.info().trns.is_none());
3010+
reader.next_frame(&mut buf).unwrap();
3011+
assert_eq!(3093270825, crc32fast::hash(&buf));
3012+
assert!(reader.info().trns.is_none());
3013+
}
29663014
}

‎src/filter.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ fn filter_paeth_stbi(a: u8, b: u8, c: u8) -> u8 {
346346
let hi = a.max(b);
347347
let t0 = if hi as i16 <= thresh { lo } else { c };
348348
let t1 = if thresh <= lo as i16 { hi } else { t0 };
349-
return t1;
349+
t1
350350
}
351351

352352
#[cfg(any(test, all(feature = "unstable", target_arch = "x86_64")))]

‎src/text_metadata.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
//! chunks. There are three kinds of text chunks.
66
//! - `tEXt`: This has a `keyword` and `text` field, and is ISO 8859-1 encoded.
77
//! - `zTXt`: This is semantically the same as `tEXt`, i.e. it has the same fields and
8-
//! encoding, but the `text` field is compressed before being written into the PNG file.
8+
//! encoding, but the `text` field is compressed before being written into the PNG file.
99
//! - `iTXt`: This chunk allows for its `text` field to be any valid UTF-8, and supports
10-
//! compression of the text field as well.
10+
//! compression of the text field as well.
1111
//!
1212
//! The `ISO 8859-1` encoding technically doesn't allow any control characters
1313
//! to be used, but in practice these values are encountered anyway. This can

0 commit comments

Comments
 (0)
Please sign in to comment.