From c8d3edfbbae4b7621a4ea282e99775088f3e0734 Mon Sep 17 00:00:00 2001 From: Fuwn Date: Wed, 18 Feb 2026 07:31:18 +0000 Subject: feat(response): Add input/sensitive-input handling --- src/response.rs | 170 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 169 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/response.rs b/src/response.rs index 779bebc..9839934 100644 --- a/src/response.rs +++ b/src/response.rs @@ -11,9 +11,24 @@ use { const CSS: &str = include_str!("../default.css"); +#[derive(serde::Deserialize)] +pub struct InputSubmission { + input: String, + target: Option, +} + +fn html_escape(input: &str) -> String { + input + .replace('&', "&") + .replace('"', """) + .replace('<', "<") + .replace('>', ">") +} + #[allow(clippy::future_not_send, clippy::too_many_lines)] pub async fn default( http_request: actix_web::HttpRequest, + input_submission: Option>, ) -> Result { if ["/proxy", "/proxy/", "/x", "/x/", "/raw", "/raw/", "/nocss", "/nocss/"] .contains(&http_request.path()) @@ -28,7 +43,19 @@ pub async fn default( } let mut configuration = configuration::Configuration::new(); - let url = match url_from_path( + let submitted_input = + if *http_request.method() == actix_web::http::Method::POST { + input_submission.as_ref().map(|submission| submission.input.clone()) + } else { + None + }; + let submitted_target = + if *http_request.method() == actix_web::http::Method::POST { + input_submission.as_ref().and_then(|submission| submission.target.clone()) + } else { + None + }; + let mut url = match url_from_path( &format!("{}{}", http_request.path(), { if !http_request.query_string().is_empty() || http_request.uri().to_string().ends_with('?') @@ -50,6 +77,25 @@ pub async fn default( ); } }; + + if let Some(target) = submitted_target { + if let Ok(parsed_target) = url::Url::parse(&target) { + if parsed_target.scheme() == "gemini" { + url = parsed_target; + } + } + } + + if let Some(input) = submitted_input { + let input = input + .replace("\r\n", "\n") + .replace('\r', "\n") + .replace('\t', "%09") + .replace('\n', "%0A"); + + url.set_query(Some(&input)); + } + let mut timer = Instant::now(); let mut response = match germ::request::request(&url).await { Ok(response) => response, @@ -106,6 +152,128 @@ pub async fn default( } } + if *response.status() == germ::request::Status::Input + || *response.status() == germ::request::Status::SensitiveInput + { + if configuration.is_raw() { + return Ok( + HttpResponse::Ok() + .content_type(format!("text/plain; charset={charset}")) + .body(response.meta().to_string()), + ); + } + + let mut html_context = format!( + r#""#, + if language.is_empty() { + String::new() + } else { + format!(" lang=\"{language}\"") + } + ); + + if !configuration.is_no_css() { + if let Some(css) = &ENVIRONMENT.css_external { + for stylesheet in css.split(',').filter(|s| !s.is_empty()) { + let _ = write!( + &mut html_context, + "", + ); + } + } else { + let _ = write!( + &mut html_context, + r#""# + ); + + if let Some(primary) = &ENVIRONMENT.primary_colour { + let _ = write!( + &mut html_context, + "" + ); + } else { + let _ = write!( + &mut html_context, + "" + ); + } + } + } + + if let Some(favicon) = &ENVIRONMENT.favicon_external { + let _ = write!( + &mut html_context, + "", + ); + } + + if let Some(head) = &ENVIRONMENT.head { + html_context.push_str(head); + } + + let _ = write!( + &mut html_context, + "{}", + html_escape(&response.meta()), + ); + + if !http_request.path().starts_with("/proxy") { + if let Some(header) = &ENVIRONMENT.header { + let _ = write!( + &mut html_context, + "
{header}
" + ); + } + } + + if let (Some(status), Some(redirected_to)) = + (redirect_response_status, redirect_url.clone()) + { + let _ = write!( + &mut html_context, + "
This page {} redirects to {}.
", + if status == germ::request::Status::PermanentRedirect { + "permanently" + } else { + "temporarily" + }, + redirected_to, + redirected_to + ); + } + + let input_url = redirect_url.unwrap_or_else(|| url.clone()); + let input_field = + if *response.status() == germ::request::Status::SensitiveInput { + "" + } else { + "" + }; + let _ = write!( + &mut html_context, + "

{}

{}
", + html_escape(&response.meta()), + html_escape(&http_request.uri().to_string()), + html_escape(input_url.as_ref()), + input_field, + ); + let mut response_builder = HttpResponse::Ok(); + + if *response.status() == germ::request::Status::SensitiveInput { + response_builder + .insert_header((actix_web::http::header::CACHE_CONTROL, "no-store")); + } + + return Ok( + response_builder + .content_type(format!("text/html; charset={charset}")) + .body(html_context), + ); + } + let mut html_context = if configuration.is_raw() { String::new() } else { -- cgit v1.2.3