Skip to content

Merge upstream mity/md4c master into the fork - #7

Open
hryhoriiK97 wants to merge 40 commits into
software-mansion-labs:masterfrom
hryhoriiK97:chore/merge-upstream
Open

hryhoriiK97 wants to merge 40 commits into
software-mansion-labs:masterfrom
hryhoriiK97:chore/merge-upstream

Conversation

@hryhoriiK97

Copy link
Copy Markdown
Collaborator

What/Why?

The fork was 38 commits behind mity/md4c. This merge brings in the permissive autolink fixes that software-mansion/enriched-markdown#961 needs (mity#449, mity#454, mity#462) and the other parser fixes since the last sync.

The ENRMRKD_ rename and the <video> block-level patch are both preserved.

Merge conflicts: README.md, src/enrmrkd.c (2 hunks), src/enrmrkd-entity.c and test/spec-permissive-autolinks.txt. All took upstream's side, then scripts/rename-upstream-symbols.pl reapplied the prefix.

Rename script fix: entity.h also matched inside enrmrkd-entity.h, so every run produced enrmrkd-enrmrkd-entity.h and broke the build, including on clean master. The second commit fixes that.

Known script issue, not fixed: the script still rewrites the upstream names in the README's explanatory sentence (md_parse(), MD_FLAG_TABLES, md4c.h). I restored that sentence by hand here, and it needs restoring after any full run.

Testing

  • scripts/run-tests.py: all spec suites and all 36 pathological inputs pass, including the <video> cases in spec-addendum.txt.
  • scripts/check-exported-symbols.sh passes.
  • In enriched-markdown, using these sources in a temporary worktree: test-core-parser.sh passes and the golden AST dump, including every Video node, is unchanged. The #961 autolink runner also passes.

Needs a follow-up in enriched-markdown: https://e.com/😀/bare in fixtures/block_ids/document.md now autolinks, so ids.txt gains one link id when these sources are vendored.

🤖 Generated with Claude Code

mity and others added 30 commits October 5, 2026 09:01
md_is_link_destination_B() ends an unbracketed destination on
ISWHITESPACE(off) || ISCNTRL(off), six comparisons for every byte of the
destination. The union of the two sets is exactly 0..32 plus 127, because
space is the only whitespace character above 31, so
(unsigned) CH(off) <= 32 || CH(off) == 127 gives the same answer in two.
md_is_autolink_uri() uses the same test on the path of a <scheme:...>
autolink and gets the same change.

The two expressions agree for every value of signed and unsigned char and
of a 16-bit WCHAR.

md2html -s on a 5 MB link-heavy document goes from 53.7 ms to 52.7 ms
(-2.0%, median of 15 interleaved rounds, aarch64, gcc -O2); md_parse()
executes 5.0% fewer instructions on it under callgrind (x86-64). spec.txt
does not move.
The suite never fed a DEL byte to md_is_link_destination_B() or
md_is_autolink_uri(), nor a '<' inside an autolink URI, so those
branches of the stop-character tests went unexercised. Add regression
examples for each.
replace the manual tests for it created in the commit
45ec7ac.
c933a91 folded md_analyze_spoiler() into md_analyze_generic() and dropped
its test that a pipe run is exactly two characters long. Since mity#419,
md_collect_marks() also collects a single '|' as a potential opener and
closer when MD_FLAG_WIKILINKS is set, so with MD_FLAG_SPOILERS enabled as
well, unresolved single pipes left over after wiki-link resolution are
paired as spoiler delimiters. "a | b | c" renders as "<p>a  b  c</p>" and
"x | y ||" emits a "</x-spoiler>" with no opener.

Restore the length test in md_analyze_marks() before handing a '|' mark to
md_analyze_generic(), and add both inputs to spec-spoilers.txt.
With MD_FLAG_PERMISSIVEURLAUTOLINKS, "http://localhost:3000" and
"https://example.com:8443/path" were not recognized: the first has a
single-component host, the second a port, and ':' is not an allowed
character after the host.

A scheme URL may now have a single-component host (the scheme makes the
intent explicit; WWW and e-mail autolinks still need two components), and
the host may be followed by ':' and a port number. This matches what
cmark-gfm does for scheme URLs.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
A trailing ':' or a non-numeric suffix after the host is not consumed as a
port and keeps the text from becoming an autolink.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Resolve bare executable names before splitting legacy command strings.
Run real-process invocation regressions from the existing test runner.

Assisted-by: OpenAI GPT-6 (Codex) <[email protected]>
Assisted-by: OpenAI GPT-6 Luna <[email protected]>
Change date format to ISO 8601 format
Change source name to md4c
Remove manual-section since the default value will be good
 * Add a note about fixing mity#247 / mity#300.
 * Add a note about fixing mity#367.
 * Add a note about relaxing MD_FLAG_PERMISSIVEURLAUTOLINKS.
The segmented path/query/fragment allowlists reject or truncate ordinary
URLs containing colons, repeated semicolons, redirect URLs and percent
escapes. A numeric port after a host is also rejected, and unmatched
opening parentheses prevent the canonical GFM www example from linking.

Scan the tail continuously with md4c's Unicode decoding and resolved-mark
checks, then trim terminal punctuation and excess closing parentheses.
Reuse ISUNICODEWHITESPACE_ so the ASCII-only build remains supported.
Keep the existing host, preceding-boundary and e-mail rules. No dependency
or public API is added. Host-colon tails remain permissive rather than
validating numeric ports; document this policy and its overlap with mity#449.

Extend the existing permissive-autolink specification with 12 examples
covering paths, queries, fragments, parentheses, Unicode, terminal
punctuation and Markdown boundaries. Nine examples fail before the fix.
The full Debug/Release suites and focused sanitizer tests pass locally.

Downstream report: https://bugs.documentfoundation.org/show_bug.cgi?id=169429
Downstream patch: https://gerrit.libreoffice.org/c/core/+/212109
Related proposal: mity#449

Signed-off-by: Emanuel Schorsch <[email protected]>
Assisted by GPT 6.1 Sol High
Disable every mark in a consumed reference label so its delimiters cannot pair with surrounding text and emit unmatched span callbacks.
The footnote-label hash table points into the definition array. Sorting that array by reference index invalidates the pointers and can leave later references unresolved. Track emission order separately, including definitions first referenced within another footnote.
When a footnote-like label has no definition, the parser currently advances its opening mark before returning false. A following inline link can then become a wiki link and leave nested spans unbalanced. Compute the label start without changing the mark, and advance it only after lookup succeeds.

The existing wiki-link regression changes output because its old expectation depended on the failed probe advancing that mark. The revised exact expectation matches the same input with footnotes disabled; an adjacent control pins both flag combinations. Add ordinary-link and superscript examples to the footnote suite.
Record each list-item opener boundary and whether the item contained a nonblank source line. Avoid mistaking the end of a paragraph for an empty item when its byte offset matches MD_BLOCK_LI, and retain content after a consumed link reference definition.

Cover both nonempty cases and the genuinely empty-item rule in the regression suite.
znewt99 and others added 10 commits October 19, 2026 20:19
Since the URL tail runs until whitespace (mity#454) and only excess ')' is
trimmed at its end, a URL enclosed in square brackets or braces swallows
the closing one: "[http://example.com/x]" links "http://example.com/x]",
where 0.6.0 kept the ']' outside the link.

Apply the same rule to ']' and '}' as to ')': only excess closing ones at
the end of the tail are excluded, so "https://example.org/a[b]c" is still
linked whole.
…L tail.

An entity reference or a backslash escape inside the URL tail is a resolved
mark, so the tail scan stopped there, and as it is not a closer mark the
whole autolink was then rejected: "https://example.org/?a=b&amp;c=d" and
"https://example.org/a\_b" did not link at all.

Treat them the same way as standard autolinks do: they are just characters
of the URL. The link text keeps them verbatim, the href resolves the entity
and keeps the backslash literal. This is also what cmark-gfm renders.

The trailing-punctuation trim never splits such a mark. A trailing entity
is excluded as a whole (cmark-gfm's ';' rule) and so is an escaped terminal
punctuation character. Whatever the trim excluded is accepted as the
follower of the autolink.

The mity#152 suppression of an autolink that forms the whole text of a bracket
link took the mark right before ']' as the autolink closer, which is no
longer true when such marks live inside the URL. Follow the opener's 'next'
instead. (Its second loop tested the wrong variable and never iterated.)
... when multiple blank lines follow each other.

This fixes O(N^2) parsing time for input generated by:

python3 -c 'import sys; sys.stdout.write("- " * 50000 + "x\n" + "\n" * 100000)'

(See also https://github.com/mity/md4c/security/advisories/GHSA-582f-rcwc-x4mp)
when jumping deep into nested list on a blank line.
This fixes the counter-example with alternating blank lines
with different number of initial spaces.

(See also https://github.com/mity/md4c/security/advisories/GHSA-582f-rcwc-x4mp)

Co-authored-by: Ilia Alshanetsky <[email protected]>
The harness evaluated q.put(prog.to_html(inp)) in the parent process and
passed its result as the Process target, so md2html always ran inline and
the TIMEOUT never fired. Handing prog.to_html to the child instead cannot
work on Windows, where the spawn start method pickles the target and
Prog.to_html is a lambda.

Let subprocess enforce the timeout: pipe_through_prog() takes a timeout,
kills md2html when it expires, and re-raises TimeoutExpired. Prog.to_html
becomes a regular method. No child Python process is needed.
At 50000 levels the plain case took 4.2 seconds on the unfixed parser
here, under the 5 second timeout, so a faster machine would pass it even
with the quadratic scan back. Double the input; the regex still matches
the innermost 50000 levels, which also keeps the alternating case under
the 65535 repetition limit some Python builds impose.
Brings in the permissive autolink fixes (mity#449, mity#454, mity#462) and the other
parser fixes since the fork last synced. The conflicts took upstream's side,
then the prefix rename was reapplied. <video> stays block-level.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
`entity.h` also matched inside `enrmrkd-entity.h`, so every run wrote
`enrmrkd-enrmrkd-entity.h` and broke the build.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants