Skip to content

Add markdown rendering options to the sqlpage configuration #823

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Feb 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# CHANGELOG.md

## 0.34.0 (unreleased)

- New configuration options:
- `markdown_allow_dangerous_html`: allow the usage of html in markdown (default: false)
- `markdown_allow_dangerous_protocol`: allow the usage of custom protocols in markdown (default: false)
- see [configuration.md](./configuration.md) for more details.

## 0.33.1 (2025-02-25)

- Fix a bug where the table component would not format numbers if sorting was not enabled.
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "sqlpage"
version = "0.33.1"
version = "0.34.0"
edition = "2021"
description = "Build data user interfaces entirely in SQL. A web server that takes .sql files and formats the query result using pre-made configurable professional-looking components."
keywords = ["web", "sql", "framework"]
Expand Down
2 changes: 2 additions & 0 deletions configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ Here are the available configuration options and their default values:
| `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. |
| `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. |
| `max_recursion_depth` | 10 | Maximum depth of recursion allowed in the `run_sql` function. Maximum value is 255. |
| `markdown_allow_dangerous_html` | false | Whether to allow raw HTML in markdown content. Only enable this if the markdown content is fully trusted (not user generated). |
| `markdown_allow_dangerous_protocol` | false | Whether to allow dangerous protocols (like javascript:) in markdown links. Only enable this if the markdown content is fully trusted (not user generated). |

Multiple configuration file formats are supported:
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.
Expand Down
15 changes: 15 additions & 0 deletions src/app_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ pub fn load_from_env() -> anyhow::Result<AppConfig> {
}

#[derive(Debug, Deserialize, PartialEq, Clone)]
#[allow(clippy::struct_excessive_bools)]
pub struct AppConfig {
#[serde(default = "default_database_url")]
pub database_url: String,
Expand Down Expand Up @@ -248,6 +249,12 @@ pub struct AppConfig {
/// Maximum depth of recursion allowed in the `run_sql` function.
#[serde(default = "default_max_recursion_depth")]
pub max_recursion_depth: u8,

#[serde(default = "default_markdown_allow_dangerous_html")]
pub markdown_allow_dangerous_html: bool,

#[serde(default = "default_markdown_allow_dangerous_protocol")]
pub markdown_allow_dangerous_protocol: bool,
}

impl AppConfig {
Expand Down Expand Up @@ -506,6 +513,14 @@ fn default_max_recursion_depth() -> u8 {
10
}

fn default_markdown_allow_dangerous_html() -> bool {
false
}

fn default_markdown_allow_dangerous_protocol() -> bool {
false
}

#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, Eq, Default)]
#[serde(rename_all = "lowercase")]
pub enum DevOrProd {
Expand Down
56 changes: 40 additions & 16 deletions src/template_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ pub fn register_all_helpers(h: &mut Handlebars<'_>, config: &AppConfig) {

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

fn markdown_helper(x: &JsonValue) -> anyhow::Result<JsonValue> {
let as_str = match x {
JsonValue::String(s) => Cow::Borrowed(s),
JsonValue::Array(arr) => Cow::Owned(
arr.iter()
.map(|v| v.as_str().unwrap_or_default())
.collect::<Vec<_>>()
.join("\n"),
),
JsonValue::Null => Cow::Owned(String::new()),
other => Cow::Owned(other.to_string()),
};
markdown::to_html_with_options(&as_str, &markdown::Options::gfm())
.map(JsonValue::String)
.map_err(|e| anyhow::anyhow!("markdown error: {e}"))
/// Helper to render markdown with configurable options
struct MarkdownHelper {
allow_dangerous_html: bool,
allow_dangerous_protocol: bool,
}

impl MarkdownHelper {
fn new(config: &AppConfig) -> Self {
Self {
allow_dangerous_html: config.markdown_allow_dangerous_html,
allow_dangerous_protocol: config.markdown_allow_dangerous_protocol,
}
}
}

impl CanHelp for MarkdownHelper {
fn call(&self, args: &[PathAndJson]) -> Result<JsonValue, String> {
let as_str = match args {
[v] => v.value(),
_ => return Err("expected one argument".to_string()),
};
let as_str = match as_str {
JsonValue::String(s) => Cow::Borrowed(s),
JsonValue::Array(arr) => Cow::Owned(
arr.iter()
.map(|v| v.as_str().unwrap_or_default())
.collect::<Vec<_>>()
.join("\n"),
),
JsonValue::Null => Cow::Owned(String::new()),
other => Cow::Owned(other.to_string()),
};
let mut options = markdown::Options::gfm();
options.compile.allow_dangerous_html = self.allow_dangerous_html;
options.compile.allow_dangerous_protocol = self.allow_dangerous_protocol;
markdown::to_html_with_options(&as_str, &options)
.map(JsonValue::String)
.map_err(|e| e.to_string())
}
}

fn buildinfo_helper(x: &JsonValue) -> anyhow::Result<JsonValue> {
Expand Down
Loading