diff --git a/.changes/fix-html-template.md b/.changes/fix-html-template.md new file mode 100644 index 000000000..203f98f9f --- /dev/null +++ b/.changes/fix-html-template.md @@ -0,0 +1,6 @@ +--- +"tauri-utils": patch +"tauri-codegen": patch +--- + +Properly serialize HTML template tags. diff --git a/core/tauri-codegen/src/context.rs b/core/tauri-codegen/src/context.rs index 00db7b754..ea330c3ca 100644 --- a/core/tauri-codegen/src/context.rs +++ b/core/tauri-codegen/src/context.rs @@ -11,7 +11,9 @@ use sha2::{Digest, Sha256}; use tauri_utils::assets::AssetKey; use tauri_utils::config::{AppUrl, Config, PatternKind, WindowUrl}; -use tauri_utils::html::{inject_nonce_token, parse as parse_html}; +use tauri_utils::html::{ + inject_nonce_token, parse as parse_html, serialize_node as serialize_html_node, +}; #[cfg(feature = "shell-scope")] use tauri_utils::config::{ShellAllowedArg, ShellAllowedArgs, ShellAllowlistScope}; @@ -37,10 +39,10 @@ fn map_core_assets( options.dangerous_disable_asset_csp_modification.clone(); move |key, path, input, csp_hashes| { if path.extension() == Some(OsStr::new("html")) { - let mut document = parse_html(String::from_utf8_lossy(input).into_owned()); - #[allow(clippy::collapsible_if)] if csp { + let mut document = parse_html(String::from_utf8_lossy(input).into_owned()); + if target == Target::Linux { ::tauri_utils::html::inject_csp_token(&mut document); } @@ -77,9 +79,9 @@ fn map_core_assets( .push(format!("'sha256-{}'", base64::encode(&hash))); } } - } - *input = document.to_string().as_bytes().to_vec(); + *input = serialize_html_node(&document); + } } Ok(()) } diff --git a/core/tauri-utils/src/html.rs b/core/tauri-utils/src/html.rs index 31985df9e..acd8e7f82 100644 --- a/core/tauri-utils/src/html.rs +++ b/core/tauri-utils/src/html.rs @@ -6,9 +6,15 @@ use std::path::{Path, PathBuf}; -use html5ever::{interface::QualName, namespace_url, ns, tendril::TendrilSink, LocalName}; +use html5ever::{ + interface::QualName, + namespace_url, ns, + serialize::{HtmlSerializer, SerializeOpts, Serializer, TraversalScope}, + tendril::TendrilSink, + LocalName, +}; pub use kuchiki::NodeRef; -use kuchiki::{Attribute, ExpandedName}; +use kuchiki::{Attribute, ExpandedName, NodeData}; use serde::Serialize; #[cfg(feature = "isolation")] use serialize_to_javascript::DefaultTemplate; @@ -24,6 +30,90 @@ pub const SCRIPT_NONCE_TOKEN: &str = "__TAURI_SCRIPT_NONCE__"; /// The token used for style nonces. pub const STYLE_NONCE_TOKEN: &str = "__TAURI_STYLE_NONCE__"; +// taken from https://github.com/kuchiki-rs/kuchiki/blob/57ee6920d835315a498e748ba4b07a851ae5e498/src/serializer.rs#L12 +fn serialize_node_ref_internal( + node: &NodeRef, + serializer: &mut S, + traversal_scope: TraversalScope, +) -> crate::Result<()> { + match (traversal_scope, node.data()) { + (ref scope, &NodeData::Element(ref element)) => { + if *scope == TraversalScope::IncludeNode { + let attrs = element.attributes.borrow(); + + // Unfortunately we need to allocate something to hold these &'a QualName + let attrs = attrs + .map + .iter() + .map(|(name, attr)| { + ( + QualName::new(attr.prefix.clone(), name.ns.clone(), name.local.clone()), + &attr.value, + ) + }) + .collect::>(); + + serializer.start_elem( + element.name.clone(), + attrs.iter().map(|&(ref name, value)| (name, &**value)), + )? + } + + let children = match element.template_contents.as_ref() { + Some(template_root) => template_root.children(), + None => node.children(), + }; + for child in children { + serialize_node_ref_internal(&child, serializer, TraversalScope::IncludeNode)? + } + + if *scope == TraversalScope::IncludeNode { + serializer.end_elem(element.name.clone())? + } + Ok(()) + } + + (_, &NodeData::DocumentFragment) | (_, &NodeData::Document(_)) => { + for child in node.children() { + serialize_node_ref_internal(&child, serializer, TraversalScope::IncludeNode)? + } + Ok(()) + } + + (TraversalScope::ChildrenOnly(_), _) => Ok(()), + + (TraversalScope::IncludeNode, &NodeData::Doctype(ref doctype)) => { + serializer.write_doctype(&doctype.name).map_err(Into::into) + } + (TraversalScope::IncludeNode, &NodeData::Text(ref text)) => { + serializer.write_text(&text.borrow()).map_err(Into::into) + } + (TraversalScope::IncludeNode, &NodeData::Comment(ref text)) => { + serializer.write_comment(&text.borrow()).map_err(Into::into) + } + (TraversalScope::IncludeNode, &NodeData::ProcessingInstruction(ref contents)) => { + let contents = contents.borrow(); + serializer + .write_processing_instruction(&contents.0, &contents.1) + .map_err(Into::into) + } + } +} + +/// Serializes the node to HTML. +pub fn serialize_node(node: &NodeRef) -> Vec { + let mut u8_vec = Vec::new(); + let mut ser = HtmlSerializer::new( + &mut u8_vec, + SerializeOpts { + traversal_scope: TraversalScope::IncludeNode, + ..Default::default() + }, + ); + serialize_node_ref_internal(node, &mut ser, TraversalScope::IncludeNode).unwrap(); + u8_vec +} + /// Parses the given HTML string. pub fn parse(html: String) -> NodeRef { kuchiki::parse_html().one(html)