#![cfg(feature = "html")]
use markkit::Markkit;
fn convert(html: &str) -> String {
let m = Markkit::builder().build();
m.extract_html_sync(html).unwrap()
}
#[test]
fn xss_script_tag() {
let md = convert("<p>Hello</p><script>document.cookie</script>");
assert!(!md.contains("document.cookie"));
assert!(!md.contains("<script>"));
}
#[test]
fn xss_event_handler() {
let md = convert(r#"<p onmouseover="alert(1)">Text</p>"#);
assert!(!md.contains("onmouseover"));
assert!(!md.contains("alert"));
assert!(md.contains("Text"));
}
#[test]
fn xss_javascript_url() {
let md = convert(r#"<a href="javascript:alert(1)">Click</a>"#);
assert!(md.contains("Click"));
}
#[test]
fn xss_data_uri_img() {
let md = convert(r#"<img src="data:text/html,<script>alert(1)</script>" alt="xss">"#);
assert!(md.contains("![xss]"));
assert!(!md.starts_with("<script>"));
}
#[test]
fn xss_style_expression() {
let md = convert(r#"<div style="background:url(javascript:alert(1))">text</div>"#);
assert!(!md.contains("javascript:"));
assert!(md.contains("text"));
}
#[test]
fn xss_svg_payload() {
let md = convert(r#"<svg onload="alert(1)"><text>SVG</text></svg>"#);
assert!(!md.contains("onload"));
assert!(!md.contains("alert"));
}
#[test]
fn xss_iframe() {
let md = convert(r#"<p>Before</p><iframe src="evil.com"></iframe><p>After</p>"#);
assert!(!md.contains("iframe"));
assert!(!md.contains("evil.com"));
assert!(md.contains("Before"));
assert!(md.contains("After"));
}
#[test]
fn deeply_nested_elements() {
let mut html = String::new();
for _ in 0..1000 {
html.push_str("<div>");
}
html.push_str("<p>Deep</p>");
for _ in 0..1000 {
html.push_str("</div>");
}
let md = convert(&html);
assert!(md.contains("Deep"));
}
#[test]
fn very_long_text_node() {
let text = "a".repeat(100_000);
let html = format!("<p>{}</p>", text);
let md = convert(&html);
assert_eq!(md.trim().len(), 100_000);
}
#[test]
fn many_elements() {
let mut html = String::new();
for i in 0..1000 {
html.push_str(&format!("<p>Paragraph {}</p>", i));
}
let md = convert(&html);
assert!(md.contains("Paragraph 0"));
assert!(md.contains("Paragraph 999"));
}
#[test]
fn resource_limit_bytes() {
let m = Markkit::builder().max_input_size(100).build();
let html = "x".repeat(200);
let result = m.extract_html_sync(&html);
assert!(result.is_err());
}
#[test]
fn unclosed_tags() {
let md = convert("<p>Unclosed paragraph<p>Another one");
assert!(md.contains("Unclosed paragraph"));
assert!(md.contains("Another one"));
}
#[test]
fn mismatched_tags() {
let md = convert("<p>Start<div>Middle</p>End</div>");
assert!(md.contains("Start"));
}
#[test]
fn empty_tags() {
let md = convert("<p></p><h1></h1><ul></ul>");
assert_eq!(md.trim(), "");
}
#[test]
fn self_closing_tags() {
let md = convert("<p>Before<br/>After</p>");
assert!(md.contains("Before"));
assert!(md.contains("After"));
}
#[test]
fn html_entities() {
let md = convert("<p>5 > 3 & 2 < 4</p>");
assert!(md.contains("5 > 3 & 2 < 4"));
}
#[test]
fn unicode_content() {
let md = convert("<p>日本語テスト 🦀 Ñoño</p>");
assert!(md.contains("日本語テスト"));
assert!(md.contains("🦀"));
assert!(md.contains("Ñoño"));
}
#[test]
fn null_bytes() {
let md = convert("<p>before\0after</p>");
assert!(md.contains("before"));
assert!(md.contains("after"));
}
#[test]
fn depth_limit_200() {
let mut html = String::new();
for _ in 0..200 {
html.push_str("<div>");
}
html.push_str("<p>Deep content</p>");
for _ in 0..200 {
html.push_str("</div>");
}
let md = convert(&html);
assert!(
md.contains("Deep content"),
"Content at depth 200 should still be extracted as text: {:?}",
md
);
}
#[test]
fn depth_limit_exceeded() {
let mut html = String::new();
for _ in 0..250 {
html.push_str("<div>");
}
html.push_str("<p>Very deep content</p>");
for _ in 0..250 {
html.push_str("</div>");
}
let md = convert(&html);
assert!(
md.contains("Very deep content"),
"Content past depth limit should be flattened to text: {:?}",
md
);
}