//! Host-owned HTML and JavaScript. Public review data enters HTML only as escaped text. use super::{Api, Error, Result, request_view}; use bytes::Bytes; use http::Response; fn escape(text: &str) -> String { text.replace('&', "&") .replace('<', "<") .replace('>', ">") .replace('"', """) .replace('\'', "'") } pub(super) fn get(ctx: &Api, path: &str) -> Result>> { let parts: Vec<_> = path.trim_matches('/').split('/').collect(); let (mime, text) = match parts.as_slice() { ["app.js"] => ( "text/javascript; charset=utf-8", include_str!("app.js").to_string(), ), ["register"] => ( "text/html; charset=utf-8", include_str!("register.html").to_string(), ), ["approve", id] => { let request = ctx .cred .request_list()? .into_iter() .find(|request| request.id == *id) .ok_or(Error(404, "request_missing"))?; let items = request .payload .get("items") .and_then(serde_json::Value::as_array) .cloned() .unwrap_or_default(); let grant = request .payload .get("grant_create") .unwrap_or(&request.payload); let grant_request = request.payload.get("grant_create").is_some() || !items.is_empty(); let duration = grant .get("duration_secs") .and_then(serde_json::Value::as_u64) .or_else(|| { grant .get("exp") .and_then(serde_json::Value::as_i64) .map(|expiry| { expiry.saturating_sub(crate::cred::store::now()).max(1) as u64 }) }) .unwrap_or(3600); let uses = grant .get("max_uses") .and_then(serde_json::Value::as_u64) .map(|uses| uses.to_string()) .unwrap_or_default(); let mut review = request_view(request)?; if !items.is_empty() { review["items"] = super::value( ctx.cred .item_list()? .into_iter() .filter(|item| items.iter().any(|id| id.as_str() == Some(&item.id))) .collect::>(), )?; } let text = include_str!("approve.html") .replace( "{{grant_request}}", if grant_request { "true" } else { "false" }, ) .replace( "{{limits_hidden}}", if grant_request { "" } else { "hidden disabled" }, ) .replace("{{duration}}", &duration.to_string()) .replace("{{uses}}", &uses) .replace("{{request_id}}", &escape(id)) .replace( "{{summary}}", &escape(review["summary"].as_str().expect("summary string")), ) .replace( "{{details}}", &escape( &serde_json::to_string_pretty(&review) .map_err(|_| Error(500, "serialization"))?, ), ); ("text/html; charset=utf-8", text) } ["entry", item, field] => { let snapshot = ctx.cred.item_get(item)?; let selected = super::auth::field(ctx, item, field)?; let text = include_str!("entry.html") .replace("{{item_id}}", &escape(item)) .replace("{{field_id}}", &escape(field)) .replace("{{item_name}}", &escape(&snapshot.name)) .replace("{{field_name}}", &escape(&selected.name)) .replace( "{{public_key}}", &serde_json::to_string(&ctx.entry.public_key()) .map_err(|_| Error(500, "serialization"))?, ); ("text/html; charset=utf-8", text) } _ => return Ok(None), }; Ok(Some( Response::builder() .header("content-type", mime) .body(Bytes::from(text)) .expect("static headers"), )) }