use std::collections::HashMap; use std::convert::TryFrom; use serde::{Deserialize, Serialize}; use crate::http_response::HttpResponse; use crate::server_config::DatumExtensionParser; use crate::subdomain_info::SubdomainInfo; use crate::text_template_result::TextTemplateResult; use crate::utils::{escape_html, gen_true}; #[derive(Clone, Deserialize, Serialize, Debug)] #[serde(tag = "type")] pub enum TextTemplateStep { #[serde(rename = "static")] Static { content: String, #[serde(default = "gen_true")] escaped: bool, }, #[serde(rename = "variable")] Variable { name: String, #[serde(default = "bool::default")] escaped: bool, }, #[serde(rename = "json")] Json { name: String }, #[serde(rename = "group")] Group { #[serde(default = "String::default")] label: String, #[serde(default = "Vec::default")] steps: Vec, }, #[serde(rename = "list")] List { name: String, item: String, #[serde(default = "bool::default")] absent_as_empty: bool, #[serde(default = "String::default")] index0: String, #[serde(default = "String::default")] index1: String, #[serde(default = "Vec::default")] steps: Vec, #[serde(default = "Vec::default")] steps_empty: Vec, }, #[serde(rename = "if")] If { name: String, #[serde(default = "bool::default")] absent_as_false: bool, #[serde(default = "gen_true")] trim: bool, #[serde(default = "Vec::default")] steps: Vec, #[serde(default = "Vec::default")] falsy_steps: Vec, }, #[serde(rename = "ifvar")] IfVar { name: String, other: String, #[serde(default = "gen_true")] trim: bool, #[serde(default = "bool::default")] case_insensitive: bool, #[serde(default = "Vec::default")] equal: Vec, #[serde(default = "Vec::default")] different: Vec, }, #[serde(rename = "render_then_redirect")] RenderThenRedirect { #[serde(default = "String::default")] label: String, #[serde(default = "Vec::default")] steps: Vec, }, #[serde(rename = "render_404")] Render404 { #[serde(default = "String::default")] label: String, }, } impl TextTemplateStep { pub fn reinflate(&self) -> String { match self { TextTemplateStep::Static { content, escaped } => { if *escaped { content.to_owned() } else { escape_html(content) } } TextTemplateStep::Variable { name, escaped } => { format!("{{{{ {}{} }}}}", if *escaped { '!' } else { '@' }, name) } TextTemplateStep::Json { name } => format!("{{{{ <{name} }}}}"), TextTemplateStep::Group { steps, .. } => steps.iter().map(|x| x.reinflate()).collect(), TextTemplateStep::List { name, item, absent_as_empty, index0, index1, steps, steps_empty, } => format!( "{{% :: @{} <= {}{} #0@{} #1@{} %}}{}{}{}{{% .. %}}", item, if *absent_as_empty { '!' } else { '@' }, name, index0, index1, steps.iter().map(|x| x.reinflate()).collect::(), if !steps_empty.is_empty() { "{% :. %}" } else { "" }, steps_empty .iter() .map(|x| x.reinflate()) .collect::() ), TextTemplateStep::If { name, absent_as_false, trim, steps, falsy_steps, } => format!( "{{% ?{} {}{} %}}{}{}{}{{% ?? %}}", if *trim { '_' } else { '-' }, if *absent_as_false { '!' } else { '@' }, name, steps.iter().map(|x| x.reinflate()).collect::(), if !falsy_steps.is_empty() { "{% ?. %}" } else { "" }, falsy_steps .iter() .map(|x| x.reinflate()) .collect::() ), TextTemplateStep::IfVar { name, other, trim, case_insensitive, equal, different, } => format!( "{{% ?$ {}{} @{} == @{} %}}{}{}{}{{% ?? %}}", if *case_insensitive { 's' } else { 'i' }, if *trim { 't' } else { 'u' }, name, other, equal.iter().map(|x| x.reinflate()).collect::(), if !different.is_empty() { "{% ?. %}" } else { "" }, different.iter().map(|x| x.reinflate()).collect::(), ), TextTemplateStep::RenderThenRedirect { steps, .. } => { format!( "{{% 302 %}}{}{{% .. %}}", steps.iter().map(|x| x.reinflate()).collect::(), ) } TextTemplateStep::Render404 { .. } => "{% 404 %}".to_owned(), } } } #[derive(Clone, Deserialize, Serialize, Debug)] #[serde(untagged)] pub enum TextTemplateData { Map(HashMap), List(Vec), Text(String), } #[derive(Debug)] pub struct TextTemplateRuntime<'a> { pub parent: Option<&'a TextTemplateRuntime<'a>>, pub current: &'a Vec, pub variables: Option>, } fn deref_dict_multiple<'a>( map: &'a HashMap, path: &[&str], i: usize, ) -> Option<&'a TextTemplateData> { if (i + 1) < path.len() { if let Some(TextTemplateData::Map(nextmap)) = map.get(path[i]) { deref_dict_multiple(nextmap, path, i + 1) } else { None } } else { map.get(path[i]) } } impl<'a> TextTemplateRuntime<'a> { pub fn new( parent: Option<&'a TextTemplateRuntime<'a>>, current: &'a Vec, variables: Option>, ) -> Self { TextTemplateRuntime { parent, current, variables, } } fn _get_vars(&self, names: &[&str]) -> Option<&TextTemplateData> { self.variables .as_ref() .and_then(|vars| deref_dict_multiple(vars, names, 0)) .or_else(|| self.parent.and_then(|rt| rt._get_vars(names))) } fn get_var(&self, name: &str) -> Option<&TextTemplateData> { self._get_vars(&name.split('>').collect::>()) } pub fn run<'f: 'a>( &'f mut self, s: &mut Vec, ) -> Result<(), TextTemplateRenderingError> { for current in self.current { match current { TextTemplateStep::Static { .. } | TextTemplateStep::Variable { .. } | TextTemplateStep::Json { .. } if s.len() + 16 > s.capacity() => { s.reserve(128); } _ => {} }; match current { TextTemplateStep::Static { content, escaped } => { s.push(if *escaped { content.to_owned() } else { escape_html(content) }); } TextTemplateStep::Variable { name, escaped } => { let vr = self.get_var(name); match vr { Some(ttd) => match ttd { TextTemplateData::Text(content) => { s.push(if *escaped { content.to_owned() } else { escape_html(content) }); } _ => Err(TextTemplateRenderingError::VariableNotATextInstead( name.to_string(), ttd.clone(), ))?, }, None => Err(TextTemplateRenderingError::VariableNotFound( name.to_string(), ))?, } } TextTemplateStep::Json { name } => { s.push(serde_json::to_string(&self.get_var(name)).map_err(|err| { TextTemplateRenderingError::HydrationSerialization(format!( "Could not hydrate content: {:?}", err )) })?); } TextTemplateStep::Group { label: _, steps } => { Self::new(Some(self), steps, self.variables.clone()).run(s)?; } TextTemplateStep::List { name, item, absent_as_empty, index0, index1, steps, steps_empty, } => { let def: Vec = vec![]; let def2: TextTemplateData = TextTemplateData::List(def); if let Some(TextTemplateData::List(data)) = self .get_var(name) .or_else(|| absent_as_empty.then_some(&def2)) { if !data.is_empty() { for (index, datum) in data.iter().enumerate() { let mut vars = HashMap::new(); vars.insert(item.to_owned(), datum.clone()); vars.insert( index0.to_owned(), TextTemplateData::Text(format!("{}", index)), ); vars.insert( index1.to_owned(), TextTemplateData::Text(format!("{}", index + 1)), ); Self::new(Some(self), steps, Some(vars)).run(s)?; } } else { Self::new(Some(self), steps_empty, self.variables.clone()).run(s)?; } } else { Err(TextTemplateRenderingError::VariableNotAList( name.to_string(), ))?; } } TextTemplateStep::If { name, absent_as_false, trim, steps, falsy_steps, } => { let def: Vec = vec![]; let def2: TextTemplateData = TextTemplateData::List(def); let var = self .get_var(name) .or_else(|| absent_as_false.then_some(&def2)) .ok_or_else(|| { TextTemplateRenderingError::VariableNotFound(name.to_string()) })?; let truthyness = match var { TextTemplateData::Map(_) => Err( TextTemplateRenderingError::VariableNotATextNorAList(name.to_string()), )?, TextTemplateData::List(a) => !a.is_empty(), TextTemplateData::Text(a) => { !trim.then(|| a.trim()).unwrap_or(a).is_empty() } }; if truthyness { Self::new(Some(self), steps, None).run(s)?; } else { Self::new(Some(self), falsy_steps, None).run(s)?; } } TextTemplateStep::IfVar { name, other, trim, case_insensitive, equal, different, } => { let var1 = self .get_var(name) .and_then(|x| match x { TextTemplateData::Text(t) => Some(t), _ => None, }) .map(|x| if *trim { x.trim() } else { x }) .map(|x| { if *case_insensitive { x.to_lowercase() } else { x.to_owned() } }) .ok_or_else(|| { TextTemplateRenderingError::VariableNotFound(name.to_string()) })?; let var2 = self .get_var(other) .and_then(|x| match x { TextTemplateData::Text(t) => Some(t), _ => None, }) .map(|x| if *trim { x.trim() } else { x }) .map(|x| { if *case_insensitive { x.to_lowercase() } else { x.to_owned() } }) .ok_or_else(|| { TextTemplateRenderingError::VariableNotFound(other.to_string()) })?; Self::new( Some(self), if var1 == var2 { equal } else { different }, None, ) .run(s)?; } TextTemplateStep::RenderThenRedirect { label: _, steps } => { let mut vs: Vec = Vec::default(); Self::new(Some(self), steps, None).run(&mut vs)?; Err(TextTemplateRenderingError::InterruptedRenderingWith( InterruptDetermination::RenderedResponse(HttpResponse::generate_302( vs.into_iter().collect::(), )), ))? } TextTemplateStep::Render404 { label: _ } => { Err(TextTemplateRenderingError::InterruptedRenderingWith( InterruptDetermination::Return404, ))? } } } Ok(()) } } #[derive(Clone, Deserialize, Serialize, Debug)] pub struct TextTemplate { #[serde(default = "Option::default")] pub mime: Option, #[serde(default = "Vec::default")] pub steps: Vec, } impl TextTemplate { pub fn render( self, d: &SubdomainInfo, q: &HashMap, h: &HashMap, p: DatumExtensionParser, s: &str, ) -> Result { let template_data = { let mut td = p .parse_str::(s) .map_err(TextTemplateRenderingError::DataDeserialization)?; Self::add_str_ttd_map_if_not_exists(&mut td, "subdomain", d.sub_domain); Self::add_str_ttd_map_if_not_exists(&mut td, "basedomain", Some(d.base_domain)); Self::add_str_ttd_map_if_not_exists(&mut td, "host", Some(d.host)); Self::add_str_ttd_map_if_not_exists( &mut td, "domain_after_first_dot", Some(d.after_first_dot), ); Self::add_str_ttd_map_if_not_exists( &mut td, "domain_before_first_dot", Some(d.before_first_dot), ); Self::add_strmap_ttd_map_if_not_exists(&mut td, "query", Some(q)); Self::add_strmap_ttd_map_if_not_exists(&mut td, "headers", Some(h)); td }; let mut vs: Vec = Vec::with_capacity(128); match template_data { TextTemplateData::Map(hm) => { TextTemplateRuntime::new(None, &self.steps, Some(hm)).run(&mut vs) } _ => Err(TextTemplateRenderingError::RootDataNotObject), }?; Ok(TextTemplateResult::new(self.mime, vs.concat(), None)) } pub fn reinflate(&self) -> String { self.steps.iter().map(|x| x.reinflate()).collect() } fn add_str_ttd_map_if_not_exists(td: &mut TextTemplateData, kw: &str, vlopt: Option<&str>) { if let TextTemplateData::Map(ref mut mp) = td { if let Some(vl) = vlopt { if !mp.contains_key(kw) { mp.insert(kw.to_string(), TextTemplateData::Text(vl.to_owned())); } }; } } fn add_strmap_ttd_map_if_not_exists( td: &mut TextTemplateData, kw: &str, vlopt: Option<&HashMap>, ) { if let TextTemplateData::Map(ref mut mp) = td { if let Some(vl) = vlopt { if !mp.contains_key(kw) { mp.insert( kw.to_string(), TextTemplateData::Map( vl.iter() .map(|(kpk, kpv)| { (kpk.to_owned(), TextTemplateData::Text(kpv.to_string())) }) .collect(), ), ); } }; } } fn _push_thing( toadd: TextTemplateStep, steps: &mut Vec, is_else: &[bool], ) -> Result<(), TemplateParsingError> { let is_elsability_empty = is_else.is_empty(); match steps.last_mut() { _ if is_elsability_empty => { steps.push(toadd); Ok(()) } Some(TextTemplateStep::Group { ref mut steps, .. }) => { Self::_push_thing(toadd, steps, &is_else[1..]) } Some(TextTemplateStep::List { ref mut steps, ref mut steps_empty, .. }) => Self::_push_thing( toadd, if is_else.first().copied() != Some(true) { steps } else { steps_empty }, &is_else[1..], ), Some(TextTemplateStep::If { ref mut steps, ref mut falsy_steps, .. }) => Self::_push_thing( toadd, if is_else.first().copied() != Some(true) { steps } else { falsy_steps }, &is_else[1..], ), Some(TextTemplateStep::IfVar { ref mut equal, ref mut different, .. }) => Self::_push_thing( toadd, if is_else.first().copied() != Some(true) { equal } else { different }, &is_else[1..], ), _ => Err(TemplateParsingError::CannotNavigateInStack), } } fn _push_static( source_vec: &[char], s: &mut usize, e: &usize, steps: &mut Vec, is_else: &[bool], ) -> Result<(), TemplateParsingError> { if s != e { let content_prev = &source_vec[*s..*e].iter().collect::(); if !content_prev.is_empty() { let toadd = TextTemplateStep::Static { content: content_prev.to_owned(), escaped: true, }; *s = *e; Self::_push_thing(toadd, steps, is_else)?; } } Ok::<(), TemplateParsingError>(()) } pub fn generate(mime: Option<&str>, source: &str) -> Result { let source_vec = source.chars().collect::>(); let mut steps: Vec = vec![TextTemplateStep::Group { label: "".to_owned(), steps: vec![], }]; let mut is_else: Vec = vec![true]; let mut s = 0usize; let mut e = 0usize; loop { if e >= source_vec.len() { Self::_push_static(&source_vec, &mut s, &e, &mut steps, &is_else)?; break; } else if source_vec.get(e).copied() == Some('{') && source_vec.get(e + 1).copied() == Some('{') && source_vec.len() - e > 2 { Self::_push_static(&source_vec, &mut s, &e, &mut steps, &is_else)?; s = e + 2; e = s; while e < source_vec.len() && !(source_vec.get(e).copied() == Some('}') && source_vec.get(e + 1).copied() == Some('}')) { e += 1; } if e >= source_vec.len() { Err(TemplateParsingError::CannotFindClosingTokenForVariable)? } let var = source_vec[s..e] .iter() .collect::() .trim() .to_owned(); if var.is_empty() { Err(TemplateParsingError::VariableUnamed)? } match var.split_at(1) { ("@", varname) => Self::_push_thing( TextTemplateStep::Variable { name: varname.to_owned(), escaped: false, }, &mut steps, &is_else, )?, ("!", varname) => Self::_push_thing( TextTemplateStep::Variable { name: varname.to_owned(), escaped: true, }, &mut steps, &is_else, )?, ("<", varname) => Self::_push_thing( TextTemplateStep::Json { name: varname.to_owned(), }, &mut steps, &is_else, )?, _ => Err(TemplateParsingError::VariableWrongPrefix)?, } s = e + 2; e = s; } else if source_vec.get(e).copied() == Some('{') && source_vec.get(e + 1).copied() == Some('%') && source_vec.len() - e > 2 { Self::_push_static(&source_vec, &mut s, &e, &mut steps, &is_else)?; s = e + 2; e = s; while e < source_vec.len() && !(source_vec.get(e).copied() == Some('%') && source_vec.get(e + 1).copied() == Some('}')) { e += 1; } if e >= source_vec.len() { Err(TemplateParsingError::CannotFindClosingTokenForDirective)? } let var = source_vec[s..e] .iter() .collect::() .trim() .to_owned(); if var.len() < 2 { Err(TemplateParsingError::DirectiveTooShort)? } let is_else_len_minus_1 = is_else.len() - 1; match var.trim() { "404" => Self::_push_thing( TextTemplateStep::Render404 { label: String::default(), }, &mut steps, &is_else, )?, "302" => { Self::_push_thing( TextTemplateStep::RenderThenRedirect { label: String::default(), steps: Vec::default(), }, &mut steps, &is_else, )?; is_else.push(false); } var => match var.split_at(2) { ("?-", var) => match var.trim().split_at(1) { ("@", var) => { let thing = TextTemplateStep::If { name: var.to_owned(), absent_as_false: false, trim: false, steps: vec![], falsy_steps: vec![], }; Self::_push_thing(thing, &mut steps, &is_else)?; is_else.push(false); } ("!", var) => { let thing = TextTemplateStep::If { name: var.to_owned(), absent_as_false: true, trim: false, steps: vec![], falsy_steps: vec![], }; Self::_push_thing(thing, &mut steps, &is_else)?; is_else.push(false); } _ => Err(TemplateParsingError::VariableWrongPrefix)?, }, ("?_", var) => match var.trim().split_at(1) { ("@", var) => { let thing = TextTemplateStep::If { name: var.to_owned(), absent_as_false: false, trim: true, steps: vec![], falsy_steps: vec![], }; Self::_push_thing(thing, &mut steps, &is_else)?; is_else.push(false); } ("!", var) => { let thing = TextTemplateStep::If { name: var.to_owned(), absent_as_false: true, trim: true, steps: vec![], falsy_steps: vec![], }; Self::_push_thing(thing, &mut steps, &is_else)?; is_else.push(false); } _ => Err(TemplateParsingError::VariableWrongPrefix)?, }, ("?$", composite) => { let (case_insensitive, trim, composite) = match composite.trim().split_at(2) { ("it" | "ti", rest) => (true, true, rest.trim()), ("iu" | "ui", rest) => (true, false, rest.trim()), ("st" | "ts", rest) => (false, true, rest.trim()), ("su" | "us", rest) => (false, false, rest.trim()), _ => Err(TemplateParsingError::VariableWrongPrefix)?, }; let (name1, other1) = composite.split_once("==").unwrap(); let thing = TextTemplateStep::IfVar { name: name1.trim()[1..].to_owned(), other: other1.trim()[1..].to_owned(), trim, case_insensitive, equal: Vec::default(), different: Vec::default(), }; Self::_push_thing(thing, &mut steps, &is_else)?; is_else.push(false); } ("::", composite) => { let (itemvar, rest) = composite.trim().split_once("<=").unwrap(); let rest = rest .split(' ') .filter(|x| !x.trim().is_empty()) .map(|x| x.to_owned()) .collect::>(); let (varname, parts) = rest.split_first().unwrap(); let thing = TextTemplateStep::List { name: varname.trim()[1..].to_owned(), item: itemvar.trim()[1..].to_owned(), absent_as_empty: varname.trim().starts_with('!'), index0: parts .iter() .find(|x| x.starts_with("#0")) .map(|x| x[3..].to_owned()) .unwrap_or_else(String::default), index1: parts .iter() .find(|x| x.starts_with("#1")) .map(|x| x[3..].to_owned()) .unwrap_or_else(String::default), steps: Vec::default(), steps_empty: Vec::default(), }; Self::_push_thing(thing, &mut steps, &is_else)?; is_else.push(false); } ("?.", ..) => is_else[is_else_len_minus_1] = true, (":.", ..) => is_else[is_else_len_minus_1] = true, ("??", ..) => { is_else.pop(); } ("..", ..) => { is_else.pop(); } _ => Err(TemplateParsingError::DirectiveWrongPrefix)?, }, }; s = e + 2; e = s; } else { e += 1; } } if is_else.len() != 1 { Err(TemplateParsingError::StackNotEmptyOnFinish) } else if let Some(TextTemplateStep::Group { steps, .. }) = steps.first() { Ok(Self { mime: mime.map(|x| x.to_owned()), steps: steps.to_owned(), }) } else { Err(TemplateParsingError::StackNotEmptyOnFinish) } } } impl TryFrom<&str> for TextTemplate { type Error = TemplateLoadingError; fn try_from(value_borrowed: &str) -> Result { DatumExtensionParser::Yaml .parse_str(value_borrowed) .map_err(TemplateLoadingError::TemplateDeserialization) } } #[derive(Copy, Clone, Deserialize, Serialize, Debug)] pub enum TemplateParsingError { StackNotEmptyOnFinish, CannotNavigateInStack, CannotFindClosingTokenForVariable, VariableUnamed, VariableWrongPrefix, CannotFindClosingTokenForDirective, DirectiveTooShort, DirectiveWrongPrefix, } #[derive(Copy, Clone, Deserialize, Serialize, Debug)] pub enum TextTemplateEvaluationError { DataDeserialization, } #[derive(Clone, Deserialize, Serialize, Debug)] pub enum TemplateLoadingError { TemplateDeserialization(String), } impl From for String { fn from(ttle: TemplateLoadingError) -> String { format!("{:?}", ttle) } } #[derive(Clone, Deserialize, Serialize, Debug)] pub enum TextTemplateRenderingError { DataDeserialization(String), RootDataNotObject, VariableNotAText(String), VariableNotATextInstead(String, TextTemplateData), VariableNotAList(String), VariableNotFound(String), VariableNotATextNorAList(String), HydrationSerialization(String), InterruptedRenderingWith(InterruptDetermination), } #[derive(Clone, Deserialize, Serialize, Debug)] pub enum InterruptDetermination { RenderedResponse(HttpResponse), Return404, } impl From for String { fn from(ttre: TextTemplateRenderingError) -> String { format!("{:?}", ttre) } }