image/io/limits.rs
1use 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]
7pub 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/// [`LimitSupport`]: ./struct.LimitSupport.html
29/// [`ImageDecoder::set_limits`]: ../trait.ImageDecoder.html#method.set_limits
30#[derive(Clone, Debug, Eq, PartialEq, Hash)]
31#[allow(missing_copy_implementations)]
32#[non_exhaustive]
33pub struct Limits {
34 /// The maximum allowed image width. This limit is strict. The default is no limit.
35 pub max_image_width: Option<u32>,
36 /// The maximum allowed image height. This limit is strict. The default is no limit.
37 pub max_image_height: Option<u32>,
38 /// The maximum allowed sum of allocations allocated by the decoder at any one time excluding
39 /// allocator overhead. This limit is non-strict by default and some decoders may ignore it.
40 /// The bytes required to store the output image count towards this value. The default is
41 /// 512MiB.
42 pub max_alloc: Option<u64>,
43}
44
45/// Add some reasonable limits.
46///
47/// **Note**: This is not equivalent to _not_ adding limits. This may be changed in future major
48/// version increases.
49impl Default for Limits {
50 fn default() -> Limits {
51 Limits {
52 max_image_width: None,
53 max_image_height: None,
54 max_alloc: Some(512 * 1024 * 1024),
55 }
56 }
57}
58
59impl Limits {
60 /// Disable all limits.
61 #[must_use]
62 pub fn no_limits() -> Limits {
63 Limits {
64 max_image_width: None,
65 max_image_height: None,
66 max_alloc: None,
67 }
68 }
69
70 /// This function checks that all currently set strict limits are supported.
71 pub fn check_support(&self, _supported: &LimitSupport) -> ImageResult<()> {
72 Ok(())
73 }
74
75 /// This function checks the `max_image_width` and `max_image_height` limits given
76 /// the image width and height.
77 pub fn check_dimensions(&self, width: u32, height: u32) -> ImageResult<()> {
78 if let Some(max_width) = self.max_image_width {
79 if width > max_width {
80 return Err(ImageError::Limits(error::LimitError::from_kind(
81 error::LimitErrorKind::DimensionError,
82 )));
83 }
84 }
85
86 if let Some(max_height) = self.max_image_height {
87 if height > max_height {
88 return Err(ImageError::Limits(error::LimitError::from_kind(
89 error::LimitErrorKind::DimensionError,
90 )));
91 }
92 }
93
94 Ok(())
95 }
96
97 /// This function checks that the current limit allows for reserving the set amount
98 /// of bytes, it then reduces the limit accordingly.
99 pub fn reserve(&mut self, amount: u64) -> ImageResult<()> {
100 if let Some(max_alloc) = self.max_alloc.as_mut() {
101 if *max_alloc < amount {
102 return Err(ImageError::Limits(error::LimitError::from_kind(
103 error::LimitErrorKind::InsufficientMemory,
104 )));
105 }
106
107 *max_alloc -= amount;
108 }
109
110 Ok(())
111 }
112
113 /// This function acts identically to [`reserve`], but takes a `usize` for convenience.
114 ///
115 /// [`reserve`]: #method.reserve
116 pub fn reserve_usize(&mut self, amount: usize) -> ImageResult<()> {
117 match u64::try_from(amount) {
118 Ok(n) => self.reserve(n),
119 Err(_) if self.max_alloc.is_some() => Err(ImageError::Limits(
120 error::LimitError::from_kind(error::LimitErrorKind::InsufficientMemory),
121 )),
122 Err(_) => {
123 // Out of bounds, but we weren't asked to consider any limit.
124 Ok(())
125 }
126 }
127 }
128
129 /// This function acts identically to [`reserve`], but accepts the width, height and color type
130 /// used to create an [`ImageBuffer`] and does all the math for you.
131 ///
132 /// [`ImageBuffer`]: crate::ImageBuffer
133 /// [`reserve`]: #method.reserve
134 pub fn reserve_buffer(
135 &mut self,
136 width: u32,
137 height: u32,
138 color_type: ColorType,
139 ) -> ImageResult<()> {
140 self.check_dimensions(width, height)?;
141 let in_memory_size = u64::from(width)
142 .saturating_mul(u64::from(height))
143 .saturating_mul(color_type.bytes_per_pixel().into());
144 self.reserve(in_memory_size)?;
145 Ok(())
146 }
147
148 /// This function increases the `max_alloc` limit with amount. Should only be used
149 /// together with [`reserve`].
150 ///
151 /// [`reserve`]: #method.reserve
152 pub fn free(&mut self, amount: u64) {
153 if let Some(max_alloc) = self.max_alloc.as_mut() {
154 *max_alloc = max_alloc.saturating_add(amount);
155 }
156 }
157
158 /// This function acts identically to [`free`], but takes a `usize` for convenience.
159 ///
160 /// [`free`]: #method.free
161 pub fn free_usize(&mut self, amount: usize) {
162 match u64::try_from(amount) {
163 Ok(n) => self.free(n),
164 Err(_) if self.max_alloc.is_some() => {
165 panic!("max_alloc is set, we should have exited earlier when the reserve failed");
166 }
167 Err(_) => {
168 // Out of bounds, but we weren't asked to consider any limit.
169 }
170 }
171 }
172}