oxidize_pdf/parser/
stack_safe.rs1use super::{ParseError, ParseResult};
8use std::collections::HashSet;
9
10pub const MAX_RECURSION_DEPTH: usize = 1000;
12
13pub const PARSING_TIMEOUT_SECS: u64 = 30;
15
16#[derive(Debug)]
18pub struct StackSafeContext {
19 pub depth: usize,
21 pub max_depth: usize,
23 pub active_stack: Vec<(u32, u16)>,
25 pub completed_refs: HashSet<(u32, u16)>,
27 pub start_time: std::time::Instant,
29 pub timeout: std::time::Duration,
31}
32
33impl Default for StackSafeContext {
34 fn default() -> Self {
35 Self::new()
36 }
37}
38
39impl StackSafeContext {
40 pub fn new() -> Self {
42 Self {
43 depth: 0,
44 max_depth: MAX_RECURSION_DEPTH,
45 active_stack: Vec::new(),
46 completed_refs: HashSet::new(),
47 start_time: std::time::Instant::now(),
48 timeout: std::time::Duration::from_secs(PARSING_TIMEOUT_SECS),
49 }
50 }
51
52 pub fn with_limits(max_depth: usize, timeout_secs: u64) -> Self {
54 Self {
55 depth: 0,
56 max_depth,
57 active_stack: Vec::new(),
58 completed_refs: HashSet::new(),
59 start_time: std::time::Instant::now(),
60 timeout: std::time::Duration::from_secs(timeout_secs),
61 }
62 }
63
64 pub fn enter(&mut self) -> ParseResult<()> {
66 if self.depth + 1 > self.max_depth {
67 return Err(ParseError::SyntaxError {
68 position: 0,
69 message: format!(
70 "Maximum recursion depth exceeded: {} (limit: {})",
71 self.depth + 1,
72 self.max_depth
73 ),
74 });
75 }
76 self.depth += 1;
77 self.check_timeout()?;
78 Ok(())
79 }
80
81 pub fn exit(&mut self) {
83 if self.depth > 0 {
84 self.depth -= 1;
85 }
86 }
87
88 pub fn push_ref(&mut self, obj_num: u32, gen_num: u16) -> ParseResult<()> {
90 let ref_key = (obj_num, gen_num);
91
92 if self.active_stack.contains(&ref_key) {
94 return Err(ParseError::SyntaxError {
95 position: 0,
96 message: format!("Circular reference detected: {obj_num} {gen_num} R"),
97 });
98 }
99
100 self.active_stack.push(ref_key);
102 Ok(())
103 }
104
105 pub fn pop_ref(&mut self) {
107 if let Some(ref_key) = self.active_stack.pop() {
108 self.completed_refs.insert(ref_key);
109 }
110 }
111
112 pub fn check_timeout(&self) -> ParseResult<()> {
114 if self.start_time.elapsed() > self.timeout {
115 return Err(ParseError::SyntaxError {
116 position: 0,
117 message: format!("Parsing timeout exceeded: {}s", self.timeout.as_secs()),
118 });
119 }
120 Ok(())
121 }
122
123 pub fn child(&self) -> Self {
125 Self {
126 depth: self.depth,
127 max_depth: self.max_depth,
128 active_stack: self.active_stack.clone(),
129 completed_refs: self.completed_refs.clone(),
130 start_time: self.start_time,
131 timeout: self.timeout,
132 }
133 }
134}
135
136pub struct RecursionGuard<'a> {
138 context: &'a mut StackSafeContext,
139}
140
141impl<'a> RecursionGuard<'a> {
142 pub fn new(context: &'a mut StackSafeContext) -> ParseResult<Self> {
144 context.enter()?;
145 Ok(Self { context })
146 }
147}
148
149impl<'a> Drop for RecursionGuard<'a> {
150 fn drop(&mut self) {
151 self.context.exit();
152 }
153}
154
155pub struct ReferenceStackGuard<'a> {
157 context: &'a mut StackSafeContext,
158}
159
160impl<'a> ReferenceStackGuard<'a> {
161 pub fn new(context: &'a mut StackSafeContext, obj_num: u32, gen_num: u16) -> ParseResult<Self> {
163 context.push_ref(obj_num, gen_num)?;
164 Ok(Self { context })
165 }
166}
167
168impl<'a> Drop for ReferenceStackGuard<'a> {
169 fn drop(&mut self) {
170 self.context.pop_ref();
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177
178 #[test]
179 fn test_recursion_limits() {
180 let mut context = StackSafeContext::with_limits(3, 60);
181
182 assert!(context.enter().is_ok());
184 assert_eq!(context.depth, 1);
185
186 assert!(context.enter().is_ok());
187 assert_eq!(context.depth, 2);
188
189 assert!(context.enter().is_ok());
190 assert_eq!(context.depth, 3);
191
192 assert!(context.enter().is_err());
194
195 context.exit();
197 assert_eq!(context.depth, 2);
198 }
199
200 #[test]
201 fn test_cycle_detection() {
202 let mut context = StackSafeContext::new();
203
204 assert!(context.push_ref(1, 0).is_ok());
206
207 assert!(context.push_ref(1, 0).is_err());
209
210 assert!(context.push_ref(2, 0).is_ok());
212
213 context.pop_ref(); context.pop_ref(); assert!(context.push_ref(1, 0).is_ok());
219 }
220
221 #[test]
222 fn test_recursion_guard() {
223 let mut context = StackSafeContext::new();
224 assert_eq!(context.depth, 0);
225
226 {
227 let _guard = RecursionGuard::new(&mut context).unwrap();
228 }
230
231 assert_eq!(context.depth, 0);
233 }
234
235 #[test]
236 fn test_reference_stack_guard() {
237 let mut context = StackSafeContext::new();
238
239 {
240 let _guard = ReferenceStackGuard::new(&mut context, 1, 0).unwrap();
241 }
244
245 assert_eq!(context.active_stack.len(), 0);
247 assert!(context.completed_refs.contains(&(1, 0)));
248
249 assert!(context.push_ref(1, 0).is_ok());
251 }
252}