Do not allow unmatched closing tag at root

This commit is contained in:
Wilson Lin 2020-07-30 20:17:55 +10:00
parent d024d21274
commit d7ddda3f21
5 changed files with 42 additions and 6 deletions

View File

@ -1,9 +1,10 @@
// Implement debug to allow .unwrap().
#[derive(Debug)]
#[derive(Debug, Eq, PartialEq)]
pub enum ErrorType {
ClosingTagMismatch { expected: String, got: String },
NotFound(&'static str),
UnexpectedEnd,
UnexpectedClosingTag,
}
impl ErrorType {
@ -18,6 +19,9 @@ impl ErrorType {
ErrorType::UnexpectedEnd => {
format!("Unexpected end of source code.")
}
ErrorType::UnexpectedClosingTag => {
format!("Unexpected closing tag.")
}
}
}
}

View File

@ -18,6 +18,11 @@ mod unit;
pub fn in_place(code: &mut [u8], cfg: &Cfg) -> Result<usize, Error> {
let mut proc = Processor::new(code);
process_content(&mut proc, cfg, Namespace::Html, None)
.and_then(|_| if !proc.at_end() {
Err(ErrorType::UnexpectedClosingTag)
} else {
Ok(())
})
.map_err(|error_type| Error {
error_type,
position: proc.read_len(),

View File

@ -362,8 +362,7 @@ impl<'d> Processor<'d> {
#[cfg(not(feature = "js-esbuild"))]
#[inline(always)]
pub fn finish(self) -> Result<usize, Error> {
// NOTE: Do not assert that we are at the end, as invalid HTML can end prematurely e.g.
// `<html>hello</html>outside`.
debug_assert!(self.at_end());
Ok(self.write_next)
}
@ -371,8 +370,7 @@ impl<'d> Processor<'d> {
#[cfg(feature = "js-esbuild")]
#[inline(always)]
pub fn finish(self) -> Result<usize, Error> {
// NOTE: Do not assert that we are at the end, as invalid HTML can end prematurely e.g.
// `<html>hello</html>outside`.
debug_assert!(self.at_end());
self.script_wg.wait();
let mut results = Arc::try_unwrap(self.script_results)
.unwrap_or_else(|_| panic!("failed to acquire script results"))

View File

@ -1,3 +1,8 @@
#[cfg(test)]
use {
crate::ErrorType
};
#[cfg(test)]
fn _eval(src: &'static [u8], expected: &'static [u8], cfg: &super::Cfg) -> () {
let mut code = src.to_vec();
@ -13,6 +18,12 @@ fn _eval(src: &'static [u8], expected: &'static [u8], cfg: &super::Cfg) -> () {
};
}
#[cfg(test)]
fn _eval_error(src: &'static [u8], expected: ErrorType, cfg: &super::Cfg) -> () {
let mut code = src.to_vec();
assert_eq!(super::in_place(&mut code, cfg).unwrap_err().error_type, expected);
}
#[cfg(test)]
fn eval(src: &'static [u8], expected: &'static [u8]) -> () {
_eval(src, expected, &super::Cfg {
@ -20,6 +31,13 @@ fn eval(src: &'static [u8], expected: &'static [u8]) -> () {
});
}
#[cfg(test)]
fn eval_error(src: &'static [u8], expected: ErrorType) -> () {
_eval_error(src, expected, &super::Cfg {
minify_js: false,
});
}
#[cfg(test)]
#[cfg(feature = "js-esbuild")]
fn eval_with_js_min(src: &'static [u8], expected: &'static [u8]) -> () {
@ -79,10 +97,21 @@ fn test_parsing_with_omitted_tags() {
eval(b"<rt><rp>1</rp><div></div>", b"<rt><rp>1</rp><div></div>");
eval(b"<div><rt></div>", b"<div><rt></div>");
eval(b"<html><head><body>", b"<html><head><body>");
eval(b"<html><head><body>", b"<html><head><body>");
// Tag names should be case insensitive.
eval(b"<rt>", b"<rt>");
}
#[test]
fn test_unmatched_closing_tag() {
eval_error(b"Hello</p>Goodbye", ErrorType::UnexpectedClosingTag);
eval_error(b"Hello<br></br>Goodbye", ErrorType::UnexpectedClosingTag);
eval_error(b"<div>Hello</p>Goodbye", ErrorType::ClosingTagMismatch { expected: "div".to_string(), got: "p".to_string() });
eval_error(b"<ul><li>a</p>", ErrorType::ClosingTagMismatch { expected: "ul".to_string(), got: "p".to_string() });
eval_error(b"<ul><li><rt>a</p>", ErrorType::ClosingTagMismatch { expected: "ul".to_string(), got: "p".to_string() });
eval_error(b"<html><head><body><ul><li><rt>a</p>", ErrorType::ClosingTagMismatch { expected: "ul".to_string(), got: "p".to_string() });
}
#[test]
fn test_removal_of_optional_tags() {
eval(b"<ul><li>1</li><li>2</li><li>3</li></ul>", b"<ul><li>1<li>2<li>3</ul>");

View File

@ -47,7 +47,7 @@ impl ContentType {
}
pub struct ProcessedContent {
pub(crate) closing_tag_omitted: bool,
pub closing_tag_omitted: bool,
}
pub fn process_content(proc: &mut Processor, cfg: &Cfg, ns: Namespace, parent: Option<ProcessorRange>) -> ProcessingResult<ProcessedContent> {