feat(module): Tiger_Module_Longform — one renderer for a listing's long-form copy - #195
Merged
Conversation
…ng-form copy Two surfaces show the same "plugin page" body — the Module Manager's "View more" and a marketplace's own listing page — and they rendered it two different ways, with two different safety policies. A listing could look safe in one and not the other. Tiger_Module_Longform is now the single component behind both. It resolves the copy from where it actually lives — inline `readme` on the listing (a paid/PASS module's repo is PRIVATE, so its marketplace serves the review copy in the feed; `body` kept as an alias) or a public repo's `tiger_md` URL, https-only, size-capped and disk-cached — and renders it once, safely. The safety change is the point. Both call sites previously ran `Tiger_Cms_Renderer::renderBody($md, 'markdown')` and then regex-stripped active content from the RESULT. That path renders through the shared Parsedown::instance() with markup ALLOWED and then runs the [shortcode] pass — correct for a trusted CMS author, wrong for a file written by whoever published the module, since it emits their markup and lets them invoke this install's shortcodes. The scrub-afterwards regex was the only thing standing between the two. Longform instead escapes inline HTML at PARSE time (safe-mode Parsedown, its own instance so the CMS singleton is untouched) and filters dangerous URL schemes. Escaping at parse time is strictly stronger than stripping tags afterwards, which is the classic thing to get subtly wrong. `_scrub()` is removed with its last caller. Also fixes a contract that had never fired: _inspectMarketplace() reads `$listing['readme']`, but no producer emitted that field, so a paid listing's "View more" only ever showed its one-line description. TigerMarketplace's module adapter now declares it, so the feed carries it and the Module Manager renders the seller's real copy. 15 unit tests, the load-bearing ones being safety: raw HTML is escaped rather than emitted, an event-handler attribute cannot survive as an attribute, a javascript: link is filtered, and an http:// body is refused WITHOUT being fetched. Full suite green (2111 tests).
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.
Why
Two surfaces show the same "plugin page" body — the Module Manager's View more and a marketplace's own listing page — and they rendered it two different ways with two different safety policies. A listing could look safe in one surface and not the other.
Tiger_Module_Longform(@api) is now the single component behind both.The safety change (the actual point)
Both call sites previously did:
renderBody('markdown')goes through the sharedParsedown::instance()with markup allowed, then runs the[shortcode]pass. That is right for a trusted CMS author and wrong for a file written by whoever published the module: it emits their markup and lets them invoke this install's shortcodes. A regex scrubber over the rendered HTML was the only thing in between.Longform escapes inline HTML at parse time (safe-mode Parsedown, on its own instance so the CMS singleton is untouched) and filters dangerous URL schemes. Escaping at parse time is strictly stronger than stripping tags afterwards.
_scrub()is removed along with its last caller.A contract that had never fired
_inspectMarketplace()reads$listing['readme']— written that way because a PASS module's repo is private, so its review copy comes from the marketplace index rather than a GitHubTIGER.mdfetch. No producer ever emitted that field, so a paid listing's View more only showed its one-line description. TigerMarketplace's module adapter now declaresreadme, so the feed carries it and this renders the seller's real copy.Verified on the live chain before opening this: dev-com's feed carries
readmeforpay, tiger-dev resolves it viaTiger_Module_Registry::listing('pay','webtigers'), and it renders to HTML headed "TigerStripe — payments for your Tiger site" with no active content.Resolution order
readmeinlinebodyinlinetiger_mdURLTIGER.md— https-only, 256 KB cap, 1 h disk cacheTests
15 new unit tests. The load-bearing ones are safety: raw HTML escaped rather than emitted, an event-handler attribute cannot survive as an attribute,
javascript:links filtered, and anhttp://body refused without being fetched.Full suite green — 2111 tests, 21490 assertions, no failures.
Note
The 15 deprecations in the run are pre-existing (vendored Parsedown 1.7.4 implicit-nullable params on PHP 8.4+);
failOnDeprecationis not set and they occur onmaintoo. Worth a separate one-line-each fix.