‘Solid’? I have never found a solid BBcode parser at all.They all seem to be a loose collection of careless regexen, easy to fool into allowing HTML-injection attacks.
For example the one John W posted can clearly be exploited with several tags including:
[img]xxx" onerror="alert('JS injection!')[/img]
plus it allows javascript:
and other dangerous URLs, fails to escape &
, disallows many URL characters (including %
!) whilst accidentally allowing others it shouldn't (the author hasn't quite understood what the backslash-escape in the string is doing there) and it fails to disallow misnested tags or tags accidentally sucked into other tags' attributes... basically it's an insecure mess, and this is par for the course with bbcode parsers.
Sorry for the unhelpful answer (it was too big to fit in a comment).
ETA re comment: Ah well it's not exactly a bbcode module, just similar. I split by lines, removed existing control characters, then used byte 01 as a surrogate for &
, 02 for <
and 03 for >
, then for each transformation step used re.split on (\x02[^\x03]*\x03)
and ran the replacement regex on every second (non-tag) part, starting with the ‘innermost’ replacements like linebreaks and emotes, then working outwards though images to links and italic/bold markup, inserting \x02html tags\x03
as it goes. Then finally HTML-encode &<>
and replace the control codes with &<>
. This stops markup getting marked up itself, which is a big source of vulnerabilities in simplistic regex-based markup.
Come to think of it, I did also write an actual Python bbcode parser, but only as a quick compatibility hack; it doesn't offer all the capabilities of full bbcode. In particular, it disallowed nesting any range tag (ie. a tag with a close-tag) inside any other range tag. This is comparatively easy to implement if that's acceptable, as you can use a single-pass regex to match any tag and have a replacement function decide how to replace based on tag name. eg.:
\[ (i|b|color|url|somethingelse) \=? ([^]]+)? \] (?: ([^]]*) \[\/\1\] )
(This is a VERBOSE
regex so the whitespace is just for readability. As much as any regex is ever readable.)
Removing nesting greatly simplifies the number of corner cases.