/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 | | } |