#![allow(clippy::needless_pass_by_value)]
use http::{Response, header};
use crate::app_state::AppState;
use crate::i18n::{COOKIE_NAME, Lang};
use crate::router::{Body, path_param, redirect};
const COOKIE_MAX_AGE: u32 = 31_536_000;
pub async fn set_lang(
_state: AppState,
parts: http::request::Parts,
params: Vec<(String, String)>,
) -> Response<Body> {
let mut response = redirect(&return_path(&parts));
if let Some(lang) = path_param(¶ms, "code").and_then(|code| Lang::from_code(&code)) {
let cookie = format!(
"{COOKIE_NAME}={}; Path=/; Max-Age={COOKIE_MAX_AGE}; SameSite=Lax; Secure; HttpOnly",
lang.code()
);
if let Ok(value) = header::HeaderValue::from_str(&cookie) {
response.headers_mut().insert(header::SET_COOKIE, value);
}
}
response
}
fn return_path(parts: &http::request::Parts) -> String {
parts
.headers
.get(header::REFERER)
.and_then(|value| value.to_str().ok())
.and_then(|referer| referer.parse::<http::Uri>().ok())
.and_then(|uri| uri.path_and_query().map(|pq| pq.as_str().to_owned()))
.filter(|path| path.starts_with('/') && !path.starts_with("//"))
.unwrap_or_else(|| "/".to_owned())
}
#[cfg(test)]
mod tests {
#![allow(clippy::missing_panics_doc)]
use http::{Request, StatusCode};
use super::return_path;
fn parts_with_referer(referer: Option<&str>) -> http::request::Parts {
let mut builder = Request::builder().uri("/lang/de");
if let Some(referer) = referer {
builder = builder.header("referer", referer);
}
builder.body(()).expect("request builds").into_parts().0
}
#[test]
fn referer_path_is_kept_but_host_is_dropped() {
let parts = parts_with_referer(Some("https://localhost:8180/csaf/ndaal-sa-2026-001?x=1"));
assert_eq!(return_path(&parts), "/csaf/ndaal-sa-2026-001?x=1");
let parts = parts_with_referer(Some("https://evil.example/csaf/ndaal-sa-2026-001"));
assert_eq!(return_path(&parts), "/csaf/ndaal-sa-2026-001");
}
#[test]
fn missing_or_unsafe_referer_falls_back_to_root() {
assert_eq!(return_path(&parts_with_referer(None)), "/");
let parts = parts_with_referer(Some("//evil.example/x"));
assert_eq!(return_path(&parts), "/");
}
#[test]
fn set_lang_response_shape_is_a_303_with_location() {
let response = crate::router::redirect("/csaf");
assert_eq!(response.status(), StatusCode::SEE_OTHER);
assert_eq!(response.headers().get("location").unwrap(), "/csaf");
}
}