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
48 changes: 48 additions & 0 deletions crates/feedparser-rs-core/src/parser/atom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ fn parse_feed_element(
{
feed.feed.link = Some(link.href.clone());
}
if feed.feed.license.is_none() && link.rel.as_deref() == Some("license")
{
feed.feed.license = Some(link.href.clone());
}
feed.feed
.links
.try_push_limited(link, limits.max_links_per_feed);
Expand Down Expand Up @@ -305,6 +309,9 @@ fn parse_entry(
if entry.link.is_none() && link.rel.as_deref() == Some("alternate") {
entry.link = Some(link.href.clone());
}
if entry.license.is_none() && link.rel.as_deref() == Some("license") {
entry.license = Some(link.href.clone());
}
entry
.links
.try_push_limited(link, limits.max_links_per_entry);
Expand Down Expand Up @@ -926,4 +933,45 @@ mod tests {
assert_eq!(feed.feed.links.len(), 1);
assert_eq!(feed.feed.tags.len(), 1);
}

#[test]
fn test_parse_atom_license_feed() {
let xml = br#"<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Test Feed</title>
<link rel="license" href="https://creativecommons.org/licenses/by/4.0/"/>
<link rel="alternate" href="https://example.com/"/>
</feed>"#;

let feed = parse_atom10(xml).unwrap();
assert_eq!(
feed.feed.license.as_deref(),
Some("https://creativecommons.org/licenses/by/4.0/")
);
assert_eq!(feed.feed.link.as_deref(), Some("https://example.com/"));
}

#[test]
fn test_parse_atom_license_entry() {
let xml = br#"<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<entry>
<title>Licensed Entry</title>
<id>urn:uuid:1</id>
<link rel="license" href="https://creativecommons.org/licenses/by-sa/3.0/"/>
<link rel="alternate" href="https://example.com/entry/1"/>
</entry>
</feed>"#;

let feed = parse_atom10(xml).unwrap();
assert_eq!(feed.entries.len(), 1);
assert_eq!(
feed.entries[0].license.as_deref(),
Some("https://creativecommons.org/licenses/by-sa/3.0/")
);
assert_eq!(
feed.entries[0].link.as_deref(),
Some("https://example.com/entry/1")
);
}
}
41 changes: 41 additions & 0 deletions crates/feedparser-rs-core/src/parser/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,47 @@ pub fn extract_xml_base(
.map(|s| s.to_string())
}

/// Extract xml:lang attribute from element
///
/// Returns the language code if xml:lang or lang attribute exists.
/// Respects `max_attribute_length` limit for `DoS` protection.
///
/// # Arguments
///
/// * `element` - The XML element to extract xml:lang from
/// * `max_attr_length` - Maximum allowed attribute length (`DoS` protection)
///
/// # Returns
///
/// * `Some(String)` - The xml:lang value if found and within length limit
/// * `None` - If attribute not found or exceeds length limit
///
/// # Examples
///
/// ```ignore
/// use feedparser_rs::parser::common::extract_xml_lang;
///
/// let element = /* BytesStart from quick-xml */;
/// if let Some(lang) = extract_xml_lang(&element, 1024) {
/// println!("Language: {}", lang);
/// }
/// ```
pub fn extract_xml_lang(
element: &quick_xml::events::BytesStart,
max_attr_length: usize,
) -> Option<String> {
element
.attributes()
.flatten()
.find(|attr| {
let key = attr.key.as_ref();
key == b"xml:lang" || key == b"lang"
})
.filter(|attr| attr.value.len() <= max_attr_length)
.and_then(|attr| attr.unescape_value().ok())
.map(|s| s.to_string())
}

/// Read text content from current XML element (handles text and CDATA)
pub fn read_text(
reader: &mut Reader<&[u8]>,
Expand Down
Loading