mirror of
https://github.com/storytold/craft-libs.git
synced 2026-10-31 15:49:54 +00:00
Merge remote-tracking branch 'upstream/main' into pdf-reader-sync
# Conflicts: # vendor/hayro-syntax/SOURCE.json
This commit is contained in:
commit
fcbf08de14
5 files changed
+96
-16
No files matched your search
Vendored
+6
@@ -175,6 +175,12 @@ comma. The unit tests of all these items come with them; the CCITT, Type 3 and c
|
||||
raster tests are adapted in `tests/upstream_raster.rs` and the text contracts in
|
||||
`tests/upstream_text.rs`. (P8)'s PdfCraft test needs a Japanese substitute font and is not
|
||||
ported.
|
||||
- A CCITT image with `/Rows 0` (unknown height) decodes at most the rows that fit the stream's
|
||||
output limit (counted in 1-bit rows) and 2^28 pixels; a known height is clamped the same way.
|
||||
Before, it decoded to the end of the data, and a bit of 2D code can repeat a whole row.
|
||||
- A JPEG 2000 image over 2^28 pixels, or over its output limit (at least 64 MiB), by the
|
||||
codestream's own dimensions is refused before decoding, and its buffer is reserved fallibly:
|
||||
the image dictionary's smaller size no longer stands in for the size the decoder allocates.
|
||||
|
||||
Paint fixes from the [PdfCraft revision 0b670caf4a090e9d83a7f41a84b4cb2144a2d33e ledger](https://github.com/storytold/pdfcraft/blob/0b670caf4a090e9d83a7f41a84b4cb2144a2d33e/vendor/README.md),
|
||||
each marked `PdfCraft patch` in the source: hayro-interpret (46) with its hayro counterpart
|
||||
|
||||
Vendored
+4
-4
@@ -146,7 +146,7 @@
|
||||
"path": "src/filter/ccitt.rs",
|
||||
"source_sha256": "45d7eb89934d28cff97a00c7b264010bccfd14f3d511e1307ad50a9fef3b53c8",
|
||||
"source_git_blob": "685d680d2fbadce608a9bd3ca925c2ccc6c5d641",
|
||||
"sha256": "b0fef2271417c3009a1de569c9573b05dd464ff76c1a5f7220f634bb5feb44e2"
|
||||
"sha256": "67d17a339639eb0fef4cc6c745b579c6ba9ae3f2f67fd3bacb1bf1389aa5ffab"
|
||||
},
|
||||
{
|
||||
"path": "src/filter/dct.rs",
|
||||
@@ -162,9 +162,9 @@
|
||||
},
|
||||
{
|
||||
"path": "src/filter/jpx.rs",
|
||||
"source_sha256": "f0a2b01c692c086d30a26562d7548f9e837af166be737d2c543de7c886a45f82",
|
||||
"source_sha256": "c9f068629c9cbe79af6793c3c125df4631ca1455691cb1335b44ff43cb1b4056",
|
||||
"source_git_blob": "74efd8b31e2b2935ff2bbceda2c1ffd36ea9c20d",
|
||||
"sha256": "5364837caa8ed2abc33a1977b5c91f6895be17c899ae0cc6fe1ebc0723a62448"
|
||||
"sha256": "dc3742454cfde9a994ce48984e8fcb7b18851efb36ce2d62e31629efea063a1b"
|
||||
},
|
||||
{
|
||||
"path": "src/filter/lzw_flate.rs",
|
||||
@@ -176,7 +176,7 @@
|
||||
"path": "src/filter/mod.rs",
|
||||
"source_sha256": "0af7145ab16b15b7a60c4b57efecb9bd5fd0cfdc9bb5f9388abb695386e63e49",
|
||||
"source_git_blob": "06bda29487d62c25dc39c1ee109091cb17e3867f",
|
||||
"sha256": "e33e74ee55dcc850db9eb64811c56b4ffe1932908ab9684dfba07f4277b1eea9"
|
||||
"sha256": "e43d7d309a4993f4a1f698208997122431b4a8b0db838bb07d4358ccbc00f02f"
|
||||
},
|
||||
{
|
||||
"path": "src/filter/run_length.rs",
|
||||
|
||||
+13
-1
@@ -8,10 +8,12 @@ use alloc::vec::Vec;
|
||||
use core::iter;
|
||||
use hayro_ccitt::{DecodeSettings, Decoder, DecoderContext, EncodingMode};
|
||||
|
||||
/// PdfCraft patch: `limit` is the most bytes the filter may produce (see `decode_limit`).
|
||||
pub(crate) fn decode(
|
||||
data: &[u8],
|
||||
params: &Dict<'_>,
|
||||
image_params: &ImageDecodeParams,
|
||||
limit: usize,
|
||||
) -> Option<FilterResult<'static>> {
|
||||
let k = params.get::<i32>(K).unwrap_or(0);
|
||||
|
||||
@@ -52,6 +54,16 @@ pub(crate) fn decode(
|
||||
if !super::ccitt_size_ok(settings.columns, settings.rows) {
|
||||
return None;
|
||||
}
|
||||
// PdfCraft patch: the decoder stops after `rows` rows; with `/Rows 0` (unknown) it ran to the
|
||||
// end of the data, and a bit of 2D code can repeat a whole row, so a small stream produced
|
||||
// gigabytes. Decode at most the rows that fit the output limit and the pixel cap. The limit
|
||||
// counts the stream's own 1-bit rows (`decode_limit`), whatever depth the rows are widened to.
|
||||
let mut settings = settings;
|
||||
let fit = super::ccitt_max_rows(settings.columns, settings.columns.div_ceil(8), limit);
|
||||
settings.rows = if settings.rows == 0 { fit } else { settings.rows.min(fit) };
|
||||
if settings.rows == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Whenever possible (if we don't have an indexed color space), we convert
|
||||
// the data as 8-bit instead of 1-bit, so that it can be easier converted
|
||||
@@ -181,7 +193,7 @@ mod tests {
|
||||
#[test]
|
||||
fn columns_past_u32_max_are_refused_not_wrapped() {
|
||||
let params = ImageDecodeParams { width: 8, height: 4, ..Default::default() };
|
||||
let decoded = |dict: &[u8]| decode(&ALL_BLACK_8X4, &Dict::from_bytes(dict).unwrap(), ¶ms);
|
||||
let decoded = |dict: &[u8]| decode(&ALL_BLACK_8X4, &Dict::from_bytes(dict).unwrap(), ¶ms, 1 << 20);
|
||||
assert!(decoded(b"<< /K -1 /Columns 8 >>").is_some());
|
||||
// 2^32 + 8 would wrap to 8 columns.
|
||||
assert!(decoded(b"<< /K -1 /Columns 4294967304 >>").is_none());
|
||||
|
||||
+45
-7
@@ -17,7 +17,15 @@ impl ImageColorSpace {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn decode(data: &[u8], params: &ImageDecodeParams) -> Option<FilterResult<'static>> {
|
||||
/// PdfCraft patch: the most pixels a JPEG 2000 image may decode to, as for JBIG2.
|
||||
const MAX_PIXELS: u64 = 1 << 28;
|
||||
|
||||
/// PdfCraft patch: the least output `limit` a JPEG 2000 image is held to: the limit comes from the
|
||||
/// image dictionary's size, which a decoder may round differently from the codestream's.
|
||||
const MIN_LIMIT: usize = 64 << 20;
|
||||
|
||||
/// PdfCraft patch: `limit` is the most bytes the filter may produce (see `decode_limit`).
|
||||
pub(crate) fn decode(data: &[u8], params: &ImageDecodeParams, limit: usize) -> Option<FilterResult<'static>> {
|
||||
use crate::object::stream::ImageColorSpace;
|
||||
|
||||
let settings = DecodeSettings {
|
||||
@@ -74,7 +82,23 @@ pub(crate) fn decode(data: &[u8], params: &ImageDecodeParams) -> Option<FilterRe
|
||||
},
|
||||
};
|
||||
let has_alpha = image.has_alpha();
|
||||
let bitmap = image.decode().ok()?;
|
||||
// PdfCraft patch: the decoder sizes its output from the codestream's own dimensions, not the
|
||||
// image dictionary's, and allocates it infallibly. Refuse more than `MAX_PIXELS` pixels or
|
||||
// more than `limit` bytes before decoding, and reserve the buffer fallibly.
|
||||
if u64::from(width) * u64::from(height) > MAX_PIXELS {
|
||||
return None;
|
||||
}
|
||||
let channels = usize::from(cs.num_components()) + usize::from(has_alpha);
|
||||
let len = (width as usize).checked_mul(height as usize)?.checked_mul(channels)?;
|
||||
let output_limit = limit.max(MIN_LIMIT);
|
||||
if len > output_limit {
|
||||
return None;
|
||||
}
|
||||
let mut bitmap = Vec::new();
|
||||
bitmap.try_reserve_exact(len).ok()?;
|
||||
bitmap.resize(len, 0);
|
||||
let mut context = hayro_jpeg2000::DecoderContext::default();
|
||||
image.decode_into(&mut bitmap, &mut context).ok()?;
|
||||
|
||||
let (mut data, mut alpha) = if !has_alpha {
|
||||
(bitmap, None)
|
||||
@@ -98,6 +122,18 @@ pub(crate) fn decode(data: &[u8], params: &ImageDecodeParams) -> Option<FilterRe
|
||||
// The decoded image is always 8-bit, so if necessary we have to rescale
|
||||
// ourselves.
|
||||
if bpc != 8 {
|
||||
// PdfCraft patch: the rescaled buffer can be larger than the 8-bit bitmap (up to 32 bits
|
||||
// per component), so bound it, with alpha, by the same output limit.
|
||||
let bits_per_row = (width as usize)
|
||||
.checked_mul(usize::from(cs.num_components()))?
|
||||
.checked_mul(usize::from(bpc))?;
|
||||
let scaled_len = bits_per_row
|
||||
.div_ceil(8)
|
||||
.checked_mul(height as usize)?
|
||||
.checked_mul(if alpha.is_some() { 2 } else { 1 })?;
|
||||
if scaled_len > output_limit {
|
||||
return None;
|
||||
}
|
||||
data = scale(&data, bpc, cs.num_components(), width, height)?;
|
||||
alpha = alpha.and_then(|alpha| scale(&alpha, bpc, cs.num_components(), width, height));
|
||||
}
|
||||
@@ -223,7 +259,9 @@ fn scale(
|
||||
.checked_mul(num_components as usize)?
|
||||
.checked_mul(bit_per_component as usize)?;
|
||||
let input_len = bits_per_row.div_ceil(8).checked_mul(height as usize)?;
|
||||
let mut input = vec![0; input_len];
|
||||
let mut input = Vec::new();
|
||||
input.try_reserve_exact(input_len).ok()?;
|
||||
input.resize(input_len, 0);
|
||||
let mut writer = BitWriter::new(&mut input, bit_per_component)?;
|
||||
let components_per_row = (num_components as usize).checked_mul(width as usize)?;
|
||||
|
||||
@@ -417,7 +455,7 @@ mod tests {
|
||||
height: 2,
|
||||
..ImageDecodeParams::default()
|
||||
};
|
||||
let result = decode(PALETTE_JP2, ¶ms).unwrap();
|
||||
let result = decode(PALETTE_JP2, ¶ms, usize::MAX).unwrap();
|
||||
let image = result.image_data.unwrap();
|
||||
assert!(matches!(image.color_space, Some(ImageColorSpace::Rgb)));
|
||||
let red = [255, 0, 0];
|
||||
@@ -437,7 +475,7 @@ mod tests {
|
||||
height: 2,
|
||||
..ImageDecodeParams::default()
|
||||
};
|
||||
let result = decode(PALETTE_JP2, ¶ms).unwrap();
|
||||
let result = decode(PALETTE_JP2, ¶ms, usize::MAX).unwrap();
|
||||
assert_eq!(&*result.data, &[0, 1, 0, 1, 1, 0, 1, 0][..]);
|
||||
}
|
||||
|
||||
@@ -479,7 +517,7 @@ mod tests {
|
||||
..ImageDecodeParams::default()
|
||||
};
|
||||
let bpc = |data: &[u8]| {
|
||||
decode(data, ¶ms)
|
||||
decode(data, ¶ms, usize::MAX)
|
||||
.and_then(|r| r.image_data)
|
||||
.map(|d| d.bits_per_component)
|
||||
};
|
||||
@@ -501,7 +539,7 @@ mod tests {
|
||||
bpc: Some(1),
|
||||
..params.clone()
|
||||
};
|
||||
let palette_bpc = decode(&one_bit_jp2(&palette), &one_bit)
|
||||
let palette_bpc = decode(&one_bit_jp2(&palette), &one_bit, usize::MAX)
|
||||
.and_then(|r| r.image_data)
|
||||
.map(|d| d.bits_per_component);
|
||||
assert_eq!(palette_bpc, Some(8), "/BitsPerComponent 1 with a palette");
|
||||
|
||||
+28
-4
@@ -110,11 +110,11 @@ impl Filter {
|
||||
#[cfg(feature = "images")]
|
||||
Self::DctDecode => dct::decode(data, params, image_params).ok_or(DecodeFailure::ImageDecode),
|
||||
#[cfg(feature = "images")]
|
||||
Self::CcittFaxDecode => ccitt::decode(data, params, image_params).ok_or(DecodeFailure::ImageDecode),
|
||||
Self::CcittFaxDecode => ccitt::decode(data, params, image_params, limit).ok_or(DecodeFailure::ImageDecode),
|
||||
#[cfg(feature = "images")]
|
||||
Self::Jbig2Decode => jbig2::decode(data, params, image_params).ok_or(DecodeFailure::ImageDecode),
|
||||
#[cfg(feature = "images")]
|
||||
Self::JpxDecode => jpx::decode(data, image_params).ok_or(DecodeFailure::ImageDecode),
|
||||
Self::JpxDecode => jpx::decode(data, image_params, limit).ok_or(DecodeFailure::ImageDecode),
|
||||
#[cfg(not(feature = "images"))]
|
||||
Self::DctDecode | Self::CcittFaxDecode | Self::Jbig2Decode | Self::JpxDecode => {
|
||||
warn!("image decoding is not supported (enable the `images` feature)");
|
||||
@@ -168,6 +168,30 @@ pub(crate) fn decode_limit(params: &ImageDecodeParams, last: bool, limits: crate
|
||||
/// real fax lines are a few thousand pixels wide.
|
||||
pub fn ccitt_size_ok(columns: u32, rows: u32) -> bool {
|
||||
const MAX_COLUMNS: u32 = 1 << 20;
|
||||
const MAX_PIXELS: u64 = 1 << 28;
|
||||
columns > 0 && columns <= MAX_COLUMNS && u64::from(columns) * u64::from(rows.max(1)) <= MAX_PIXELS
|
||||
columns > 0 && columns <= MAX_COLUMNS && u64::from(columns) * u64::from(rows.max(1)) <= MAX_CCITT_PIXELS
|
||||
}
|
||||
|
||||
/// PdfCraft patch: the most pixels a CCITT image may decode to.
|
||||
const MAX_CCITT_PIXELS: u64 = 1 << 28;
|
||||
|
||||
/// PdfCraft patch: the most rows of `columns` pixels, `row_bytes` bytes each once decoded, that fit
|
||||
/// both `limit` bytes and the pixel cap; at least one when one row fits.
|
||||
pub(crate) fn ccitt_max_rows(columns: u32, row_bytes: u32, limit: usize) -> u32 {
|
||||
let by_pixels = MAX_CCITT_PIXELS / u64::from(columns.max(1));
|
||||
let by_bytes = u64::try_from(limit).unwrap_or(u64::MAX) / u64::from(row_bytes.max(1));
|
||||
u32::try_from(by_pixels.min(by_bytes)).unwrap_or(u32::MAX)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod ccitt_tests {
|
||||
use super::ccitt_max_rows;
|
||||
|
||||
/// PdfCraft patch: an image of unknown height decodes at most the rows that fit both the output
|
||||
/// limit (in 1-bit rows) and the 2^28-pixel cap.
|
||||
#[test]
|
||||
fn unknown_ccitt_rows_fit_the_limit_and_the_pixel_cap() {
|
||||
assert_eq!(ccitt_max_rows(1728, 216, 216 * 100), 100);
|
||||
assert_eq!(ccitt_max_rows(1 << 20, 1 << 17, usize::MAX), 1 << 8);
|
||||
assert_eq!(ccitt_max_rows(1728, 216, 100), 0);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user