use std::collections::HashMap;
use rdocx_html::{HtmlInput, HtmlOptions, ImageData, to_html_fragment, to_markdown};
use rdocx_oxml::document::CT_Document;
use rdocx_oxml::properties::{CT_RPr, CT_Shd};
use rdocx_oxml::styles::CT_Styles;
use rdocx_oxml::text::{CT_P, CT_R, HyperlinkSpan};
fn input_from(doc: CT_Document, hyperlink_urls: HashMap<String, String>) -> HtmlInput {
HtmlInput {
document: doc,
styles: CT_Styles::new_default(),
numbering: None,
images: HashMap::new(),
hyperlink_urls,
}
}
fn linked_paragraph(url: &str) -> HtmlInput {
let mut doc = CT_Document::new();
let mut p = CT_P::new();
p.runs.push(CT_R::new("click me"));
p.hyperlinks.push(HyperlinkSpan {
rel_id: Some("rId7".to_string()),
anchor: None,
run_start: 0,
run_end: 1,
});
doc.body.add_paragraph(p);
input_from(doc, HashMap::from([("rId7".to_string(), url.to_string())]))
}
#[test]
fn dangerous_url_schemes_are_dropped() {
for url in [
"javascript:alert(1)",
"JaVaScRiPt:alert(1)",
" javascript:alert(1)",
"data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==",
"vbscript:msgbox(1)",
] {
let input = linked_paragraph(url);
let html = to_html_fragment(&input, &HtmlOptions::default());
let markdown = to_markdown(&input);
assert!(!html.contains("<a "), "emitted a link for {url}: {html}");
assert!(!markdown.contains(url), "markdown kept {url}: {markdown}");
assert!(html.contains("click me"));
assert!(markdown.contains("click me"));
}
}
#[test]
fn ordinary_url_schemes_still_link() {
for url in [
"https://example.com/a?b=c",
"http://example.com",
"mailto:someone@example.com",
"#internal-anchor",
"relative/page.html",
] {
let input = linked_paragraph(url);
let html = to_html_fragment(&input, &HtmlOptions::default());
assert!(html.contains("<a href="), "no link for {url}: {html}");
}
}
#[test]
fn font_names_cannot_escape_the_style_attribute() {
let mut doc = CT_Document::new();
let mut p = CT_P::new();
let mut run = CT_R::new("styled");
run.properties = Some(CT_RPr {
font_ascii: Some("Arial\" onmouseover=\"alert(1)".to_string()),
..Default::default()
});
p.runs.push(run);
doc.body.add_paragraph(p);
let html = to_html_fragment(&input_from(doc, HashMap::new()), &HtmlOptions::default());
assert!(!html.contains("onmouseover"), "{html}");
assert!(html.contains("styled"));
}
#[test]
fn colours_must_be_hex_to_reach_the_output() {
let mut doc = CT_Document::new();
let mut p = CT_P::new();
let mut run = CT_R::new("shaded");
run.properties = Some(CT_RPr {
color: Some("red;} body{display:none} .x{".to_string()),
shading: Some(CT_Shd {
val: "clear".to_string(),
color: None,
fill: Some("\"><script>alert(1)</script>".to_string()),
}),
..Default::default()
});
p.runs.push(run);
doc.body.add_paragraph(p);
let html = to_html_fragment(&input_from(doc, HashMap::new()), &HtmlOptions::default());
assert!(!html.contains("<script"), "{html}");
assert!(!html.contains("display:none"), "{html}");
assert!(html.contains("shaded"));
}
#[test]
fn valid_colours_are_preserved() {
let mut doc = CT_Document::new();
let mut p = CT_P::new();
let mut run = CT_R::new("red text");
run.properties = Some(CT_RPr {
color: Some("FF0000".to_string()),
..Default::default()
});
p.runs.push(run);
doc.body.add_paragraph(p);
let html = to_html_fragment(&input_from(doc, HashMap::new()), &HtmlOptions::default());
assert!(html.contains("color:#FF0000"), "{html}");
}
#[test]
fn image_content_type_is_escaped() {
let mut doc = CT_Document::new();
let mut p = CT_P::new();
p.runs.push(CT_R::new("caption"));
doc.body.add_paragraph(p);
let mut input = input_from(doc, HashMap::new());
input.images.insert(
"rId1".to_string(),
ImageData {
data: vec![1, 2, 3],
content_type: "image/png\"><script>alert(1)</script>".to_string(),
},
);
let html = to_html_fragment(&input, &HtmlOptions::default());
assert!(!html.contains("<script"), "{html}");
}
#[test]
fn text_content_is_escaped() {
let mut doc = CT_Document::new();
let mut p = CT_P::new();
p.runs
.push(CT_R::new("<script>alert('xss')</script> & more"));
doc.body.add_paragraph(p);
let html = to_html_fragment(&input_from(doc, HashMap::new()), &HtmlOptions::default());
assert!(!html.contains("<script"), "{html}");
assert!(html.contains("<script>"), "{html}");
assert!(html.contains("& more"), "{html}");
}