pub fn escape(input: &str) -> String {
let mut out = String::with_capacity(input.len() + 8);
for ch in input.chars() {
match ch {
'&' => out.push_str("&"),
'<' => out.push_str("<"),
'>' => out.push_str(">"),
'\\' | '`' | '*' | '_' | '[' | ']' | '|' => {
out.push('\\');
out.push(ch);
}
c if c.is_control() => out.push(' '),
c => out.push(c),
}
}
out
}
pub fn escape_code(input: &str) -> String {
let mut out = String::with_capacity(input.len());
for c in input.chars() {
match c {
'`' => out.push('\''),
'<' => out.push_str("<"),
'>' => out.push_str(">"),
'&' => out.push_str("&"),
c if c.is_control() => out.push(' '),
c => out.push(c),
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn neutralizes_html() {
let out = escape("<img src=x onerror=alert(1)>");
assert!(!out.contains('<'));
assert!(!out.contains('>'));
assert!(out.contains("<img"));
}
#[test]
fn strips_newlines() {
let out = escape("line1\n## injected heading");
assert!(!out.contains('\n'));
}
#[test]
fn code_span_safe() {
let out = escape_code("evil`code`span");
assert!(!out.contains('`'));
}
}