Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ jobs:

- name: Run tests with coverage
working-directory: crates/feedparser-rs-node
run: npm test -- --coverage
run: npm run test:coverage
continue-on-error: true

- name: Upload Node.js coverage to codecov
Expand Down
36 changes: 30 additions & 6 deletions crates/feedparser-rs-core/src/http/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,19 @@ impl FeedHttpClient {
}

/// Sets a custom User-Agent header
///
/// # Security
///
/// User-Agent is truncated to 512 bytes to prevent header injection attacks.
#[must_use]
pub fn with_user_agent(mut self, agent: String) -> Self {
self.user_agent = agent;
// Truncate to 512 bytes to prevent header injection
const MAX_USER_AGENT_LEN: usize = 512;
self.user_agent = if agent.len() > MAX_USER_AGENT_LEN {
agent.chars().take(MAX_USER_AGENT_LEN).collect()
} else {
agent
};
self
}

Expand Down Expand Up @@ -125,16 +135,30 @@ impl FeedHttpClient {
HeaderValue::from_static("gzip, deflate, br"),
);

// Conditional GET headers
// Conditional GET headers with length validation
if let Some(etag_val) = etag {
Self::insert_header(&mut headers, IF_NONE_MATCH, etag_val, "ETag")?;
// Truncate ETag to 1KB to prevent oversized headers
const MAX_ETAG_LEN: usize = 1024;
let sanitized_etag = if etag_val.len() > MAX_ETAG_LEN {
&etag_val[..MAX_ETAG_LEN]
} else {
etag_val
};
Self::insert_header(&mut headers, IF_NONE_MATCH, sanitized_etag, "ETag")?;
}

if let Some(modified_val) = modified {
// Truncate Last-Modified to 64 bytes (RFC 822 dates are ~30 bytes)
const MAX_MODIFIED_LEN: usize = 64;
let sanitized_modified = if modified_val.len() > MAX_MODIFIED_LEN {
&modified_val[..MAX_MODIFIED_LEN]
} else {
modified_val
};
Self::insert_header(
&mut headers,
IF_MODIFIED_SINCE,
modified_val,
sanitized_modified,
"Last-Modified",
)?;
}
Expand All @@ -161,8 +185,8 @@ impl FeedHttpClient {
let status = response.status().as_u16();
let url = response.url().to_string();

// Convert headers to HashMap
let mut headers_map = HashMap::new();
// Convert headers to HashMap with pre-allocated capacity
let mut headers_map = HashMap::with_capacity(response.headers().len());
for (name, value) in response.headers() {
if let Ok(val_str) = value.to_str() {
headers_map.insert(name.to_string(), val_str.to_string());
Expand Down
229 changes: 212 additions & 17 deletions crates/feedparser-rs-core/src/parser/rss.rs

Large diffs are not rendered by default.

10 changes: 9 additions & 1 deletion crates/feedparser-rs-core/src/types/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use super::{
common::{
Content, Enclosure, Link, MediaContent, MediaThumbnail, Person, Source, Tag, TextConstruct,
},
podcast::ItunesEntryMeta,
podcast::{ItunesEntryMeta, PodcastPerson, PodcastTranscript},
};
use chrono::{DateTime, Utc};

Expand Down Expand Up @@ -67,6 +67,10 @@ pub struct Entry {
pub media_thumbnails: Vec<MediaThumbnail>,
/// Media RSS content items
pub media_content: Vec<MediaContent>,
/// Podcast 2.0 transcripts for this episode
pub podcast_transcripts: Vec<PodcastTranscript>,
/// Podcast 2.0 persons for this episode (hosts, guests, etc.)
pub podcast_persons: Vec<PodcastPerson>,
}

impl Entry {
Expand All @@ -78,6 +82,8 @@ impl Entry {
/// - 1 author
/// - 2-3 tags
/// - 0-1 enclosures
/// - 2 podcast transcripts (typical for podcasts with multiple languages)
/// - 4 podcast persons (host, co-hosts, guests)
///
/// # Examples
///
Expand All @@ -98,6 +104,8 @@ impl Entry {
dc_subject: Vec::with_capacity(2),
media_thumbnails: Vec::with_capacity(1),
media_content: Vec::with_capacity(1),
podcast_transcripts: Vec::with_capacity(2),
podcast_persons: Vec::with_capacity(4),
..Default::default()
}
}
Expand Down
4 changes: 4 additions & 0 deletions crates/feedparser-rs-node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,9 @@ feedparser-rs-core = { path = "../feedparser-rs-core" }
napi = { workspace = true, features = ["napi9", "error_anyhow"] }
napi-derive = { workspace = true }

[features]
default = ["http"]
http = ["feedparser-rs-core/http"]

[build-dependencies]
napi-build = "2.1"
Loading