Repository navigation
Conversation
`HTMLExtractor.parse_starttag` passes `<` through as data and returns `i + 1` when `check_for_whole_start_tag` finds no closing `>`. The `locatetagend` match behind that check accepts `<` in tag and attribute names and runs across whitespace and blank lines, so in text such as `a<b a<b ...` with no `>` after it, every `<` scans to the end of the raw data again and n of them cost O(n^2). `'a<b ' * 25000` (100 KB) takes about 20 seconds to convert. Unlike the comment close in Python-Markdown#1676, a failed match does not rule out a later one: in `<a b<c=" >` the first tag is unclosed but `<c=" >` is a tag. So `check_for_whole_start_tag` now matches the same text as `locatetagend` one attribute at a time and records, for the current `rawdata`, the positions from which the attributes ran out without a `>` and the extent of the last unclosed tag name. The match is greedy and never backtracks past the start of an attribute, so a later `<` whose tag name ends in the same place, or whose attributes reach one of those positions, has the same result and is not scanned again. Output is unchanged.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
HTMLExtractor.parse_starttagpasses<through as text and moves on one character whencheck_for_whole_start_tagfinds no closing>. Thelocatetagendmatch behind that check accepts<inside tag and attribute names and carries on across spaces, newlines and blank lines, so in text likea<b a<b a<bwith no>after it, every<scans to the end of the document again. Prose with comparisons in it is enough.markdown.markdown(s * n)with no extensions on CPython 3.11, in seconds:#1676 fixed the same kind of rescan for comments, but its skip does not carry over to start tags. An unclosed tag does not rule out a later one inside it: in
<a b<c=" >the first tag is unclosed but<c=" >is a tag. Skipping only when no>follows is not enough either, since a backtick or an unclosed quote where an attribute would start also ends the match (the fourth row). Socheck_for_whole_start_tagnow matches the same text aslocatetagendone attribute at a time, and remembers, for the currentrawdata, the positions from which the attributes ran out without a>and where the last unclosed tag name ends. The match is greedy and never backtracks past the start of an attribute, so a later<whose tag name ends in the same place, or whose attributes reach one of those positions, has the same result. The second assertion in the new test is the tag name case:<a@ahas no space, so each name runs to the end.For every
<followed by a letter, the new check returns the same result as the oldlocatetagendmatch on all strings of up to 7 tokens from<a,<,a, space, newline,/,=,',", backtick and>(16M strings) and on 300,000 random longer ones, with the positions checked in order and shuffled. Mistakes planted in the remembered positions were caught by the same comparison. Rendered output matches master on 100,000 random documents mixing raw HTML and Markdown, with no extensions,md_in_htmlandextra. The docs folder withextraand a 400 KB HTML-heavy page take the same time as before. The full suite passes on 3.11 with Pygments 2.7.1 and on 3.13 without it, flake8 is clean, and the new test takes 26 s on master and 0.08 s here.Python releases from before the 2025
html.parsersecurity fix (3.11.12, for example) found the end of a start tag withlocatestarttagend_tolerant, andHTMLExtractornow uses thelocatetagendbehaviour there too. The suite passes on 3.11.12. Of the same 100,000 documents, 154 render differently from master on 3.11.12, and 133 of those now match what current releases produce.AI Assistance Disclosure
No AI tools were used in preparing this PR.
AI tools were used and I have disclosed which ones, and have fully reviewed and verified their output.
AI tools were use to assist with the following:
Checklist