From 9bbdddc03b615af2b19b7acf8007fb177714b308 Mon Sep 17 00:00:00 2001 From: Adrian Kumpf <8999358+adriankumpf@users.noreply.github.com> Date: Wed, 25 Feb 2026 14:25:23 +0100 Subject: [PATCH] Replace JS template interpolation with DOM APIs Use serde_json::to_string() to safely encode Rust strings as JS string literals and DOM element creation (.textContent, .value) on the JS side to insert into the page. --- Cargo.lock | 1 - Cargo.toml | 1 - src/main.rs | 87 +++++++++++++++++++++++++++++++++++------------------ 3 files changed, 58 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 34490bd..6630a75 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2852,7 +2852,6 @@ dependencies = [ "muda", "oauth2", "reqwest 0.13.2", - "serde", "serde_json", "simple_logger", "static_vcruntime", diff --git a/Cargo.toml b/Cargo.toml index 9628edf..5551f1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,6 @@ reqwest = { version = "0.13.2", default-features = false, features = [ "json", "rustls", ] } -serde = { version = "1.0.228", default-features = false, features = ["derive"] } serde_json = "1.0.149" simple_logger = { version = "5.2.0", default-features = false, features = [ "stderr", diff --git a/src/main.rs b/src/main.rs index ae0002f..d84632f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -168,12 +168,16 @@ fn main() -> anyhow::Result<()> { Event::UserEvent(UserEvent::Failure(error)) => { log::error!("{error}"); - webview.evaluate_script(&render_error_view(error)).unwrap(); + if let Err(e) = webview.evaluate_script(&render_error_view(error)) { + log::error!("Failed to render error view: {e}"); + } } Event::UserEvent(UserEvent::Tokens(token)) => { println!("{token}"); - webview.evaluate_script(&render_tokens_view(token)).unwrap(); + if let Err(e) = webview.evaluate_script(&render_tokens_view(token)) { + log::error!("Failed to render tokens view: {e}"); + } } Event::UserEvent(UserEvent::LoginCanceled) => { @@ -244,36 +248,61 @@ fn handle_redirect(url: &Url, client: auth::Client) -> UserEvent { } } +// Encode a string as a JSON string literal for safe JS interpolation. +#[expect(clippy::unwrap_used)] // serde_json string serialization is infallible +fn js_string(s: &str) -> String { + serde_json::to_string(s).unwrap() +} + fn render_error_view(error: anyhow::Error) -> String { - r#" - const html = ` -

An error occurred. Please try again ...

-

{msg}

- `; - document.querySelector("h1.h1").outerHTML = html; - "# - .replace("{msg}", &error.to_string()) + let msg = js_string(&error.to_string()); + format!( + r#"(function() {{ + var target = document.querySelector("h1.h1"); + var h4 = document.createElement("h4"); + h4.style.textAlign = "center"; + h4.textContent = "An error occurred. Please try again ..."; + var p = document.createElement("p"); + p.style.cssText = "text-align:center;color:red;margin-bottom:20px"; + p.textContent = {msg}; + target.replaceWith(h4, p); + }})()"# + ) } fn render_tokens_view(tokens: auth::Tokens) -> String { - r#" - const html = ` -

Access Token

- -

Refresh Token

- - - Valid for {expires_in} - - `; + let access = js_string(tokens.access.secret()); + let refresh = js_string(tokens.refresh.secret()); + let expires = js_string(&tokens.expires_in.to_string()); + format!( + r#"(function() {{ + var target = document.querySelector("h1.h1"); + var frag = document.createDocumentFragment(); - document.querySelector("h1.h1").outerHTML = html; - "# - .replace("{access_token}", tokens.access.secret()) - .replace("{refresh_token}", tokens.refresh.secret()) - .replace("{expires_in}", &format!("{}", tokens.expires_in)) + function addToken(label, value) {{ + var h4 = document.createElement("h4"); + h4.style.textAlign = "center"; + h4.textContent = label; + frag.appendChild(h4); + var ta = document.createElement("textarea"); + ta.readOnly = true; + ta.cols = 100; + ta.rows = 12; + ta.style.cssText = "resize:none;padding:4px;font-size:0.9em"; + ta.value = value; + ta.addEventListener("click", function() {{ this.setSelectionRange(0, this.value.length); }}); + frag.appendChild(ta); + }} + + addToken("Access Token", {access}); + addToken("Refresh Token", {refresh}); + + var small = document.createElement("small"); + small.style.cssText = "margin-top:12px;margin-bottom:20px;text-align:center;color:seagreen"; + small.textContent = "Valid for " + {expires}; + frag.appendChild(small); + + target.replaceWith(frag); + }})()"# + ) }