fix: don't entity-encode HTML special characters inside <script> or <style> (closes #837) (#846)

This commit is contained in:
Greg Johnston 2023-04-10 13:15:15 -04:00 committed by GitHub
parent 2c7ee0d415
commit f969fd7eff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 71 additions and 23 deletions

View file

@ -244,19 +244,28 @@ fn fragments_to_chunks(
impl View {
/// Consumes the node and renders it into an HTML string.
pub fn render_to_string(self, _cx: Scope) -> Cow<'static, str> {
self.render_to_string_helper()
self.render_to_string_helper(false)
}
pub(crate) fn render_to_string_helper(self) -> Cow<'static, str> {
pub(crate) fn render_to_string_helper(
self,
dont_escape_text: bool,
) -> Cow<'static, str> {
match self {
View::Text(node) => {
html_escape::encode_safe(&node.content).to_string().into()
if dont_escape_text {
node.content
} else {
html_escape::encode_safe(&node.content).to_string().into()
}
}
View::Component(node) => {
let content = || {
node.children
.into_iter()
.map(|node| node.render_to_string_helper())
.map(|node| {
node.render_to_string_helper(dont_escape_text)
})
.join("")
};
cfg_if! {
@ -283,7 +292,8 @@ impl View {
}
View::Suspense(id, node) => format!(
"<!--suspense-open-{id}-->{}<!--suspense-close-{id}-->",
View::CoreComponent(node).render_to_string_helper()
View::CoreComponent(node)
.render_to_string_helper(dont_escape_text)
)
.into(),
View::CoreComponent(node) => {
@ -333,7 +343,9 @@ impl View {
t.content
}
} else {
child.render_to_string_helper()
child.render_to_string_helper(
dont_escape_text,
)
}
} else {
"".into()
@ -356,7 +368,9 @@ impl View {
let id = node.id;
let content = || {
node.child.render_to_string_helper()
node.child.render_to_string_helper(
dont_escape_text,
)
};
#[cfg(debug_assertions)]
@ -409,6 +423,8 @@ impl View {
}
}
View::Element(el) => {
let is_script_or_style =
el.name == "script" || el.name == "style";
let el_html = if let ElementChildren::Chunks(chunks) =
el.children
{
@ -416,9 +432,8 @@ impl View {
.into_iter()
.map(|chunk| match chunk {
StringOrView::String(string) => string,
StringOrView::View(view) => {
view().render_to_string_helper()
}
StringOrView::View(view) => view()
.render_to_string_helper(is_script_or_style),
})
.join("")
.into()
@ -460,7 +475,11 @@ impl View {
ElementChildren::Empty => "".into(),
ElementChildren::Children(c) => c
.into_iter()
.map(View::render_to_string_helper)
.map(|v| {
v.render_to_string_helper(
is_script_or_style,
)
})
.join("")
.into(),
ElementChildren::InnerHtml(h) => h,

View file

@ -213,7 +213,7 @@ impl View {
/// Renders the view into a set of HTML chunks that can be streamed.
pub fn into_stream_chunks(self, cx: Scope) -> VecDeque<StreamChunk> {
let mut chunks = VecDeque::new();
self.into_stream_chunks_helper(cx, &mut chunks);
self.into_stream_chunks_helper(cx, &mut chunks, false);
chunks
}
@ -221,6 +221,7 @@ impl View {
self,
cx: Scope,
chunks: &mut VecDeque<StreamChunk>,
dont_escape_text: bool,
) {
match self {
View::Suspense(id, _) => {
@ -241,18 +242,21 @@ impl View {
let name = crate::ssr::to_kebab_case(&node.name);
chunks.push_back(StreamChunk::Sync(format!(r#"<!--hk={}|leptos-{name}-start-->"#, HydrationCtx::to_string(&node.id, false)).into()));
for child in node.children {
child.into_stream_chunks_helper(cx, chunks);
child.into_stream_chunks_helper(cx, chunks, dont_escape_text);
}
chunks.push_back(StreamChunk::Sync(format!(r#"<!--hk={}|leptos-{name}-end-->"#, HydrationCtx::to_string(&node.id, true)).into()));
} else {
for child in node.children {
child.into_stream_chunks_helper(cx, chunks);
child.into_stream_chunks_helper(cx, chunks, dont_escape_text);
}
chunks.push_back(StreamChunk::Sync(format!(r#"<!--hk={}-->"#, HydrationCtx::to_string(&node.id, true)).into()))
}
}
}
View::Element(el) => {
let is_script_or_style =
el.name == "script" || el.name == "style";
#[cfg(debug_assertions)]
if let Some(id) = &el.view_marker {
chunks.push_back(StreamChunk::Sync(
@ -266,7 +270,11 @@ impl View {
chunks.push_back(StreamChunk::Sync(string))
}
StringOrView::View(view) => {
view().into_stream_chunks_helper(cx, chunks);
view().into_stream_chunks_helper(
cx,
chunks,
is_script_or_style,
);
}
}
}
@ -318,7 +326,11 @@ impl View {
ElementChildren::Empty => {}
ElementChildren::Children(children) => {
for child in children {
child.into_stream_chunks_helper(cx, chunks);
child.into_stream_chunks_helper(
cx,
chunks,
is_script_or_style,
);
}
}
ElementChildren::InnerHtml(inner_html) => {
@ -387,22 +399,33 @@ impl View {
// into one single node, so we need to artificially make the
// browser create the dynamic text as it's own text node
if let View::Text(t) = child {
let content = if dont_escape_text {
t.content.into()
} else {
html_escape::encode_safe(
&t.content,
)
.to_string()
.into()
};
chunks.push_back(
if !cfg!(debug_assertions) {
StreamChunk::Sync(
format!(
"<!>{}",
html_escape::encode_safe(&t.content)
content
)
.into(),
)
} else {
StreamChunk::Sync(html_escape::encode_safe(&t.content).to_string().into())
StreamChunk::Sync(content)
},
);
} else {
child.into_stream_chunks_helper(
cx, chunks,
cx,
chunks,
dont_escape_text,
);
}
}
@ -435,7 +458,9 @@ impl View {
);
node.child
.into_stream_chunks_helper(
cx, chunks,
cx,
chunks,
dont_escape_text,
);
chunks.push_back(
StreamChunk::Sync(

View file

@ -393,6 +393,7 @@ fn element_to_tokens_ssr(
.to_string()
.replace("svg::", "")
.replace("math::", "");
let is_script_or_style = tag_name == "script" || tag_name == "style";
template.push('<');
template.push_str(&tag_name);
@ -461,9 +462,12 @@ fn element_to_tokens_ssr(
}
Node::Text(text) => {
if let Some(value) = value_to_string(&text.value) {
template.push_str(&html_escape::encode_safe(
&value,
));
let value = if is_script_or_style {
value.into()
} else {
html_escape::encode_safe(&value)
};
template.push_str(&value);
} else {
template.push_str("{}");
let value = text.value.as_ref();