/* * This is the echoip-slatecave templating engine. * It wraps around tera in is specialized for echoip-slatecave. */ use axum::{ headers::HeaderValue, http::StatusCode, http::header::SET_COOKIE, response::Html, response::IntoResponse, response::Response, response::Json, }; use axum_extra::extract::cookie::Cookie; use axum_extra::extract::cookie; use tera::Tera; use toml::Table; use crate::DigResult; use crate::IpResult; use crate::config::DnsResolverConfig; use crate::settings::*; /* The echoip view */ #[derive(serde::Serialize, Clone)] #[serde(untagged)] pub enum View { Asn { asn: u32 }, Dig { query: String, result: DigResult }, DnsResolver{ config: DnsResolverConfig }, DnsResolverList, Index { result: IpResult, user_agent: Option }, Ip { result: IpResult }, Message{ title: String, message: String }, #[serde(rename="404")] NotFound, } impl View { pub fn template_name(&self) -> String { match self { View::Asn{..} => "asn", View::Dig{..} => "dig", View::DnsResolver{..} => "dns_resolver", View::DnsResolverList => "dns_resolver_list", View::Index{..} => "index", View::Ip{..} => "ip", View::Message{..} => "message", View::NotFound => "404", }.to_string() } } /* The engine itself */ #[derive(Clone)] pub struct Engine { pub tera: Tera, pub template_config: Option, } impl Engine { pub async fn render_view( &self, settings: &QuerySettings, view: &View, ) -> Response { let mut response = match settings.format { ResponseFormat::TextHtml | ResponseFormat::TextPlain => { let template_name = view.template_name(); let mut context = tera::Context::new(); context.insert("view", &template_name); //intented for shared macros context.insert("format", &settings.format.to_string()); context.insert("language", &settings.lang); context.insert("dns_resolvers", &settings.available_dns_resolvers); context.insert("dns_resolver_id", &settings.dns_resolver_id); context.insert("data", &view); context.insert("extra", &self.template_config); match self.tera.render(&(template_name+&settings.format.to_file_extension()), &context) { Ok(text) => match settings.format { ResponseFormat::TextHtml => Html(text).into_response(), _ => text.into_response(), } Err(e) => { println!("There was an error while rendering template {}: {e:?}", view.template_name()); ( StatusCode::INTERNAL_SERVER_ERROR, format!("Template error in {}, contact owner or see logs.\n", view.template_name()) ).into_response() } } } //TODO: Plain Text should have its own matcher ResponseFormat::ApplicationJson => { match view { View::Dig{result, ..} => { Json(result).into_response() }, View::Index{result, ..} | View::Ip{result, ..} => { Json(result).into_response() }, View::DnsResolverList => { Json(settings.available_dns_resolvers.clone()).into_response() }, View::DnsResolver{ config } => { Json(config).into_response() } _ => Json(view).into_response(), } } }; match view { View::NotFound => *response.status_mut() = StatusCode::NOT_FOUND, _ => {}, } let cookie = Cookie::build("dns_resolver",settings.dns_resolver_id.clone()) .path("/") .same_site(cookie::SameSite::Strict) .finish(); if let Ok(header_value) = HeaderValue::from_str(&cookie.to_string()) { response.headers_mut().append( SET_COOKIE, header_value, ); } response } }