mirror of
https://github.com/storytold/lightcraft.git
synced 2026-11-11 16:31:32 -06:00
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:
co-authored by
Claude Haiku 5.5
parent
4ae3089420
commit
e0519d4300
@@ -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("AA"), "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>")
|
||||
|
||||
@@ -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
@@ -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'));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user