Skip to content

IndexError: table inside a blockquote at end of input (html_block.py, heading.py) #415

Description

@WilcoPancras

Describe the bug

Input that ends on a blockquote marker while a table is open inside that quote raises
IndexError: string index out of range.

Two block rules are affected, both reached as terminator rules from rules_block/table.py:190:

rule line
rules_block/html_block.py if state.src[pos] != "<":
rules_block/heading.py `ch: str

Both compute pos = state.bMarks[startLine] + state.tShift[startLine], which equals len(state.src)
for the empty final line a trailing > produces. In markdown-it (JS) the equivalent is
state.src.charCodeAt(pos), which returns NaN out of range instead of throwing — the port hazard
tracked in #190.

Sibling rules already defend against exactly this: hr.py and blockquote.py wrap the same index in
try: ... except IndexError: return False (added for #185 / #204). html_block.py and heading.py
appear to have been missed.

html_block shadows heading because it runs first, so disabling html moves the traceback rather
than fixing it.

Reproduce the bug

from markdown_it import MarkdownIt

# html_block.py:47
MarkdownIt().enable("table").parse("> | a | b |\n> |---|---|\n>")

# heading.py:22 (same input, html_block bails at its options check first)
MarkdownIt("commonmark", {"html": False}).enable("table").parse("> | a | b |\n> |---|---|\n>")

Both raise:

IndexError: string index out of range

Needs table enabled, so gfm-like and js-default are affected; plain commonmark is not, since
without the table rule the terminator rules are never run on that line.

Variants (all with MarkdownIt().enable("table"))
input result
"> | a | b |\n> |---|---|\n>" IndexError
"> | a | b |\n> |---|---|\n> " (marker + space) IndexError
"> > | a | b |\n> > |---|---|\n> >" (nested) IndexError
"> | a | b |\n> |---|---|\n> | 1 | 2 |\n>" (body row, then bare marker) IndexError
"> | a | b |\n> |---|---|\n>\n" (trailing newline) ok
"> | a | b |\n> |---|---|\n> | 1 | 2 |" (complete) ok
"| a | b |\n|---|---|" (no blockquote) ok
"> text\n>" (no table) ok

A trailing newline makes it disappear, which is why this is easy to miss in a document renderer and
hard to avoid in an incremental one. I hit it rendering a partially-streamed LLM reply with
rich.markdown> |---|---|\n> is not markdown anyone writes, but it exists for a few
milliseconds every time a quoted table is streamed a token at a time. rich parses in
Markdown.__init__, so it raises at construction and takes down the caller.

Suggested fix

The guard the sibling rules already use:

--- a/markdown_it/rules_block/html_block.py
+++ b/markdown_it/rules_block/html_block.py
-    if state.src[pos] != "<":
-        return False
+    try:
+        if state.src[pos] != "<":
+            return False
+    except IndexError:
+        return False
--- a/markdown_it/rules_block/heading.py
+++ b/markdown_it/rules_block/heading.py
-    ch: str | None = state.src[pos]
-
-    if ch != "#" or pos >= maximum:
+    if pos >= maximum or state.src[pos] != "#":
         return False

Happy to open a PR with these plus tests if the approach looks right — though if #190 is still the
preferred direction, that would fix this class of bug at the source and these two rules are simply
further evidence for it.

List your environment

  • markdown-it-py 4.2.0 (the two lines above are unchanged on master as of this writing)
  • Python 3.12, Linux
  • No optional dependencies needed to reproduce

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions