You've already forked authentication-service
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:
@@ -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 })
|
||||
}
|
||||
}
|
||||
|
@@ -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),
|
||||
|
@@ -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"
|
||||
|
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user