diff --git a/crates/engine/src/cmd/format.rs b/crates/engine/src/cmd/format.rs index 7561b6ddc..a935a6d01 100644 --- a/crates/engine/src/cmd/format.rs +++ b/crates/engine/src/cmd/format.rs @@ -204,14 +204,17 @@ const COMMA: &str = "_(* #,##0.00_);_(* \\(#,##0.00\\);_(* \"-\"??_);_(@_)"; /// currency; `symbol` is how the control channel picks another one, e.g. `"L"` for lempiras). fn accounting(s: &mut Session, p: &Json) -> Result { let symbol = str_param(p, "symbol").unwrap_or("$"); - if symbol.contains(['"', '\\']) { - return Err(bad("home.accounting", "the symbol can't contain a quote or a backslash")); + // The symbol goes into a quoted literal: a quote or a backslash would end it, and a control + // character would reach the format's text as-is. + if symbol.contains(['"', '\\']) || symbol.chars().any(char::is_control) { + return Err(bad("home.accounting", "the symbol can't contain a quote, a backslash or a control character")); } set_fmt(s, p, &accounting_format(symbol)) } fn accounting_format(symbol: &str) -> String { - format!("_({symbol:?}* #,##0.00_);_({symbol:?}* \\(#,##0.00\\);_({symbol:?}* \"-\"??_);_(@_)") + let symbol = format!("\"{symbol}\""); + format!("_({symbol}* #,##0.00_);_({symbol}* \\(#,##0.00\\);_({symbol}* \"-\"??_);_(@_)") } fn with(p: &Json, key: &str, v: Json) -> Json { diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index f4d17bd7e..97e460cf9 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -1274,9 +1274,10 @@ fn accounting_format_takes_the_symbol_it_is_given() { // Without a symbol it stays the default `$` format, character for character. s.execute("home.accounting", json!({"range": "A1"})).unwrap(); assert_eq!(text(&s, "A1"), " $ 1,234.50 "); - // A quote or a backslash would break the format string, so the symbol is refused. - for bad in ["\"", "\\"] { - assert!(s.execute("home.accounting", json!({"range": "A1", "symbol": bad})).is_err(), "{bad}"); + // A quote or a backslash would end the format's quoted text, and a control character would + // reach it as-is, so those symbols are refused. + for bad in ["\"", "\\", "\n", "L\t"] { + assert!(s.execute("home.accounting", json!({"range": "A1", "symbol": bad})).is_err(), "{bad:?}"); } }