Repository: raphlinus/pulldown-cmark (rust)

Issue:
[fuzz result] misparse on single-line header with backslash
Events from pulldown-cmark:

```rust
"# \\\n\n\n" -> [
  Start(Heading { level: H1, id: None, classes: [], attrs: [] })
  End(Heading(H1))
]
```

Events from pandoc:

```rust
"# \\\n\n\n" -> [
  Start(Heading { level: H1, id: None, classes: [], attrs: [] })
    Text(Boxed("\\"))
  End(Heading(H1))
]
```

Events from commonmark.js:

```rust
"# \\\n\n\n" -> [
  Start(Heading { level: H1, id: None, classes: [], attrs: [] })
    Text(Boxed("\\"))
  End(Heading(H1))
]
```


Pull request description:
Fix trailing backslash corner case on ATX headers
Fixes #819

Patch:
diff --git a/src/firstpass.rs b/src/firstpass.rs
index 3c87eaa..1f9eb14 100644
--- a/src/firstpass.rs
+++ b/src/firstpass.rs
@@ -1319,7 +1319,12 @@ impl<'a, 'b> FirstPass<'a, 'b> {
             self.parse_line(ix, Some(content_end), TableParseMode::Disabled);
             (header_end, content_end, attrs)
         } else {
-            ix = self.parse_line(ix, None, TableParseMode::Disabled).0;
+            let (line_ix, line_brk) = self.parse_line(ix, None, TableParseMode::Disabled);
+            ix = line_ix;
+            // Backslash at end is actually hard line break
+            if let Some(Item { start, end, body: ItemBody::HardBreak(true) }) = line_brk {
+                self.tree.append_text(start, end, false);
+            }
             (ix, ix, None)
         };
         self.tree[header_node_idx].item.end = end;


Test patch:
diff --git a/tests/html.rs b/tests/html.rs
index d95d0da..693a034 100644
--- a/tests/html.rs
+++ b/tests/html.rs
@@ -325,3 +325,27 @@ fn trim_space_before_soft_break() {
     html::push_html(&mut s, Parser::new(&original));
     assert_eq!(expected, s);
 }
+
+// Can't easily use regression.txt due to newline normalization.
+#[test]
+fn issue_819() {
+    let original = [
+        "# \\", "# \\\n", "# \\\n\n", "# \\\r\n", "# \\\r\n\r\n", "# \\\n\r\n", "# \\\r\n\n"
+    ];
+    let expected = "<h1>\\</h1>";
+
+    for orig in original {
+        let mut s = String::new();
+        html::push_html(&mut s, Parser::new(&orig));
+        // Trailing newline doesn't matter. Just the actual HTML.
+        assert_eq!(expected, s.trim_end_matches('\n'));
+    }
+    for orig in original {
+        let mut s = String::new();
+        let mut opts = Options::empty();
+        opts.insert(Options::ENABLE_HEADING_ATTRIBUTES);
+        html::push_html(&mut s, Parser::new_ext(&orig, opts));
+        // Trailing newline doesn't matter. Just the actual HTML.
+        assert_eq!(expected, s.trim_end_matches('\n'));
+    }
+}