PDF: bound what every stream filter may decode to
decode_stream_within now bounds the output of each stage of a /Filter chain, not only FlateDecode. RunLength, LZW, ASCIIHex and ASCII85 stop when their output would pass the limit, a full LZW table takes no more entries, PNG predictor rows are sized with checked arithmetic and only allocated when a whole row fits in the data, and CCITT checks /Rows x row bytes against the limit before allocating. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
8c665a19e2
commit
6364ddf555
|
|
@ -493,10 +493,16 @@ fn pack(changes: &[usize], columns: usize, black_is_1: bool, out: &mut Vec<u8>)
|
|||
}
|
||||
|
||||
/// Decode CCITT fax data. Damaged data ends the image at the last good row (rows already
|
||||
/// decoded are kept); `None` only when no row decodes.
|
||||
pub fn decode(data: &[u8], p: &Params) -> Option<Vec<u8>> {
|
||||
/// decoded are kept); `None` when no row decodes or the output would exceed `max`.
|
||||
pub fn decode(data: &[u8], p: &Params, max: usize) -> Option<Vec<u8>> {
|
||||
let columns = p.columns.clamp(1, 1 << 16);
|
||||
let max_rows = if p.rows > 0 { p.rows.min(1 << 20) } else { 1 << 20 };
|
||||
let row_bytes = columns.checked_add(7)? / 8;
|
||||
// With `/Rows` the image is padded to that many rows (below), so its size is known up front.
|
||||
let padded_len = if p.rows > 0 { max_rows.checked_mul(row_bytes)? } else { 0 };
|
||||
if row_bytes > max || padded_len > max {
|
||||
return None;
|
||||
}
|
||||
let mut r = Reader { data, bit: 0 };
|
||||
let mut out = vec![];
|
||||
let mut refl: Vec<usize> = vec![];
|
||||
|
|
@ -530,14 +536,17 @@ pub fn decode(data: &[u8], p: &Params) -> Option<Vec<u8>> {
|
|||
if two_d { row_2d(&mut r, &refl, columns).ok() } else { row_1d(&mut r, columns) }
|
||||
};
|
||||
let Some(line) = line else { break };
|
||||
// Unknown row counts are bounded as rows arrive, without returning a prefix.
|
||||
if out.len().checked_add(row_bytes)? > max {
|
||||
return None;
|
||||
}
|
||||
pack(&line, columns, p.black_is_1, &mut out);
|
||||
refl = line;
|
||||
rows += 1;
|
||||
}
|
||||
// Rows missing from damaged or short data stay white.
|
||||
if p.rows > 0 && rows > 0 && rows < p.rows {
|
||||
let row_bytes = columns.div_ceil(8);
|
||||
out.resize(p.rows.min(1 << 20) * row_bytes, if p.black_is_1 { 0 } else { 0xFF });
|
||||
out.resize(padded_len, if p.black_is_1 { 0 } else { 0xFF });
|
||||
}
|
||||
(rows > 0).then_some(out)
|
||||
}
|
||||
|
|
@ -726,14 +735,14 @@ pub(crate) mod tests {
|
|||
for k in [-1, 0, 1, 3] {
|
||||
let data = encode(&img, k);
|
||||
let p = Params { k, columns: cols, rows, ..Params::default() };
|
||||
let out = decode(&data, &p).unwrap_or_else(|| panic!("{cols}×{rows} K {k}: no rows"));
|
||||
let out = decode(&data, &p, 1 << 20).unwrap_or_else(|| panic!("{cols}×{rows} K {k}: no rows"));
|
||||
assert_eq!(unpack(&out, cols, rows), img, "{cols}×{rows} K {k}");
|
||||
// Rows unknown: stop at the end of the data / EOFB.
|
||||
let out = decode(&data, &Params { rows: 0, ..p.clone() }).unwrap();
|
||||
let out = decode(&data, &Params { rows: 0, ..p.clone() }, 1 << 20).unwrap();
|
||||
assert_eq!(unpack(&out, cols, rows), img, "{cols}×{rows} K {k}, no /Rows");
|
||||
// BlackIs1 inverts.
|
||||
let inv = decode(&data, &Params { black_is_1: true, ..p }).unwrap();
|
||||
assert!(inv.iter().zip(&decode(&data, &Params { k, columns: cols, rows, ..Params::default() }).unwrap()).all(|(a, b)| a ^ b == 0xFF));
|
||||
let inv = decode(&data, &Params { black_is_1: true, ..p }, 1 << 20).unwrap();
|
||||
assert!(inv.iter().zip(&decode(&data, &Params { k, columns: cols, rows, ..Params::default() }, 1 << 20).unwrap()).all(|(a, b)| a ^ b == 0xFF));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -769,9 +778,9 @@ pub(crate) mod tests {
|
|||
];
|
||||
for (i, (k, data)) in cases.iter().enumerate() {
|
||||
let p = Params { k: *k, columns: 61, rows: 23, black_is_1: true, end_of_line: *k >= 0, ..Params::default() };
|
||||
let out = decode(&hex(data), &p).unwrap();
|
||||
let out = decode(&hex(data), &p, 1 << 20).unwrap();
|
||||
assert_eq!(out, raw, "case {i} (K {k})");
|
||||
let out = decode(&hex(data), &Params { rows: 0, ..p }).unwrap();
|
||||
let out = decode(&hex(data), &Params { rows: 0, ..p }, 1 << 20).unwrap();
|
||||
assert_eq!(out, raw, "case {i} (K {k}), no /Rows");
|
||||
}
|
||||
}
|
||||
|
|
@ -781,7 +790,7 @@ pub(crate) mod tests {
|
|||
let img = pattern(64, 20, 9);
|
||||
let data = encode(&img, -1);
|
||||
for cut in [0, 1, data.len() / 3, data.len() - 3] {
|
||||
let out = decode(&data[..cut], &Params { k: -1, columns: 64, rows: 20, ..Params::default() });
|
||||
let out = decode(&data[..cut], &Params { k: -1, columns: 64, rows: 20, ..Params::default() }, 1 << 20);
|
||||
if let Some(out) = out {
|
||||
assert_eq!(out.len(), 20 * 8);
|
||||
}
|
||||
|
|
@ -791,8 +800,8 @@ pub(crate) mod tests {
|
|||
*b ^= (i * 37) as u8;
|
||||
}
|
||||
for k in [-1, 0, 2] {
|
||||
let _ = decode(&garbage, &Params { k, columns: 64, rows: 20, ..Params::default() });
|
||||
let _ = decode(&garbage, &Params { k, columns: 1, rows: 0, ..Params::default() });
|
||||
let _ = decode(&garbage, &Params { k, columns: 64, rows: 20, ..Params::default() }, 1 << 20);
|
||||
let _ = decode(&garbage, &Params { k, columns: 1, rows: 0, ..Params::default() }, 1 << 20);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -304,7 +304,7 @@ pub fn parse_num(w: &[u8]) -> Option<f64> {
|
|||
|
||||
// ------------------------------------------------------------------ filters
|
||||
|
||||
/// The most bytes one FlateDecode filter may inflate to. Deflate expands up to about 1000:1, so
|
||||
/// The most bytes one stream filter may decode to. Deflate expands up to about 1000:1, so
|
||||
/// unbounded, a few MB of PDF ask for gigabytes and a failed allocation aborts the process. The
|
||||
/// largest image `image::decode` accepts (64 Mi pixels) of four 16-bit components is 512 MiB;
|
||||
/// 1 GiB leaves room for more components and is PdfCraft's ceiling for one stream. 32-bit
|
||||
|
|
@ -324,7 +324,8 @@ pub fn decode_stream(file: &File, d: &Dict, raw: &[u8]) -> Option<Vec<u8>> {
|
|||
decode_stream_within(file, d, raw, MAX_INFLATED)
|
||||
}
|
||||
|
||||
/// [`decode_stream`] with each FlateDecode filter's output bounded by `max` bytes.
|
||||
/// [`decode_stream`] with every filter's output bounded by `max` bytes. Exceeding the
|
||||
/// bound gives `None`, never a truncated prefix; predictors share the same bound.
|
||||
pub fn decode_stream_within(file: &File, d: &Dict, raw: &[u8], max: usize) -> Option<Vec<u8>> {
|
||||
let filters: Vec<String> = match d.get("Filter").map(|f| file.resolve(f)) {
|
||||
None => vec![],
|
||||
|
|
@ -341,19 +342,22 @@ pub fn decode_stream_within(file: &File, d: &Dict, raw: &[u8], max: usize) -> Op
|
|||
for (i, f) in filters.iter().enumerate() {
|
||||
data = match f.as_str() {
|
||||
"FlateDecode" | "Fl" => inflate(&data, max)?,
|
||||
"ASCIIHexDecode" | "AHx" => ascii_hex(&data),
|
||||
"ASCII85Decode" | "A85" => ascii85(&data),
|
||||
"ASCIIHexDecode" | "AHx" => ascii_hex(&data, max)?,
|
||||
"ASCII85Decode" | "A85" => ascii85_within(&data, max)?,
|
||||
"LZWDecode" | "LZW" => {
|
||||
lzw(&data, parms.get(i).and_then(|p| p.as_ref()).and_then(|p| p.get("EarlyChange")).and_then(Obj::num).unwrap_or(1.0) != 0.0)
|
||||
lzw(&data, parms.get(i).and_then(|p| p.as_ref()).and_then(|p| p.get("EarlyChange")).and_then(Obj::num).unwrap_or(1.0) != 0.0, max)?
|
||||
}
|
||||
"RunLengthDecode" | "RL" => run_length(&data),
|
||||
"CCITTFaxDecode" | "CCF" => crate::ccitt::decode(&data, &ccitt_params(file, parms.get(i).and_then(|p| p.as_ref())))?,
|
||||
"RunLengthDecode" | "RL" => run_length(&data, max)?,
|
||||
"CCITTFaxDecode" | "CCF" => crate::ccitt::decode(&data, &ccitt_params(file, parms.get(i).and_then(|p| p.as_ref())), max)?,
|
||||
_ => return None,
|
||||
};
|
||||
if data.len() > max {
|
||||
return None;
|
||||
}
|
||||
if let Some(Some(p)) = parms.get(i)
|
||||
&& matches!(f.as_str(), "FlateDecode" | "Fl" | "LZWDecode" | "LZW")
|
||||
{
|
||||
data = predict(&data, p);
|
||||
data = predict(&data, p, max)?;
|
||||
}
|
||||
}
|
||||
Some(data)
|
||||
|
|
@ -402,15 +406,35 @@ pub fn salvage_len(data_len: usize, max: usize) -> usize {
|
|||
data_len.saturating_mul(8).saturating_add(1024).min(max)
|
||||
}
|
||||
|
||||
fn ascii_hex(data: &[u8]) -> Vec<u8> {
|
||||
let mut hex: Vec<u8> = data.iter().copied().take_while(|c| *c != b'>').filter(u8::is_ascii_hexdigit).collect();
|
||||
if hex.len() % 2 == 1 {
|
||||
hex.push(b'0');
|
||||
fn ascii_hex(data: &[u8], max: usize) -> Option<Vec<u8>> {
|
||||
let mut out = vec![];
|
||||
let mut high = None;
|
||||
for c in data.iter().copied().take_while(|c| *c != b'>').filter(u8::is_ascii_hexdigit) {
|
||||
if let Some(h) = high.take() {
|
||||
if out.len() >= max {
|
||||
return None;
|
||||
}
|
||||
out.push((h << 4) | hexv(c));
|
||||
} else {
|
||||
high = Some(hexv(c));
|
||||
}
|
||||
}
|
||||
hex.chunks(2).map(|p| (hexv(p[0]) << 4) | hexv(p[1])).collect()
|
||||
if let Some(h) = high {
|
||||
if out.len() >= max {
|
||||
return None;
|
||||
}
|
||||
out.push(h << 4);
|
||||
}
|
||||
Some(out)
|
||||
}
|
||||
|
||||
pub fn ascii85(data: &[u8]) -> Vec<u8> {
|
||||
// The PostScript reader uses this unbounded entry point. No Vec can hold more
|
||||
// than usize::MAX bytes, so its output cannot exceed this bound.
|
||||
ascii85_within(data, usize::MAX).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn ascii85_within(data: &[u8], max: usize) -> Option<Vec<u8>> {
|
||||
let mut out = vec![];
|
||||
let mut group = [0u32; 5];
|
||||
let mut n = 0;
|
||||
|
|
@ -423,11 +447,19 @@ pub fn ascii85(data: &[u8]) -> Vec<u8> {
|
|||
i += 1;
|
||||
match c {
|
||||
b'~' => break,
|
||||
b'z' if n == 0 => out.extend_from_slice(&[0; 4]),
|
||||
b'z' if n == 0 => {
|
||||
if out.len().checked_add(4)? > max {
|
||||
return None;
|
||||
}
|
||||
out.extend_from_slice(&[0; 4]);
|
||||
}
|
||||
b'!'..=b'u' => {
|
||||
group[n] = (c - b'!') as u32;
|
||||
n += 1;
|
||||
if n == 5 {
|
||||
if out.len().checked_add(4)? > max {
|
||||
return None;
|
||||
}
|
||||
let v = group.iter().fold(0u32, |a, &d| a.wrapping_mul(85).wrapping_add(d));
|
||||
out.extend_from_slice(&v.to_be_bytes());
|
||||
n = 0;
|
||||
|
|
@ -437,16 +469,19 @@ pub fn ascii85(data: &[u8]) -> Vec<u8> {
|
|||
}
|
||||
}
|
||||
if n > 1 {
|
||||
if out.len().checked_add(n - 1)? > max {
|
||||
return None;
|
||||
}
|
||||
for g in group.iter_mut().skip(n) {
|
||||
*g = 84;
|
||||
}
|
||||
let v = group.iter().fold(0u32, |a, &d| a.wrapping_mul(85).wrapping_add(d));
|
||||
out.extend_from_slice(&v.to_be_bytes()[..n - 1]);
|
||||
}
|
||||
out
|
||||
Some(out)
|
||||
}
|
||||
|
||||
fn lzw(data: &[u8], early: bool) -> Vec<u8> {
|
||||
fn lzw(data: &[u8], early: bool, max: usize) -> Option<Vec<u8>> {
|
||||
let mut out = vec![];
|
||||
let mut table: Vec<Vec<u8>> = (0..=255u16).map(|b| vec![b as u8]).collect();
|
||||
table.push(vec![]);
|
||||
|
|
@ -466,7 +501,7 @@ fn lzw(data: &[u8], early: bool) -> Vec<u8> {
|
|||
prev = None;
|
||||
continue;
|
||||
}
|
||||
257 => return out,
|
||||
257 => return Some(out),
|
||||
_ => {}
|
||||
}
|
||||
let entry = if code < table.len() {
|
||||
|
|
@ -476,10 +511,16 @@ fn lzw(data: &[u8], early: bool) -> Vec<u8> {
|
|||
e.push(p[0]);
|
||||
e
|
||||
} else {
|
||||
return out;
|
||||
return Some(out);
|
||||
};
|
||||
if out.len().checked_add(entry.len())? > max {
|
||||
return None;
|
||||
}
|
||||
out.extend_from_slice(&entry);
|
||||
if let Some(p) = prev {
|
||||
// PDF codes have at most 12 bits. Freeze a full table until a clear code.
|
||||
if let Some(p) = prev
|
||||
&& table.len() < 4096
|
||||
{
|
||||
let mut e = p;
|
||||
e.push(entry[0]);
|
||||
table.push(e);
|
||||
|
|
@ -491,43 +532,61 @@ fn lzw(data: &[u8], early: bool) -> Vec<u8> {
|
|||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
Some(out)
|
||||
}
|
||||
|
||||
fn run_length(data: &[u8]) -> Vec<u8> {
|
||||
fn run_length(data: &[u8], max: usize) -> Option<Vec<u8>> {
|
||||
let mut out = vec![];
|
||||
let mut i = 0;
|
||||
while i < data.len() {
|
||||
let l = data[i] as usize;
|
||||
i += 1;
|
||||
if l < 128 {
|
||||
out.extend_from_slice(&data[i.min(data.len())..(i + l + 1).min(data.len())]);
|
||||
i += l + 1;
|
||||
let end = i.saturating_add(l + 1).min(data.len());
|
||||
let literal = data.get(i..end)?;
|
||||
if out.len().checked_add(literal.len())? > max {
|
||||
return None;
|
||||
}
|
||||
out.extend_from_slice(literal);
|
||||
i = end;
|
||||
} else if l > 128 {
|
||||
if let Some(&b) = data.get(i) {
|
||||
if out.len().checked_add(257 - l)? > max {
|
||||
return None;
|
||||
}
|
||||
out.extend(std::iter::repeat_n(b, 257 - l));
|
||||
}
|
||||
i += 1;
|
||||
i = i.saturating_add(1);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
out
|
||||
Some(out)
|
||||
}
|
||||
|
||||
/// PNG / TIFF predictors (`/Predictor`, `/Colors`, `/BitsPerComponent`, `/Columns`).
|
||||
fn predict(data: &[u8], p: &Dict) -> Vec<u8> {
|
||||
fn predict(data: &[u8], p: &Dict, max: usize) -> Option<Vec<u8>> {
|
||||
if data.len() > max {
|
||||
return None;
|
||||
}
|
||||
let g = |k: &str, d: f64| p.get(k).and_then(Obj::num).unwrap_or(d) as usize;
|
||||
let pred = g("Predictor", 1.0);
|
||||
if pred < 10 {
|
||||
return data.to_vec();
|
||||
return Some(data.to_vec());
|
||||
}
|
||||
let bpp = (g("Colors", 1.0) * g("BitsPerComponent", 8.0)).div_ceil(8).max(1);
|
||||
let row = (g("Columns", 1.0) * g("Colors", 1.0) * g("BitsPerComponent", 8.0)).div_ceil(8);
|
||||
// A row that is zero bytes wide, overflows, or is not shorter than the data leaves no
|
||||
// complete row: nothing comes out, as it always did for the first and last, and the row
|
||||
// buffer (sized from `/Columns`) is not allocated.
|
||||
let Some(pixel_bits) = g("Colors", 1.0).checked_mul(g("BitsPerComponent", 8.0)) else { return Some(vec![]) };
|
||||
let Some(bpp) = pixel_bits.checked_add(7).map(|n| (n / 8).max(1)) else { return Some(vec![]) };
|
||||
let Some(row) = g("Columns", 1.0).checked_mul(pixel_bits).and_then(|n| n.checked_add(7)).map(|n| n / 8).filter(|&n| n > 0 && n < data.len()) else {
|
||||
return Some(vec![]);
|
||||
};
|
||||
let stride = row.checked_add(1)?;
|
||||
let mut out = Vec::with_capacity(data.len());
|
||||
let mut prev = vec![0u8; row];
|
||||
for chunk in data.chunks(row + 1) {
|
||||
if chunk.len() < row + 1 {
|
||||
for chunk in data.chunks(stride) {
|
||||
if chunk.len() < stride {
|
||||
break;
|
||||
}
|
||||
let ft = chunk[0];
|
||||
|
|
@ -558,7 +617,7 @@ fn predict(data: &[u8], p: &Dict) -> Vec<u8> {
|
|||
out.extend_from_slice(&cur);
|
||||
prev = cur;
|
||||
}
|
||||
out
|
||||
Some(out)
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ the file
|
||||
|
|
|
|||
|
|
@ -204,6 +204,127 @@ fn nested_bomb_in_the_second_flate_filter_is_refused() {
|
|||
assert_eq!(object::decode_stream_within(&file, &chain, &raw, MAX).map(|v| v.len()), None);
|
||||
}
|
||||
|
||||
fn bounded_filter(dict: &str, raw: &[u8], max: usize) -> Option<Vec<u8>> {
|
||||
let object::Obj::Dict(d) = object::Lexer::new(dict.as_bytes(), 0).next().unwrap() else { panic!("not a dictionary") };
|
||||
object::decode_stream_within(&object::File::parse(b""), &d, raw, max)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn predictor_huge_columns_decode_to_nothing() {
|
||||
// A row of a petabyte used to be allocated before looking at the data (an allocation
|
||||
// failure aborts the process, so this test took the whole test binary down).
|
||||
let raw = miniz_oxide::deflate::compress_to_vec_zlib(&[0, 42], 6);
|
||||
let dict = "<< /Filter /FlateDecode /DecodeParms << /Predictor 12 /Columns 1000000000000000 >> >>";
|
||||
assert_eq!(bounded_filter(dict, &raw, 16), Some(vec![]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn predictor_overflow_and_empty_rows_decode_to_nothing() {
|
||||
let raw = miniz_oxide::deflate::compress_to_vec_zlib(&[0, 42], 6);
|
||||
for parms in [
|
||||
format!("/Columns {} /Colors 2 /BitsPerComponent 8", usize::MAX),
|
||||
format!("/Columns 1 /Colors {} /BitsPerComponent 8", usize::MAX),
|
||||
"/Columns 0".into(),
|
||||
// A row is two bytes plus its tag byte: longer than the two bytes of data.
|
||||
"/Columns 2".into(),
|
||||
] {
|
||||
assert_eq!(bounded_filter(&format!("<< /Filter /FlateDecode /DecodeParms << /Predictor 12 {parms} >> >>"), &raw, 16), Some(vec![]));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_length_chain_is_bounded() {
|
||||
// Each stage expands repetitions of 0x81, which is itself an RL repeat code.
|
||||
let raw = [0x81, 0x81, 128];
|
||||
let chain = "<< /Filter [/RunLengthDecode /RL /RunLengthDecode] >>";
|
||||
assert_eq!(bounded_filter(chain, &raw, 128).map(|v| v.len()), None);
|
||||
assert_eq!(bounded_filter("<< /Filter /RL >>", &raw, 128), Some(vec![0x81; 128]));
|
||||
// An over-limit intermediate stage must fail even if the next stage shrinks it.
|
||||
assert_eq!(bounded_filter("<< /Filter [/RL /AHx] >>", &raw, 127), None);
|
||||
}
|
||||
|
||||
fn lzw_codes(codes: impl IntoIterator<Item = u16>, early: bool) -> Vec<u8> {
|
||||
let mut w = crate::ccitt::tests::W::default();
|
||||
let (mut bits, mut entries, mut prev) = (9, 258, false);
|
||||
for code in codes {
|
||||
w.put(code, bits);
|
||||
match code {
|
||||
256 => (bits, entries, prev) = (9, 258, false),
|
||||
257 => break,
|
||||
_ => {
|
||||
if prev && entries < 4096 {
|
||||
entries += 1;
|
||||
}
|
||||
prev = true;
|
||||
if entries + usize::from(early) >= (1 << bits) && bits < 12 {
|
||||
bits += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
w.bytes
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lzw_long_run_is_bounded_and_full_table_decodes() {
|
||||
// Grow strings of A until the 12-bit table is full, then send literal A codes.
|
||||
// Both old and new implementations use well under 100 MB for this fixture.
|
||||
for early in [false, true] {
|
||||
let raw = lzw_codes([256, 65].into_iter().chain(258..=4095).chain(std::iter::repeat_n(65, 62_500)).chain([257]), early);
|
||||
assert!((95_000..105_000).contains(&raw.len()));
|
||||
let dict = format!("<< /Filter /LZWDecode /DecodeParms << /EarlyChange {} >> >>", usize::from(early));
|
||||
assert_eq!(bounded_filter(&dict, &raw, 1 << 20).map(|v| v.len()), None);
|
||||
let expected = 3839 * 3840 / 2 + 62_500;
|
||||
assert_eq!(bounded_filter(&dict, &raw, expected), Some(vec![b'A'; expected]));
|
||||
// Clear restores the initial dictionary and code width after saturation.
|
||||
let cleared = lzw_codes([256, 65].into_iter().chain(258..=4095).chain([256, 66, 257]), early);
|
||||
let mut expected = vec![b'A'; 3839 * 3840 / 2];
|
||||
expected.push(b'B');
|
||||
assert_eq!(bounded_filter(&dict, &cleared, expected.len()), Some(expected));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ccitt_declared_and_unknown_rows_are_bounded() {
|
||||
// Eight white Group 4 rows. The old decoder pads declared missing rows to white.
|
||||
assert_eq!(bounded_filter("<< /Filter /CCF /DecodeParms << /K -1 /Columns 64 /Rows 1024 >> >>", &[0xFF], 16), None);
|
||||
assert_eq!(bounded_filter("<< /Filter /CCF /DecodeParms << /K -1 /Columns 64 >> >>", &[0xFF], 16), None);
|
||||
assert_eq!(bounded_filter("<< /Filter /CCF /DecodeParms << /K -1 /Columns 8 /Rows 8 >> >>", &[0xFF], 8), Some(vec![0xFF; 8]));
|
||||
// Padding below the limit remains unchanged.
|
||||
assert_eq!(bounded_filter("<< /Filter /CCF /DecodeParms << /K -1 /Columns 8 /Rows 16 >> >>", &[0xFF], 16), Some(vec![0xFF; 16]));
|
||||
// Without `/Rows` the size is never computed up front: 2^20 rows of 4096 bytes overflow a
|
||||
// 32-bit `usize`, but eight rows of this width decode on every target.
|
||||
assert_eq!(bounded_filter("<< /Filter /CCF /DecodeParms << /K -1 /Columns 32768 >> >>", &[0xFF], 32768), Some(vec![0xFF; 32768]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ascii85_and_hex_outputs_are_bounded() {
|
||||
assert_eq!(bounded_filter("<< /Filter /A85 >>", b"zz", 7), None);
|
||||
assert_eq!(bounded_filter("<< /Filter /A85 >>", b"zz", 8), Some(vec![0; 8]));
|
||||
assert_eq!(bounded_filter("<< /Filter /A85 >>", b"87cURD]i,\"Ebo80", 12), Some(b"Hello World!".to_vec()));
|
||||
assert_eq!(bounded_filter("<< /Filter /A85 >>", b"87cURD]i,\"Ebo80", 11), None);
|
||||
assert_eq!(bounded_filter("<< /Filter /AHx >>", b"4142434>", 3), None);
|
||||
assert_eq!(bounded_filter("<< /Filter /AHx >>", b"4142434>", 4), Some(b"ABC@".to_vec()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn predictors_and_normal_lzw_decode_within_limit() {
|
||||
for predictor in [1, 2, 12] {
|
||||
let (data, expected) = if predictor == 12 { (vec![0, 10, 20, 2, 1, 2], vec![10, 20, 11, 22]) } else { (vec![10, 20], vec![10, 20]) };
|
||||
let raw = miniz_oxide::deflate::compress_to_vec_zlib(&data, 6);
|
||||
let dict = format!("<< /Filter /FlateDecode /DecodeParms << /Predictor {predictor} /Columns 2 >> >>");
|
||||
assert_eq!(bounded_filter(&dict, &raw, data.len()), Some(expected));
|
||||
}
|
||||
for early in [false, true] {
|
||||
let raw = lzw_codes([256, 65, 66, 258, 257], early);
|
||||
assert_eq!(bounded_filter("<< /Filter /LZW >>", &raw, 4), Some(b"ABAB".to_vec()));
|
||||
assert_eq!(bounded_filter("<< /Filter /LZW >>", &raw, 3), None);
|
||||
}
|
||||
assert_eq!(bounded_filter("<< /Filter /RL >>", &[2, b'a', b'b', b'c', 254, b'd', 128], 6), Some(b"abcddd".to_vec()));
|
||||
assert_eq!(bounded_filter("<< /Filter /RL >>", &[2, b'a', b'b', b'c', 128], 2), None);
|
||||
assert_eq!(bounded_filter("<< /Filter /RL >>", &[128], 0), Some(vec![]));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ M13.6: text, images,
|
||||
// patterns, soft masks, blend modes, pages
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue