Skip to content

Commit 2f8ccec

Browse files
committed
feat(media): add Media RSS full attributes and podcast:value parsing
Phase 2 of namespace completion: - Add MediaContent type with full Media RSS spec (medium, bitrate, framerate, expression, isDefault) - Add MediaThumbnail type with NTP time offset support - Add PodcastValue and PodcastValueRecipient types for value-for-value monetization - Implement podcast:value parsing with nested valueRecipient elements - Add max_value_recipients DoS limit (default: 20) - Add SSRF security warnings to MediaContent.url and MediaThumbnail.url - Add media_content_to_enclosure() conversion helper - Add comprehensive unit tests for all new types
1 parent e93d51a commit 2f8ccec

File tree

7 files changed

+888
-24
lines changed

7 files changed

+888
-24
lines changed

crates/feedparser-rs-core/src/limits.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,14 @@ pub struct ParserLimits {
135135
///
136136
/// Default: 50 persons
137137
pub max_podcast_persons: usize,
138+
139+
/// Maximum number of podcast value recipients per feed
140+
///
141+
/// Podcast 2.0 value recipients for payment splitting.
142+
/// Prevents `DoS` from feeds with excessive recipient lists.
143+
///
144+
/// Default: 20 recipients
145+
pub max_value_recipients: usize,
138146
}
139147

140148
impl Default for ParserLimits {
@@ -161,6 +169,7 @@ impl Default for ParserLimits {
161169
max_podcast_transcripts: 20,
162170
max_podcast_funding: 20,
163171
max_podcast_persons: 50,
172+
max_value_recipients: 20,
164173
}
165174
}
166175
}
@@ -199,6 +208,7 @@ impl ParserLimits {
199208
max_podcast_transcripts: 5,
200209
max_podcast_funding: 5,
201210
max_podcast_persons: 10,
211+
max_value_recipients: 5,
202212
}
203213
}
204214

@@ -235,6 +245,7 @@ impl ParserLimits {
235245
max_podcast_transcripts: 100,
236246
max_podcast_funding: 50,
237247
max_podcast_persons: 200,
248+
max_value_recipients: 50,
238249
}
239250
}
240251

@@ -433,4 +444,44 @@ mod tests {
433444
assert!(msg.contains("200000000"));
434445
assert!(msg.contains("100000000"));
435446
}
447+
448+
#[test]
449+
fn test_max_value_recipients_default() {
450+
let limits = ParserLimits::default();
451+
assert_eq!(limits.max_value_recipients, 20);
452+
}
453+
454+
#[test]
455+
fn test_max_value_recipients_strict() {
456+
let limits = ParserLimits::strict();
457+
assert_eq!(limits.max_value_recipients, 5);
458+
assert!(limits.max_value_recipients < ParserLimits::default().max_value_recipients);
459+
}
460+
461+
#[test]
462+
fn test_max_value_recipients_permissive() {
463+
let limits = ParserLimits::permissive();
464+
assert_eq!(limits.max_value_recipients, 50);
465+
assert!(limits.max_value_recipients > ParserLimits::default().max_value_recipients);
466+
}
467+
468+
#[test]
469+
fn test_value_recipients_limit_enforcement() {
470+
let limits = ParserLimits::default();
471+
472+
// Within limit
473+
assert!(limits
474+
.check_collection_size(19, limits.max_value_recipients, "value_recipients")
475+
.is_ok());
476+
477+
// At limit
478+
assert!(limits
479+
.check_collection_size(20, limits.max_value_recipients, "value_recipients")
480+
.is_err());
481+
482+
// Exceeds limit
483+
let result = limits.check_collection_size(21, limits.max_value_recipients, "value_recipients");
484+
assert!(result.is_err());
485+
assert!(matches!(result, Err(LimitError::CollectionTooLarge { .. })));
486+
}
436487
}

0 commit comments

Comments
 (0)