diff --git a/crates/engine/src/preset_luminar.rs b/crates/engine/src/preset_luminar.rs
index e88304568..73b008d9f 100644
--- a/crates/engine/src/preset_luminar.rs
+++ b/crates/engine/src/preset_luminar.rs
@@ -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;+41;"), "+65;+41;");
+ assert_eq!(unescape("AA"), "AA");
+ }
+
/// One effect's parameter as Luminar writes it.
fn param(name: &str, v: f64) -> String {
format!("{name}OptionalDataType0Value{v}")
diff --git a/crates/lensdb/src/xml.rs b/crates/lensdb/src/xml.rs
index dc611a647..5d4a39dc8 100644
--- a/crates/lensdb/src/xml.rs
+++ b/crates/lensdb/src/xml.rs
@@ -43,8 +43,10 @@ fn entity(name: &str) -> Option {
_ => {
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) {
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'));
+ }
+}
diff --git a/crates/meta/src/xmp.rs b/crates/meta/src/xmp.rs
index 2dbf59482..6f8a7d402 100644
--- a/crates/meta/src/xmp.rs
+++ b/crates/meta/src/xmp.rs
@@ -203,8 +203,10 @@ fn entity(name: &str) -> Option {
_ => {
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'));
+ }
}