diff --git a/src/html.rs b/src/html.rs index 91a488d8..0e6868ce 100644 --- a/src/html.rs +++ b/src/html.rs @@ -14,7 +14,7 @@ use std::default::Default; use crate::core::{parse_content_type, MonolithOptions}; use crate::css::embed_css; -use crate::js::attr_is_event_handler; +use crate::js::{attr_is_event_handler, escape_script_end_tag}; use crate::session::Session; use crate::url::{ clean_url, create_data_url, is_url_and_has_protocol, resolve_url, Url, EMPTY_IMAGE_DATA_URL, @@ -765,10 +765,9 @@ pub fn retrieve_and_embed_asset( if let NodeData::Text { ref contents } = text_node.data { let mut tendril = contents.borrow_mut(); tendril.clear(); - tendril.push_slice( - &String::from_utf8_lossy(&data) - .replace("", "<\\/script>"), - ); + tendril.push_slice(&escape_script_end_tag( + &String::from_utf8_lossy(&data), + )); } node.children.borrow_mut().push(text_node.clone()); diff --git a/src/js.rs b/src/js.rs index a6b463a9..136e5067 100644 --- a/src/js.rs +++ b/src/js.rs @@ -100,3 +100,37 @@ pub fn attr_is_event_handler(attr_name: &str) -> bool { .iter() .any(|a| attr_name.eq_ignore_ascii_case(a)) } + +// Escapes anything an HTML parser could read as a closing SCRIPT tag, so that +// inlined code can't break out of the SCRIPT element it's being embedded into. +// The tag name matches ASCII case-insensitively and has to be followed by +// whitespace, a solidus, or ">" to count as an end tag (WHATWG HTML 13.2.5.22): +// https://html.spec.whatwg.org/#script-data-end-tag-name-state +pub fn escape_script_end_tag(code: &str) -> String { + let bytes: &[u8] = code.as_bytes(); + let mut result: String = String::with_capacity(code.len()); + let mut copied: usize = 0; + let mut i: usize = 0; + + while i + 8 <= bytes.len() { + if bytes[i] == b'<' + && bytes[i + 1] == b'/' + && bytes[i + 2..i + 8].eq_ignore_ascii_case(b"script") + && (i + 8 == bytes.len() + || matches!( + bytes[i + 8], + b'\t' | b'\n' | b'\x0c' | b'\r' | b' ' | b'/' | b'>' + )) + { + result.push_str(&code[copied..=i]); + result.push('\\'); + copied = i + 1; + i += 2; + } else { + i += 1; + } + } + + result.push_str(&code[copied..]); + result +} diff --git a/tests/_data_/script-escape/index.html b/tests/_data_/script-escape/index.html new file mode 100644 index 00000000..2dc7613d --- /dev/null +++ b/tests/_data_/script-escape/index.html @@ -0,0 +1,9 @@ + + +
+