1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
use std::fmt::{self, Display, Write};

struct EscapingWriter<'a> {
    inner: &'a mut Write
}

impl<'a> EscapingWriter<'a> {
    fn new(inner: &'a mut Write) -> EscapingWriter<'a> {
        EscapingWriter { inner: inner }
    }
}

impl<'a> Write for EscapingWriter<'a> {
    fn write_str(&mut self, buf: &str) -> fmt::Result {
        // Sneaky use of String::split, capturing the separator:

        let mut separator = '_';
        for part in buf.split(|x| { separator = x; (x == '<') || (x == '&') || (x == '\'') || (x == '"') }) {
            self.inner.write_str(part)?;

            match separator {
                '<' => self.inner.write_str("&lt;"),
                '&' => self.inner.write_str("&amp;"),
                '\'' => self.inner.write_str("&apos;"),
                '"' => self.inner.write_str("&quot;"),
                _ => Ok(()),
            }?;
        }

        Ok(())
    }
}

pub trait DisplayHtmlSafe {
    fn safe_fmt(&self, &mut fmt::Formatter) -> fmt::Result;
}

impl<T: Display> DisplayHtmlSafe for T {
    #[cfg(feature = "specialization")]
    default fn safe_fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let mut escaping_writer = EscapingWriter::new(f);
        write!(&mut escaping_writer, "{}", &self)
    }

    #[cfg(not(feature = "specialization"))]
    fn safe_fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let mut escaping_writer = EscapingWriter::new(f);
        write!(&mut escaping_writer, "{}", &self)
    }
}

macro_rules! display_is_html_safe {
    ($x : ident) => {
        #[cfg(feature = "specialization")]
        impl DisplayHtmlSafe for $x {
            fn safe_fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
                Display::fmt(&self, f)
            }
        }
    }
}

display_is_html_safe!(u8);
display_is_html_safe!(i8);
display_is_html_safe!(u16);
display_is_html_safe!(i16);
display_is_html_safe!(u32);
display_is_html_safe!(i32);
display_is_html_safe!(u64);
display_is_html_safe!(i64);
display_is_html_safe!(usize);
display_is_html_safe!(isize);

display_is_html_safe!(f32);
display_is_html_safe!(f64);

display_is_html_safe!(bool);

#[cfg(test)]
mod test {
    use super::*;

    struct Fake<'a> { text: &'a str }
    impl<'a> Display for Fake<'a> {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            self.text.safe_fmt(f)
        }
    }

    #[test]
    fn it_works() {
        assert_eq!(
            " &lt; &amp; &quot; &apos; ",
            format!("{}", Fake { text: " < & \" ' " })
        );
    }

    #[test]
    fn it_handles_tight_packed_string() {
        assert_eq!(
            "&lt;&amp;&quot;&apos;",
            format!("{}", Fake { text: "<&\"'" })
        );
    }
}