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_html_nesting_depth: usize,
110
111 pub max_text_length: usize,
120
121 pub max_feed_size_bytes: usize,
127
128 pub max_attribute_length: usize,
134
135 pub max_podcast_soundbites: usize,
141
142 pub max_podcast_transcripts: usize,
148
149 pub max_podcast_funding: usize,
155
156 pub max_podcast_persons: usize,
162
163 pub max_value_recipients: usize,
170
171 pub max_podcast_alternate_enclosures: usize,
175
176 pub max_podcast_alternate_enclosure_sources: usize,
180
181 pub max_podcast_podroll: usize,
185
186 pub max_podcast_social_interact: usize,
190
191 pub max_podcast_txt: usize,
195
196 pub max_podcast_follow: usize,
200
201 pub max_podcast_chat: usize,
207
208 pub max_podcast_value_time_splits: usize,
215
216 pub max_json_extensions: usize,
229}
230
231impl Default for ParserLimits {
232 fn default() -> Self {
237 Self {
238 max_entries: 10_000,
239 max_links_per_feed: 100,
240 max_links_per_entry: 50,
241 max_authors: 20,
242 max_contributors: 20,
243 max_tags: 100,
244 max_content_blocks: 10,
245 max_enclosures: 20,
246 max_namespaces: 100,
247 max_nesting_depth: 100,
248 max_html_nesting_depth: 100,
249 max_text_length: 10 * 1024 * 1024, max_feed_size_bytes: 100 * 1024 * 1024, max_attribute_length: 64 * 1024, max_podcast_soundbites: 10,
253 max_podcast_transcripts: 20,
254 max_podcast_funding: 20,
255 max_podcast_persons: 50,
256 max_value_recipients: 20,
257 max_podcast_alternate_enclosures: 20,
258 max_podcast_alternate_enclosure_sources: 10,
259 max_podcast_podroll: 50,
260 max_podcast_social_interact: 20,
261 max_podcast_txt: 20,
262 max_podcast_follow: 20,
263 max_podcast_chat: 20,
264 max_podcast_value_time_splits: 20,
265 max_json_extensions: 100,
266 }
267 }
268}
269
270impl ParserLimits {
271 #[must_use]
285 pub const fn strict() -> Self {
286 Self {
287 max_entries: 1_000,
288 max_links_per_feed: 20,
289 max_links_per_entry: 10,
290 max_authors: 5,
291 max_contributors: 5,
292 max_tags: 20,
293 max_content_blocks: 3,
294 max_enclosures: 5,
295 max_namespaces: 20,
296 max_nesting_depth: 50,
297 max_html_nesting_depth: 50,
298 max_text_length: 1024 * 1024, max_feed_size_bytes: 10 * 1024 * 1024, max_attribute_length: 8 * 1024, max_podcast_soundbites: 5,
302 max_podcast_transcripts: 5,
303 max_podcast_funding: 5,
304 max_podcast_persons: 10,
305 max_value_recipients: 5,
306 max_podcast_alternate_enclosures: 5,
307 max_podcast_alternate_enclosure_sources: 3,
308 max_podcast_podroll: 10,
309 max_podcast_social_interact: 5,
310 max_podcast_txt: 5,
311 max_podcast_follow: 5,
312 max_podcast_chat: 5,
313 max_podcast_value_time_splits: 5,
314 max_json_extensions: 20,
315 }
316 }
317
318 #[must_use]
332 pub const fn permissive() -> Self {
333 Self {
334 max_entries: 100_000,
335 max_links_per_feed: 500,
336 max_links_per_entry: 200,
337 max_authors: 100,
338 max_contributors: 100,
339 max_tags: 500,
340 max_content_blocks: 50,
341 max_enclosures: 100,
342 max_namespaces: 500,
343 max_nesting_depth: 200,
344 max_html_nesting_depth: 200,
345 max_text_length: 50 * 1024 * 1024, max_feed_size_bytes: 500 * 1024 * 1024, max_attribute_length: 256 * 1024, max_podcast_soundbites: 50,
349 max_podcast_transcripts: 100,
350 max_podcast_funding: 50,
351 max_podcast_persons: 200,
352 max_value_recipients: 50,
353 max_podcast_alternate_enclosures: 100,
354 max_podcast_alternate_enclosure_sources: 50,
355 max_podcast_podroll: 200,
356 max_podcast_social_interact: 100,
357 max_podcast_txt: 100,
358 max_podcast_follow: 100,
359 max_podcast_chat: 100,
360 max_podcast_value_time_splits: 50,
361 max_json_extensions: 500,
362 }
363 }
364
365 pub const fn check_feed_size(&self, size: usize) -> Result<(), LimitError> {
373 if size > self.max_feed_size_bytes {
374 Err(LimitError::FeedTooLarge {
375 size,
376 max: self.max_feed_size_bytes,
377 })
378 } else {
379 Ok(())
380 }
381 }
382
383 pub const fn check_collection_size(
391 &self,
392 current: usize,
393 limit: usize,
394 name: &'static str,
395 ) -> Result<(), LimitError> {
396 if current >= limit {
397 Err(LimitError::CollectionTooLarge {
398 name,
399 size: current,
400 max: limit,
401 })
402 } else {
403 Ok(())
404 }
405 }
406
407 pub const fn check_nesting_depth(&self, depth: usize) -> Result<(), LimitError> {
413 if depth > self.max_nesting_depth {
414 Err(LimitError::NestingTooDeep {
415 depth,
416 max: self.max_nesting_depth,
417 })
418 } else {
419 Ok(())
420 }
421 }
422
423 pub const fn check_text_length(&self, length: usize) -> Result<(), LimitError> {
429 if length > self.max_text_length {
430 Err(LimitError::TextTooLong {
431 length,
432 max: self.max_text_length,
433 })
434 } else {
435 Ok(())
436 }
437 }
438}
439
440#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
442#[allow(missing_docs)] pub enum LimitError {
444 #[error("Feed size ({size} bytes) exceeds maximum ({max} bytes)")]
446 FeedTooLarge { size: usize, max: usize },
447
448 #[error("Collection '{name}' has {size} items, exceeds maximum ({max})")]
450 CollectionTooLarge {
451 name: &'static str,
452 size: usize,
453 max: usize,
454 },
455
456 #[error("XML nesting depth ({depth}) exceeds maximum ({max})")]
458 NestingTooDeep { depth: usize, max: usize },
459
460 #[error("Text field length ({length} bytes) exceeds maximum ({max} bytes)")]
462 TextTooLong { length: usize, max: usize },
463}
464
465#[cfg(test)]
466mod tests {
467 use super::*;
468
469 #[test]
470 fn test_default_limits() {
471 let limits = ParserLimits::default();
472 assert_eq!(limits.max_entries, 10_000);
473 assert_eq!(limits.max_feed_size_bytes, 100 * 1024 * 1024);
474 }
475
476 #[test]
477 fn test_strict_limits() {
478 let limits = ParserLimits::strict();
479 assert_eq!(limits.max_entries, 1_000);
480 assert!(limits.max_entries < ParserLimits::default().max_entries);
481 }
482
483 #[test]
484 fn test_permissive_limits() {
485 let limits = ParserLimits::permissive();
486 assert_eq!(limits.max_entries, 100_000);
487 assert!(limits.max_entries > ParserLimits::default().max_entries);
488 }
489
490 #[test]
491 fn test_check_feed_size_ok() {
492 let limits = ParserLimits::default();
493 assert!(limits.check_feed_size(1024).is_ok());
494 }
495
496 #[test]
497 fn test_check_feed_size_too_large() {
498 let limits = ParserLimits::default();
499 let result = limits.check_feed_size(200 * 1024 * 1024);
500 assert!(result.is_err());
501 assert!(matches!(result, Err(LimitError::FeedTooLarge { .. })));
502 }
503
504 #[test]
505 fn test_check_collection_size_ok() {
506 let limits = ParserLimits::default();
507 assert!(
508 limits
509 .check_collection_size(50, limits.max_entries, "entries")
510 .is_ok()
511 );
512 }
513
514 #[test]
515 fn test_check_collection_size_too_large() {
516 let limits = ParserLimits::default();
517 let result = limits.check_collection_size(10_001, limits.max_entries, "entries");
518 assert!(result.is_err());
519 assert!(matches!(result, Err(LimitError::CollectionTooLarge { .. })));
520 }
521
522 #[test]
523 fn test_check_nesting_depth_ok() {
524 let limits = ParserLimits::default();
525 assert!(limits.check_nesting_depth(50).is_ok());
526 }
527
528 #[test]
529 fn test_check_nesting_depth_too_deep() {
530 let limits = ParserLimits::default();
531 let result = limits.check_nesting_depth(101);
532 assert!(result.is_err());
533 assert!(matches!(result, Err(LimitError::NestingTooDeep { .. })));
534 }
535
536 #[test]
537 fn test_check_text_length_ok() {
538 let limits = ParserLimits::default();
539 assert!(limits.check_text_length(1024).is_ok());
540 }
541
542 #[test]
543 fn test_check_text_length_too_long() {
544 let limits = ParserLimits::default();
545 let result = limits.check_text_length(20 * 1024 * 1024);
546 assert!(result.is_err());
547 assert!(matches!(result, Err(LimitError::TextTooLong { .. })));
548 }
549
550 #[test]
551 fn test_limit_error_display() {
552 let err = LimitError::FeedTooLarge {
553 size: 200_000_000,
554 max: 100_000_000,
555 };
556 let msg = err.to_string();
557 assert!(msg.contains("200000000"));
558 assert!(msg.contains("100000000"));
559 }
560
561 #[test]
562 fn test_max_value_recipients_default() {
563 let limits = ParserLimits::default();
564 assert_eq!(limits.max_value_recipients, 20);
565 }
566
567 #[test]
568 fn test_max_value_recipients_strict() {
569 let limits = ParserLimits::strict();
570 assert_eq!(limits.max_value_recipients, 5);
571 assert!(limits.max_value_recipients < ParserLimits::default().max_value_recipients);
572 }
573
574 #[test]
575 fn test_max_value_recipients_permissive() {
576 let limits = ParserLimits::permissive();
577 assert_eq!(limits.max_value_recipients, 50);
578 assert!(limits.max_value_recipients > ParserLimits::default().max_value_recipients);
579 }
580
581 #[test]
582 fn test_value_recipients_limit_enforcement() {
583 let limits = ParserLimits::default();
584
585 assert!(
587 limits
588 .check_collection_size(19, limits.max_value_recipients, "value_recipients")
589 .is_ok()
590 );
591
592 assert!(
594 limits
595 .check_collection_size(20, limits.max_value_recipients, "value_recipients")
596 .is_err()
597 );
598
599 let result =
601 limits.check_collection_size(21, limits.max_value_recipients, "value_recipients");
602 assert!(result.is_err());
603 assert!(matches!(result, Err(LimitError::CollectionTooLarge { .. })));
604 }
605
606 #[test]
607 fn test_max_podcast_chat_tiers() {
608 assert_eq!(ParserLimits::default().max_podcast_chat, 20);
609 assert_eq!(ParserLimits::strict().max_podcast_chat, 5);
610 assert_eq!(ParserLimits::permissive().max_podcast_chat, 100);
611 assert!(ParserLimits::strict().max_podcast_chat < ParserLimits::default().max_podcast_chat);
612 assert!(
613 ParserLimits::permissive().max_podcast_chat > ParserLimits::default().max_podcast_chat
614 );
615 }
616
617 #[test]
618 fn test_podcast_chat_limit_enforcement() {
619 let limits = ParserLimits::default();
620 assert!(
621 limits
622 .check_collection_size(19, limits.max_podcast_chat, "chat")
623 .is_ok()
624 );
625 let result = limits.check_collection_size(20, limits.max_podcast_chat, "chat");
626 assert!(result.is_err());
627 assert!(matches!(result, Err(LimitError::CollectionTooLarge { .. })));
628 }
629
630 #[test]
631 fn test_max_podcast_value_time_splits_tiers() {
632 assert_eq!(ParserLimits::default().max_podcast_value_time_splits, 20);
633 assert_eq!(ParserLimits::strict().max_podcast_value_time_splits, 5);
634 assert_eq!(ParserLimits::permissive().max_podcast_value_time_splits, 50);
635 assert!(
636 ParserLimits::strict().max_podcast_value_time_splits
637 < ParserLimits::default().max_podcast_value_time_splits
638 );
639 assert!(
640 ParserLimits::permissive().max_podcast_value_time_splits
641 > ParserLimits::default().max_podcast_value_time_splits
642 );
643 }
644
645 #[test]
646 fn test_podcast_value_time_splits_limit_enforcement() {
647 let limits = ParserLimits::default();
648 assert!(
649 limits
650 .check_collection_size(19, limits.max_podcast_value_time_splits, "time_splits")
651 .is_ok()
652 );
653 let result =
654 limits.check_collection_size(20, limits.max_podcast_value_time_splits, "time_splits");
655 assert!(result.is_err());
656 assert!(matches!(result, Err(LimitError::CollectionTooLarge { .. })));
657 }
658
659 #[test]
660 fn test_max_json_extensions_tiers() {
661 assert_eq!(ParserLimits::default().max_json_extensions, 100);
662 assert_eq!(ParserLimits::strict().max_json_extensions, 20);
663 assert_eq!(ParserLimits::permissive().max_json_extensions, 500);
664 assert!(
665 ParserLimits::strict().max_json_extensions
666 < ParserLimits::default().max_json_extensions
667 );
668 assert!(
669 ParserLimits::permissive().max_json_extensions
670 > ParserLimits::default().max_json_extensions
671 );
672 }
673}