@@ -46,69 +46,69 @@ mod escape;
46
46
pub trait ToHtml {
47
47
/// Creates an HTML representation of `self`.
48
48
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 ( )
52
52
}
53
53
54
54
/// Appends an HTML representation of `self` to the given buffer.
55
55
///
56
56
/// Its default implementation just calls `.to_html()`, but you may
57
57
/// 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 )
60
60
}
61
61
}
62
62
63
63
impl ToHtml for str {
64
- fn push_html_to ( & self , buffer : & mut Html ) {
64
+ fn push_html_to ( & self , builder : & mut HtmlBuilder ) {
65
65
// 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 ( ) ) ;
67
67
}
68
68
}
69
69
70
70
impl 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 ) ;
73
73
}
74
74
}
75
75
76
76
impl < ' 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 ) ;
79
79
}
80
80
}
81
81
82
82
impl < ' 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 ) ;
85
85
}
86
86
}
87
87
88
88
impl < ' 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 ) ;
91
91
}
92
92
}
93
93
94
94
impl < ' 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 ) ;
97
97
}
98
98
}
99
99
100
100
impl < 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 ) ;
103
103
}
104
104
}
105
105
106
106
macro_rules! impl_to_html_with_display {
107
107
( $( $ty: ty) * ) => {
108
108
$(
109
109
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}" ) ;
112
112
}
113
113
}
114
114
) *
@@ -123,9 +123,9 @@ macro_rules! impl_to_html_with_itoa {
123
123
( $( $ty: ty) * ) => {
124
124
$(
125
125
impl ToHtml for $ty {
126
- fn push_html_to( & self , buffer : & mut Html ) {
126
+ fn push_html_to( & self , builder : & mut HtmlBuilder ) {
127
127
// 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 ) ;
129
129
}
130
130
}
131
131
) *
@@ -179,8 +179,8 @@ impl_to_html_with_itoa! {
179
179
///
180
180
/// - **I have performance-sensitive rendering code that needs direct
181
181
/// 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.
184
184
///
185
185
/// - **I have special requirements and the other options don't work for
186
186
/// me.**
@@ -274,6 +274,75 @@ impl Html {
274
274
}
275
275
}
276
276
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
+
277
346
/// For internal use only.
278
347
#[ doc( hidden) ]
279
348
pub fn with_capacity ( capacity : usize ) -> Self {
@@ -287,11 +356,6 @@ impl Html {
287
356
value. push_html_to ( self ) ;
288
357
}
289
358
290
- /// Exposes the underlying buffer as a `&str`.
291
- pub fn as_str ( & self ) -> & str {
292
- & self . inner
293
- }
294
-
295
359
/// Exposes the underlying buffer as a `&mut String`.
296
360
///
297
361
/// This is useful for performance-sensitive use cases that need
@@ -300,12 +364,16 @@ impl Html {
300
364
/// # Example
301
365
///
302
366
/// ```rust
303
- /// use maud::Html ;
367
+ /// use maud::{HtmlBuilder, ToHtml} ;
304
368
/// # mod base64 { pub fn encode(_: &mut String, _: &[u8]) {} }
305
369
///
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
+ /// }
309
377
/// }
310
378
/// ```
311
379
///
@@ -323,56 +391,21 @@ impl Html {
323
391
self . inner . to_mut ( )
324
392
}
325
393
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 )
329
399
}
330
400
}
331
401
332
- impl Write for Html {
402
+ impl Write for HtmlBuilder {
333
403
fn write_str ( & mut self , text : & str ) -> fmt:: Result {
334
404
self . push ( text) ;
335
405
Ok ( ( ) )
336
406
}
337
407
}
338
408
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
-
376
409
#[ cfg( feature = "rocket" ) ]
377
410
mod rocket_support {
378
411
extern crate std;
0 commit comments