halldyll_core/security/
limits.rs

1//! Limits - Resource limits
2
3use std::time::{Duration, Instant};
4
5/// Resource limits
6#[derive(Debug, Clone)]
7pub struct ResourceLimits {
8    /// Max response size (bytes)
9    pub max_response_size: u64,
10    /// Max parsing time (ms)
11    pub max_parse_time_ms: u64,
12    /// Max decompressed size (bytes)
13    pub max_decompressed_size: u64,
14    /// Max compression ratio (anti zip-bomb)
15    pub max_compression_ratio: f64,
16    /// Max DOM depth
17    pub max_dom_depth: usize,
18    /// Max DOM elements
19    pub max_dom_elements: usize,
20}
21
22impl Default for ResourceLimits {
23    fn default() -> Self {
24        Self {
25            max_response_size: 50 * 1024 * 1024,      // 50 MB
26            max_parse_time_ms: 30000,                  // 30 secondes
27            max_decompressed_size: 100 * 1024 * 1024, // 100 MB
28            max_compression_ratio: 100.0,
29            max_dom_depth: 100,
30            max_dom_elements: 100000,
31        }
32    }
33}
34
35impl ResourceLimits {
36    /// New limits
37    pub fn new() -> Self {
38        Self::default()
39    }
40
41    /// Check response size (returns main Error type)
42    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    /// Check response size (returns LimitError)
54    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    /// Check decompressed size
66    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    /// Check compression ratio
78    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    /// Check parsing time
94    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    /// Check DOM depth
106    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    /// Check DOM element count
118    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/// Limit error
131#[derive(Debug, Clone)]
132pub enum LimitError {
133    /// Response size exceeded
134    ResponseSizeExceeded {
135        /// Actual size in bytes
136        size: u64,
137        /// Maximum allowed size in bytes
138        max: u64,
139    },
140    /// Decompressed size exceeded
141    DecompressedSizeExceeded {
142        /// Actual decompressed size in bytes
143        size: u64,
144        /// Maximum allowed size in bytes
145        max: u64,
146    },
147    /// Compression ratio exceeded
148    CompressionRatioExceeded {
149        /// Actual compression ratio
150        ratio: f64,
151        /// Maximum allowed ratio
152        max: f64,
153    },
154    /// Parsing time exceeded
155    ParseTimeExceeded {
156        /// Elapsed time in milliseconds
157        elapsed_ms: u64,
158        /// Maximum allowed time in milliseconds
159        max_ms: u64,
160    },
161    /// DOM depth exceeded
162    DomDepthExceeded {
163        /// Actual DOM depth
164        depth: usize,
165        /// Maximum allowed depth
166        max: usize,
167    },
168    /// DOM elements exceeded
169    DomElementsExceeded {
170        /// Actual element count
171        count: usize,
172        /// Maximum allowed count
173        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
204/// Timer to limit parsing time
205pub struct ParseTimer {
206    started_at: Instant,
207    max_duration: Duration,
208}
209
210impl ParseTimer {
211    /// New timer
212    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    /// Check if time is exceeded
220    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    /// Elapsed time in ms
233    pub fn elapsed_ms(&self) -> u64 {
234        self.started_at.elapsed().as_millis() as u64
235    }
236
237    /// Remaining time in ms
238    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}