diff --git a/examples/core/state_generation/src/templates/build_paths.rs b/examples/core/state_generation/src/templates/build_paths.rs index 763cb9ff1a..7e38d04549 100644 --- a/examples/core/state_generation/src/templates/build_paths.rs +++ b/examples/core/state_generation/src/templates/build_paths.rs @@ -57,5 +57,6 @@ pub async fn get_build_paths() -> RenderFnResult> { "".to_string(), "test".to_string(), "blah/test/blah".to_string(), + "a test".to_string(), // Perseus caan even handle paths with special characters! ]) } diff --git a/packages/perseus-actix-web/src/initial_load.rs b/packages/perseus-actix-web/src/initial_load.rs index ec80eb2772..8cfa089fa9 100644 --- a/packages/perseus-actix-web/src/initial_load.rs +++ b/packages/perseus-actix-web/src/initial_load.rs @@ -2,7 +2,7 @@ use crate::conv_req::convert_req; use actix_web::{http::StatusCode, web, HttpRequest, HttpResponse}; use fmterr::fmt_err; use perseus::{ - errors::err_to_status_code, + errors::{err_to_status_code, ServerError}, i18n::{TranslationsManager, Translator}, router::{match_route_atomic, RouteInfoAtomic, RouteVerdictAtomic}, server::{ @@ -49,6 +49,20 @@ pub async fn initial_load( let templates = &opts.templates_map; let error_pages = &opts.error_pages; let path = req.path(); + let path = match urlencoding::decode(path) { + Ok(path) => path.to_string(), + Err(err) => { + return return_error_page( + path, + 400, + &fmt_err(&ServerError::UrlDecodeFailed { source: err }), + None, + error_pages, + html_shell.as_ref(), + ) + } + }; + let path = path.as_str(); let path_slice = get_path_slice(path); // Create a closure to make returning error pages easier (most have the same // data) diff --git a/packages/perseus-axum/src/initial_load.rs b/packages/perseus-axum/src/initial_load.rs index 27b6dd2593..6e4c5bdcaf 100644 --- a/packages/perseus-axum/src/initial_load.rs +++ b/packages/perseus-axum/src/initial_load.rs @@ -5,7 +5,7 @@ use axum::{ }; use fmterr::fmt_err; use perseus::{ - errors::err_to_status_code, + errors::{err_to_status_code, ServerError}, i18n::{TranslationsManager, Translator}, router::{match_route_atomic, RouteInfoAtomic, RouteVerdictAtomic}, server::{ @@ -50,11 +50,25 @@ pub async fn initial_load_handler( translations_manager: Arc, global_state: Arc>, ) -> (StatusCode, HeaderMap, Html) { + let error_pages = &opts.error_pages; let path = http_req.uri().path().to_string(); + let path = match urlencoding::decode(&path) { + Ok(path) => path.to_string(), + Err(err) => { + return return_error_page( + path.as_str(), + 400, + &fmt_err(&ServerError::UrlDecodeFailed { source: err }), + None, + error_pages, + html_shell.as_ref(), + ) + } + }; + let path = path.as_str(); let http_req = Request::from_parts(http_req.into_parts().0, ()); let templates = &opts.templates_map; - let error_pages = &opts.error_pages; let path_slice = get_path_slice(&path); // Create a closure to make returning error pages easier (most have the same // data) diff --git a/packages/perseus-warp/src/initial_load.rs b/packages/perseus-warp/src/initial_load.rs index 6ac241eb5f..7f082b9eec 100644 --- a/packages/perseus-warp/src/initial_load.rs +++ b/packages/perseus-warp/src/initial_load.rs @@ -1,6 +1,6 @@ use fmterr::fmt_err; use perseus::{ - errors::err_to_status_code, + errors::{err_to_status_code, ServerError}, i18n::{TranslationsManager, Translator}, router::{match_route_atomic, RouteInfoAtomic, RouteVerdictAtomic}, server::{ @@ -43,9 +43,22 @@ pub async fn initial_load_handler( translations_manager: Arc, global_state: Arc>, ) -> Response { + let error_pages = &opts.error_pages; + let path = match urlencoding::decode(path.as_str()) { + Ok(path) => path.to_string(), + Err(err) => { + return return_error_page( + path.as_str(), + 400, + &fmt_err(&ServerError::UrlDecodeFailed { source: err }), + None, + error_pages, + html_shell.as_ref(), + ) + } + }; let path = path.as_str(); let templates = &opts.templates_map; - let error_pages = &opts.error_pages; let path_slice = get_path_slice(path); // Create a closure to make returning error pages easier (most have the same // data) diff --git a/packages/perseus/src/errors.rs b/packages/perseus/src/errors.rs index 4784c5b3e0..bac0fe39f1 100644 --- a/packages/perseus/src/errors.rs +++ b/packages/perseus/src/errors.rs @@ -104,6 +104,11 @@ pub enum ServerError { #[source] source: std::io::Error, }, + #[error("failed to decode url provided (probably malformed request)")] + UrlDecodeFailed { + #[source] + source: std::string::FromUtf8Error, + }, #[error(transparent)] GlobalStateError(#[from] GlobalStateError), #[error(transparent)] diff --git a/packages/perseus/src/router/get_initial_view.rs b/packages/perseus/src/router/get_initial_view.rs index 1e34e9c233..39a464ec9f 100644 --- a/packages/perseus/src/router/get_initial_view.rs +++ b/packages/perseus/src/router/get_initial_view.rs @@ -36,6 +36,10 @@ pub(crate) fn get_initial_view( let error_pages = &render_ctx.error_pages; let pss = &render_ctx.page_state_store; + let path = js_sys::decode_uri_component(&path) + .unwrap() + .as_string() + .unwrap(); // Start by figuring out what template we should be rendering let path_segments = path .split('/') diff --git a/packages/perseus/src/router/get_subsequent_view.rs b/packages/perseus/src/router/get_subsequent_view.rs index 290e5774dc..8bf25aecb1 100644 --- a/packages/perseus/src/router/get_subsequent_view.rs +++ b/packages/perseus/src/router/get_subsequent_view.rs @@ -58,6 +58,10 @@ pub(crate) async fn get_subsequent_view( let error_pages = &render_ctx.error_pages; let pss = &render_ctx.page_state_store; + let path = js_sys::decode_uri_component(&path) + .unwrap() + .as_string() + .unwrap(); let path_with_locale = match locale.as_str() { "xx-XX" => path.clone(), locale => format!("{}/{}", locale, &path),