use std::any::Any;
use std::sync::{Arc, RwLock};
use crate::context::IExpressionContext;
use crate::exceptions::TemplateProcessingException;
use crate::util::{Utf16String, ValidateError};
use super::{
IStandardExpression, IStandardVariableExpression, SimpleExpression,
StandardExpressionExecutionContext, StandardExpressionResult, StandardExpressions,
TemplateValue,
};
pub struct VariableExpression {
expression: Utf16String,
convert_to_string: bool,
cached_expression: RwLock<Option<Arc<dyn Any + Send + Sync>>>,
}
impl VariableExpression {
pub fn new(expression: Option<Utf16String>) -> Result<Self, ValidateError> {
Self::with_convert_to_string(expression, false)
}
pub fn with_convert_to_string(
expression: Option<Utf16String>,
convert_to_string: bool,
) -> Result<Self, ValidateError> {
let expression = expression.ok_or_else(|| ValidateError::IllegalArgument {
message: Some("Expression cannot be null".to_owned()),
})?;
Ok(Self {
expression,
convert_to_string,
cached_expression: RwLock::new(None),
})
}
pub fn get_expression_value(&self) -> &Utf16String {
&self.expression
}
pub(crate) fn parse_variable_expression(input: &Utf16String) -> Option<Self> {
parse_delimited(input, b'$' as u16).and_then(|(expression, convert)| {
Self::with_convert_to_string(Some(expression), convert).ok()
})
}
}
impl IStandardVariableExpression for VariableExpression {
fn get_expression(&self) -> Option<&Utf16String> {
Some(&self.expression)
}
fn get_use_selection_as_root(&self) -> bool {
false
}
fn get_convert_to_string(&self) -> bool {
self.convert_to_string
}
fn get_cached_expression(&self) -> Option<Arc<dyn Any + Send + Sync>> {
self.cached_expression
.read()
.unwrap_or_else(|poisoned| poisoned.into_inner())
.clone()
}
fn set_cached_expression(&self, cached_expression: Option<Arc<dyn Any + Send + Sync>>) {
*self
.cached_expression
.write()
.unwrap_or_else(|poisoned| poisoned.into_inner()) = cached_expression;
}
}
impl IStandardExpression for VariableExpression {
fn get_string_representation(&self) -> StandardExpressionResult<Utf16String> {
Ok(build_representation(
b'$' as u16,
&self.expression,
self.convert_to_string,
))
}
fn execute_with_context(
&self,
context: &dyn IExpressionContext,
execution_context: &'static StandardExpressionExecutionContext,
) -> StandardExpressionResult<Option<Arc<TemplateValue>>> {
execute_variable(context, self, execution_context)
}
}
impl SimpleExpression for VariableExpression {}
pub(crate) fn parse_delimited(input: &Utf16String, selector: u16) -> Option<(Utf16String, bool)> {
let units = input.as_utf16();
let mut start = 0;
while start < units.len() && regex_whitespace(units[start]) {
start += 1;
}
let mut end = units.len();
while end > start && regex_whitespace(units[end - 1]) {
end -= 1;
}
if end.saturating_sub(start) < 4
|| units[start] != selector
|| units[start + 1] != b'{' as u16
|| units[end - 1] != b'}' as u16
{
return None;
}
let content = &units[start + 2..end - 1];
if content.is_empty() {
return None;
}
if content.len() > 2 && content[0] == b'{' as u16 && content[content.len() - 1] == b'}' as u16 {
return Some((
Utf16String::from_utf16(content[1..content.len() - 1].to_vec()),
true,
));
}
Some((Utf16String::from_utf16(content.to_vec()), false))
}
pub(crate) fn build_representation(
selector: u16,
expression: &Utf16String,
convert_to_string: bool,
) -> Utf16String {
let mut units = vec![selector, b'{' as u16];
if convert_to_string {
units.push(b'{' as u16);
}
units.extend_from_slice(expression.as_utf16());
if convert_to_string {
units.push(b'}' as u16);
}
units.push(b'}' as u16);
Utf16String::from_utf16(units)
}
pub(crate) fn execute_variable(
context: &dyn IExpressionContext,
expression: &dyn IStandardVariableExpression,
execution_context: &'static StandardExpressionExecutionContext,
) -> StandardExpressionResult<Option<Arc<TemplateValue>>> {
let evaluator =
StandardExpressions::get_variable_expression_evaluator(context.get_configuration())?;
let evaluator_context = if expression.get_convert_to_string() {
execution_context.with_type_conversion()
} else {
execution_context.without_type_conversion()
};
let result = evaluator.evaluate(context, expression, evaluator_context)?;
if !execution_context.get_forbid_unsafe_expression_results()
|| result.as_deref().is_none_or(|value| {
matches!(
value,
TemplateValue::Null | TemplateValue::Number(_) | TemplateValue::Boolean(_)
)
})
{
return Ok(result);
}
Err(Box::new(TemplateProcessingException::new(Some(
"Only variable expressions returning numbers or booleans are allowed in this context, \
any other data types are not trusted in the context of this expression, including \
Strings or any other object that could be rendered as a text literal. A typical case \
is HTML attributes for event handlers (e.g. \"onload\"), in which textual data from \
variables should better be output to \"data-*\" attributes and then read from the event \
handler."
.to_owned(),
))))
}
fn regex_whitespace(unit: u16) -> bool {
matches!(unit, 0x09..=0x0d | 0x20)
}