@@ -46,69 +46,69 @@ mod escape;
4646pub trait ToHtml {
4747 /// Creates an HTML representation of `self`.
4848 fn to_html ( & self ) -> Html {
49- let mut buffer = Html :: default ( ) ;
50- self . push_html_to ( & mut buffer ) ;
51- buffer
49+ let mut builder = HtmlBuilder :: new ( ) ;
50+ self . push_html_to ( & mut builder ) ;
51+ builder . finalize ( )
5252 }
5353
5454 /// Appends an HTML representation of `self` to the given buffer.
5555 ///
5656 /// Its default implementation just calls `.to_html()`, but you may
5757 /// override it with something more efficient.
58- fn push_html_to ( & self , buffer : & mut Html ) {
59- self . to_html ( ) . push_html_to ( buffer )
58+ fn push_html_to ( & self , builder : & mut HtmlBuilder ) {
59+ self . to_html ( ) . push_html_to ( builder )
6060 }
6161}
6262
6363impl ToHtml for str {
64- fn push_html_to ( & self , buffer : & mut Html ) {
64+ fn push_html_to ( & self , builder : & mut HtmlBuilder ) {
6565 // XSS-Safety: Special characters will be escaped by `escape_to_string`.
66- escape:: escape_to_string ( self , buffer . as_mut_string_unchecked ( ) ) ;
66+ escape:: escape_to_string ( self , builder . as_mut_string_unchecked ( ) ) ;
6767 }
6868}
6969
7070impl ToHtml for String {
71- fn push_html_to ( & self , buffer : & mut Html ) {
72- str:: push_html_to ( self , buffer ) ;
71+ fn push_html_to ( & self , builder : & mut HtmlBuilder ) {
72+ str:: push_html_to ( self , builder ) ;
7373 }
7474}
7575
7676impl < ' a > ToHtml for Cow < ' a , str > {
77- fn push_html_to ( & self , buffer : & mut Html ) {
78- str:: push_html_to ( self , buffer ) ;
77+ fn push_html_to ( & self , builder : & mut HtmlBuilder ) {
78+ str:: push_html_to ( self , builder ) ;
7979 }
8080}
8181
8282impl < ' a > ToHtml for Arguments < ' a > {
83- fn push_html_to ( & self , buffer : & mut Html ) {
84- let _ = buffer . write_fmt ( * self ) ;
83+ fn push_html_to ( & self , builder : & mut HtmlBuilder ) {
84+ let _ = builder . write_fmt ( * self ) ;
8585 }
8686}
8787
8888impl < ' a , T : ToHtml + ?Sized > ToHtml for & ' a T {
89- fn push_html_to ( & self , buffer : & mut Html ) {
90- T :: push_html_to ( self , buffer ) ;
89+ fn push_html_to ( & self , builder : & mut HtmlBuilder ) {
90+ T :: push_html_to ( self , builder ) ;
9191 }
9292}
9393
9494impl < ' a , T : ToHtml + ?Sized > ToHtml for & ' a mut T {
95- fn push_html_to ( & self , buffer : & mut Html ) {
96- T :: push_html_to ( self , buffer ) ;
95+ fn push_html_to ( & self , builder : & mut HtmlBuilder ) {
96+ T :: push_html_to ( self , builder ) ;
9797 }
9898}
9999
100100impl < T : ToHtml + ?Sized > ToHtml for Box < T > {
101- fn push_html_to ( & self , buffer : & mut Html ) {
102- T :: push_html_to ( self , buffer ) ;
101+ fn push_html_to ( & self , builder : & mut HtmlBuilder ) {
102+ T :: push_html_to ( self , builder ) ;
103103 }
104104}
105105
106106macro_rules! impl_to_html_with_display {
107107 ( $( $ty: ty) * ) => {
108108 $(
109109 impl ToHtml for $ty {
110- fn push_html_to( & self , buffer : & mut Html ) {
111- let _ = write!( buffer , "{self}" ) ;
110+ fn push_html_to( & self , builder : & mut HtmlBuilder ) {
111+ let _ = write!( builder , "{self}" ) ;
112112 }
113113 }
114114 ) *
@@ -123,9 +123,9 @@ macro_rules! impl_to_html_with_itoa {
123123 ( $( $ty: ty) * ) => {
124124 $(
125125 impl ToHtml for $ty {
126- fn push_html_to( & self , buffer : & mut Html ) {
126+ fn push_html_to( & self , builder : & mut HtmlBuilder ) {
127127 // XSS-Safety: The characters '0' through '9', and '-', are HTML safe.
128- let _ = itoa:: fmt( buffer . as_mut_string_unchecked( ) , * self ) ;
128+ let _ = itoa:: fmt( builder . as_mut_string_unchecked( ) , * self ) ;
129129 }
130130 }
131131 ) *
@@ -179,8 +179,8 @@ impl_to_html_with_itoa! {
179179///
180180/// - **I have performance-sensitive rendering code that needs direct
181181/// access to the buffer.**
182- /// - Use [`Html ::as_mut_string_unchecked`], and ask a security
183- /// expert for review.
182+ /// - Use [`HtmlBuilder ::as_mut_string_unchecked`], and ask a
183+ /// security expert for review.
184184///
185185/// - **I have special requirements and the other options don't work for
186186/// me.**
@@ -274,6 +274,75 @@ impl Html {
274274 }
275275 }
276276
277+ /// Exposes the underlying buffer as a `&str`.
278+ pub fn as_str ( & self ) -> & str {
279+ & self . inner
280+ }
281+
282+ /// Converts the inner value to a `String`.
283+ pub fn into_string ( self ) -> String {
284+ self . inner . into_owned ( )
285+ }
286+ }
287+
288+ impl ToHtml for Html {
289+ fn push_html_to ( & self , builder : & mut HtmlBuilder ) {
290+ // XSS-Safety: `self` is already guaranteed to be trusted HTML.
291+ builder. as_mut_string_unchecked ( ) . push_str ( self . as_str ( ) ) ;
292+ }
293+ }
294+
295+ impl From < Html > for String {
296+ fn from ( html : Html ) -> String {
297+ html. into_string ( )
298+ }
299+ }
300+
301+ /// The literal string `<!DOCTYPE html>`.
302+ ///
303+ /// # Example
304+ ///
305+ /// A minimal web page:
306+ ///
307+ /// ```rust
308+ /// use maud::{DOCTYPE, html};
309+ ///
310+ /// let page = html! {
311+ /// (DOCTYPE)
312+ /// html {
313+ /// head {
314+ /// meta charset="utf-8";
315+ /// title { "Test page" }
316+ /// }
317+ /// body {
318+ /// p { "Hello, world!" }
319+ /// }
320+ /// }
321+ /// };
322+ /// ```
323+ pub const DOCTYPE : Html = Html :: from_const_unchecked ( "<!DOCTYPE html>" ) ;
324+
325+ /// A partially created fragment of HTML.
326+ ///
327+ /// Unlike [`Html`], an `HtmlBuilder` might have unclosed elements or
328+ /// attributes.
329+ ///
330+ /// This type cannot be constructed by hand. The [`html!`] macro creates
331+ /// one internally, and passes it to [`ToHtml::push_html_to`].
332+ #[ derive( Clone , Debug ) ]
333+ pub struct HtmlBuilder {
334+ inner : Cow < ' static , str > ,
335+ }
336+
337+ impl HtmlBuilder {
338+ /// For internal use only.
339+ #[ doc( hidden) ]
340+ pub fn new ( ) -> Self {
341+ Self {
342+ inner : Cow :: Owned ( String :: new ( ) ) ,
343+ }
344+ }
345+
277346 /// For internal use only.
278347 #[ doc( hidden) ]
279348 pub fn with_capacity ( capacity : usize ) -> Self {
@@ -287,11 +356,6 @@ impl Html {
287356 value. push_html_to ( self ) ;
288357 }
289358
290- /// Exposes the underlying buffer as a `&str`.
291- pub fn as_str ( & self ) -> & str {
292- & self . inner
293- }
294-
295359 /// Exposes the underlying buffer as a `&mut String`.
296360 ///
297361 /// This is useful for performance-sensitive use cases that need
@@ -300,12 +364,16 @@ impl Html {
300364 /// # Example
301365 ///
302366 /// ```rust
303- /// use maud::Html ;
367+ /// use maud::{HtmlBuilder, ToHtml} ;
304368 /// # mod base64 { pub fn encode(_: &mut String, _: &[u8]) {} }
305369 ///
306- /// fn append_base64_to_html(buffer: &mut Html, bytes: &[u8]) {
307- /// // XSS-Safety: The characters [A-Za-z0-9+/=] are all HTML-safe.
308- /// base64::encode(buffer.as_mut_string_unchecked(), bytes);
370+ /// struct Base64<'a>(&'a [u8]);
371+ ///
372+ /// impl<'a> ToHtml for Base64<'a> {
373+ /// fn push_html_to(&self, builder: &mut HtmlBuilder) {
374+ /// // XSS-Safety: The characters [A-Za-z0-9+/=] are all HTML-safe.
375+ /// base64::encode(builder.as_mut_string_unchecked(), self.0);
376+ /// }
309377 /// }
310378 /// ```
311379 ///
@@ -323,56 +391,21 @@ impl Html {
323391 self . inner . to_mut ( )
324392 }
325393
326- /// Converts the inner value to a `String`.
327- pub fn into_string ( self ) -> String {
328- self . inner . into_owned ( )
394+ /// For internal use only.
395+ #[ doc( hidden) ]
396+ pub fn finalize ( self ) -> Html {
397+ // XSS-Safety: This is called from the `html!` macro, which enforces safety itself.
398+ Html :: from_unchecked ( self . inner )
329399 }
330400}
331401
332- impl Write for Html {
402+ impl Write for HtmlBuilder {
333403 fn write_str ( & mut self , text : & str ) -> fmt:: Result {
334404 self . push ( text) ;
335405 Ok ( ( ) )
336406 }
337407}
338408
339- impl ToHtml for Html {
340- fn push_html_to ( & self , buffer : & mut Html ) {
341- // XSS-Safety: `self` is already guaranteed to be trusted HTML.
342- buffer. as_mut_string_unchecked ( ) . push_str ( self . as_str ( ) ) ;
343- }
344- }
345-
346- impl From < Html > for String {
347- fn from ( html : Html ) -> String {
348- html. into_string ( )
349- }
350- }
351-
352- /// The literal string `<!DOCTYPE html>`.
353- ///
354- /// # Example
355- ///
356- /// A minimal web page:
357- ///
358- /// ```rust
359- /// use maud::{DOCTYPE, html};
360- ///
361- /// let page = html! {
362- /// (DOCTYPE)
363- /// html {
364- /// head {
365- /// meta charset="utf-8";
366- /// title { "Test page" }
367- /// }
368- /// body {
369- /// p { "Hello, world!" }
370- /// }
371- /// }
372- /// };
373- /// ```
374- pub const DOCTYPE : Html = Html :: from_const_unchecked ( "<!DOCTYPE html>" ) ;
375-
376409#[ cfg( feature = "rocket" ) ]
377410mod rocket_support {
378411 extern crate std;
0 commit comments