/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 | | /// [`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] |
33 | | pub 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. |
49 | | impl Default for Limits { |
50 | 33.6k | fn default() -> Limits { |
51 | 33.6k | Limits { |
52 | 33.6k | max_image_width: None, |
53 | 33.6k | max_image_height: None, |
54 | 33.6k | max_alloc: Some(512 * 1024 * 1024), |
55 | 33.6k | } |
56 | 33.6k | } |
57 | | } |
58 | | |
59 | | impl Limits { |
60 | | /// Disable all limits. |
61 | | #[must_use] |
62 | 7.02k | pub fn no_limits() -> Limits { |
63 | 7.02k | Limits { |
64 | 7.02k | max_image_width: None, |
65 | 7.02k | max_image_height: None, |
66 | 7.02k | max_alloc: None, |
67 | 7.02k | } |
68 | 7.02k | } |
69 | | |
70 | | /// This function checks that all currently set strict limits are supported. |
71 | 34.1k | pub fn check_support(&self, _supported: &LimitSupport) -> ImageResult<()> { |
72 | 34.1k | Ok(()) |
73 | 34.1k | } |
74 | | |
75 | | /// This function checks the `max_image_width` and `max_image_height` limits given |
76 | | /// the image width and height. |
77 | 33.4k | pub fn check_dimensions(&self, width: u32, height: u32) -> ImageResult<()> { |
78 | 33.4k | if let Some(max_width) = self.max_image_width { |
79 | 0 | if width > max_width { |
80 | 0 | return Err(ImageError::Limits(error::LimitError::from_kind( |
81 | 0 | error::LimitErrorKind::DimensionError, |
82 | 0 | ))); |
83 | 0 | } |
84 | 33.4k | } |
85 | | |
86 | 33.4k | if let Some(max_height) = self.max_image_height { |
87 | 0 | if height > max_height { |
88 | 0 | return Err(ImageError::Limits(error::LimitError::from_kind( |
89 | 0 | error::LimitErrorKind::DimensionError, |
90 | 0 | ))); |
91 | 0 | } |
92 | 33.4k | } |
93 | | |
94 | 33.4k | Ok(()) |
95 | 33.4k | } |
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 | 23.0k | pub fn reserve(&mut self, amount: u64) -> ImageResult<()> { |
100 | 23.0k | if let Some(max_alloc) = self.max_alloc.as_mut() { |
101 | 23.0k | if *max_alloc < amount { |
102 | 416 | return Err(ImageError::Limits(error::LimitError::from_kind( |
103 | 416 | error::LimitErrorKind::InsufficientMemory, |
104 | 416 | ))); |
105 | 22.6k | } |
106 | | |
107 | 22.6k | *max_alloc -= amount; |
108 | 0 | } |
109 | | |
110 | 22.6k | Ok(()) |
111 | 23.0k | } |
112 | | |
113 | | /// This function acts identically to [`reserve`], but takes a `usize` for convenience. |
114 | | /// |
115 | | /// [`reserve`]: #method.reserve |
116 | 1.38k | pub fn reserve_usize(&mut self, amount: usize) -> ImageResult<()> { |
117 | 1.38k | match u64::try_from(amount) { |
118 | 1.38k | Ok(n) => self.reserve(n), |
119 | 0 | Err(_) if self.max_alloc.is_some() => Err(ImageError::Limits( |
120 | 0 | error::LimitError::from_kind(error::LimitErrorKind::InsufficientMemory), |
121 | 0 | )), |
122 | | Err(_) => { |
123 | | // Out of bounds, but we weren't asked to consider any limit. |
124 | 0 | Ok(()) |
125 | | } |
126 | | } |
127 | 1.38k | } |
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 | 0 | pub fn reserve_buffer( |
135 | 0 | &mut self, |
136 | 0 | width: u32, |
137 | 0 | height: u32, |
138 | 0 | color_type: ColorType, |
139 | 0 | ) -> ImageResult<()> { |
140 | 0 | self.check_dimensions(width, height)?; |
141 | 0 | let in_memory_size = u64::from(width) |
142 | 0 | .saturating_mul(u64::from(height)) |
143 | 0 | .saturating_mul(color_type.bytes_per_pixel().into()); |
144 | 0 | self.reserve(in_memory_size)?; |
145 | 0 | Ok(()) |
146 | 0 | } |
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 | 1.37k | pub fn free(&mut self, amount: u64) { |
153 | 1.37k | if let Some(max_alloc) = self.max_alloc.as_mut() { |
154 | 1.37k | *max_alloc = max_alloc.saturating_add(amount); |
155 | 1.37k | } |
156 | 1.37k | } |
157 | | |
158 | | /// This function acts identically to [`free`], but takes a `usize` for convenience. |
159 | | /// |
160 | | /// [`free`]: #method.free |
161 | 1.37k | pub fn free_usize(&mut self, amount: usize) { |
162 | 1.37k | match u64::try_from(amount) { |
163 | 1.37k | Ok(n) => self.free(n), |
164 | 0 | Err(_) if self.max_alloc.is_some() => { |
165 | 0 | panic!("max_alloc is set, we should have exited earlier when the reserve failed"); |
166 | | } |
167 | 0 | Err(_) => { |
168 | 0 | // Out of bounds, but we weren't asked to consider any limit. |
169 | 0 | } |
170 | | } |
171 | 1.37k | } |
172 | | } |