ironmark/lib.rs
1#![deny(clippy::undocumented_unsafe_blocks)]
2
3//! # ironmark
4//!
5//! A fast, CommonMark 0.31.2 compliant Markdown-to-HTML parser with extensions.
6//!
7//! ## Usage
8//!
9//! ```
10//! use ironmark::{parse, ParseOptions};
11//!
12//! // With defaults (all extensions enabled)
13//! let html = parse("# Hello, **world**!", &ParseOptions::default());
14//!
15//! // Disable specific extensions
16//! let opts = ParseOptions {
17//! enable_strikethrough: false,
18//! enable_tables: false,
19//! ..Default::default()
20//! };
21//! let html = parse("Plain CommonMark only.", &opts);
22//! ```
23//!
24//! ## Security
25//!
26//! When rendering **untrusted** input, enable these options:
27//!
28//! ```
29//! use ironmark::{parse, ParseOptions};
30//!
31//! let opts = ParseOptions {
32//! disable_raw_html: true, // escape HTML blocks & inline HTML
33//! max_input_size: 1_000_000, // limit input to 1 MB
34//! ..Default::default()
35//! };
36//! let html = parse("<script>alert(1)</script>", &opts);
37//! assert!(!html.contains("<script>"));
38//! ```
39//!
40//! Additionally, `javascript:`, `vbscript:`, and `data:` URIs (except `data:image/…`)
41//! are **always** stripped from link and image destinations regardless of options.
42//!
43//! ## Extensions
44//!
45//! All extensions are enabled by default via [`ParseOptions`]:
46//!
47//! | Syntax | HTML | Option |
48//! |---|---|---|
49//! | `~~text~~` | `<del>` | `enable_strikethrough` |
50//! | `==text==` | `<mark>` | `enable_highlight` |
51//! | `++text++` | `<u>` | `enable_underline` |
52//! | `\| table \|` | `<table>` | `enable_tables` |
53//! | `- [x] task` | checkbox | `enable_task_lists` |
54//! | bare URLs | `<a>` | `enable_autolink` |
55//! | newlines | `<br />` | `hard_breaks` |
56
57pub mod ast;
58mod block;
59mod entities;
60mod html;
61mod inline;
62mod render;
63
64pub use ast::{Block, ListKind, TableAlignment, TableData};
65pub use block::{parse, parse_to_ast};
66
67#[inline(always)]
68pub(crate) fn is_ascii_punctuation(b: u8) -> bool {
69 matches!(b, b'!'..=b'/' | b':'..=b'@' | b'['..=b'`' | b'{'..=b'~')
70}
71
72#[inline(always)]
73pub(crate) fn utf8_char_len(first: u8) -> usize {
74 if first < 0x80 {
75 1
76 } else if first < 0xE0 {
77 2
78 } else if first < 0xF0 {
79 3
80 } else {
81 4
82 }
83}
84
85/// Options for customizing Markdown parsing behavior.
86pub struct ParseOptions {
87 /// When `true`, every newline inside a paragraph becomes a hard line break (`<br />`),
88 /// similar to GitHub Flavored Markdown. Default: `true`.
89 pub hard_breaks: bool,
90 /// Enable `==highlight==` syntax → `<mark>`. Default: `true`.
91 pub enable_highlight: bool,
92 /// Enable `~~strikethrough~~` syntax → `<del>`. Default: `true`.
93 pub enable_strikethrough: bool,
94 /// Enable `++underline++` syntax → `<u>`. Default: `true`.
95 pub enable_underline: bool,
96 /// Enable pipe table syntax. Default: `true`.
97 pub enable_tables: bool,
98 /// Automatically detect bare URLs (`https://...`) and emails (`user@example.com`)
99 /// and wrap them in `<a>` tags. Default: `true`.
100 pub enable_autolink: bool,
101 /// Enable GitHub-style task lists (`- [ ] unchecked`, `- [x] checked`)
102 /// in list items. Default: `true`.
103 pub enable_task_lists: bool,
104 /// When `true`, raw HTML blocks and inline HTML are escaped instead of passed
105 /// through verbatim. This prevents XSS when rendering untrusted markdown.
106 /// Default: `false`.
107 pub disable_raw_html: bool,
108 /// Maximum nesting depth for block-level containers (blockquotes, list items).
109 /// Once exceeded, further nesting is treated as paragraph text.
110 /// Default: `128`.
111 pub max_nesting_depth: usize,
112 /// Maximum input size in bytes. Inputs exceeding this limit are truncated.
113 /// `0` means no limit. Default: `0`.
114 pub max_input_size: usize,
115}
116
117impl Default for ParseOptions {
118 fn default() -> Self {
119 Self {
120 hard_breaks: true,
121 enable_highlight: true,
122 enable_strikethrough: true,
123 enable_underline: true,
124 enable_tables: true,
125 enable_autolink: true,
126 enable_task_lists: true,
127 disable_raw_html: false,
128 max_nesting_depth: 128,
129 max_input_size: 0,
130 }
131 }
132}