escapade/lib.rs
1#![deny(missing_docs)]
2
3//! escapade - type assisted html safety
4//!
5//!`escapade` is inspired by ActiveSupports SafeBuffer.
6//!
7//! `escapade` provides String concatenation and writing, but automatically escapes any HTML in the data in the process. This prevents accidental unescaped writes to the output.
8//!
9//! The library provides both a String type for HTML-safe concatenation and a writer, wrapping types implementing `Write`.
10//!
11//! The library works with any type that implements `AsRef<str>`.
12mod encode;
13mod entities;
14
15use encode::encode_attribute;
16use encode::encode_attribute_w;
17use std::io::Write;
18use std::io;
19
20/// An escaped string-like value
21///
22/// Escaped wraps a value with the bounds `AsRef<str>`.
23/// It can work on any of those values, but any operations
24/// on them will return `Escaped<String>` and thus allocate.
25pub struct Escaped<T: AsRef<str>> {
26 inner: T
27}
28
29impl<T: AsRef<str>> Escaped<T> {
30 /// Consumes the escaped marker and returns
31 /// the wrapped value.
32 pub fn into_inner(self) -> T {
33 self.inner
34 }
35}
36
37/// Trait marking a value as appendable to `Escaped`
38///
39/// Values marked as `Append` can be appended to `Escaped`.
40pub trait Append<T> {
41 /// Append any string-like value
42 fn append_str(&mut self, string: T);
43}
44
45impl<T: Escapable> Append<T> for Escaped<String> {
46 fn append_str(&mut self, string: T) {
47 self.append_str(string.escape())
48 }
49}
50
51impl<T: AsRef<str>> Append<Escaped<T>> for Escaped<String> {
52 fn append_str(&mut self, string: Escaped<T>) {
53 self.inner.push_str(string.inner.as_ref());
54 }
55}
56
57impl<T: AsRef<str>> Escaped<T> {
58 fn as_ref(&self) -> &str {
59 self.inner.as_ref()
60 }
61}
62
63/// A wrapper for writer automatically escaping text written to it
64pub struct EscapedWriter<T: Write> {
65 inner: T
66}
67
68impl<T: Write> EscapedWriter<T> {
69 /// Create a new `EscapedWriter`
70 pub fn new(inner: T) -> Self {
71 EscapedWriter { inner: inner }
72 }
73
74 /// Consume the writer, returning the wrapped value
75 pub fn into_inner(self) -> T {
76 self.inner
77 }
78}
79
80/// Marks values as escapable
81///
82/// `Escapable` values can either be escaped by hand
83/// or by appending them to either a `SafeWriter` or
84/// an `Escaped` value.
85///
86/// `Escapable` values can also be considered safe
87/// by calling the appropriate value. Safe values
88/// are exempted from further escaping.
89pub trait Escapable: AsRef<str> {
90 /// Escape the value at hand and return
91 /// an escaped String.
92 fn escape(&self) -> Escaped<String>;
93 /// Mark the value as safe, exempting it from
94 /// further escaping.
95 fn safe(&self) -> Escaped<String>;
96}
97
98impl<T: AsRef<str>> Escapable for T {
99 fn escape(&self) -> Escaped<String> {
100 Escaped { inner: encode_attribute(self.as_ref()) }
101 }
102
103 fn safe(&self) -> Escaped<String> {
104 Escaped { inner: self.as_ref().into() }
105 }
106}
107
108/// Escaped writing to buffers
109///
110/// This trait handles writing of different kinds of values
111/// to an `EscapedWriter`. It is intended to be implemented
112/// for `EscapedWriter` for each kind of value that is allowed
113/// to be written to.
114///
115/// The implementor must properly escape the passed value before
116/// writing it.
117pub trait EscapedWrite<T> {
118 /// Write the passed string-like value to the writer,
119 /// returning the writers Result.
120 fn write_str(&mut self, value: T) -> io::Result<()>;
121}
122
123impl<X: AsRef<str>, W: Write> EscapedWrite<Escaped<X>> for EscapedWriter<W> {
124 fn write_str(&mut self, value: Escaped<X>) -> io::Result<()> {
125 self.inner.write_all(value.as_ref().as_bytes())
126 }
127}
128
129impl<'a, X: Escapable, W: Write> EscapedWrite<X> for EscapedWriter<W> {
130 fn write_str(&mut self, value: X) -> io::Result<()> {
131 encode_attribute_w(value.as_ref(), &mut self.inner)
132 }
133}
134