Skip to content

Commit 811568f

Browse files
committed
Add markdown rendering options to the sqlpage configuration
1 parent 0041cb0 commit 811568f

File tree

5 files changed

+63
-18
lines changed

5 files changed

+63
-18
lines changed

configuration.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ Here are the available configuration options and their default values:
3434
| `content_security_policy` | `script-src 'self' 'nonce-XXX` | The [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) to set in the HTTP headers. If you get CSP errors in the browser console, you can set this to the empty string to disable CSP. |
3535
| `system_root_ca_certificates` | false | Whether to use the system root CA certificates to validate SSL certificates when making http requests with `sqlpage.fetch`. If set to false, SQLPage will use its own set of root CA certificates. If the `SSL_CERT_FILE` or `SSL_CERT_DIR` environment variables are set, they will be used instead of the system root CA certificates. |
3636
| `max_recursion_depth` | 10 | Maximum depth of recursion allowed in the `run_sql` function. Maximum value is 255. |
37+
| `markdown_allow_dangerous_html` | false | Whether to allow raw HTML in markdown content. When disabled, HTML tags will be escaped. |
38+
| `markdown_allow_dangerous_protocol` | false | Whether to allow dangerous protocols (like javascript:) in markdown links. When disabled, only safe protocols are allowed. |
3739

3840
Multiple configuration file formats are supported:
3941
you can use a [`.json5`](https://json5.org/) file, a [`.toml`](https://toml.io/) file, or a [`.yaml`](https://en.wikipedia.org/wiki/YAML#Syntax) file.

sqlpage/sqlpage.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
{
2-
"database_url": "sqlite:///tmp/bug.db?mode=rwc"
2+
"database_url": "sqlite:///tmp/bug.db?mode=rwc",
3+
"markdown_allow_dangerous_html": true,
4+
"markdown_allow_dangerous_protocol": true
35
}

src/app_config.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,12 @@ pub struct AppConfig {
248248
/// Maximum depth of recursion allowed in the `run_sql` function.
249249
#[serde(default = "default_max_recursion_depth")]
250250
pub max_recursion_depth: u8,
251+
252+
#[serde(default = "default_markdown_allow_dangerous_html")]
253+
pub markdown_allow_dangerous_html: bool,
254+
255+
#[serde(default = "default_markdown_allow_dangerous_protocol")]
256+
pub markdown_allow_dangerous_protocol: bool,
251257
}
252258

253259
impl AppConfig {
@@ -506,6 +512,14 @@ fn default_max_recursion_depth() -> u8 {
506512
10
507513
}
508514

515+
fn default_markdown_allow_dangerous_html() -> bool {
516+
false
517+
}
518+
519+
fn default_markdown_allow_dangerous_protocol() -> bool {
520+
false
521+
}
522+
509523
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, Eq, Default)]
510524
#[serde(rename_all = "lowercase")]
511525
pub enum DevOrProd {

src/template_helpers.rs

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ pub fn register_all_helpers(h: &mut Handlebars<'_>, config: &AppConfig) {
6363

6464
// icon helper: generate an image with the specified icon
6565
h.register_helper("icon_img", Box::new(IconImgHelper(site_prefix)));
66-
register_helper(h, "markdown", markdown_helper as EH);
66+
register_helper(h, "markdown", MarkdownHelper::new(config));
6767
register_helper(h, "buildinfo", buildinfo_helper as EH);
6868
register_helper(h, "typeof", typeof_helper as H);
6969
register_helper(h, "rfc2822_date", rfc2822_date_helper as EH);
@@ -247,21 +247,45 @@ fn typeof_helper(v: &JsonValue) -> JsonValue {
247247
.into()
248248
}
249249

250-
fn markdown_helper(x: &JsonValue) -> anyhow::Result<JsonValue> {
251-
let as_str = match x {
252-
JsonValue::String(s) => Cow::Borrowed(s),
253-
JsonValue::Array(arr) => Cow::Owned(
254-
arr.iter()
255-
.map(|v| v.as_str().unwrap_or_default())
256-
.collect::<Vec<_>>()
257-
.join("\n"),
258-
),
259-
JsonValue::Null => Cow::Owned(String::new()),
260-
other => Cow::Owned(other.to_string()),
261-
};
262-
markdown::to_html_with_options(&as_str, &markdown::Options::gfm())
263-
.map(JsonValue::String)
264-
.map_err(|e| anyhow::anyhow!("markdown error: {e}"))
250+
/// Helper to render markdown with configurable options
251+
struct MarkdownHelper {
252+
allow_dangerous_html: bool,
253+
allow_dangerous_protocol: bool,
254+
}
255+
256+
impl MarkdownHelper {
257+
fn new(config: &AppConfig) -> Self {
258+
Self {
259+
allow_dangerous_html: config.markdown_allow_dangerous_html,
260+
allow_dangerous_protocol: config.markdown_allow_dangerous_protocol,
261+
}
262+
}
263+
}
264+
265+
impl CanHelp for MarkdownHelper {
266+
fn call(&self, args: &[PathAndJson]) -> Result<JsonValue, String> {
267+
let as_str = match args {
268+
[v] => v.value(),
269+
_ => return Err("expected one argument".to_string()),
270+
};
271+
let as_str = match as_str {
272+
JsonValue::String(s) => Cow::Borrowed(s),
273+
JsonValue::Array(arr) => Cow::Owned(
274+
arr.iter()
275+
.map(|v| v.as_str().unwrap_or_default())
276+
.collect::<Vec<_>>()
277+
.join("\n"),
278+
),
279+
JsonValue::Null => Cow::Owned(String::new()),
280+
other => Cow::Owned(other.to_string()),
281+
};
282+
let mut options = markdown::Options::gfm();
283+
options.compile.allow_dangerous_html = self.allow_dangerous_html;
284+
options.compile.allow_dangerous_protocol = self.allow_dangerous_protocol;
285+
markdown::to_html_with_options(&as_str, &options)
286+
.map(JsonValue::String)
287+
.map_err(|e| e.to_string())
288+
}
265289
}
266290

267291
fn buildinfo_helper(x: &JsonValue) -> anyhow::Result<JsonValue> {

src/webserver/database/sql.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,7 @@ impl VisitorMut for ParameterExtractor {
749749
match value {
750750
Expr::Identifier(ident) => {
751751
if let Some(param) = extract_ident_param(ident) {
752+
log::trace!("Extracted parameter {param} from identifier {ident}");
752753
*value = self.make_placeholder();
753754
self.parameters.push(param);
754755
}
@@ -757,8 +758,10 @@ impl VisitorMut for ParameterExtractor {
757758
// this check is to avoid recursively replacing placeholders in the form of '?', or '$1', '$2', which we emit ourselves
758759
{
759760
let new_expr = self.make_placeholder();
761+
log::trace!("Extracted SQL placeholder {param}, replacing with {new_expr}");
760762
let name = std::mem::take(param);
761-
self.parameters.push(map_param(name));
763+
let parameter = map_param(name);
764+
self.parameters.push(parameter);
762765
*value = new_expr;
763766
}
764767
Expr::Function(Function {

0 commit comments

Comments
 (0)