1use std::time::{Duration, Instant};
4
5#[derive(Debug, Clone)]
7pub struct ResourceLimits {
8 pub max_response_size: u64,
10 pub max_parse_time_ms: u64,
12 pub max_decompressed_size: u64,
14 pub max_compression_ratio: f64,
16 pub max_dom_depth: usize,
18 pub max_dom_elements: usize,
20}
21
22impl Default for ResourceLimits {
23 fn default() -> Self {
24 Self {
25 max_response_size: 50 * 1024 * 1024, max_parse_time_ms: 30000, max_decompressed_size: 100 * 1024 * 1024, max_compression_ratio: 100.0,
29 max_dom_depth: 100,
30 max_dom_elements: 100000,
31 }
32 }
33}
34
35impl ResourceLimits {
36 pub fn new() -> Self {
38 Self::default()
39 }
40
41 pub fn check_response_size(&self, size: u64) -> crate::types::error::Result<()> {
43 if size > self.max_response_size {
44 Err(crate::types::error::Error::SizeExceeded {
45 max: self.max_response_size,
46 actual: size,
47 })
48 } else {
49 Ok(())
50 }
51 }
52
53 pub fn check_response_size_limit(&self, size: u64) -> Result<(), LimitError> {
55 if size > self.max_response_size {
56 Err(LimitError::ResponseSizeExceeded {
57 size,
58 max: self.max_response_size,
59 })
60 } else {
61 Ok(())
62 }
63 }
64
65 pub fn check_decompressed_size(&self, size: u64) -> Result<(), LimitError> {
67 if size > self.max_decompressed_size {
68 Err(LimitError::DecompressedSizeExceeded {
69 size,
70 max: self.max_decompressed_size,
71 })
72 } else {
73 Ok(())
74 }
75 }
76
77 pub fn check_compression_ratio(&self, compressed: u64, decompressed: u64) -> Result<(), LimitError> {
79 if compressed == 0 {
80 return Ok(());
81 }
82 let ratio = decompressed as f64 / compressed as f64;
83 if ratio > self.max_compression_ratio {
84 Err(LimitError::CompressionRatioExceeded {
85 ratio,
86 max: self.max_compression_ratio,
87 })
88 } else {
89 Ok(())
90 }
91 }
92
93 pub fn check_parse_time(&self, elapsed_ms: u64) -> crate::types::error::Result<()> {
95 if elapsed_ms > self.max_parse_time_ms {
96 Err(crate::types::error::Error::ResourceLimit(format!(
97 "Parse time {}ms exceeds limit {}ms",
98 elapsed_ms, self.max_parse_time_ms
99 )))
100 } else {
101 Ok(())
102 }
103 }
104
105 pub fn check_dom_depth(&self, depth: usize) -> Result<(), LimitError> {
107 if depth > self.max_dom_depth {
108 Err(LimitError::DomDepthExceeded {
109 depth,
110 max: self.max_dom_depth,
111 })
112 } else {
113 Ok(())
114 }
115 }
116
117 pub fn check_dom_elements(&self, count: usize) -> Result<(), LimitError> {
119 if count > self.max_dom_elements {
120 Err(LimitError::DomElementsExceeded {
121 count,
122 max: self.max_dom_elements,
123 })
124 } else {
125 Ok(())
126 }
127 }
128}
129
130#[derive(Debug, Clone)]
132pub enum LimitError {
133 ResponseSizeExceeded {
135 size: u64,
137 max: u64,
139 },
140 DecompressedSizeExceeded {
142 size: u64,
144 max: u64,
146 },
147 CompressionRatioExceeded {
149 ratio: f64,
151 max: f64,
153 },
154 ParseTimeExceeded {
156 elapsed_ms: u64,
158 max_ms: u64,
160 },
161 DomDepthExceeded {
163 depth: usize,
165 max: usize,
167 },
168 DomElementsExceeded {
170 count: usize,
172 max: usize,
174 },
175}
176
177impl std::fmt::Display for LimitError {
178 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
179 match self {
180 LimitError::ResponseSizeExceeded { size, max } => {
181 write!(f, "Response size {} exceeds limit {}", size, max)
182 }
183 LimitError::DecompressedSizeExceeded { size, max } => {
184 write!(f, "Decompressed size {} exceeds limit {}", size, max)
185 }
186 LimitError::CompressionRatioExceeded { ratio, max } => {
187 write!(f, "Compression ratio {:.2} exceeds limit {:.2}", ratio, max)
188 }
189 LimitError::ParseTimeExceeded { elapsed_ms, max_ms } => {
190 write!(f, "Parse time {}ms exceeds limit {}ms", elapsed_ms, max_ms)
191 }
192 LimitError::DomDepthExceeded { depth, max } => {
193 write!(f, "DOM depth {} exceeds limit {}", depth, max)
194 }
195 LimitError::DomElementsExceeded { count, max } => {
196 write!(f, "DOM elements {} exceeds limit {}", count, max)
197 }
198 }
199 }
200}
201
202impl std::error::Error for LimitError {}
203
204pub struct ParseTimer {
206 started_at: Instant,
207 max_duration: Duration,
208}
209
210impl ParseTimer {
211 pub fn new(max_ms: u64) -> Self {
213 Self {
214 started_at: Instant::now(),
215 max_duration: Duration::from_millis(max_ms),
216 }
217 }
218
219 pub fn check(&self) -> Result<(), LimitError> {
221 let elapsed = self.started_at.elapsed();
222 if elapsed > self.max_duration {
223 Err(LimitError::ParseTimeExceeded {
224 elapsed_ms: elapsed.as_millis() as u64,
225 max_ms: self.max_duration.as_millis() as u64,
226 })
227 } else {
228 Ok(())
229 }
230 }
231
232 pub fn elapsed_ms(&self) -> u64 {
234 self.started_at.elapsed().as_millis() as u64
235 }
236
237 pub fn remaining_ms(&self) -> u64 {
239 let elapsed = self.started_at.elapsed();
240 if elapsed >= self.max_duration {
241 0
242 } else {
243 (self.max_duration - elapsed).as_millis() as u64
244 }
245 }
246}