From 0b9405d7e9f150d7f7abef429d7e7aeff44d6473 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=90=E5=B0=8F=E9=B8=AD?= Date: Sun, 16 Aug 2026 00:42:33 +0800 Subject: [PATCH] fix(site-replication): validate tombstone children structurally MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Second review round: a well-delimited root could still smuggle malformed content — e.g. passed the no- bool { let Some(body) = rest[open_end + 1..].trim_end().strip_suffix("") else { return false; }; - !body.contains("…), none of + // them a Rule. A dangling open tag or stray text is malformed, not a + // tombstone. + let mut body = body.trim(); + while !body.is_empty() { + let Some(rest) = body.strip_prefix('<') else { + return false; + }; + let name_len = rest.bytes().take_while(|byte| byte.is_ascii_alphanumeric()).count(); + if name_len == 0 { + return false; + } + let name = &rest[..name_len]; + if name == "Rule" { + return false; + } + let after_name = &rest[name_len..]; + if let Some(tail) = after_name.trim_start().strip_prefix("/>") { + body = tail.trim_start(); + continue; + } + let Some(content_start) = after_name.find('>') else { + return false; + }; + let content = &after_name[content_start + 1..]; + let close = format!(""); + let Some(content_end) = content.find(&close) else { + return false; + }; + if content[..content_end].contains('<') { + return false; + } + body = content[content_end + close.len()..].trim_start(); + } + true } /// The ILM expiry statement this site contributes to its SRInfo bucket entry @@ -15027,6 +15062,22 @@ mod tests { assert!(!is_zero_rule_lifecycle_tombstone(b"garbage")); assert!(!is_zero_rule_lifecycle_tombstone(b"")); assert!(!is_zero_rule_lifecycle_tombstone(b"")); + // Malformed children inside a well-delimited root are still rejected + // (second review round): a dangling open tag, stray text, an + // unclosed child, or nested markup is not a tombstone. + assert!(!is_zero_rule_lifecycle_tombstone( + b"" + )); + assert!(!is_zero_rule_lifecycle_tombstone( + b"stray text" + )); + assert!(!is_zero_rule_lifecycle_tombstone( + b"" + )); + // A self-closing non-Rule child stays acceptable. + assert!(is_zero_rule_lifecycle_tombstone( + b"" + )); } /// The staleness axis an incoming lc-config item must beat: the expiry