1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-08-09 04:22:45 +03:00

templates: escape translation placeholders

This commit is contained in:
Quentin Gliech
2023-10-03 09:43:58 +02:00
parent 15ad89aa82
commit 2b645f7be4
4 changed files with 60 additions and 10 deletions

View File

@@ -484,11 +484,22 @@ fn format_value(value: &Value, placeholder: &Placeholder) -> Result<String, Form
}
}
enum FormattedMessagePart<'a> {
pub enum FormattedMessagePart<'a> {
/// A literal text part of the message. It should not be escaped.
Text(&'a str),
/// A placeholder part of the message. It should be escaped.
Placeholder(String),
}
impl<'a> FormattedMessagePart<'a> {
fn len(&self) -> usize {
match self {
FormattedMessagePart::Text(text) => text.len(),
FormattedMessagePart::Placeholder(placeholder) => placeholder.len(),
}
}
}
impl std::fmt::Display for FormattedMessagePart<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
@@ -500,6 +511,27 @@ impl std::fmt::Display for FormattedMessagePart<'_> {
pub struct FormattedMessage<'a> {
parts: Vec<FormattedMessagePart<'a>>,
total_len: usize,
}
impl FormattedMessage<'_> {
/// Returns the length of the formatted message (not the number of parts).
#[must_use]
pub fn len(&self) -> usize {
self.total_len
}
/// Returns `true` if the formatted message is empty.
#[must_use]
pub fn is_empty(&self) -> bool {
self.total_len == 0
}
/// Returns the list of parts of the formatted message.
#[must_use]
pub fn parts(&self) -> &[FormattedMessagePart<'_>] {
&self.parts
}
}
impl std::fmt::Display for FormattedMessage<'_> {
@@ -529,6 +561,8 @@ impl Message {
// Holds the current index of the placeholder we are formatting, which is used
// by non-named, non-indexed placeholders
let mut current_placeholder = 0usize;
// Compute the total length of the formatted message
let mut total_len = 0usize;
for part in self.parts() {
let formatted = match part {
Part::Percent => FormattedMessagePart::Text("%"),
@@ -563,9 +597,10 @@ impl Message {
FormattedMessagePart::Placeholder(formatted)
}
};
total_len += formatted.len();
parts.push(formatted);
}
Ok(FormattedMessage { parts })
Ok(FormattedMessage { parts, total_len })
}
}

View File

@@ -19,10 +19,9 @@ mod formatter;
mod message;
mod parser;
use thiserror::Error;
pub use self::{
argument::{Argument, List as ArgumentList},
formatter::{FormatError, FormattedMessage, FormattedMessagePart},
message::Message,
};
@@ -79,7 +78,7 @@ pub(crate) use arg_list_inner;
#[allow(unused_imports)]
pub(crate) use sprintf;
#[derive(Debug, Error)]
#[derive(Debug, thiserror::Error)]
#[error(transparent)]
enum Error {
Format(#[from] self::formatter::FormatError),

View File

@@ -16,7 +16,7 @@ walkdir = "2.4.0"
anyhow.workspace = true
thiserror.workspace = true
minijinja = { workspace = true, features = ["loader", "json", "speedups"] }
minijinja = { workspace = true, features = ["loader", "json", "speedups", "unstable_machinery"] }
serde.workspace = true
serde_json.workspace = true
serde_urlencoded = "0.7.1"

View File

@@ -25,10 +25,12 @@ use std::{
};
use camino::Utf8Path;
use mas_i18n::{Argument, ArgumentList, DataLocale, Translator};
use mas_i18n::{sprintf::FormattedMessagePart, Argument, ArgumentList, DataLocale, Translator};
use mas_router::UrlBuilder;
use mas_spa::ViteManifest;
use minijinja::{
escape_formatter,
machinery::make_string_output,
value::{from_args, Kwargs, Object, SeqObject, ViaDeserialize},
Error, ErrorKind, State, Value,
};
@@ -258,12 +260,26 @@ impl Object for Translate {
.collect();
let list = res?;
let formatted = message.format(&list).map_err(|e| {
let formatted = message.format_(&list).map_err(|e| {
Error::new(ErrorKind::InvalidOperation, "Could not format message").with_source(e)
})?;
// TODO: escape
Ok(Value::from_safe_string(formatted))
let mut buf = String::with_capacity(formatted.len());
let mut output = make_string_output(&mut buf);
for part in formatted.parts() {
match part {
FormattedMessagePart::Text(text) => {
// Literal text, just write it
output.write_str(text)?;
}
FormattedMessagePart::Placeholder(placeholder) => {
// Placeholder, escape it
escape_formatter(&mut output, state, &placeholder.as_str().into())?;
}
}
}
Ok(Value::from_safe_string(buf))
}
}