Coverage Report

Created: 2026-06-18 07:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/image/src/io/limits.rs
Line
Count
Source
1
use crate::{error, ColorType, ImageError, ImageResult};
2
3
/// Set of supported strict limits for a decoder.
4
#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)]
5
#[allow(missing_copy_implementations)]
6
#[non_exhaustive]
7
pub struct LimitSupport {}
8
9
/// Resource limits for decoding.
10
///
11
/// Limits can be either *strict* or *non-strict*. Non-strict limits are best-effort
12
/// limits where the library does not guarantee that limit will not be exceeded. Do note
13
/// that it is still considered a bug if a non-strict limit is exceeded.
14
/// Some of the underlying decoders do not support such limits, so one cannot
15
/// rely on these limits being supported. For strict limits, the library makes a stronger
16
/// guarantee that the limit will not be exceeded. Exceeding a strict limit is considered
17
/// a critical bug. If a decoder cannot guarantee that it will uphold a strict limit, it
18
/// *must* fail with [`error::LimitErrorKind::Unsupported`].
19
///
20
/// The only currently supported strict limits are the `max_image_width` and `max_image_height`
21
/// limits, but more will be added in the future. [`LimitSupport`] will default to support
22
/// being false, and decoders should enable support for the limits they support in
23
/// [`ImageDecoder::set_limits`].
24
///
25
/// The limit check should only ever fail if a limit will be exceeded or an unsupported strict
26
/// limit is used.
27
///
28
/// [`ImageDecoder::set_limits`]: crate::ImageDecoder::set_limits
29
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
30
#[allow(missing_copy_implementations)]
31
#[non_exhaustive]
32
pub struct Limits {
33
    /// The maximum allowed image width. This limit is strict. The default is no limit.
34
    pub max_image_width: Option<u32>,
35
    /// The maximum allowed image height. This limit is strict. The default is no limit.
36
    pub max_image_height: Option<u32>,
37
    /// The maximum allowed sum of allocations allocated by the decoder at any one time excluding
38
    /// allocator overhead. This limit is non-strict by default and some decoders may ignore it.
39
    /// The bytes required to store the output image count towards this value. The default is
40
    /// 512MiB.
41
    pub max_alloc: Option<u64>,
42
}
43
44
/// Add some reasonable limits.
45
///
46
/// **Note**: This is not equivalent to _not_ adding limits. This may be changed in future major
47
/// version increases.
48
impl Default for Limits {
49
100k
    fn default() -> Limits {
50
100k
        Limits {
51
100k
            max_image_width: None,
52
100k
            max_image_height: None,
53
100k
            max_alloc: Some(512 * 1024 * 1024),
54
100k
        }
55
100k
    }
56
}
57
58
impl Limits {
59
    /// Disable all limits.
60
    #[must_use]
61
21.1k
    pub fn no_limits() -> Limits {
62
21.1k
        Limits {
63
21.1k
            max_image_width: None,
64
21.1k
            max_image_height: None,
65
21.1k
            max_alloc: None,
66
21.1k
        }
67
21.1k
    }
68
69
    /// This function checks that all currently set strict limits are supported.
70
61.6k
    pub fn check_support(&self, _supported: &LimitSupport) -> ImageResult<()> {
71
61.6k
        Ok(())
72
61.6k
    }
73
74
54.8k
    pub(crate) fn check_layout_dimensions(
75
54.8k
        &self,
76
54.8k
        layout: &crate::io::DecoderPreparedImage,
77
54.8k
    ) -> ImageResult<()> {
78
54.8k
        self.check_dimensions(layout.layout.width, layout.layout.height)
79
54.8k
    }
80
81
    /// This function checks the `max_image_width` and `max_image_height` limits given
82
    /// the image width and height.
83
85.1k
    pub fn check_dimensions(&self, width: u32, height: u32) -> ImageResult<()> {
84
85.1k
        if let Some(max_width) = self.max_image_width {
85
0
            if width > max_width {
86
0
                return Err(ImageError::Limits(error::LimitError::from_kind(
87
0
                    error::LimitErrorKind::DimensionError,
88
0
                )));
89
0
            }
90
85.1k
        }
91
92
85.1k
        if let Some(max_height) = self.max_image_height {
93
0
            if height > max_height {
94
0
                return Err(ImageError::Limits(error::LimitError::from_kind(
95
0
                    error::LimitErrorKind::DimensionError,
96
0
                )));
97
0
            }
98
85.1k
        }
99
100
85.1k
        Ok(())
101
85.1k
    }
102
103
    /// This function checks that the current limit allows for reserving the set amount
104
    /// of bytes, it then reduces the limit accordingly.
105
46.3k
    pub fn reserve(&mut self, amount: u64) -> ImageResult<()> {
106
46.3k
        if let Some(max_alloc) = self.max_alloc.as_mut() {
107
46.3k
            if *max_alloc < amount {
108
810
                return Err(ImageError::Limits(error::LimitError::from_kind(
109
810
                    error::LimitErrorKind::InsufficientMemory,
110
810
                )));
111
45.5k
            }
112
113
45.5k
            *max_alloc -= amount;
114
0
        }
115
116
45.5k
        Ok(())
117
46.3k
    }
118
119
    /// This function acts identically to [`reserve`], but takes a `usize` for convenience.
120
    ///
121
    /// [`reserve`]: Self::reserve
122
3.36k
    pub fn reserve_usize(&mut self, amount: usize) -> ImageResult<()> {
123
3.36k
        match u64::try_from(amount) {
124
3.36k
            Ok(n) => self.reserve(n),
125
0
            Err(_) if self.max_alloc.is_some() => Err(ImageError::Limits(
126
0
                error::LimitError::from_kind(error::LimitErrorKind::InsufficientMemory),
127
0
            )),
128
            Err(_) => {
129
                // Out of bounds, but we weren't asked to consider any limit.
130
0
                Ok(())
131
            }
132
        }
133
3.36k
    }
134
135
    /// This function acts identically to [`reserve`], but accepts the width, height and color type
136
    /// used to create an [`ImageBuffer`] and does all the math for you.
137
    ///
138
    /// [`ImageBuffer`]: crate::ImageBuffer
139
    /// [`reserve`]: Self::reserve
140
3.74k
    pub fn reserve_buffer(
141
3.74k
        &mut self,
142
3.74k
        width: u32,
143
3.74k
        height: u32,
144
3.74k
        color_type: ColorType,
145
3.74k
    ) -> ImageResult<()> {
146
3.74k
        self.check_dimensions(width, height)?;
147
3.74k
        let in_memory_size = u64::from(width)
148
3.74k
            .saturating_mul(u64::from(height))
149
3.74k
            .saturating_mul(color_type.bytes_per_pixel().into());
150
3.74k
        self.reserve(in_memory_size)?;
151
3.74k
        Ok(())
152
3.74k
    }
153
154
    /// This function increases the `max_alloc` limit with amount. Should only be used
155
    /// together with [`reserve`].
156
    ///
157
    /// [`reserve`]: Self::reserve
158
3.34k
    pub fn free(&mut self, amount: u64) {
159
3.34k
        if let Some(max_alloc) = self.max_alloc.as_mut() {
160
3.34k
            *max_alloc = max_alloc.saturating_add(amount);
161
3.34k
        }
162
3.34k
    }
163
164
    /// This function acts identically to [`free`], but takes a `usize` for convenience.
165
    ///
166
    /// [`free`]: Self::free
167
3.34k
    pub fn free_usize(&mut self, amount: usize) {
168
3.34k
        match u64::try_from(amount) {
169
3.34k
            Ok(n) => self.free(n),
170
0
            Err(_) if self.max_alloc.is_some() => {
171
0
                panic!("max_alloc is set, we should have exited earlier when the reserve failed");
172
            }
173
0
            Err(_) => {
174
0
                // Out of bounds, but we weren't asked to consider any limit.
175
0
            }
176
        }
177
3.34k
    }
178
}