Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8459f413a | ||
|
|
26e760e303 | ||
|
|
a4878e9658 | ||
|
|
73285481cd | ||
|
|
b065539c07 | ||
|
|
1c5800daa0 | ||
|
|
885d762def | ||
|
|
2c9e02d347 | ||
|
|
b318481c55 | ||
|
|
1774b362d3 | ||
|
|
f2fec92283 | ||
|
|
be0619d6a1 | ||
|
|
e92cf219a9 | ||
|
|
3e5f977460 | ||
|
|
f0666aebbf | ||
|
|
65a1fbe8ae | ||
|
|
03b6ed413b | ||
|
|
5b7814c994 | ||
|
|
a15d619471 | ||
|
|
458a713827 | ||
|
|
2f6ad07f28 | ||
|
|
153ed48ee7 | ||
|
|
420667955a | ||
|
|
3bf19822a2 | ||
|
|
2bb37a4ecd | ||
|
|
b41ac105ab | ||
|
|
1e10b9803b | ||
|
|
2f4e491874 | ||
|
|
285b881e27 | ||
|
|
bdfe2f55fd | ||
|
|
eda5a8ac30 | ||
|
|
bd1e37d78b | ||
|
|
ad0f75222a |
No files matched your search
@@ -9,7 +9,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
rust: ["1.34.2", stable, beta, nightly]
|
rust: ["1.48", stable, beta, nightly]
|
||||||
command: [build, test]
|
command: [build, test]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
- name: test
|
- name: test
|
||||||
run: >
|
run: >
|
||||||
cargo test --tests --benches --no-default-features --features "$FEATURES"
|
cargo test --tests --benches --no-default-features --features "$FEATURES"
|
||||||
if: ${{ matrix.rust != '1.34.2' }}
|
if: ${{ matrix.rust != '1.48' }}
|
||||||
env:
|
env:
|
||||||
FEATURES: ${{ matrix.features }}
|
FEATURES: ${{ matrix.features }}
|
||||||
rustfmt:
|
rustfmt:
|
||||||
|
|||||||
+33
@@ -1,3 +1,36 @@
|
|||||||
|
# Version 0.7.1
|
||||||
|
|
||||||
|
New features:
|
||||||
|
* Encoding signed integer formats is now supported.
|
||||||
|
* Extensive fuzzing with `cargo fuzz`.
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
* Tile decoding should be a little faster, requires one less intermediate buffer.
|
||||||
|
* Images whose IFDs form a cycle due to their offsets will now raise an error
|
||||||
|
when the cycle would be entered (jumping back should still be supported).
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
* Fixed a regression that caused conflict between strips and tile images,
|
||||||
|
causing errors in decoding some images.
|
||||||
|
* Use checked integer arithmetic in limit calculations, fixes overflows.
|
||||||
|
* IFD Tags are now always cleared between images.
|
||||||
|
* Found by fuzzing: Several memory limit overflows; JPEG now correctly
|
||||||
|
validates offsets and a minimum size of its table; Check upper limit of strip
|
||||||
|
byte size correctly;
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
Our CI has warned that this version no longer builds on `1.34.2` out of the
|
||||||
|
box. We're still committed to the MSRV on this major version yet one
|
||||||
|
dependency—`flate2`—has already bumped it in a SemVer compatible version of its
|
||||||
|
own. This is out-of-our-control (cargo's dependency resolution does not allow
|
||||||
|
us to address this in a reasonable manner).
|
||||||
|
|
||||||
|
This can be address this by pinning the version of `flate2` to `1.0.21` in your
|
||||||
|
own files. However, you should understand that this puts you in considerable
|
||||||
|
maintenance debt as you will no longer receive any updates for this dependency
|
||||||
|
and any package that _requires_ a new version of the `1.0` series would be
|
||||||
|
incompatible with this requirement (cargo might yell at you very loudly).
|
||||||
|
|
||||||
# Version 0.7.0
|
# Version 0.7.0
|
||||||
|
|
||||||
New features:
|
New features:
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tiff"
|
name = "tiff"
|
||||||
version = "0.7.0"
|
version = "0.7.1"
|
||||||
authors = [
|
authors = [
|
||||||
"ccgn",
|
"ccgn",
|
||||||
"bvssvni <bvssvni@gmail.com>",
|
"bvssvni <bvssvni@gmail.com>",
|
||||||
|
|||||||
@@ -44,3 +44,11 @@ Formats and interpretations not listed above or with empty entries are unsupport
|
|||||||
- Baseline tags
|
- Baseline tags
|
||||||
- `ExtraSamples`
|
- `ExtraSamples`
|
||||||
- Extension tags
|
- Extension tags
|
||||||
|
|
||||||
|
## Fuzzing
|
||||||
|
|
||||||
|
This crate uses [cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz) in order to test the image parser.
|
||||||
|
|
||||||
|
After installing it with `cargo install cargo-fuzz` on a nightly rustc, the
|
||||||
|
fuzzing harness can be run with recommended settings using
|
||||||
|
`cargo fuzz run decode_image -snone -- -timeout=5`.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
target
|
||||||
|
corpus
|
||||||
|
artifacts
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
[package]
|
||||||
|
name = "tiff-fuzz"
|
||||||
|
version = "0.0.0"
|
||||||
|
authors = ["Automatically generated"]
|
||||||
|
publish = false
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
cargo-fuzz = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
libfuzzer-sys = "0.4"
|
||||||
|
|
||||||
|
[dependencies.tiff]
|
||||||
|
path = ".."
|
||||||
|
|
||||||
|
# Prevent this from interfering with workspaces
|
||||||
|
[workspace]
|
||||||
|
members = ["."]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "decode_image"
|
||||||
|
path = "fuzz_targets/decode_image.rs"
|
||||||
|
test = false
|
||||||
|
doc = false
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#![no_main]
|
||||||
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
|
||||||
|
fuzz_target!(|data: &[u8]| {
|
||||||
|
let mut decoder = if let Ok(d) = tiff::decoder::Decoder::new(std::io::Cursor::new(data)) {
|
||||||
|
d
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut limits = tiff::decoder::Limits::default();
|
||||||
|
limits.decoding_buffer_size = 1_000_000;
|
||||||
|
limits.ifd_value_size = 1_000_000;
|
||||||
|
limits.intermediate_buffer_size = 1_000_000;
|
||||||
|
|
||||||
|
decoder = decoder.with_limits(limits);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if let Err(_) = decoder.read_image() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !decoder.more_images() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(_) = decoder.next_image() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
+20
-13
@@ -349,19 +349,26 @@ impl Entry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let bo = decoder.byte_order();
|
let bo = decoder.byte_order();
|
||||||
let value_bytes = self.count
|
|
||||||
* match self.type_ {
|
let tag_size = match self.type_ {
|
||||||
Type::BYTE | Type::SBYTE | Type::ASCII | Type::UNDEFINED => 1,
|
Type::BYTE | Type::SBYTE | Type::ASCII | Type::UNDEFINED => 1,
|
||||||
Type::SHORT | Type::SSHORT => 2,
|
Type::SHORT | Type::SSHORT => 2,
|
||||||
Type::LONG | Type::SLONG | Type::FLOAT | Type::IFD => 4,
|
Type::LONG | Type::SLONG | Type::FLOAT | Type::IFD => 4,
|
||||||
Type::LONG8
|
Type::LONG8
|
||||||
| Type::SLONG8
|
| Type::SLONG8
|
||||||
| Type::DOUBLE
|
| Type::DOUBLE
|
||||||
| Type::RATIONAL
|
| Type::RATIONAL
|
||||||
| Type::SRATIONAL
|
| Type::SRATIONAL
|
||||||
| Type::IFD8 => 8,
|
| Type::IFD8 => 8,
|
||||||
Type::__NonExhaustive => unreachable!(),
|
Type::__NonExhaustive => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let value_bytes = match self.count.checked_mul(tag_size) {
|
||||||
|
Some(n) => n,
|
||||||
|
None => {
|
||||||
|
return Err(TiffError::LimitsExceeded);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Case 2: there is one value.
|
// Case 2: there is one value.
|
||||||
if self.count == 1 {
|
if self.count == 1 {
|
||||||
|
|||||||
+151
-87
@@ -1,4 +1,4 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::io::{self, Read, Seek};
|
use std::io::{self, Read, Seek};
|
||||||
use std::{cmp, ops::Range};
|
use std::{cmp, ops::Range};
|
||||||
@@ -251,7 +251,6 @@ struct TileAttributes {
|
|||||||
padding_right: usize,
|
padding_right: usize,
|
||||||
/// length of padding for bottommost tile in pixels
|
/// length of padding for bottommost tile in pixels
|
||||||
padding_down: usize,
|
padding_down: usize,
|
||||||
/// A simple buffer right paddding is read into
|
|
||||||
tile_samples: usize,
|
tile_samples: usize,
|
||||||
/// Sample count of one row of one tile
|
/// Sample count of one row of one tile
|
||||||
row_samples: usize,
|
row_samples: usize,
|
||||||
@@ -294,8 +293,6 @@ struct TileDecodeState {
|
|||||||
current_tile: usize,
|
current_tile: usize,
|
||||||
tile_offsets: Vec<u64>,
|
tile_offsets: Vec<u64>,
|
||||||
tile_bytes: Vec<u64>,
|
tile_bytes: Vec<u64>,
|
||||||
/// Buffer used for skipping horizontal padding
|
|
||||||
padding_buffer: Vec<u8>,
|
|
||||||
/// Pixel width of one row of the decoding result (tile / whole image)
|
/// Pixel width of one row of the decoding result (tile / whole image)
|
||||||
result_width: usize,
|
result_width: usize,
|
||||||
}
|
}
|
||||||
@@ -381,6 +378,7 @@ where
|
|||||||
strip_decoder: Option<StripDecodeState>,
|
strip_decoder: Option<StripDecodeState>,
|
||||||
tile_decoder: Option<TileDecodeState>,
|
tile_decoder: Option<TileDecodeState>,
|
||||||
tile_attributes: Option<TileAttributes>,
|
tile_attributes: Option<TileAttributes>,
|
||||||
|
seen_ifds: HashSet<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
trait Wrapping {
|
trait Wrapping {
|
||||||
@@ -547,6 +545,7 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
strip_decoder: None,
|
strip_decoder: None,
|
||||||
tile_decoder: None,
|
tile_decoder: None,
|
||||||
tile_attributes: None,
|
tile_attributes: None,
|
||||||
|
seen_ifds: HashSet::new(),
|
||||||
}
|
}
|
||||||
.init()
|
.init()
|
||||||
}
|
}
|
||||||
@@ -643,7 +642,7 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.next_ifd = match self.read_ifd_offset()? {
|
self.next_ifd = match self.read_ifd_offset_nonrepeating()? {
|
||||||
0 => None,
|
0 => None,
|
||||||
n => Some(n),
|
n => Some(n),
|
||||||
};
|
};
|
||||||
@@ -665,58 +664,66 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
self.width = self.get_tag_u32(Tag::ImageWidth)?;
|
self.width = self.get_tag_u32(Tag::ImageWidth)?;
|
||||||
self.height = self.get_tag_u32(Tag::ImageLength)?;
|
self.height = self.get_tag_u32(Tag::ImageLength)?;
|
||||||
self.strip_decoder = None;
|
self.strip_decoder = None;
|
||||||
|
self.tile_decoder = None;
|
||||||
|
self.tile_attributes = None;
|
||||||
|
|
||||||
self.photometric_interpretation = self
|
self.photometric_interpretation = self
|
||||||
.find_tag_unsigned(Tag::PhotometricInterpretation)?
|
.find_tag_unsigned(Tag::PhotometricInterpretation)?
|
||||||
.and_then(PhotometricInterpretation::from_u16)
|
.and_then(PhotometricInterpretation::from_u16)
|
||||||
.ok_or(TiffUnsupportedError::UnknownInterpretation)?;
|
.ok_or(TiffUnsupportedError::UnknownInterpretation)?;
|
||||||
|
|
||||||
if let Some(val) = self.find_tag_unsigned(Tag::Compression)? {
|
// Try to parse both the compression method and the number, format, and bits of the included samples.
|
||||||
self.compression_method = CompressionMethod::from_u16(val)
|
// If they are not explicitly specified, those tags are reset to their default values and not carried from previous images.
|
||||||
.ok_or(TiffUnsupportedError::UnknownCompressionMethod)?;
|
self.compression_method = match self.find_tag_unsigned(Tag::Compression)? {
|
||||||
}
|
Some(val) => CompressionMethod::from_u16(val)
|
||||||
if let Some(val) = self.find_tag_unsigned(Tag::SamplesPerPixel)? {
|
.ok_or(TiffUnsupportedError::UnknownCompressionMethod)?,
|
||||||
self.samples = val;
|
None => CompressionMethod::None,
|
||||||
}
|
};
|
||||||
if let Some(vals) = self.find_tag_unsigned_vec(Tag::SampleFormat)? {
|
|
||||||
self.sample_format = vals
|
|
||||||
.into_iter()
|
|
||||||
.map(SampleFormat::from_u16_exhaustive)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// TODO: for now, only homogenous formats across samples are supported.
|
self.samples = self.find_tag_unsigned(Tag::SamplesPerPixel)?.unwrap_or(1);
|
||||||
if !self.sample_format.windows(2).all(|s| s[0] == s[1]) {
|
|
||||||
return Err(TiffUnsupportedError::UnsupportedSampleFormat(
|
self.sample_format = match self.find_tag_unsigned_vec(Tag::SampleFormat)? {
|
||||||
self.sample_format.clone(),
|
Some(vals) => {
|
||||||
)
|
let sample_format: Vec<_> = vals
|
||||||
.into());
|
.into_iter()
|
||||||
}
|
.map(SampleFormat::from_u16_exhaustive)
|
||||||
}
|
.collect();
|
||||||
match self.samples {
|
|
||||||
1 | 3 | 4 => {
|
// TODO: for now, only homogenous formats across samples are supported.
|
||||||
if let Some(val) = self.find_tag_unsigned_vec(Tag::BitsPerSample)? {
|
if !sample_format.windows(2).all(|s| s[0] == s[1]) {
|
||||||
self.bits_per_sample = val;
|
return Err(TiffUnsupportedError::UnsupportedSampleFormat(
|
||||||
|
self.sample_format.clone(),
|
||||||
|
)
|
||||||
|
.into());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
_ => return Err(TiffUnsupportedError::UnsupportedSampleDepth(self.samples).into()),
|
|
||||||
}
|
|
||||||
|
|
||||||
self.chunk_type =
|
sample_format
|
||||||
match (
|
}
|
||||||
self.get_tag_u32(Tag::RowsPerStrip),
|
None => vec![SampleFormat::Uint],
|
||||||
self.get_tag_u32(Tag::TileWidth),
|
};
|
||||||
self.get_tag_u32(Tag::TileLength),
|
|
||||||
) {
|
self.bits_per_sample = match self.samples {
|
||||||
(Ok(_), Err(_), Err(_)) => ChunkType::Strip,
|
1 | 3 | 4 => self
|
||||||
(Err(_), Ok(_), Ok(_)) => ChunkType::Tile,
|
.find_tag_unsigned_vec(Tag::BitsPerSample)?
|
||||||
// TODO: The spec says not to use both strip-oriented fields and tile-oriented fields.
|
.unwrap_or_else(|| vec![1]),
|
||||||
// We can relax this later if it becomes a problem
|
_ => return Err(TiffUnsupportedError::UnsupportedSampleDepth(self.samples).into()),
|
||||||
_ => return Err(TiffError::FormatError(TiffFormatError::Format(
|
};
|
||||||
String::from(
|
|
||||||
"Neither strips nor tiles were found or both were used in the same file",
|
let ifd = self.ifd.as_ref().unwrap();
|
||||||
),
|
self.chunk_type = match (
|
||||||
))),
|
ifd.contains_key(&Tag::StripByteCounts),
|
||||||
};
|
ifd.contains_key(&Tag::StripOffsets),
|
||||||
|
ifd.contains_key(&Tag::TileByteCounts),
|
||||||
|
ifd.contains_key(&Tag::TileOffsets),
|
||||||
|
) {
|
||||||
|
(true, true, false, false) => ChunkType::Strip,
|
||||||
|
(false, false, true, true) => ChunkType::Tile,
|
||||||
|
(_, _, _, _) => {
|
||||||
|
return Err(TiffError::FormatError(
|
||||||
|
TiffFormatError::StripTileTagConflict,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -731,6 +738,15 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
self.byte_order
|
self.byte_order
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_ifd_offset_nonrepeating(&mut self) -> TiffResult<u64> {
|
||||||
|
let result = self.read_ifd_offset()?;
|
||||||
|
if self.seen_ifds.insert(result) {
|
||||||
|
Ok(result)
|
||||||
|
} else {
|
||||||
|
Err(TiffError::FormatError(TiffFormatError::CycleInOffsets))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn read_ifd_offset(&mut self) -> Result<u64, io::Error> {
|
pub fn read_ifd_offset(&mut self) -> Result<u64, io::Error> {
|
||||||
if self.bigtiff {
|
if self.bigtiff {
|
||||||
@@ -889,7 +905,7 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
};
|
};
|
||||||
dir.insert(tag, entry);
|
dir.insert(tag, entry);
|
||||||
}
|
}
|
||||||
self.next_ifd = match self.read_ifd_offset()? {
|
self.next_ifd = match self.read_ifd_offset_nonrepeating()? {
|
||||||
0 => None,
|
0 => None,
|
||||||
n => Some(n),
|
n => Some(n),
|
||||||
};
|
};
|
||||||
@@ -1119,6 +1135,7 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
length,
|
length,
|
||||||
buffer.len(),
|
buffer.len(),
|
||||||
buffer.byte_len(),
|
buffer.byte_len(),
|
||||||
|
self.limits.intermediate_buffer_size,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Read into output buffer.
|
// Read into output buffer.
|
||||||
@@ -1176,7 +1193,6 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
|
|
||||||
let tile_decoder = self.tile_decoder.as_mut().unwrap();
|
let tile_decoder = self.tile_decoder.as_mut().unwrap();
|
||||||
let line_samples = tile_decoder.result_width * self.bits_per_sample.len();
|
let line_samples = tile_decoder.result_width * self.bits_per_sample.len();
|
||||||
let padding_buffer = &mut tile_decoder.padding_buffer;
|
|
||||||
|
|
||||||
let mut reader = Self::create_reader(
|
let mut reader = Self::create_reader(
|
||||||
&mut self.reader,
|
&mut self.reader,
|
||||||
@@ -1184,6 +1200,7 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
compressed_length,
|
compressed_length,
|
||||||
tile_samples,
|
tile_samples,
|
||||||
byte_len,
|
byte_len,
|
||||||
|
self.limits.intermediate_buffer_size,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
for row in 0..(tile_length - padding_down) {
|
for row in 0..(tile_length - padding_down) {
|
||||||
@@ -1205,10 +1222,11 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
|
|
||||||
let row = &mut buf[(row_start * byte_len)..(row_end * byte_len)];
|
let row = &mut buf[(row_start * byte_len)..(row_end * byte_len)];
|
||||||
reader.read_exact(row)?;
|
reader.read_exact(row)?;
|
||||||
|
|
||||||
// Skip horizontal padding
|
// Skip horizontal padding
|
||||||
// TODO: find a better way of skipping the padding
|
|
||||||
if padding_right > 0 {
|
if padding_right > 0 {
|
||||||
reader.read_exact(padding_buffer)?
|
let len = u64::try_from(padding_right_samples * byte_len)?;
|
||||||
|
io::copy(&mut reader.by_ref().take(len), &mut io::sink())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::fix_endianness(&mut buffer.subrange(row_start..row_end), self.byte_order);
|
Self::fix_endianness(&mut buffer.subrange(row_start..row_end), self.byte_order);
|
||||||
@@ -1227,17 +1245,20 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
compressed_length: u64,
|
compressed_length: u64,
|
||||||
samples: usize, // Expected chunk length in samples
|
samples: usize, // Expected chunk length in samples
|
||||||
byte_len: usize, // Byte length of the samples in result buffer
|
byte_len: usize, // Byte length of the samples in result buffer
|
||||||
|
intermediate_buffer_size: usize,
|
||||||
) -> TiffResult<Box<dyn Read + 'r>> {
|
) -> TiffResult<Box<dyn Read + 'r>> {
|
||||||
Ok(match compression_method {
|
Ok(match compression_method {
|
||||||
CompressionMethod::None => Box::new(reader),
|
CompressionMethod::None => Box::new(reader),
|
||||||
CompressionMethod::LZW => Box::new(
|
CompressionMethod::LZW => {
|
||||||
LZWReader::new(
|
let clen = usize::try_from(compressed_length)?;
|
||||||
reader,
|
|
||||||
usize::try_from(compressed_length)?,
|
if samples * byte_len > intermediate_buffer_size || clen > intermediate_buffer_size
|
||||||
samples * byte_len,
|
{
|
||||||
)?
|
return Err(TiffError::LimitsExceeded);
|
||||||
.1,
|
}
|
||||||
),
|
|
||||||
|
Box::new(LZWReader::new(reader, clen, samples * byte_len)?.1)
|
||||||
|
}
|
||||||
CompressionMethod::PackBits => {
|
CompressionMethod::PackBits => {
|
||||||
Box::new(PackBitsReader::new(reader, usize::try_from(compressed_length)?)?.1)
|
Box::new(PackBitsReader::new(reader, usize::try_from(compressed_length)?)?.1)
|
||||||
}
|
}
|
||||||
@@ -1277,7 +1298,13 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
return Ok(0);
|
return Ok(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((self.height + rows_per_strip - 1) / rows_per_strip)
|
// rows_per_strip - 1 can never fail since we know it's at least 1
|
||||||
|
let height = match self.height.checked_add(rows_per_strip - 1) {
|
||||||
|
Some(h) => h,
|
||||||
|
None => return Err(TiffError::IntSizeError),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(height / rows_per_strip)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Number of tiles in image
|
/// Number of tiles in image
|
||||||
@@ -1309,6 +1336,14 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
let tile_width = usize::try_from(self.get_tag_u32(Tag::TileWidth)?)?;
|
let tile_width = usize::try_from(self.get_tag_u32(Tag::TileWidth)?)?;
|
||||||
let tile_length = usize::try_from(self.get_tag_u32(Tag::TileLength)?)?;
|
let tile_length = usize::try_from(self.get_tag_u32(Tag::TileLength)?)?;
|
||||||
|
|
||||||
|
if tile_width == 0 {
|
||||||
|
return Err(TiffFormatError::InvalidTagValueType(Tag::TileWidth).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if tile_length == 0 {
|
||||||
|
return Err(TiffFormatError::InvalidTagValueType(Tag::TileLength).into());
|
||||||
|
}
|
||||||
|
|
||||||
let tiles_across = (usize::try_from(self.width)? + tile_width - 1) / tile_width;
|
let tiles_across = (usize::try_from(self.width)? + tile_width - 1) / tile_width;
|
||||||
let tiles_down = (usize::try_from(self.height)? + tile_length - 1) / tile_length;
|
let tiles_down = (usize::try_from(self.height)? + tile_length - 1) / tile_length;
|
||||||
|
|
||||||
@@ -1335,27 +1370,12 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_tile_decoder(
|
fn update_tile_decoder(&mut self, result_width: usize) -> TiffResult<()> {
|
||||||
&mut self,
|
|
||||||
result_width: usize,
|
|
||||||
buffer_byte_len: usize,
|
|
||||||
) -> TiffResult<()> {
|
|
||||||
let samples_per_pixel = self.bits_per_sample.len();
|
|
||||||
|
|
||||||
if self.tile_decoder.is_none() {
|
if self.tile_decoder.is_none() {
|
||||||
let tile_attrs = self.tile_attributes.as_ref().unwrap();
|
|
||||||
|
|
||||||
let padding_buffer_size =
|
|
||||||
tile_attrs.padding_right * samples_per_pixel * buffer_byte_len;
|
|
||||||
if padding_buffer_size > self.limits.intermediate_buffer_size {
|
|
||||||
return Err(TiffError::LimitsExceeded);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.tile_decoder = Some(TileDecodeState {
|
self.tile_decoder = Some(TileDecodeState {
|
||||||
current_tile: 0,
|
current_tile: 0,
|
||||||
tile_offsets: self.get_tag_u64_vec(Tag::TileOffsets)?,
|
tile_offsets: self.get_tag_u64_vec(Tag::TileOffsets)?,
|
||||||
tile_bytes: self.get_tag_u64_vec(Tag::TileByteCounts)?,
|
tile_bytes: self.get_tag_u64_vec(Tag::TileByteCounts)?,
|
||||||
padding_buffer: vec![0; padding_buffer_size],
|
|
||||||
result_width: 0, // needs to be updated for differently padded_tiles, see below
|
result_width: 0, // needs to be updated for differently padded_tiles, see below
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1371,7 +1391,17 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
|
|
||||||
let jpeg_tables: Option<Vec<u8>> = match self.find_tag(Tag::JPEGTables) {
|
let jpeg_tables: Option<Vec<u8>> = match self.find_tag(Tag::JPEGTables) {
|
||||||
Ok(None) => None,
|
Ok(None) => None,
|
||||||
Ok(_) => Some(self.get_tag_u8_vec(Tag::JPEGTables)?),
|
Ok(_) => {
|
||||||
|
let vec = self.get_tag_u8_vec(Tag::JPEGTables)?;
|
||||||
|
|
||||||
|
if vec.len() < 2 {
|
||||||
|
return Err(TiffError::FormatError(
|
||||||
|
TiffFormatError::InvalidTagValueType(Tag::JPEGTables),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(vec)
|
||||||
|
}
|
||||||
Err(e) => return Err(e),
|
Err(e) => return Err(e),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1386,11 +1416,26 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if offsets[0] as usize > self.limits.intermediate_buffer_size
|
||||||
|
|| bytes
|
||||||
|
.iter()
|
||||||
|
.any(|&x| x as usize > self.limits.intermediate_buffer_size)
|
||||||
|
{
|
||||||
|
return Err(TiffError::LimitsExceeded);
|
||||||
|
}
|
||||||
|
|
||||||
let mut res_img = Vec::with_capacity(offsets[0] as usize);
|
let mut res_img = Vec::with_capacity(offsets[0] as usize);
|
||||||
|
|
||||||
for (idx, offset) in offsets.iter().enumerate() {
|
for (idx, offset) in offsets.iter().enumerate() {
|
||||||
self.goto_offset(*offset)?;
|
self.goto_offset(*offset)?;
|
||||||
let jpeg_reader = JpegReader::new(&mut self.reader, bytes[idx], &jpeg_tables)?;
|
let length = bytes[idx];
|
||||||
|
|
||||||
|
if jpeg_tables.is_some() && length < 2 {
|
||||||
|
return Err(TiffError::FormatError(
|
||||||
|
TiffFormatError::InvalidTagValueType(Tag::JPEGTables),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let jpeg_reader = JpegReader::new(&mut self.reader, length, &jpeg_tables)?;
|
||||||
let mut decoder = jpeg::Decoder::new(jpeg_reader);
|
let mut decoder = jpeg::Decoder::new(jpeg_reader);
|
||||||
|
|
||||||
match decoder.decode() {
|
match decoder.decode() {
|
||||||
@@ -1442,10 +1487,18 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
let sized_width = usize::try_from(self.width)?;
|
let sized_width = usize::try_from(self.width)?;
|
||||||
let sized_height = usize::try_from(self.height)?;
|
let sized_height = usize::try_from(self.height)?;
|
||||||
|
|
||||||
// Ignore potential vertical padding on the bottommost strip
|
let strip_height_without_padding = index
|
||||||
let strip_height = cmp::min(rows_per_strip, sized_height - index * rows_per_strip);
|
.checked_mul(rows_per_strip)
|
||||||
|
.and_then(|x| sized_height.checked_sub(x))
|
||||||
|
.ok_or(TiffError::IntSizeError)?;
|
||||||
|
|
||||||
let buffer_size = sized_width * strip_height * self.bits_per_sample.len();
|
// Ignore potential vertical padding on the bottommost strip
|
||||||
|
let strip_height = rows_per_strip.min(strip_height_without_padding);
|
||||||
|
|
||||||
|
let buffer_size = sized_width
|
||||||
|
.checked_mul(strip_height)
|
||||||
|
.and_then(|x| x.checked_mul(self.bits_per_sample.len()))
|
||||||
|
.ok_or(TiffError::LimitsExceeded)?;
|
||||||
|
|
||||||
if buffer.len() < buffer_size {
|
if buffer.len() < buffer_size {
|
||||||
return Err(TiffError::FormatError(
|
return Err(TiffError::FormatError(
|
||||||
@@ -1536,7 +1589,13 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn result_buffer(&self, width: usize, height: usize) -> TiffResult<DecodingResult> {
|
fn result_buffer(&self, width: usize, height: usize) -> TiffResult<DecodingResult> {
|
||||||
let buffer_size = width * height * self.bits_per_sample.len();
|
let buffer_size = match width
|
||||||
|
.checked_mul(height)
|
||||||
|
.and_then(|x| x.checked_mul(self.bits_per_sample.len()))
|
||||||
|
{
|
||||||
|
Some(s) => s,
|
||||||
|
None => return Err(TiffError::LimitsExceeded),
|
||||||
|
};
|
||||||
|
|
||||||
let max_sample_bits = self.bits_per_sample.iter().cloned().max().unwrap_or(8);
|
let max_sample_bits = self.bits_per_sample.iter().cloned().max().unwrap_or(8);
|
||||||
match self.sample_format.first().unwrap_or(&SampleFormat::Uint) {
|
match self.sample_format.first().unwrap_or(&SampleFormat::Uint) {
|
||||||
@@ -1605,7 +1664,7 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
let tile_length = tile_attrs.tile_length - padding_down;
|
let tile_length = tile_attrs.tile_length - padding_down;
|
||||||
|
|
||||||
let mut result = self.result_buffer(tile_width, tile_length)?;
|
let mut result = self.result_buffer(tile_width, tile_length)?;
|
||||||
self.update_tile_decoder(tile_width, result.as_buffer(0).byte_len())?;
|
self.update_tile_decoder(tile_width)?;
|
||||||
|
|
||||||
self.read_tile_to_buffer(&mut result.as_buffer(0), tile)?;
|
self.read_tile_to_buffer(&mut result.as_buffer(0), tile)?;
|
||||||
|
|
||||||
@@ -1619,7 +1678,7 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
let mut result = self.result_buffer(width, usize::try_from(self.height)?)?;
|
let mut result = self.result_buffer(width, usize::try_from(self.height)?)?;
|
||||||
|
|
||||||
self.init_tile_attributes()?;
|
self.init_tile_attributes()?;
|
||||||
self.update_tile_decoder(width, result.as_buffer(0).byte_len())?;
|
self.update_tile_decoder(width)?;
|
||||||
|
|
||||||
let tile_attrs = self.tile_attributes.as_ref().unwrap();
|
let tile_attrs = self.tile_attributes.as_ref().unwrap();
|
||||||
let tiles_across = tile_attrs.tiles_across;
|
let tiles_across = tile_attrs.tiles_across;
|
||||||
@@ -1638,8 +1697,13 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
let rows_per_strip =
|
let rows_per_strip =
|
||||||
usize::try_from(self.get_tag_u32(Tag::RowsPerStrip).unwrap_or(self.height))?;
|
usize::try_from(self.get_tag_u32(Tag::RowsPerStrip).unwrap_or(self.height))?;
|
||||||
|
|
||||||
let samples_per_strip =
|
let samples_per_strip = match usize::try_from(self.width)?
|
||||||
usize::try_from(self.width)? * rows_per_strip * self.bits_per_sample.len();
|
.checked_mul(rows_per_strip)
|
||||||
|
.and_then(|x| x.checked_mul(self.bits_per_sample.len()))
|
||||||
|
{
|
||||||
|
Some(s) => s,
|
||||||
|
None => return Err(TiffError::LimitsExceeded),
|
||||||
|
};
|
||||||
|
|
||||||
let mut result =
|
let mut result =
|
||||||
self.result_buffer(usize::try_from(self.width)?, usize::try_from(self.height)?)?;
|
self.result_buffer(usize::try_from(self.width)?, usize::try_from(self.height)?)?;
|
||||||
|
|||||||
@@ -225,6 +225,18 @@ impl JpegReader {
|
|||||||
|
|
||||||
match jpeg_tables {
|
match jpeg_tables {
|
||||||
Some(tables) => {
|
Some(tables) => {
|
||||||
|
assert!(
|
||||||
|
tables.len() >= 2,
|
||||||
|
"jpeg_tables, if given, must be at least 2 bytes long. Got {:?}",
|
||||||
|
tables
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
length >= 2,
|
||||||
|
"if jpeg_tables is given, length must be at least 2 bytes long, got {}",
|
||||||
|
length
|
||||||
|
);
|
||||||
|
|
||||||
let mut jpeg_data = tables.clone();
|
let mut jpeg_data = tables.clone();
|
||||||
let truncated_length = jpeg_data.len() - 2;
|
let truncated_length = jpeg_data.len() - 2;
|
||||||
jpeg_data.truncate(truncated_length);
|
jpeg_data.truncate(truncated_length);
|
||||||
|
|||||||
@@ -20,6 +20,14 @@ impl ColorType for Gray8 {
|
|||||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct GrayI8;
|
||||||
|
impl ColorType for GrayI8 {
|
||||||
|
type Inner = i8;
|
||||||
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||||
|
const BITS_PER_SAMPLE: &'static [u16] = &[8];
|
||||||
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Gray16;
|
pub struct Gray16;
|
||||||
impl ColorType for Gray16 {
|
impl ColorType for Gray16 {
|
||||||
type Inner = u16;
|
type Inner = u16;
|
||||||
@@ -28,6 +36,14 @@ impl ColorType for Gray16 {
|
|||||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct GrayI16;
|
||||||
|
impl ColorType for GrayI16 {
|
||||||
|
type Inner = i16;
|
||||||
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||||
|
const BITS_PER_SAMPLE: &'static [u16] = &[16];
|
||||||
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Gray32;
|
pub struct Gray32;
|
||||||
impl ColorType for Gray32 {
|
impl ColorType for Gray32 {
|
||||||
type Inner = u32;
|
type Inner = u32;
|
||||||
@@ -36,6 +52,14 @@ impl ColorType for Gray32 {
|
|||||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct GrayI32;
|
||||||
|
impl ColorType for GrayI32 {
|
||||||
|
type Inner = i32;
|
||||||
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||||
|
const BITS_PER_SAMPLE: &'static [u16] = &[32];
|
||||||
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Gray32Float;
|
pub struct Gray32Float;
|
||||||
impl ColorType for Gray32Float {
|
impl ColorType for Gray32Float {
|
||||||
type Inner = f32;
|
type Inner = f32;
|
||||||
@@ -52,6 +76,14 @@ impl ColorType for Gray64 {
|
|||||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct GrayI64;
|
||||||
|
impl ColorType for GrayI64 {
|
||||||
|
type Inner = i64;
|
||||||
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||||
|
const BITS_PER_SAMPLE: &'static [u16] = &[64];
|
||||||
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Gray64Float;
|
pub struct Gray64Float;
|
||||||
impl ColorType for Gray64Float {
|
impl ColorType for Gray64Float {
|
||||||
type Inner = f64;
|
type Inner = f64;
|
||||||
|
|||||||
@@ -63,6 +63,8 @@ pub enum TiffFormatError {
|
|||||||
SignedIntegerExpected(Value),
|
SignedIntegerExpected(Value),
|
||||||
Format(String),
|
Format(String),
|
||||||
RequiredTagEmpty(Tag),
|
RequiredTagEmpty(Tag),
|
||||||
|
StripTileTagConflict,
|
||||||
|
CycleInOffsets,
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
/// Do not match against this variant. It may get removed.
|
/// Do not match against this variant. It may get removed.
|
||||||
__NonExhaustive,
|
__NonExhaustive,
|
||||||
@@ -113,6 +115,8 @@ impl fmt::Display for TiffFormatError {
|
|||||||
}
|
}
|
||||||
Format(ref val) => write!(fmt, "Invalid format: {:?}.", val),
|
Format(ref val) => write!(fmt, "Invalid format: {:?}.", val),
|
||||||
RequiredTagEmpty(ref val) => write!(fmt, "Required tag {:?} was empty.", val),
|
RequiredTagEmpty(ref val) => write!(fmt, "Required tag {:?} was empty.", val),
|
||||||
|
StripTileTagConflict => write!(fmt, "File should contain either (StripByteCounts and StripOffsets) or (TileByteCounts and TileOffsets), other combination was found."),
|
||||||
|
CycleInOffsets => write!(fmt, "File contained a cycle in the list of IFDs"),
|
||||||
__NonExhaustive => unreachable!(),
|
__NonExhaustive => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -284,3 +284,242 @@ fn test_tiled_incremental() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_div_zero() {
|
||||||
|
use tiff::{TiffError, TiffFormatError};
|
||||||
|
|
||||||
|
let image = [
|
||||||
|
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 40, 1, 0, 0,
|
||||||
|
0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 1, 0, 0, 39, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 3, 0, 1, 0, 0, 0, 158, 0, 0, 251, 67, 1, 3, 0,
|
||||||
|
1, 0, 0, 0, 40, 0, 0, 0, 66, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 178, 178, 178, 178,
|
||||||
|
178, 178, 178,
|
||||||
|
];
|
||||||
|
|
||||||
|
let err = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||||
|
|
||||||
|
match err {
|
||||||
|
TiffError::FormatError(TiffFormatError::StripTileTagConflict) => {}
|
||||||
|
unexpected => panic!("Unexpected error {}", unexpected),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_too_many_value_bytes() {
|
||||||
|
let image = [
|
||||||
|
73, 73, 43, 0, 8, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 8, 0, 0, 0,
|
||||||
|
23, 0, 12, 0, 0, 65, 4, 0, 1, 6, 0, 0, 1, 16, 0, 1, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, 59, 73, 84, 186, 202, 83, 240, 66, 1, 53, 22, 56, 47,
|
||||||
|
0, 0, 0, 0, 0, 0, 1, 222, 4, 0, 58, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 4, 0, 0, 100, 0,
|
||||||
|
0, 89, 89, 89, 89, 89, 89, 89, 89, 96, 1, 20, 89, 89, 89, 89, 18,
|
||||||
|
];
|
||||||
|
|
||||||
|
let error = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||||
|
|
||||||
|
match error {
|
||||||
|
tiff::TiffError::LimitsExceeded => {}
|
||||||
|
unexpected => panic!("Unexpected error {}", unexpected),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bad_strip_count() {
|
||||||
|
let image = [
|
||||||
|
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 100, 0, 0, 0, 1, 1, 4, 0, 1, 0, 0,
|
||||||
|
0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 0, 0, 0, 0, 0, 246, 16, 0, 0, 22, 1, 4, 0, 1,
|
||||||
|
0, 0, 0, 40, 0, 251, 255, 23, 1, 4, 0, 1, 0, 0, 0, 48, 178, 178, 178, 178, 178, 178, 178,
|
||||||
|
178, 178, 178,
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap();
|
||||||
|
|
||||||
|
let err = decoder.read_image().unwrap_err();
|
||||||
|
|
||||||
|
match err {
|
||||||
|
tiff::TiffError::IntSizeError => {}
|
||||||
|
unexpected => panic!("Unexpected error {}", unexpected),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_too_big_buffer_size() {
|
||||||
|
let image = [
|
||||||
|
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 99, 255, 255, 254, 1, 1, 4, 0, 1,
|
||||||
|
0, 0, 0, 158, 0, 0, 251, 3, 1, 3, 255, 254, 255, 255, 0, 1, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 17, 1, 4, 0, 9, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 0, 2, 0, 0, 0, 63, 0, 0, 0,
|
||||||
|
22, 1, 4, 0, 1, 0, 0, 0, 44, 0, 0, 0, 23, 1, 4, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 1, 0, 178,
|
||||||
|
178,
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap();
|
||||||
|
|
||||||
|
let err = decoder.read_image().unwrap_err();
|
||||||
|
|
||||||
|
match err {
|
||||||
|
tiff::TiffError::LimitsExceeded => {}
|
||||||
|
unexpected => panic!("Unexpected error {}", unexpected),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stripped_image_overflow() {
|
||||||
|
let image = [
|
||||||
|
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 100, 0, 0, 148, 1, 1, 4, 0, 1, 0,
|
||||||
|
0, 0, 158, 0, 0, 251, 3, 1, 3, 255, 254, 255, 255, 0, 1, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 0, 2, 0, 0, 0, 63, 0, 0, 0, 22,
|
||||||
|
1, 4, 0, 1, 0, 0, 0, 44, 0, 248, 255, 23, 1, 4, 0, 1, 0, 0, 0, 178, 178, 178, 0, 1, 178,
|
||||||
|
178, 178,
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap();
|
||||||
|
|
||||||
|
let err = decoder.read_image().unwrap_err();
|
||||||
|
|
||||||
|
match err {
|
||||||
|
tiff::TiffError::LimitsExceeded => {}
|
||||||
|
unexpected => panic!("Unexpected error {}", unexpected),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn oom() {
|
||||||
|
let image = [
|
||||||
|
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 40, 1, 0, 0,
|
||||||
|
0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 7, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 2, 0, 0, 0,
|
||||||
|
17, 1, 4, 0, 1, 0, 0, 0, 3, 77, 0, 0, 1, 1, 3, 0, 1, 0, 0, 0, 3, 128, 0, 0, 22, 1, 4, 0, 1,
|
||||||
|
0, 0, 0, 40, 0, 0, 0, 23, 1, 4, 0, 1, 0, 0, 0, 178, 48, 178, 178, 178, 178, 162, 178,
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap();
|
||||||
|
|
||||||
|
let err = decoder.read_image().unwrap_err();
|
||||||
|
|
||||||
|
match err {
|
||||||
|
tiff::TiffError::LimitsExceeded => {}
|
||||||
|
unexpected => panic!("Unexpected error {}", unexpected),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn oom_2() {
|
||||||
|
let image = [
|
||||||
|
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 40, 1, 0, 0,
|
||||||
|
0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 5, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 3, 0, 1, 0, 0, 0, 3, 128, 0, 0, 22, 1, 4, 0, 1,
|
||||||
|
0, 0, 0, 40, 0, 0, 0, 23, 1, 4, 0, 1, 0, 0, 0, 48, 178, 178, 178, 0, 1, 0, 13, 13,
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap();
|
||||||
|
|
||||||
|
let err = decoder.read_image().unwrap_err();
|
||||||
|
|
||||||
|
match err {
|
||||||
|
tiff::TiffError::LimitsExceeded => {}
|
||||||
|
unexpected => panic!("Unexpected error {}", unexpected),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_jpeg_tag() {
|
||||||
|
use tiff::tags;
|
||||||
|
use tiff::{TiffError, TiffFormatError};
|
||||||
|
|
||||||
|
let image = [
|
||||||
|
73, 73, 42, 0, 8, 0, 0, 0, 15, 0, 0, 254, 44, 1, 0, 0, 0, 0, 0, 32, 0, 0, 0, 1, 4, 0, 1, 0,
|
||||||
|
0, 0, 0, 1, 0, 0, 91, 1, 1, 0, 0, 0, 0, 0, 242, 4, 0, 0, 0, 22, 0, 56, 77, 0, 77, 1, 0, 0,
|
||||||
|
73, 42, 0, 1, 4, 0, 1, 0, 0, 0, 4, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 158, 0, 0, 251, 3, 1,
|
||||||
|
3, 0, 1, 0, 0, 0, 7, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 2, 0, 0, 0, 17, 1, 4, 0, 1, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 1, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 4, 61, 1, 18, 0, 1, 0, 0, 0, 202, 0, 0, 0, 17,
|
||||||
|
1, 100, 0, 129, 0, 0, 0, 0, 0, 0, 0, 232, 254, 252, 255, 254, 255, 255, 255, 1, 29, 0, 0,
|
||||||
|
22, 1, 3, 0, 1, 0, 0, 0, 16, 0, 0, 0, 23, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 123, 73, 254, 0,
|
||||||
|
73,
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap();
|
||||||
|
|
||||||
|
let err = decoder.read_image().unwrap_err();
|
||||||
|
|
||||||
|
match err {
|
||||||
|
TiffError::FormatError(TiffFormatError::InvalidTagValueType(tags::Tag::JPEGTables)) => {}
|
||||||
|
unexpected => panic!("Unexpected error {}", unexpected),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_jpeg_tag_2() {
|
||||||
|
use tiff::tags;
|
||||||
|
use tiff::{TiffError, TiffFormatError};
|
||||||
|
|
||||||
|
let image = [
|
||||||
|
73, 73, 42, 0, 8, 0, 0, 0, 16, 0, 254, 0, 4, 0, 1, 0, 0, 0, 0, 0, 0, 242, 0, 1, 4, 0, 1, 0,
|
||||||
|
0, 0, 0, 129, 16, 0, 1, 1, 4, 0, 1, 0, 0, 0, 214, 0, 0, 248, 253, 1, 3, 0, 1, 0, 0, 0, 64,
|
||||||
|
0, 0, 0, 3, 1, 3, 0, 1, 0, 0, 0, 7, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 1, 0, 0, 64, 14, 1, 0,
|
||||||
|
2, 0, 0, 148, 0, 206, 0, 0, 0, 17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
|
||||||
|
1, 0, 0, 0, 22, 1, 4, 0, 17, 0, 0, 201, 1, 0, 0, 0, 23, 1, 2, 0, 20, 0, 0, 0, 194, 0, 0, 0,
|
||||||
|
91, 1, 7, 0, 5, 0, 0, 0, 64, 0, 0, 0, 237, 254, 65, 255, 255, 255, 255, 255, 1, 0, 0, 0,
|
||||||
|
22, 1, 4, 0, 1, 0, 0, 0, 42, 0, 0, 0, 23, 1, 255, 255, 255, 255, 255, 36, 36, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 36, 73, 73, 0, 42, 36, 36, 36, 36, 0, 0, 8, 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap();
|
||||||
|
|
||||||
|
let err = decoder.read_image().unwrap_err();
|
||||||
|
|
||||||
|
match err {
|
||||||
|
TiffError::FormatError(TiffFormatError::InvalidTagValueType(tags::Tag::JPEGTables)) => {}
|
||||||
|
unexpected => panic!("Unexpected error {}", unexpected),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multiple_images_strip_height() {
|
||||||
|
use tiff::TiffError;
|
||||||
|
|
||||||
|
let image = [
|
||||||
|
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 2, 0, 0, 0, 61, 1, 9, 0, 46, 22,
|
||||||
|
128, 0, 0, 0, 0, 1, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 17, 1, 4, 0, 27, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 1, 1, 3, 0, 1, 0, 0, 0, 17, 1, 0, 231, 22, 1, 1, 0, 1, 0, 0, 0, 130, 0, 0, 0, 23, 1, 4,
|
||||||
|
0, 14, 0, 0, 0, 0, 0, 0, 0, 133, 133, 133, 77, 77, 77, 0, 0, 22, 128, 0, 255, 255, 255,
|
||||||
|
255, 255,
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap();
|
||||||
|
|
||||||
|
let _ = decoder.read_image().unwrap();
|
||||||
|
let err = decoder.read_image().unwrap_err();
|
||||||
|
|
||||||
|
match err {
|
||||||
|
TiffError::IntSizeError => {}
|
||||||
|
unexpected => panic!("Unexpected error {}", unexpected),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn timeout() {
|
||||||
|
use tiff::{TiffError, TiffFormatError};
|
||||||
|
|
||||||
|
let image = [
|
||||||
|
73, 73, 42, 0, 8, 0, 0, 0, 16, 0, 254, 0, 4, 0, 1, 68, 0, 0, 0, 2, 0, 32, 254, 252, 0, 109,
|
||||||
|
0, 129, 0, 0, 0, 32, 0, 58, 0, 1, 4, 0, 1, 0, 6, 0, 0, 0, 8, 0, 0, 1, 73, 73, 42, 0, 8, 0,
|
||||||
|
0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 21, 0, 0, 0, 61, 1, 255, 128, 9, 0, 0, 8, 0, 1, 113, 2,
|
||||||
|
3, 1, 3, 0, 1, 0, 0, 0, 5, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 112, 0, 0, 36, 0, 0,
|
||||||
|
0, 112, 56, 200, 0, 5, 0, 0, 64, 0, 0, 1, 0, 4, 0, 0, 0, 2, 0, 6, 1, 3, 0, 1, 0, 0, 0, 0,
|
||||||
|
0, 0, 4, 17, 1, 1, 0, 93, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 3, 6, 0, 231, 22, 1,
|
||||||
|
1, 0, 1, 0, 0, 0, 2, 64, 118, 36, 23, 1, 1, 0, 43, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 4, 0, 8,
|
||||||
|
0, 0, 73, 73, 42, 0, 8, 0, 0, 0, 0, 0, 32,
|
||||||
|
];
|
||||||
|
|
||||||
|
let error = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||||
|
|
||||||
|
match error {
|
||||||
|
TiffError::FormatError(TiffFormatError::CycleInOffsets) => {}
|
||||||
|
e => panic!("Unexpected error {:?}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_no_rows_per_strip() {
|
||||||
|
test_image_sum_u8("no_rows_per_strip.tiff", ColorType::RGB(8), 99448840);
|
||||||
|
}
|
||||||
Binary file not shown.
Reference in new issue
Block a user