mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-09 04:22:20 +00:00
Track XMP attribute quotes only inside tags (#5271)
This commit is contained in:
1 parent
4ca5fccf9e
commit
475ba82fea
1 file changed
+30
-10
+30
-10
@@ -294,24 +294,31 @@ fn element_span(xmp: &str, name: &str) -> Option<(usize, usize)> {
|
||||
/// start and end of its value, and its quote character. Only matches inside a start tag.
|
||||
fn attribute_span(xmp: &str, name: &str) -> Option<(usize, usize, usize, char)> {
|
||||
let mut from = 0;
|
||||
// The last `<` and `>` before the match, found by one pass over the packet rather than by
|
||||
// searching back from every match.
|
||||
let (mut scanned, mut last_open, mut last_close) = (0, None, None);
|
||||
// Track tag and quote state in one pass rather than searching back from every match.
|
||||
let (mut scanned, mut in_tag, mut quote) = (0, false, None);
|
||||
while let Some(i) = xmp.get(from..).and_then(|s| s.find(name)).map(|i| i + from) {
|
||||
from = i + name.len();
|
||||
let before = xmp.get(..i)?;
|
||||
let gap = xmp.get(scanned..i)?;
|
||||
#[cfg(test)]
|
||||
crate::work(gap.len());
|
||||
if let Some(at) = gap.rfind('<') {
|
||||
last_open = Some(scanned + at);
|
||||
}
|
||||
if let Some(at) = gap.rfind('>') {
|
||||
last_close = Some(scanned + at);
|
||||
for c in gap.chars() {
|
||||
if let Some(q) = quote {
|
||||
if c == q {
|
||||
quote = None;
|
||||
}
|
||||
} else {
|
||||
match c {
|
||||
'<' => in_tag = true,
|
||||
'>' if in_tag => in_tag = false,
|
||||
'\'' | '\"' if in_tag => quote = Some(c),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
scanned = i;
|
||||
// Preceded by whitespace, and inside a tag (the last `<` comes after the last `>`).
|
||||
if !before.ends_with(char::is_whitespace) || last_open < last_close || last_open.is_none() {
|
||||
// Preceded by whitespace, and inside a start tag.
|
||||
if !before.ends_with(char::is_whitespace) || !in_tag || quote.is_some() {
|
||||
continue;
|
||||
}
|
||||
let rest = xmp.get(from..)?;
|
||||
@@ -417,6 +424,19 @@ mod tests {
|
||||
assert!(out.contains("<dc:title><rdf:Alt><rdf:li xml:lang=\"x-default\">New</rdf:li></rdf:Alt></dc:title></rdf:Description>"), "{out}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attribute_like_text_inside_another_quoted_attribute_is_not_a_property() {
|
||||
let packet = "<rdf:Description note='someone wrote dc:title=\"forged\" here'/>";
|
||||
assert_eq!(attribute_span(packet, "dc:title"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apostrophe_in_text_does_not_hide_a_later_attribute() {
|
||||
let packet = "<rdf:Description>don't</rdf:Description><rdf:Description dc:title='real'/>";
|
||||
let found = attribute_span(packet, "dc:title").and_then(|(_, start, end, quote)| packet.get(start..end).map(|value| (value, quote)));
|
||||
assert_eq!(found, Some(("real", '\'')));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hostile_packets_do_not_panic() {
|
||||
for packet in ["", "<", "<dc:title", "<dc:title>", "<dc:title x='", " dc:title=", "<a dc:title=\"", "</rdf:RDF>", "é<dc:title é>"] {
|
||||
|
||||
Reference in new issue
Block a user