test(examples): forbid authored browser javascript

req: examples/005
This commit is contained in:
slhx agent
2026-05-11 08:11:06 +02:00
parent 816552e735
commit 0de4c82a16
2 changed files with 73 additions and 0 deletions
+70
View File
@@ -0,0 +1,70 @@
use std::path::{Path, PathBuf};
#[test]
fn canonical_examples_do_not_author_browser_javascript() {
// req: examples/005
let root = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap();
let examples = root.join("examples");
let mut failures = Vec::new();
scan_examples(&examples, &mut |path, text| {
if path.components().any(|part| part.as_os_str() == "tests") {
return;
}
for (line_no, line) in text.lines().enumerate() {
let trimmed = line.trim();
if trimmed.contains("<script") && !trimmed.contains(r#"<script src="/slhx.js" defer></script>"#) {
failures.push(format!("{}:{}: inline <script> is not allowed", path.display(), line_no + 1));
}
if contains_inline_event_handler(trimmed) {
failures.push(format!("{}:{}: inline on*= handler is not allowed", path.display(), line_no + 1));
}
if trimmed.to_ascii_lowercase().contains("javascript:") {
failures.push(format!("{}:{}: javascript: URL is not allowed", path.display(), line_no + 1));
}
}
});
assert!(failures.is_empty(), "{}", failures.join("\n"));
}
fn scan_examples(dir: &Path, visit: &mut impl FnMut(&Path, &str)) {
for entry in std::fs::read_dir(dir).unwrap() {
let entry = entry.unwrap();
let path = entry.path();
if path.is_dir() {
scan_examples(&path, visit);
continue;
}
if !is_example_source(&path) {
continue;
}
let text = std::fs::read_to_string(&path).unwrap();
visit(&path, &text);
}
}
fn is_example_source(path: &PathBuf) -> bool {
matches!(path.extension().and_then(|ext| ext.to_str()), Some("rs" | "heml" | "html"))
}
fn contains_inline_event_handler(line: &str) -> bool {
let bytes = line.as_bytes();
let mut i = 0;
while i + 3 < bytes.len() {
let boundary = bytes[i].is_ascii_whitespace() || bytes[i] == b'<';
if boundary && bytes[i + 1] == b'o' && bytes[i + 2] == b'n' && bytes[i + 3].is_ascii_lowercase() {
let mut j = i + 4;
while j < bytes.len() && bytes[j].is_ascii_lowercase() {
j += 1;
}
while j < bytes.len() && bytes[j].is_ascii_whitespace() {
j += 1;
}
if j < bytes.len() && bytes[j] == b'=' {
return true;
}
}
i += 1;
}
false
}