1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct ParserLimits {
25 pub max_entries: usize,
32
33 pub max_links_per_feed: usize,
39
40 pub max_links_per_entry: usize,
46
47 pub max_authors: usize,
51
52 pub max_contributors: usize,
56
57 pub max_tags: usize,
61
62 pub max_content_blocks: usize,
68
69 pub max_enclosures: usize,
75
76 pub max_namespaces: usize,
82
83 pub max_nesting_depth: usize,
89
90 pub max_text_length: usize,
96
97 pub max_feed_size_bytes: usize,
103
104 pub max_attribute_length: usize,
110
111 pub max_podcast_soundbites: usize,
117
118 pub max_podcast_transcripts: usize,
124
125 pub max_podcast_funding: usize,
131
132 pub max_podcast_persons: usize,
138
139 pub max_value_recipients: usize,
146
147 pub max_podcast_alternate_enclosures: usize,
151
152 pub max_podcast_alternate_enclosure_sources: usize,
156
157 pub max_podcast_podroll: usize,
161
162 pub max_podcast_social_interact: usize,
166
167 pub max_podcast_txt: usize,
171
172 pub max_podcast_follow: usize,
176}
177
178impl Default for ParserLimits {
179 fn default() -> Self {
184 Self {
185 max_entries: 10_000,
186 max_links_per_feed: 100,
187 max_links_per_entry: 50,
188 max_authors: 20,
189 max_contributors: 20,
190 max_tags: 100,
191 max_content_blocks: 10,
192 max_enclosures: 20,
193 max_namespaces: 100,
194 max_nesting_depth: 100,
195 max_text_length: 10 * 1024 * 1024, max_feed_size_bytes: 100 * 1024 * 1024, max_attribute_length: 64 * 1024, max_podcast_soundbites: 10,
199 max_podcast_transcripts: 20,
200 max_podcast_funding: 20,
201 max_podcast_persons: 50,
202 max_value_recipients: 20,
203 max_podcast_alternate_enclosures: 20,
204 max_podcast_alternate_enclosure_sources: 10,
205 max_podcast_podroll: 50,
206 max_podcast_social_interact: 20,
207 max_podcast_txt: 20,
208 max_podcast_follow: 20,
209 }
210 }
211}
212
213impl ParserLimits {
214 #[must_use]
228 pub const fn strict() -> Self {
229 Self {
230 max_entries: 1_000,
231 max_links_per_feed: 20,
232 max_links_per_entry: 10,
233 max_authors: 5,
234 max_contributors: 5,
235 max_tags: 20,
236 max_content_blocks: 3,
237 max_enclosures: 5,
238 max_namespaces: 20,
239 max_nesting_depth: 50,
240 max_text_length: 1024 * 1024, max_feed_size_bytes: 10 * 1024 * 1024, max_attribute_length: 8 * 1024, max_podcast_soundbites: 5,
244 max_podcast_transcripts: 5,
245 max_podcast_funding: 5,
246 max_podcast_persons: 10,
247 max_value_recipients: 5,
248 max_podcast_alternate_enclosures: 5,
249 max_podcast_alternate_enclosure_sources: 3,
250 max_podcast_podroll: 10,
251 max_podcast_social_interact: 5,
252 max_podcast_txt: 5,
253 max_podcast_follow: 5,
254 }
255 }
256
257 #[must_use]
271 pub const fn permissive() -> Self {
272 Self {
273 max_entries: 100_000,
274 max_links_per_feed: 500,
275 max_links_per_entry: 200,
276 max_authors: 100,
277 max_contributors: 100,
278 max_tags: 500,
279 max_content_blocks: 50,
280 max_enclosures: 100,
281 max_namespaces: 500,
282 max_nesting_depth: 200,
283 max_text_length: 50 * 1024 * 1024, max_feed_size_bytes: 500 * 1024 * 1024, max_attribute_length: 256 * 1024, max_podcast_soundbites: 50,
287 max_podcast_transcripts: 100,
288 max_podcast_funding: 50,
289 max_podcast_persons: 200,
290 max_value_recipients: 50,
291 max_podcast_alternate_enclosures: 100,
292 max_podcast_alternate_enclosure_sources: 50,
293 max_podcast_podroll: 200,
294 max_podcast_social_interact: 100,
295 max_podcast_txt: 100,
296 max_podcast_follow: 100,
297 }
298 }
299
300 pub const fn check_feed_size(&self, size: usize) -> Result<(), LimitError> {
308 if size > self.max_feed_size_bytes {
309 Err(LimitError::FeedTooLarge {
310 size,
311 max: self.max_feed_size_bytes,
312 })
313 } else {
314 Ok(())
315 }
316 }
317
318 pub const fn check_collection_size(
326 &self,
327 current: usize,
328 limit: usize,
329 name: &'static str,
330 ) -> Result<(), LimitError> {
331 if current >= limit {
332 Err(LimitError::CollectionTooLarge {
333 name,
334 size: current,
335 max: limit,
336 })
337 } else {
338 Ok(())
339 }
340 }
341
342 pub const fn check_nesting_depth(&self, depth: usize) -> Result<(), LimitError> {
348 if depth > self.max_nesting_depth {
349 Err(LimitError::NestingTooDeep {
350 depth,
351 max: self.max_nesting_depth,
352 })
353 } else {
354 Ok(())
355 }
356 }
357
358 pub const fn check_text_length(&self, length: usize) -> Result<(), LimitError> {
364 if length > self.max_text_length {
365 Err(LimitError::TextTooLong {
366 length,
367 max: self.max_text_length,
368 })
369 } else {
370 Ok(())
371 }
372 }
373}
374
375#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
377#[allow(missing_docs)] pub enum LimitError {
379 #[error("Feed size ({size} bytes) exceeds maximum ({max} bytes)")]
381 FeedTooLarge { size: usize, max: usize },
382
383 #[error("Collection '{name}' has {size} items, exceeds maximum ({max})")]
385 CollectionTooLarge {
386 name: &'static str,
387 size: usize,
388 max: usize,
389 },
390
391 #[error("XML nesting depth ({depth}) exceeds maximum ({max})")]
393 NestingTooDeep { depth: usize, max: usize },
394
395 #[error("Text field length ({length} bytes) exceeds maximum ({max} bytes)")]
397 TextTooLong { length: usize, max: usize },
398}
399
400#[cfg(test)]
401mod tests {
402 use super::*;
403
404 #[test]
405 fn test_default_limits() {
406 let limits = ParserLimits::default();
407 assert_eq!(limits.max_entries, 10_000);
408 assert_eq!(limits.max_feed_size_bytes, 100 * 1024 * 1024);
409 }
410
411 #[test]
412 fn test_strict_limits() {
413 let limits = ParserLimits::strict();
414 assert_eq!(limits.max_entries, 1_000);
415 assert!(limits.max_entries < ParserLimits::default().max_entries);
416 }
417
418 #[test]
419 fn test_permissive_limits() {
420 let limits = ParserLimits::permissive();
421 assert_eq!(limits.max_entries, 100_000);
422 assert!(limits.max_entries > ParserLimits::default().max_entries);
423 }
424
425 #[test]
426 fn test_check_feed_size_ok() {
427 let limits = ParserLimits::default();
428 assert!(limits.check_feed_size(1024).is_ok());
429 }
430
431 #[test]
432 fn test_check_feed_size_too_large() {
433 let limits = ParserLimits::default();
434 let result = limits.check_feed_size(200 * 1024 * 1024);
435 assert!(result.is_err());
436 assert!(matches!(result, Err(LimitError::FeedTooLarge { .. })));
437 }
438
439 #[test]
440 fn test_check_collection_size_ok() {
441 let limits = ParserLimits::default();
442 assert!(
443 limits
444 .check_collection_size(50, limits.max_entries, "entries")
445 .is_ok()
446 );
447 }
448
449 #[test]
450 fn test_check_collection_size_too_large() {
451 let limits = ParserLimits::default();
452 let result = limits.check_collection_size(10_001, limits.max_entries, "entries");
453 assert!(result.is_err());
454 assert!(matches!(result, Err(LimitError::CollectionTooLarge { .. })));
455 }
456
457 #[test]
458 fn test_check_nesting_depth_ok() {
459 let limits = ParserLimits::default();
460 assert!(limits.check_nesting_depth(50).is_ok());
461 }
462
463 #[test]
464 fn test_check_nesting_depth_too_deep() {
465 let limits = ParserLimits::default();
466 let result = limits.check_nesting_depth(101);
467 assert!(result.is_err());
468 assert!(matches!(result, Err(LimitError::NestingTooDeep { .. })));
469 }
470
471 #[test]
472 fn test_check_text_length_ok() {
473 let limits = ParserLimits::default();
474 assert!(limits.check_text_length(1024).is_ok());
475 }
476
477 #[test]
478 fn test_check_text_length_too_long() {
479 let limits = ParserLimits::default();
480 let result = limits.check_text_length(20 * 1024 * 1024);
481 assert!(result.is_err());
482 assert!(matches!(result, Err(LimitError::TextTooLong { .. })));
483 }
484
485 #[test]
486 fn test_limit_error_display() {
487 let err = LimitError::FeedTooLarge {
488 size: 200_000_000,
489 max: 100_000_000,
490 };
491 let msg = err.to_string();
492 assert!(msg.contains("200000000"));
493 assert!(msg.contains("100000000"));
494 }
495
496 #[test]
497 fn test_max_value_recipients_default() {
498 let limits = ParserLimits::default();
499 assert_eq!(limits.max_value_recipients, 20);
500 }
501
502 #[test]
503 fn test_max_value_recipients_strict() {
504 let limits = ParserLimits::strict();
505 assert_eq!(limits.max_value_recipients, 5);
506 assert!(limits.max_value_recipients < ParserLimits::default().max_value_recipients);
507 }
508
509 #[test]
510 fn test_max_value_recipients_permissive() {
511 let limits = ParserLimits::permissive();
512 assert_eq!(limits.max_value_recipients, 50);
513 assert!(limits.max_value_recipients > ParserLimits::default().max_value_recipients);
514 }
515
516 #[test]
517 fn test_value_recipients_limit_enforcement() {
518 let limits = ParserLimits::default();
519
520 assert!(
522 limits
523 .check_collection_size(19, limits.max_value_recipients, "value_recipients")
524 .is_ok()
525 );
526
527 assert!(
529 limits
530 .check_collection_size(20, limits.max_value_recipients, "value_recipients")
531 .is_err()
532 );
533
534 let result =
536 limits.check_collection_size(21, limits.max_value_recipients, "value_recipients");
537 assert!(result.is_err());
538 assert!(matches!(result, Err(LimitError::CollectionTooLarge { .. })));
539 }
540}