From 811568f1b912ea019a387aceffe4986c211ed1fc Mon Sep 17 00:00:00 2001 From: lovasoa Date: Fri, 21 Feb 2025 07:12:12 +0100 Subject: [PATCH 1/6] Add markdown rendering options to the sqlpage configuration --- configuration.md | 2 ++ sqlpage/sqlpage.json | 4 ++- src/app_config.rs | 14 +++++++++ src/template_helpers.rs | 56 +++++++++++++++++++++++++---------- src/webserver/database/sql.rs | 5 +++- 5 files changed, 63 insertions(+), 18 deletions(-) diff --git a/configuration.md b/configuration.md index f7782f85..66cf241b 100644 --- a/configuration.md +++ b/configuration.md @@ -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. When disabled, HTML tags will be escaped. | +| `markdown_allow_dangerous_protocol` | false | Whether to allow dangerous protocols (like javascript:) in markdown links. When disabled, only safe protocols are allowed. | 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. diff --git a/sqlpage/sqlpage.json b/sqlpage/sqlpage.json index 1c457969..546353b8 100644 --- a/sqlpage/sqlpage.json +++ b/sqlpage/sqlpage.json @@ -1,3 +1,5 @@ { - "database_url": "sqlite:///tmp/bug.db?mode=rwc" + "database_url": "sqlite:///tmp/bug.db?mode=rwc", + "markdown_allow_dangerous_html": true, + "markdown_allow_dangerous_protocol": true } diff --git a/src/app_config.rs b/src/app_config.rs index 6ddc4587..a7426d50 100644 --- a/src/app_config.rs +++ b/src/app_config.rs @@ -248,6 +248,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 { @@ -506,6 +512,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 { diff --git a/src/template_helpers.rs b/src/template_helpers.rs index a28173b8..1ff65796 100644 --- a/src/template_helpers.rs +++ b/src/template_helpers.rs @@ -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); @@ -247,21 +247,45 @@ fn typeof_helper(v: &JsonValue) -> JsonValue { .into() } -fn markdown_helper(x: &JsonValue) -> anyhow::Result { - 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::>() - .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 { + 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::>() + .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 { diff --git a/src/webserver/database/sql.rs b/src/webserver/database/sql.rs index df422fc1..5f2f8a8f 100644 --- a/src/webserver/database/sql.rs +++ b/src/webserver/database/sql.rs @@ -749,6 +749,7 @@ impl VisitorMut for ParameterExtractor { match value { Expr::Identifier(ident) => { if let Some(param) = extract_ident_param(ident) { + log::trace!("Extracted parameter {param} from identifier {ident}"); *value = self.make_placeholder(); self.parameters.push(param); } @@ -757,8 +758,10 @@ impl VisitorMut for ParameterExtractor { // this check is to avoid recursively replacing placeholders in the form of '?', or '$1', '$2', which we emit ourselves { let new_expr = self.make_placeholder(); + log::trace!("Extracted SQL placeholder {param}, replacing with {new_expr}"); let name = std::mem::take(param); - self.parameters.push(map_param(name)); + let parameter = map_param(name); + self.parameters.push(parameter); *value = new_expr; } Expr::Function(Function { From eaec12ee517f952f27444ff004e08df7ec04c3bd Mon Sep 17 00:00:00 2001 From: lovasoa Date: Tue, 25 Feb 2025 18:20:01 +0100 Subject: [PATCH 2/6] fmt --- src/template_helpers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/template_helpers.rs b/src/template_helpers.rs index 1ff65796..1e297872 100644 --- a/src/template_helpers.rs +++ b/src/template_helpers.rs @@ -255,7 +255,7 @@ struct MarkdownHelper { impl MarkdownHelper { fn new(config: &AppConfig) -> Self { - Self { + Self { allow_dangerous_html: config.markdown_allow_dangerous_html, allow_dangerous_protocol: config.markdown_allow_dangerous_protocol, } From 3d8000afa8f6df2af92dbfd3a345a85bca8bd6df Mon Sep 17 00:00:00 2001 From: lovasoa Date: Tue, 25 Feb 2025 18:24:44 +0100 Subject: [PATCH 3/6] silence clippy --- src/app_config.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app_config.rs b/src/app_config.rs index a7426d50..69602268 100644 --- a/src/app_config.rs +++ b/src/app_config.rs @@ -143,6 +143,7 @@ pub fn load_from_env() -> anyhow::Result { } #[derive(Debug, Deserialize, PartialEq, Clone)] +#[allow(clippy::struct_excessive_bools)] pub struct AppConfig { #[serde(default = "default_database_url")] pub database_url: String, From 5511ab30ed381f36a02ab7f974d5c46735908e0e Mon Sep 17 00:00:00 2001 From: lovasoa Date: Tue, 25 Feb 2025 18:34:27 +0100 Subject: [PATCH 4/6] revert changes to conf --- sqlpage/sqlpage.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sqlpage/sqlpage.json b/sqlpage/sqlpage.json index 546353b8..c46ad7a5 100644 --- a/sqlpage/sqlpage.json +++ b/sqlpage/sqlpage.json @@ -1,5 +1,3 @@ { - "database_url": "sqlite:///tmp/bug.db?mode=rwc", - "markdown_allow_dangerous_html": true, - "markdown_allow_dangerous_protocol": true -} + "database_url": "sqlite:///tmp/bug.db?mode=rwc" +} \ No newline at end of file From 8821567251a3b51229a03da273c3c3c0de3c282d Mon Sep 17 00:00:00 2001 From: lovasoa Date: Tue, 25 Feb 2025 18:38:37 +0100 Subject: [PATCH 5/6] docs --- CHANGELOG.md | 7 +++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- configuration.md | 4 ++-- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92c30420..518007f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/Cargo.lock b/Cargo.lock index e5053a03..a88ba7b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3127,7 +3127,7 @@ dependencies = [ [[package]] name = "sqlpage" -version = "0.33.1" +version = "0.34.0" dependencies = [ "actix-multipart", "actix-rt", diff --git a/Cargo.toml b/Cargo.toml index d066713c..7d7d2d50 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] diff --git a/configuration.md b/configuration.md index 66cf241b..c3d8b4dd 100644 --- a/configuration.md +++ b/configuration.md @@ -34,8 +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. When disabled, HTML tags will be escaped. | -| `markdown_allow_dangerous_protocol` | false | Whether to allow dangerous protocols (like javascript:) in markdown links. When disabled, only safe protocols are allowed. | +| `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. From 0de64d14fd9274c21242c7e541dde502c0976d70 Mon Sep 17 00:00:00 2001 From: lovasoa Date: Tue, 25 Feb 2025 18:42:27 +0100 Subject: [PATCH 6/6] fmt --- sqlpage/sqlpage.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlpage/sqlpage.json b/sqlpage/sqlpage.json index c46ad7a5..1c457969 100644 --- a/sqlpage/sqlpage.json +++ b/sqlpage/sqlpage.json @@ -1,3 +1,3 @@ { "database_url": "sqlite:///tmp/bug.db?mode=rwc" -} \ No newline at end of file +}