mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-26 03:17:28 -06:00
Merge PR #3511: fix(engine): validate bookmark names by character count and character classes
This commit is contained in:
@@ -467,9 +467,11 @@ fn link(s: &mut Session, v: &Value) -> CmdResult {
|
||||
sel_result(s)
|
||||
}
|
||||
|
||||
/// Whether `name` can name a bookmark: no spaces, at most 40 characters, not starting with a digit.
|
||||
/// Whether `name` can name a bookmark: starts with a letter or underscore, contains only alphanumeric characters
|
||||
/// or underscores, and is at most 40 characters (#3491). Word allows leading underscores for hidden bookmarks (_Toc, _Ref).
|
||||
pub fn bookmark_name_ok(name: &str) -> bool {
|
||||
!name.is_empty() && !name.contains(' ') && name.len() <= 40 && !name.chars().next().is_some_and(|c| c.is_ascii_digit())
|
||||
let mut chars = name.chars();
|
||||
chars.next().is_some_and(|c| c.is_alphabetic() || c == '_') && chars.all(|c| c.is_alphanumeric() || c == '_') && name.chars().count() <= 40
|
||||
}
|
||||
|
||||
/// Where bookmark `name` is: see [`bookmark_span`]. `None` when there is no such bookmark.
|
||||
@@ -1185,6 +1187,47 @@ mod tests {
|
||||
assert_eq!(crate::cmd::references::bookmark_text(&s.doc, "Spot").as_deref(), Some("delta"));
|
||||
}
|
||||
|
||||
/// #3491: Bookmark names are validated by Unicode character count and character classes.
|
||||
#[test]
|
||||
fn bookmark_name_validation_rules() {
|
||||
// Valid names:
|
||||
assert!(bookmark_name_ok("Mark1"));
|
||||
assert!(bookmark_name_ok("A"));
|
||||
assert!(bookmark_name_ok("Alpha_Beta_123"));
|
||||
// Valid leading underscore (Word hidden bookmarks, e.g. _Toc, _Ref, _Hidden):
|
||||
assert!(bookmark_name_ok("_Hidden"));
|
||||
assert!(bookmark_name_ok("_foo"));
|
||||
// Valid 27-character Unicode Cyrillic name (51 UTF-8 bytes):
|
||||
assert!(bookmark_name_ok("быстрый_доступ_к_документам"));
|
||||
// Valid 40-character name:
|
||||
let exactly_40 = "a".repeat(40);
|
||||
assert!(bookmark_name_ok(&exactly_40));
|
||||
let exactly_40_unicode = "я".repeat(40);
|
||||
assert!(bookmark_name_ok(&exactly_40_unicode));
|
||||
|
||||
// Invalid: empty
|
||||
assert!(!bookmark_name_ok(""));
|
||||
// Invalid: leading digit
|
||||
assert!(!bookmark_name_ok("1mark"));
|
||||
// Invalid: symbols / punctuation
|
||||
assert!(!bookmark_name_ok("a&b"));
|
||||
assert!(!bookmark_name_ok("a!b"));
|
||||
assert!(!bookmark_name_ok("a-b"));
|
||||
assert!(!bookmark_name_ok("a.b"));
|
||||
assert!(!bookmark_name_ok("foo bar"));
|
||||
// Invalid: more than 40 characters
|
||||
assert!(!bookmark_name_ok(&"a".repeat(41)));
|
||||
assert!(!bookmark_name_ok(&"я".repeat(41)));
|
||||
|
||||
// Verification through `insert.bookmark` command
|
||||
let mut s = Session::new(wordcraft_doc::Document::from_text("Test content"));
|
||||
assert!(s.run("insert.bookmark", &json!({"name": "быстрый_доступ_к_документам"})).is_ok());
|
||||
assert!(s.run("insert.bookmark", &json!({"name": "_Hidden"})).is_ok());
|
||||
assert!(s.run("insert.bookmark", &json!({"name": "1mark"})).is_err());
|
||||
assert!(s.run("insert.bookmark", &json!({"name": "a&b"})).is_err());
|
||||
assert!(s.run("insert.bookmark", &json!({"name": "a!b"})).is_err());
|
||||
}
|
||||
|
||||
/// Insert › Symbol with a font: only the symbol takes it, the caret's font carries on after.
|
||||
#[test]
|
||||
fn a_mixed_symbol_string_keeps_symbols_and_text_apart() {
|
||||
|
||||
Reference in New Issue
Block a user