Repository: raphlinus/pulldown-cmark (rust)

Issue:
[fuzz result] quoted text is either just text, or a link title, but not both
Consider this markdown:

```markdown
[foo]: https://example.com
'[foo]'bar
```

The quoted text '[foo]' should either be treated as a paragraph, or as a title attribute. The reference implementation thinks just text, not a title. But pulldown-cmark treats it as *both*, generating `<p>'<a href="https://example.com" title="[foo]">foo</a>'bar</p>`.

<details>

Events from pulldown-cmark:

```rust
"[R>]:[\n'\n[R>]'6\n" -> [
  Start(Paragraph)
    Text(Boxed("'"))
    SoftBreak
    Start(Link { link_type: Inline, dest_url: Borrowed("["), title: Borrowed("\n[R>]"), id: Borrowed("") })
      Text(Borrowed("R>"))
    End(Link)
    Text(Borrowed("'6"))
  End(Paragraph)
]
```

Events from pandoc:

```rust
"[R>]:[\n'\n[R>]'6\n" -> [
  Start(Paragraph)
    Text(Boxed("'"))
    SoftBreak
    Start(Link { link_type: Inline, dest_url: Inlined(InlineStr { inner: [91, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], len: 1 }), title: Inlined(InlineStr { inner: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], len: 0 }), id: Borrowed("") })
      Text(Boxed("R>"))
    End(Link)
    Text(Boxed("'6"))
  End(Paragraph)
]
```

Events from commonmark.js:

```rust
"[R>]:[\n'\n[R>]'6\n" -> [
  Start(Paragraph)
    Text(Boxed("'"))
    SoftBreak
    Start(Link { link_type: Inline, dest_url: Inlined(InlineStr { inner: [91, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], len: 1 }), title: Inlined(InlineStr { inner: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], len: 0 }), id: Borrowed("") })
      Text(Boxed("R>"))
    End(Link)
    Text(Boxed("'6"))
  End(Paragraph)
]
```

</details>

Pull request:
Avoid updating linkdef when title isn't followed by NL
The quoted text '[foo]' should either be treated as a paragraph, or as a title attribute. The reference implementation thinks it's just text, not a title. But pulldown-cmark treated it as both, generating `<p>'<a href="https://example.com" title="[foo]">foo</a>'bar</p>`, which is definitely wrong.

Fixes #791

Patch:
diff --git a/specs/regression.txt b/specs/regression.txt
index c66fa0f..81664e6 100644
--- a/specs/regression.txt
+++ b/specs/regression.txt
@@ -1985,3 +1985,25 @@ second</a></p>
 <p>"</p>
 <p><a href="https://example.com">first</a></p>
 ````````````````````````````````
+
+
+ISSUE 791
+
+```````````````````````````````` example
+[foo]: https://example.com
+'[foo]'bar
+.
+<p>'<a href="https://example.com">foo</a>'bar</p>
+````````````````````````````````
+
+```````````````````````````````` example
+- [foo]: https://example.com
+'[foo]'
+[foo]
+.
+<ul>
+<li>
+<a href="https://example.com" title="[foo]">foo</a>
+</li>
+</ul>
+````````````````````````````````
\ No newline at end of file
diff --git a/src/firstpass.rs b/src/firstpass.rs
index dbd2e0d..18ce268 100644
--- a/src/firstpass.rs
+++ b/src/firstpass.rs
@@ -1578,19 +1578,14 @@ impl<'a, 'b> FirstPass<'a, 'b> {
         // if this fails but newline == 1, return also a refdef without title
         if let Some((title_length, title)) = self.scan_refdef_title(&self.text[i..]) {
             i += title_length;
-            backup.1.span = span_start..i;
-            backup.1.title = Some(unescape(title));
-        } else if newlines > 0 {
-            return Some(backup);
-        } else {
-            return None;
-        };
-
-        // scan EOL
-        if let Some(bytes) = scan_blank_line(&bytes[i..]) {
-            backup.0 = i + bytes - start;
-            Some(backup)
-        } else if newlines > 0 {
+            if scan_blank_line(&bytes[i..]).is_some() {
+                backup.0 = i - start;
+                backup.1.span = span_start..i;
+                backup.1.title = Some(unescape(title));
+                return Some(backup);
+            }
+        }
+        if newlines > 0 {
             Some(backup)
         } else {
             None


Test patch:
diff --git a/tests/suite/regression.rs b/tests/suite/regression.rs
index ec3dbd1..6db31bb 100644
--- a/tests/suite/regression.rs
+++ b/tests/suite/regression.rs
@@ -2219,6 +2219,7 @@ second]
     test_markdown_html(original, expected, false, false, false);
 }
 
+  
 #[test]
 fn regression_test_139() {
     let original = r##"[first
@@ -2344,3 +2345,29 @@ fn regression_test_145() {
 
     test_markdown_html(original, expected, false, false, false);
 }
+
+#[test]
+fn regression_test_146() {
+    let original = r##"[foo]: https://example.com
+'[foo]'bar
+"##;
+    let expected = r##"<p>'<a href="https://example.com">foo</a>'bar</p>
+"##;
+
+  test_markdown_html(original, expected, false, false, false);
+}
+  
+#[test]
+fn regression_test_147() {
+    let original = r##"- [foo]: https://example.com
+'[foo]'
+[foo]
+"##;
+    let expected = r##"<ul>
+<li>
+<a href="https://example.com" title="[foo]">foo</a>
+</li>
+</ul>
+"##;
+    test_markdown_html(original, expected, false, false, false);
+}
\ No newline at end of file