From 2606e2f4fcf7fffa302dd0b60df350ecfd01e3d1 Mon Sep 17 00:00:00 2001 From: Noah Hellman Date: Sun, 12 Mar 2023 19:51:38 +0100 Subject: [PATCH 01/13] afl: gen -> parse, parse only --- Makefile | 2 +- tests/afl/Cargo.toml | 4 ++-- tests/afl/src/gen.rs | 13 ------------- tests/afl/src/parse.rs | 9 +++++++++ 4 files changed, 12 insertions(+), 16 deletions(-) delete mode 100644 tests/afl/src/gen.rs create mode 100644 tests/afl/src/parse.rs diff --git a/Makefile b/Makefile index 9a9c45f..cc67ad8 100644 --- a/Makefile +++ b/Makefile @@ -53,7 +53,7 @@ bench: cov: suite suite_bench LLVM_COV=llvm-cov LLVM_PROFDATA=llvm-profdata cargo llvm-cov --features=suite,suite_bench --workspace --html --ignore-run-fail -AFL_TARGET?=gen +AFL_TARGET?=parse AFL_JOBS?=1 AFL_TARGET_CRASH?=crashes diff --git a/tests/afl/Cargo.toml b/tests/afl/Cargo.toml index adf9924..52e6595 100644 --- a/tests/afl/Cargo.toml +++ b/tests/afl/Cargo.toml @@ -8,5 +8,5 @@ afl = "0.11" jotdown = { path = "../../", features = ["deterministic"] } [[bin]] -name = "gen" -path = "src/gen.rs" +name = "parse" +path = "src/parse.rs" diff --git a/tests/afl/src/gen.rs b/tests/afl/src/gen.rs deleted file mode 100644 index 6bd2664..0000000 --- a/tests/afl/src/gen.rs +++ /dev/null @@ -1,13 +0,0 @@ -use afl::fuzz; - -use jotdown::Render; - -fn main() { - fuzz!(|data: &[u8]| { - if let Ok(s) = std::str::from_utf8(data) { - let p = jotdown::Parser::new(s); - let mut output = String::new(); - jotdown::html::Renderer.push(p, &mut output).unwrap(); - } - }); -} diff --git a/tests/afl/src/parse.rs b/tests/afl/src/parse.rs new file mode 100644 index 0000000..8262eb9 --- /dev/null +++ b/tests/afl/src/parse.rs @@ -0,0 +1,9 @@ +use afl::fuzz; + +fn main() { + fuzz!(|data: &[u8]| { + if let Ok(s) = std::str::from_utf8(data) { + jotdown::Parser::new(s).last().unwrap(); + } + }); +} From 3e56903885fe82dc7ee69fe8442a40ee98e8bfb4 Mon Sep 17 00:00:00 2001 From: Noah Hellman Date: Sun, 12 Mar 2023 18:38:29 +0100 Subject: [PATCH 02/13] afl: mv parse target impl to lib --- tests/afl/src/lib.rs | 5 +++++ tests/afl/src/parse.rs | 8 +------- 2 files changed, 6 insertions(+), 7 deletions(-) create mode 100644 tests/afl/src/lib.rs diff --git a/tests/afl/src/lib.rs b/tests/afl/src/lib.rs new file mode 100644 index 0000000..a4f190b --- /dev/null +++ b/tests/afl/src/lib.rs @@ -0,0 +1,5 @@ +pub fn parse(data: &[u8]) { + if let Ok(s) = std::str::from_utf8(data) { + jotdown::Parser::new(s).last(); + } +} diff --git a/tests/afl/src/parse.rs b/tests/afl/src/parse.rs index 8262eb9..af56f9e 100644 --- a/tests/afl/src/parse.rs +++ b/tests/afl/src/parse.rs @@ -1,9 +1,3 @@ -use afl::fuzz; - fn main() { - fuzz!(|data: &[u8]| { - if let Ok(s) = std::str::from_utf8(data) { - jotdown::Parser::new(s).last().unwrap(); - } - }); + afl::fuzz!(|data: &[u8]| { jotdown_afl::parse(data) }); } From 8f70f596b9f029f53103418831d5cacd2573a89f Mon Sep 17 00:00:00 2001 From: Noah Hellman Date: Sun, 12 Mar 2023 18:47:59 +0100 Subject: [PATCH 03/13] afl: add main file for testing that crashes have been resolved previously, binary of the main crate was used, but targets may have more validation than simply checking for panics --- Makefile | 5 ++--- tests/afl/Cargo.toml | 5 +++++ tests/afl/src/main.rs | 17 +++++++++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 tests/afl/src/main.rs diff --git a/Makefile b/Makefile index cc67ad8..afc8818 100644 --- a/Makefile +++ b/Makefile @@ -78,13 +78,12 @@ afl_quick: afl_crash: set +e; \ for f in $$(find tests/afl/out -path '*/${AFL_TARGET_CRASH}/id*'); do \ - echo "cat $$f | RUST_BACKTRACE=1 cargo run"; \ - out=$$(cat $$f | RUST_BACKTRACE=1 cargo run 2>&1); \ + echo $$f; \ + out=$$(cat $$f | (cd tests/afl && RUST_BACKTRACE=1 cargo run ${AFL_TARGET} 2>&1)); \ if [ $$? -ne 0 ]; then \ echo; \ echo "FAIL"; \ echo "$$out"; \ - echo "cat $$f | RUST_BACKTRACE=1 cargo run"; \ exit 1; \ fi; \ done diff --git a/tests/afl/Cargo.toml b/tests/afl/Cargo.toml index 52e6595..36dcf82 100644 --- a/tests/afl/Cargo.toml +++ b/tests/afl/Cargo.toml @@ -2,11 +2,16 @@ name = "jotdown-afl" version = "0.1.0" edition = "2021" +default-run = "main" [dependencies] afl = "0.11" jotdown = { path = "../../", features = ["deterministic"] } +[[bin]] +name = "main" +path = "src/main.rs" + [[bin]] name = "parse" path = "src/parse.rs" diff --git a/tests/afl/src/main.rs b/tests/afl/src/main.rs new file mode 100644 index 0000000..c04247b --- /dev/null +++ b/tests/afl/src/main.rs @@ -0,0 +1,17 @@ +use std::io::Read; + +fn main() { + let mut args = std::env::args(); + let _program = args.next(); + let target = args.next().expect("no target"); + assert_eq!(args.next(), None); + + let f = match target.as_str() { + "parse" => jotdown_afl::parse, + _ => panic!("unknown target '{}'", target), + }; + + let mut input = Vec::new(); + std::io::stdin().read_to_end(&mut input).unwrap(); + f(&input); +} From 9a7c57f524a69d15733a2629999ad7e596b593c9 Mon Sep 17 00:00:00 2001 From: Noah Hellman Date: Sat, 11 Mar 2023 23:56:27 +0100 Subject: [PATCH 04/13] afl: add html target, checking for invalid html --- tests/afl/Cargo.toml | 5 ++ tests/afl/src/html.rs | 3 + tests/afl/src/lib.rs | 155 ++++++++++++++++++++++++++++++++++++++++++ tests/afl/src/main.rs | 1 + 4 files changed, 164 insertions(+) create mode 100644 tests/afl/src/html.rs diff --git a/tests/afl/Cargo.toml b/tests/afl/Cargo.toml index 36dcf82..6b92727 100644 --- a/tests/afl/Cargo.toml +++ b/tests/afl/Cargo.toml @@ -7,6 +7,7 @@ default-run = "main" [dependencies] afl = "0.11" jotdown = { path = "../../", features = ["deterministic"] } +html5ever = "0.26" [[bin]] name = "main" @@ -15,3 +16,7 @@ path = "src/main.rs" [[bin]] name = "parse" path = "src/parse.rs" + +[[bin]] +name = "html" +path = "src/html.rs" diff --git a/tests/afl/src/html.rs b/tests/afl/src/html.rs new file mode 100644 index 0000000..ae8b3f0 --- /dev/null +++ b/tests/afl/src/html.rs @@ -0,0 +1,3 @@ +fn main() { + afl::fuzz!(|data: &[u8]| { jotdown_afl::html(data) }); +} diff --git a/tests/afl/src/lib.rs b/tests/afl/src/lib.rs index a4f190b..694720b 100644 --- a/tests/afl/src/lib.rs +++ b/tests/afl/src/lib.rs @@ -1,5 +1,160 @@ +use jotdown::Render; + +use html5ever::tendril; +use html5ever::tendril::TendrilSink; +use html5ever::tokenizer; +use html5ever::tree_builder; + pub fn parse(data: &[u8]) { if let Ok(s) = std::str::from_utf8(data) { jotdown::Parser::new(s).last(); } } + +pub fn html(data: &[u8]) { + if data.iter().any(|i| *i == 0) { + return; + } + if let Ok(s) = std::str::from_utf8(data) { + if !s.contains("=html") { + let p = jotdown::Parser::new(s); + let mut html = "\n".to_string(); + jotdown::html::Renderer.push(p, &mut html).unwrap(); + validate_html(&html); + } + } +} + +fn validate_html(html: &str) { + let mut has_error = false; + + html5ever::parse_document( + Dom { + names: Vec::new(), + has_error: &mut has_error, + line_no: 1, + }, + html5ever::ParseOpts { + tokenizer: tokenizer::TokenizerOpts { + exact_errors: true, + ..tokenizer::TokenizerOpts::default() + }, + tree_builder: tree_builder::TreeBuilderOpts { + exact_errors: true, + scripting_enabled: false, + ..tree_builder::TreeBuilderOpts::default() + }, + }, + ) + .from_utf8() + .read_from(&mut std::io::Cursor::new(html)) + .unwrap(); + + if has_error { + eprintln!("html:"); + html.split('\n').enumerate().for_each(|(i, l)| { + eprintln!("{:>2}:{}", i + 1, l); + }); + eprintln!("\n"); + panic!(); + } +} + +struct Dom<'a> { + names: Vec, + has_error: &'a mut bool, + line_no: u64, +} + +impl<'a> tree_builder::TreeSink for Dom<'a> { + type Handle = usize; + type Output = Self; + + fn get_document(&mut self) -> usize { + 0 + } + + fn finish(self) -> Self { + self + } + + fn same_node(&self, x: &usize, y: &usize) -> bool { + x == y + } + + fn elem_name(&self, i: &usize) -> html5ever::ExpandedName { + self.names[i - 1].expanded() + } + + fn create_element( + &mut self, + name: html5ever::QualName, + _: Vec, + _: tree_builder::ElementFlags, + ) -> usize { + self.names.push(name); + self.names.len() + } + + fn parse_error(&mut self, msg: std::borrow::Cow<'static, str>) { + let whitelist = &[ + "Bad character", // bad characters in input will pass through + "Duplicate attribute", // djot is case-sensitive while html is not + // tags may be nested incorrectly, e.g. within + "Unexpected token Tag", + "Found special tag while closing generic tag", + "Formatting element not current node", + "Formatting element not open", + // FIXME bug caused by empty table at end of list + "No matching tag to close", + "Unexpected open element while closing", + ]; + if !whitelist.iter().any(|e| msg.starts_with(e)) { + *self.has_error = true; + eprintln!("{}: {}\n", self.line_no, msg); + } + } + + fn set_quirks_mode(&mut self, _: tree_builder::QuirksMode) {} + + fn set_current_line(&mut self, l: u64) { + self.line_no = l; + } + + fn append(&mut self, _: &usize, _: tree_builder::NodeOrText) {} + fn append_before_sibling(&mut self, _: &usize, _: tree_builder::NodeOrText) {} + fn append_based_on_parent_node( + &mut self, + _: &usize, + _: &usize, + _: tree_builder::NodeOrText, + ) { + } + fn append_doctype_to_document( + &mut self, + _: tendril::StrTendril, + _: tendril::StrTendril, + _: tendril::StrTendril, + ) { + } + fn remove_from_parent(&mut self, _: &usize) {} + fn reparent_children(&mut self, _: &usize, _: &usize) {} + + fn mark_script_already_started(&mut self, _: &usize) {} + + fn add_attrs_if_missing(&mut self, _: &usize, _: Vec) { + panic!(); + } + + fn create_pi(&mut self, _: tendril::StrTendril, _: tendril::StrTendril) -> usize { + panic!() + } + + fn get_template_contents(&mut self, _: &usize) -> usize { + panic!(); + } + + fn create_comment(&mut self, _: tendril::StrTendril) -> usize { + panic!() + } +} diff --git a/tests/afl/src/main.rs b/tests/afl/src/main.rs index c04247b..b0a66c8 100644 --- a/tests/afl/src/main.rs +++ b/tests/afl/src/main.rs @@ -8,6 +8,7 @@ fn main() { let f = match target.as_str() { "parse" => jotdown_afl::parse, + "html" => jotdown_afl::html, _ => panic!("unknown target '{}'", target), }; From 0d884a65d5a9f01f932bb0060b4f6716238b5148 Mon Sep 17 00:00:00 2001 From: Noah Hellman Date: Sun, 12 Mar 2023 20:03:58 +0100 Subject: [PATCH 05/13] afl: add debug feature leave out debug prints when actually fuzzing to increase fuzz performance --- Makefile | 4 ++-- tests/afl/Cargo.toml | 4 ++++ tests/afl/src/lib.rs | 24 ++++++++++++++++++++++-- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index afc8818..7bb9cb2 100644 --- a/Makefile +++ b/Makefile @@ -60,7 +60,7 @@ AFL_TARGET_CRASH?=crashes afl: rm -rf tests/afl/out (cd tests/afl && \ - cargo afl build --release --config profile.release.debug-assertions=true && \ + cargo afl build --no-default-features --release --config profile.release.debug-assertions=true && \ (AFL_NO_UI=1 cargo afl fuzz -i in -o out -Mm target/release/${AFL_TARGET} &) && \ for i in $$(seq $$((${AFL_JOBS} - 1))); do \ AFL_NO_UI=1 cargo afl fuzz -i in -o out -Ss$$i target/release/${AFL_TARGET} & \ @@ -71,7 +71,7 @@ afl: afl_quick: rm -rf tests/afl/out (cd tests/afl && \ - cargo afl build --release --config profile.release.debug-assertions=true && \ + cargo afl build --no-default-features --release --config profile.release.debug-assertions=true && \ AFL_NO_UI=1 AFL_BENCH_UNTIL_CRASH=1 \ cargo afl fuzz -i in -o out -V 60 target/release/${AFL_TARGET}) diff --git a/tests/afl/Cargo.toml b/tests/afl/Cargo.toml index 6b92727..70e156b 100644 --- a/tests/afl/Cargo.toml +++ b/tests/afl/Cargo.toml @@ -20,3 +20,7 @@ path = "src/parse.rs" [[bin]] name = "html" path = "src/html.rs" + +[features] +default = ["debug"] +debug = [] diff --git a/tests/afl/src/lib.rs b/tests/afl/src/lib.rs index 694720b..530a6ae 100644 --- a/tests/afl/src/lib.rs +++ b/tests/afl/src/lib.rs @@ -26,13 +26,18 @@ pub fn html(data: &[u8]) { } fn validate_html(html: &str) { + #[cfg(feature = "debug")] let mut has_error = false; html5ever::parse_document( Dom { names: Vec::new(), + #[cfg(feature = "debug")] has_error: &mut has_error, + #[cfg(feature = "debug")] line_no: 1, + #[cfg(not(feature = "debug"))] + _lifetime: std::marker::PhantomData, }, html5ever::ParseOpts { tokenizer: tokenizer::TokenizerOpts { @@ -50,6 +55,7 @@ fn validate_html(html: &str) { .read_from(&mut std::io::Cursor::new(html)) .unwrap(); + #[cfg(feature = "debug")] if has_error { eprintln!("html:"); html.split('\n').enumerate().for_each(|(i, l)| { @@ -62,8 +68,12 @@ fn validate_html(html: &str) { struct Dom<'a> { names: Vec, + #[cfg(feature = "debug")] has_error: &'a mut bool, + #[cfg(feature = "debug")] line_no: u64, + #[cfg(not(feature = "debug"))] + _lifetime: std::marker::PhantomData<&'a ()>, } impl<'a> tree_builder::TreeSink for Dom<'a> { @@ -110,16 +120,26 @@ impl<'a> tree_builder::TreeSink for Dom<'a> { "Unexpected open element while closing", ]; if !whitelist.iter().any(|e| msg.starts_with(e)) { - *self.has_error = true; - eprintln!("{}: {}\n", self.line_no, msg); + #[cfg(feature = "debug")] + { + *self.has_error = true; + eprintln!("{}: {}\n", self.line_no, msg); + } + #[cfg(not(feature = "debug"))] + { + panic!("invalid html"); + } } } fn set_quirks_mode(&mut self, _: tree_builder::QuirksMode) {} + #[cfg(feature = "debug")] fn set_current_line(&mut self, l: u64) { self.line_no = l; } + #[cfg(not(feature = "debug"))] + fn set_current_line(&mut self, _: u64) {} fn append(&mut self, _: &usize, _: tree_builder::NodeOrText) {} fn append_before_sibling(&mut self, _: &usize, _: tree_builder::NodeOrText) {} From bd831058f7ef208014673cc9609046e10e48cbc5 Mon Sep 17 00:00:00 2001 From: Noah Hellman Date: Sun, 12 Mar 2023 19:04:04 +0100 Subject: [PATCH 06/13] make: add afl_tmin target minimize all failing cases to help debugging --- Makefile | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7bb9cb2..d317391 100644 --- a/Makefile +++ b/Makefile @@ -77,7 +77,8 @@ afl_quick: afl_crash: set +e; \ - for f in $$(find tests/afl/out -path '*/${AFL_TARGET_CRASH}/id*'); do \ + failures="$$(find . -path './tmin/*') $$(find tests/afl/out -path '*/${AFL_TARGET_CRASH}/id*')"; \ + for f in $$failures; do \ echo $$f; \ out=$$(cat $$f | (cd tests/afl && RUST_BACKTRACE=1 cargo run ${AFL_TARGET} 2>&1)); \ if [ $$? -ne 0 ]; then \ @@ -88,6 +89,13 @@ afl_crash: fi; \ done +afl_tmin: + rm -rf tmin + mkdir tmin + for f in $$(find tests/afl/out -path '*/${AFL_TARGET_CRASH}/id*'); do \ + cargo afl tmin -i $$f -o tmin/$$(basename $$f) tests/afl/target/release/${AFL_TARGET}; \ + done + clean: cargo clean git submodule deinit -f --all From 5768b249074a5798938923ccbbc40fa8103838dc Mon Sep 17 00:00:00 2001 From: Noah Hellman Date: Sun, 12 Mar 2023 11:05:37 +0100 Subject: [PATCH 07/13] html: escape quotes in img alt text --- src/html.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/html.rs b/src/html.rs index 7fff549..cbd8130 100644 --- a/src/html.rs +++ b/src/html.rs @@ -388,6 +388,7 @@ impl<'s, I: Iterator>, W: std::fmt::Write> Writer<'s, I, W> { } } Event::Str(s) => match self.raw { + Raw::None if self.text_only => self.write_attr(&s)?, Raw::None => self.write_text(&s)?, Raw::Html => self.out.write_str(&s)?, Raw::Other => {} From fc374be56c621aa088d73fc18d257a0b695e508e Mon Sep 17 00:00:00 2001 From: Noah Hellman Date: Sun, 12 Mar 2023 17:02:42 +0100 Subject: [PATCH 08/13] html: escape img src values --- src/html.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/html.rs b/src/html.rs index cbd8130..6c4afe8 100644 --- a/src/html.rs +++ b/src/html.rs @@ -361,11 +361,11 @@ impl<'s, I: Iterator>, W: std::fmt::Write> Writer<'s, I, W> { Container::Link(..) => self.out.write_str("")?, Container::Image(src, ..) => { self.text_only = false; - if src.is_empty() { - self.out.write_str(r#"">"#)?; - } else { - write!(self.out, r#"" src="{}">"#, src)?; + if !src.is_empty() { + self.out.write_str(r#"" src=""#)?; + self.write_attr(&src)?; } + self.out.write_str(r#"">"#)?; } Container::Verbatim => self.out.write_str("")?, Container::Math { display } => { From c6022004bb8560b9b68348da8e758a1e623e1d0c Mon Sep 17 00:00:00 2001 From: Noah Hellman Date: Sun, 12 Mar 2023 17:03:44 +0100 Subject: [PATCH 09/13] html: fix alt text on nested images --- src/html.rs | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/html.rs b/src/html.rs index 6c4afe8..7f1862f 100644 --- a/src/html.rs +++ b/src/html.rs @@ -67,7 +67,7 @@ struct Writer<'s, I: Iterator>, W> { events: std::iter::Peekable>, out: W, raw: Raw, - text_only: bool, + img_alt_text: usize, list_tightness: Vec, encountered_footnote: bool, footnote_number: Option, @@ -81,7 +81,7 @@ impl<'s, I: Iterator>, W: std::fmt::Write> Writer<'s, I, W> { events: FilteredEvents { events }.peekable(), out, raw: Raw::None, - text_only: false, + img_alt_text: 0, list_tightness: Vec::new(), encountered_footnote: false, footnote_number: None, @@ -97,7 +97,7 @@ impl<'s, I: Iterator>, W: std::fmt::Write> Writer<'s, I, W> { if c.is_block() && !self.first_line { self.out.write_char('\n')?; } - if self.text_only && !matches!(c, Container::Image(..)) { + if self.img_alt_text > 0 && !matches!(c, Container::Image(..)) { continue; } match &c { @@ -171,8 +171,12 @@ impl<'s, I: Iterator>, W: std::fmt::Write> Writer<'s, I, W> { } } Container::Image(..) => { - self.text_only = true; - self.out.write_str(" self.out.write_str(" { @@ -283,7 +287,9 @@ impl<'s, I: Iterator>, W: std::fmt::Write> Writer<'s, I, W> { } } Container::Image(..) => { - self.out.write_str(r#" alt=""#)?; + if self.img_alt_text == 1 { + self.out.write_str(r#" alt=""#)?; + } } Container::Math { display } => { self.out @@ -296,7 +302,7 @@ impl<'s, I: Iterator>, W: std::fmt::Write> Writer<'s, I, W> { if c.is_block_container() && !matches!(c, Container::Footnote { .. }) { self.out.write_char('\n')?; } - if self.text_only && !matches!(c, Container::Image(..)) { + if self.img_alt_text > 0 && !matches!(c, Container::Image(..)) { continue; } match c { @@ -360,12 +366,14 @@ impl<'s, I: Iterator>, W: std::fmt::Write> Writer<'s, I, W> { Container::Span => self.out.write_str("")?, Container::Link(..) => self.out.write_str("")?, Container::Image(src, ..) => { - self.text_only = false; - if !src.is_empty() { - self.out.write_str(r#"" src=""#)?; - self.write_attr(&src)?; + if self.img_alt_text == 1 { + if !src.is_empty() { + self.out.write_str(r#"" src=""#)?; + self.write_attr(&src)?; + } + self.out.write_str(r#"">"#)?; } - self.out.write_str(r#"">"#)?; + self.img_alt_text -= 1; } Container::Verbatim => self.out.write_str("")?, Container::Math { display } => { @@ -388,7 +396,7 @@ impl<'s, I: Iterator>, W: std::fmt::Write> Writer<'s, I, W> { } } Event::Str(s) => match self.raw { - Raw::None if self.text_only => self.write_attr(&s)?, + Raw::None if self.img_alt_text > 0 => self.write_attr(&s)?, Raw::None => self.write_text(&s)?, Raw::Html => self.out.write_str(&s)?, Raw::Other => {} From 33d8215a2a9733f608b45a846e662e92ae2eb4e3 Mon Sep 17 00:00:00 2001 From: Noah Hellman Date: Sun, 12 Mar 2023 17:04:17 +0100 Subject: [PATCH 10/13] html: fix invalid html for footnote inside image --- src/html.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/html.rs b/src/html.rs index 7f1862f..1a1d9e0 100644 --- a/src/html.rs +++ b/src/html.rs @@ -402,11 +402,13 @@ impl<'s, I: Iterator>, W: std::fmt::Write> Writer<'s, I, W> { Raw::Other => {} }, Event::FootnoteReference(_tag, number) => { - write!( - self.out, - r##"{}"##, - number, number, number - )?; + if self.img_alt_text == 0 { + write!( + self.out, + r##"{}"##, + number, number, number + )?; + } } Event::Symbol(sym) => write!(self.out, ":{}:", sym)?, Event::LeftSingleQuote => self.out.write_str("‘")?, From 0719b2de6572ea6027f8c4407154dd408dc29901 Mon Sep 17 00:00:00 2001 From: Noah Hellman Date: Sun, 12 Mar 2023 17:12:56 +0100 Subject: [PATCH 11/13] block: fix class attribute parsing match reference implementation --- src/block.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/block.rs b/src/block.rs index c7f5692..d108c62 100644 --- a/src/block.rs +++ b/src/block.rs @@ -734,9 +734,8 @@ impl IdentifiedBlock { f @ ('`' | ':' | '~') => { let fence_length = 1 + (&mut chars).take_while(|c| *c == f).count(); let spec = &line_t[fence_length..].trim_start(); - let valid_spec = if f == ':' && !spec.starts_with('=') { - spec.chars().next().map_or(true, attr::is_name_start) - && spec.chars().skip(1).all(attr::is_name) + let valid_spec = if f == ':' { + spec.chars().all(attr::is_name) } else { !spec.chars().any(char::is_whitespace) && !spec.chars().any(|c| c == '`') }; From 14065177aec40cbeba133c0df335406ca59707d6 Mon Sep 17 00:00:00 2001 From: Noah Hellman Date: Sun, 12 Mar 2023 17:13:14 +0100 Subject: [PATCH 12/13] attr: fix name/key/value validation match reference implementation --- src/attr.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/attr.rs b/src/attr.rs index 9980f69..cd6eaaa 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -262,7 +262,7 @@ impl> Parser { } } s @ (ClassFirst | IdentifierFirst) => { - if is_name_start(c) { + if is_name(c) { match s { ClassFirst => Class, IdentifierFirst => Identifier, @@ -344,12 +344,8 @@ impl> Parser { } } -pub fn is_name_start(c: char) -> bool { - c.is_ascii_alphanumeric() || matches!(c, '_' | ':') -} - pub fn is_name(c: char) -> bool { - is_name_start(c) || c.is_ascii_digit() || matches!(c, '-') + c.is_ascii_alphanumeric() || matches!(c, ':' | '_' | '-') } enum Element { From 6266e6eb49b4d1e1045ac07ae0ee1e0f718856f4 Mon Sep 17 00:00:00 2001 From: Noah Hellman Date: Fri, 17 Mar 2023 19:01:29 +0100 Subject: [PATCH 13/13] ci: add fuzz html step --- .github/workflows/ci.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a62334c..8244981 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,10 +77,13 @@ jobs: rustup update nightly rustup default nightly cargo install afl - - name: "Fuzz" + - name: "Fuzz parser" run: | echo core | sudo tee /proc/sys/kernel/core_pattern - make afl_quick + AFL_TARGET=parse make afl_quick + - name: "Fuzz html" + run: | + AFL_TARGET=html make afl_quick bench: name: Benchmark runs-on: ubuntu-latest