1use crate::{Error, Result};
2
3#[allow(clippy::struct_field_names)]
5#[derive(Clone, Copy, Debug, Eq, PartialEq)]
6pub struct ParseLimits {
7 max_depth: usize,
8 max_items: usize,
9 max_byte_string_length: usize,
10 max_integer_digits: usize,
11 max_total_input: usize,
12 max_owned_allocation: usize,
13}
14
15#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
17pub struct ParseOptions {
18 limits: ParseLimits,
19}
20
21impl ParseOptions {
22 #[must_use]
24 pub const fn new(limits: ParseLimits) -> Self {
25 Self { limits }
26 }
27
28 #[must_use]
30 pub const fn limits(self) -> ParseLimits {
31 self.limits
32 }
33}
34
35#[derive(Debug)]
36pub(crate) struct AllocationBudget {
37 limits: ParseLimits,
38 used: usize,
39}
40
41impl AllocationBudget {
42 pub(crate) const fn new(limits: ParseLimits) -> Self {
43 Self { limits, used: 0 }
44 }
45
46 pub(crate) fn charge(&mut self, amount: usize) -> Result<()> {
47 self.used = self.limits.checked_owned_allocation(self.used, amount)?;
48 Ok(())
49 }
50}
51
52impl ParseLimits {
53 #[must_use]
55 pub const fn new(
56 max_depth: usize,
57 max_items: usize,
58 max_byte_string_length: usize,
59 max_total_input: usize,
60 max_owned_allocation: usize,
61 ) -> Self {
62 Self {
63 max_depth,
64 max_items,
65 max_byte_string_length,
66 max_integer_digits: 4_096,
67 max_total_input,
68 max_owned_allocation,
69 }
70 }
71
72 #[must_use]
74 pub const fn max_depth(self) -> usize {
75 self.max_depth
76 }
77
78 #[must_use]
80 pub const fn max_items(self) -> usize {
81 self.max_items
82 }
83
84 #[must_use]
86 pub const fn max_byte_string_length(self) -> usize {
87 self.max_byte_string_length
88 }
89
90 #[must_use]
92 pub const fn max_integer_digits(self) -> usize {
93 self.max_integer_digits
94 }
95
96 #[must_use]
98 pub const fn with_max_integer_digits(mut self, maximum: usize) -> Self {
99 self.max_integer_digits = maximum;
100 self
101 }
102
103 #[must_use]
105 pub const fn max_total_input(self) -> usize {
106 self.max_total_input
107 }
108
109 #[must_use]
111 pub const fn max_owned_allocation(self) -> usize {
112 self.max_owned_allocation
113 }
114
115 pub const fn check_depth(self, actual: usize) -> Result<()> {
121 check_limit("depth", actual, self.max_depth)
122 }
123
124 pub const fn check_items(self, actual: usize) -> Result<()> {
130 check_limit("item count", actual, self.max_items)
131 }
132
133 pub const fn check_byte_string_length(self, actual: usize) -> Result<()> {
139 check_limit("byte-string length", actual, self.max_byte_string_length)
140 }
141
142 pub const fn check_integer_digits(self, actual: usize) -> Result<()> {
148 check_limit("integer digits", actual, self.max_integer_digits)
149 }
150
151 pub const fn check_total_input(self, actual: usize) -> Result<()> {
157 check_limit("total input", actual, self.max_total_input)
158 }
159
160 pub const fn checked_owned_allocation(self, current: usize, added: usize) -> Result<usize> {
167 let Some(total) = current.checked_add(added) else {
168 return Err(Error::resource_limit(
169 "owned allocation",
170 usize::MAX,
171 self.max_owned_allocation,
172 ));
173 };
174 if total > self.max_owned_allocation {
175 Err(Error::resource_limit(
176 "owned allocation",
177 total,
178 self.max_owned_allocation,
179 ))
180 } else {
181 Ok(total)
182 }
183 }
184}
185
186impl Default for ParseLimits {
187 fn default() -> Self {
188 Self::new(
189 128,
190 1_000_000,
191 128 * 1024 * 1024,
192 256 * 1024 * 1024,
193 256 * 1024 * 1024,
194 )
195 }
196}
197
198const fn check_limit(limit: &'static str, actual: usize, maximum: usize) -> Result<()> {
199 if actual > maximum {
200 Err(Error::resource_limit(limit, actual, maximum))
201 } else {
202 Ok(())
203 }
204}