From 5942c09d700d2621edda0918c118ba61a97acf42 Mon Sep 17 00:00:00 2001 From: Jonathan Cornaz Date: Thu, 31 Oct 2024 19:08:27 +0100 Subject: [PATCH] encode attribute values --- CHANGELOG.md | 5 +++-- src/attributes.rs | 6 +++++- src/lib.rs | 9 +++++++-- src/nodes.rs | 7 +++++++ tests/render_spec.rs | 25 +++++++++++++++++++++---- 5 files changed, 43 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d59e8a4..a4641cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,8 +10,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). * Escape HTML strings * `id` and `class` attributes * Nodes - * Html document: `head` and `body` - * Text `h1`, `h2`, `h3`, `h4`, `h5`, `h6` and `text` + * Html document (`head` and `body`) + * Text (`h1`, `h2`, `h3`, `h4`, `h5`, `h6` and `text`) + * Container (`div`) * `Node`, `Attribute` and `Document` types [Unreleased]: https://github.com/jcornaz/fun-html/compare/...HEAD diff --git a/src/attributes.rs b/src/attributes.rs index dc8b4ad..1cf5f0a 100644 --- a/src/attributes.rs +++ b/src/attributes.rs @@ -2,8 +2,12 @@ use std::borrow::Cow; use crate::Attribute; +pub fn attr(key: &'static str, value: impl Into>) -> Attribute { + Attribute::new(key, value) +} + pub fn id(id: impl Into>) -> Attribute { - Attribute::new("id", id) + attr("id", id) } pub fn class<'a>(classes: impl IntoIterator) -> Attribute { diff --git a/src/lib.rs b/src/lib.rs index e6ee297..78652ad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,7 +85,12 @@ impl Display for Node { write!(f, "<{tag}")?; for attribute in attributes { match &attribute.value { - AttributeValue::String(s) => write!(f, " {}=\"{s}\"", attribute.key)?, + AttributeValue::String(s) => write!( + f, + " {}=\"{}\"", + attribute.key, + html_escape::encode_double_quoted_attribute(s) + )?, } } write!(f, ">")?; @@ -94,7 +99,7 @@ impl Display for Node { } write!(f, "")?; } - NodeInner::Text(text) => write!(f, "{}", html_escape::encode_safe(text))?, + NodeInner::Text(text) => write!(f, "{}", html_escape::encode_text(text))?, } Ok(()) } diff --git a/src/nodes.rs b/src/nodes.rs index 3bd913f..2414fa0 100644 --- a/src/nodes.rs +++ b/src/nodes.rs @@ -2,6 +2,13 @@ use std::borrow::Cow; use crate::{Attribute, Node, NodeInner}; +pub fn div( + attributes: impl IntoIterator, + children: impl IntoIterator, +) -> Node { + Node::new("div", attributes, children) +} + pub fn head( attributes: impl IntoIterator, children: impl IntoIterator, diff --git a/tests/render_spec.rs b/tests/render_spec.rs index 987e167..a9d60ff 100644 --- a/tests/render_spec.rs +++ b/tests/render_spec.rs @@ -14,12 +14,19 @@ fn should_render_empty_document() { #[test] fn should_render_attributes() { - let node = h1([class(["underlined", "blue"])], [text("Hello world!")]); - let string = format!("{node}"); - assert_eq!(string, "

Hello world!

") + let node = h1( + [class(["underlined", "blue"]), attr("foo", "bar")], + [text("Hello world!")], + ); + assert_eq!( + node.to_string(), + "

Hello world!

" + ) } #[rstest] +#[case(div([attr("foo", "bar")], [text("hello")]), "
hello
")] +#[case(div([attr("foo", "bar".to_string())], [text("hello".to_string())]), "
hello
")] #[case(head([id("foo")], [text("hello")]), "hello")] #[case(body([id("foo")], [text("hello")]), "hello")] #[case(h1([id("foo")], [text("hello")]), "

hello

")] @@ -36,8 +43,18 @@ fn should_render_node(#[case] def: Node, #[case] expected: &str) { fn text_should_be_escaped() { let input = ""; let string = text(input).to_string(); + assert_eq!(string, "<script>alert('hello');</script>"); +} + +#[rstest] +fn attribute_should_be_escaped() { + let string = div( + [attr("foo", "