This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
A zero-copy BBCode parser for Rust supporting phpBB and XenForo syntax. Converts BBCode to safe, XSS-protected HTML.
# Build
cargo build
cargo build --release
# Run tests
cargo test
cargo test --release
# Run a single test
cargo test test_name
# Run property-based tests (41 tests with thousands of random inputs)
cargo test --test proptest
# Run benchmarks
cargo bench
# Run examples
cargo run --example attach
cargo run --example debug
cargo run --example xss_check
# Enable plugins feature (linkme-based distributed registration)
cargo build --features plugins
# Fuzzing (requires nightly Rust)
cargo +nightly fuzz list # List fuzz targets
cargo +nightly fuzz run fuzz_parse # Fuzz main parser
cargo +nightly fuzz run fuzz_url # Fuzz URL handling (XSS focus)
cargo +nightly fuzz run fuzz_img # Fuzz image tags
cargo +nightly fuzz run fuzz_style # Fuzz color/font/size (CSS injection)
cargo +nightly fuzz run fuzz_html_escape # Fuzz HTML escapingThe library follows a three-stage pipeline: Tokenize → Parse → Render
Input BBCode → Tokenizer → Tokens → Parser → AST (Document) → Renderer → HTML
-
tokenizer.rs: Zero-copy tokenizer usingwinnowparser combinators. ProducesTokenenum (Text, LineBreak, Url, OpenTag, CloseTag). All string data references the original input. -
parser.rs: Converts tokens into AST (DocumentcontainingNodes). Handles tag nesting validation, forbidden ancestors, required parents, verbatim content, and max depth limits. UsesTagRegistryto resolve tag definitions. -
ast.rs: Core data structures -Document,Node(Text/LineBreak/AutoUrl/Tag),TagNode,TagOption(None/Scalar/Map),TagType(Inline/Block/Verbatim/SelfClosing/Void). -
renderer.rs: Converts AST to HTML with XSS protection. Supports custom tag handlers viaCustomTagHandlertrait. Validates colors, fonts, sizes, and URLs. -
tags.rs: Tag definitions -TagDef(static, compile-time),CustomTagDef(runtime, owned strings),TagRegistryfor lookup. ContainsSTANDARD_TAGSarray with all built-in tags.
Custom tags are added in two places:
- Parser: Register
CustomTagDefwithparser.register_custom_tag() - Renderer: Register
CustomTagHandlerwithrenderer.register_handler()
See examples/attach.rs for a complete custom tag implementation with batch data fetching.
Tags support aliases via the aliases field. For example, [attachment] is an alias for [attach]:
CustomTagDef {
name: "attach".into(),
aliases: vec!["attachment".into()],
// ...
}Both [attach]123[/attach] and [attachment]123[/attachment] will be handled identically.
Custom handlers are checked before built-in rendering. To override a built-in tag like [url]:
struct CustomUrlHandler;
impl CustomTagHandler for CustomUrlHandler {
fn tag_name(&self) -> &str { "url" }
fn render(&self, tag: &TagNode, ctx: &RenderContext, output: &mut String) -> bool {
// Custom rendering logic here
// Return true to indicate the tag was handled
// Return false to fall through to built-in rendering
true
}
}
renderer.register_handler(Arc::new(CustomUrlHandler));- Zero-copy parsing: Tokens and AST nodes use
&strorCow<str>referencing original input - Tag case insensitivity: Tag names normalized to lowercase internally, raw case preserved
- Graceful degradation: Unknown/broken tags render as escaped text
- XSS protection: URL scheme validation, HTML escaping, event handler blocking
- Verbatim tags:
[code],[plain]content not parsed for BBCode
Inline:[b],[i],[color]- nest freelyBlock:[quote],[list],[table]- structural elementsVerbatim:[code],[plain],[icode]- content not parsedSelfClosing:[hr],[br],[*]- no closing tag neededVoid:[img]- renders as void HTML element
Primary goal: XenForo compatibility. Secondary: phpBB compatibility.
Implemented (simple rendering):
[b], [i], [u], [s], [color], [font], [size], [sub], [sup], [url], [email], [img], [quote], [code], [icode], [php], [html], [plain], [list], [*], [left], [center], [right], [justify], [indent], [heading], [hr], [br], [spoiler], [ispoiler], [user], [table], [tr], [th], [td]
| Tag | Type | Requires Prefetch | Notes |
|---|---|---|---|
[attach] |
Complex | Yes - attachment entities | See examples/attach.rs for pattern |
[media] |
Complex | Yes - media site configs | YouTube, Vimeo, etc. via oEmbed or callbacks |
[url unfurl="true"] |
Complex | Yes - UnfurlResult data | Rich URL previews with title/description/image |
[embed] |
Complex | Yes - entity loading | Embeds other posts/content with permission checks |
| Tag | Type | Notes |
|---|---|---|
[attachment] |
Complex | Alias for [attach] - implement via custom handler |
| Tag | Reason |
|---|---|
[flash] |
Deprecated technology, security risk, not supported by modern browsers |
XenForo uses getBbCodeRenderOptions() on entities to provide prefetched data. Our equivalent:
// 1. First pass: collect IDs from AST
let attachment_ids = collect_attachment_ids(&document);
// 2. Batch fetch from database
let attachments = fetch_attachments(attachment_ids).await;
// 3. Render with context
let mut renderer = Renderer::new();
renderer.set_context("attachments", attachments);
let html = renderer.render(&document);- Quote metadata injection: XenForo's
[quote]supportspost_id,user_id,timeattributes that generate URLs at render time - Image proxy:
[img]can route through image proxy service for dimension fetching - Media site callbacks:
[media]has per-site helper classes (YouTube.php, Vimeo.php) - Cookie consent: Third-party embeds can require consent before loading
- UID suffix system: phpBB stores
[b:uid]text[/b:uid]- not needed for our use case - Bitfield optimization: phpBB tracks which BBCodes are used per post - optimization only
- PHP syntax highlighting:
[code=php]uses PHP'shighlight_string()- use syntect/tree-sitter instead