| d23464c0 Andy McCall ↶ |
1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
use pulldown_cmark::{html, Event, Options, Parser, Tag}; |
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
pub fn render(text: &str, base_file: &str, base_raw: &str) -> String { |
| 10 |
let mut opts = Options::empty(); |
| 11 |
opts.insert(Options::ENABLE_TABLES); |
| 12 |
opts.insert(Options::ENABLE_STRIKETHROUGH); |
| 13 |
opts.insert(Options::ENABLE_TASKLISTS); |
| 14 |
opts.insert(Options::ENABLE_FOOTNOTES); |
| 15 |
opts.insert(Options::ENABLE_HEADING_ATTRIBUTES); |
| 16 |
let parser = Parser::new_ext(text, opts).map(|ev| match ev { |
| 17 |
Event::Start(Tag::Link { link_type, dest_url, title, id }) => { |
| 18 |
Event::Start(Tag::Link { link_type, dest_url: rebase(&dest_url, base_file).into(), title, id }) |
| 19 |
} |
| 20 |
Event::Start(Tag::Image { link_type, dest_url, title, id }) => { |
| 21 |
Event::Start(Tag::Image { link_type, dest_url: rebase(&dest_url, base_raw).into(), title, id }) |
| 22 |
} |
| 23 |
other => other, |
| 24 |
}); |
| 25 |
let mut out = String::with_capacity(text.len() * 2); |
| 26 |
html::push_html(&mut out, parser); |
| 27 |
let mut builder = ammonia::Builder::default(); |
| 28 |
builder.add_generic_attributes(["id", "class"]); |
| 29 |
builder.add_tag_attributes("input", ["type", "checked", "disabled"]); |
| 30 |
builder.add_tags(["input"]); |
| 31 |
builder.link_rel(Some("noopener noreferrer")); |
| 32 |
builder.clean(&out).to_string() |
| 33 |
} |
| 34 |
|
| 35 |
|
| 36 |
fn rebase(url: &str, base: &str) -> String { |
| 37 |
if url.starts_with('#') || url.contains("://") || url.starts_with("mailto:") || url.starts_with('/') || url.starts_with("data:") { |
| 38 |
return url.to_string(); |
| 39 |
} |
| 40 |
let url = url.strip_prefix("./").unwrap_or(url); |
| 41 |
format!("{base}{url}") |
| 42 |
} |
| 43 |
|
| 44 |
#[cfg(test)] |
| 45 |
mod tests { |
| 46 |
use super::*; |
| 47 |
|
| 48 |
#[test] |
| 49 |
fn renders_and_sanitises() { |
| 50 |
let html = render( |
| 51 |
"# Hi\n\nSee [docs](docs/x.md) and  <script>alert(1)</script>\n\n- [ ] task\n", |
| 52 |
"/a/b/file/main/", |
| 53 |
"/a/b/file/main/", |
| 54 |
); |
| 55 |
assert!(html.contains("<h1>Hi</h1>")); |
| 56 |
assert!(html.contains("href=\"/a/b/file/main/docs/x.md\"")); |
| 57 |
assert!(html.contains("src=\"/a/b/file/main/img.png\"")); |
| 58 |
assert!(!html.contains("<script")); |
| 59 |
assert!(html.contains("type=\"checkbox\"")); |
| 60 |
assert!(render("[x](https://e.com) [y](#frag)", "/p/", "/p/").contains("href=\"https://e.com\"")); |
| 61 |
} |
| 62 |
} |