Reject signed numeric character references in XML entity decoding

Found while testing crates/meta/src/xmp.rs:184 (entity); the same pattern is in crates/lensdb/src/xml.rs:45 and crates/engine/src/preset_luminar.rs:90

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
NetterSilver
2026-10-24 06:02:56 +00:00
co-authored by Claude Haiku 5.5
parent 4ae3089420
commit e0519d4300
3 changed files with 37 additions and 5 deletions
+8 -1
View File
@@ -89,8 +89,9 @@ fn unescape(t: &str) -> String {
_ => ent
.strip_prefix("#x")
.or_else(|| ent.strip_prefix("#X"))
.filter(|h| h.bytes().all(|b| b.is_ascii_hexdigit()))
.and_then(|h| u32::from_str_radix(h, 16).ok())
.or_else(|| ent.strip_prefix('#').and_then(|d| d.parse().ok()))
.or_else(|| ent.strip_prefix('#').filter(|d| d.bytes().all(|b| b.is_ascii_digit())).and_then(|d| d.parse().ok()))
.and_then(char::from_u32),
};
match c {
@@ -485,6 +486,12 @@ mod tests {
use crate::preset_import::read_presets;
use serde_json::json;
#[test]
fn unescape_rejects_signed_numeric_references() {
assert_eq!(unescape("&#+65;&#x+41;"), "&#+65;&#x+41;");
assert_eq!(unescape("&#65;&#x41;"), "AA");
}
/// One effect's parameter as Luminar writes it.
fn param(name: &str, v: f64) -> String {
format!("<key>{name}</key><dict><key>OptionalDataType</key><integer>0</integer><key>Value</key><real>{v}</real></dict>")
+17 -2
View File
@@ -43,8 +43,10 @@ fn entity(name: &str) -> Option<char> {
_ => {
let n = name.strip_prefix('#')?;
let v = match n.strip_prefix('x') {
Some(h) => u32::from_str_radix(h, 16).ok()?,
None => n.parse().ok()?,
Some(h) if h.bytes().all(|b| b.is_ascii_hexdigit()) => u32::from_str_radix(h, 16).ok()?,
Some(_) => return None,
None if n.bytes().all(|b| b.is_ascii_digit()) => n.parse().ok()?,
None => return None,
};
char::from_u32(v)?
}
@@ -134,3 +136,16 @@ fn close(stack: &mut Vec<Node>) {
p.children.push(n);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn entity_rejects_signed_numeric_references() {
assert_eq!(entity("#+65"), None);
assert_eq!(entity("#x+41"), None);
assert_eq!(entity("#65"), Some('A'));
assert_eq!(entity("#x41"), Some('A'));
}
}
+12 -2
View File
@@ -203,8 +203,10 @@ fn entity(name: &str) -> Option<char> {
_ => {
let n = name.strip_prefix('#')?;
let v = match n.strip_prefix(['x', 'X']) {
Some(h) => u32::from_str_radix(h, 16).ok()?,
None => n.parse().ok()?,
Some(h) if h.bytes().all(|b| b.is_ascii_hexdigit()) => u32::from_str_radix(h, 16).ok()?,
Some(_) => return None,
None if n.bytes().all(|b| b.is_ascii_digit()) => n.parse().ok()?,
None => return None,
};
char::from_u32(v).filter(|&c| is_xml_char(c))?
}
@@ -1171,4 +1173,12 @@ mod tests {
assert_eq!(entity("#x41"), Some('A'));
assert_eq!(entity("#9"), Some('\t'));
}
#[test]
fn entity_rejects_signed_numeric_references() {
assert_eq!(entity("#+65"), None);
assert_eq!(entity("#x+41"), None);
assert_eq!(entity("#65"), Some('A'));
assert_eq!(entity("#x41"), Some('A'));
}
}