diff --git a/Cargo.lock b/Cargo.lock index 532a728..bd856c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1485,6 +1485,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -1823,6 +1833,7 @@ dependencies = [ "tower-http", "tracing", "tracing-subscriber", + "url", ] [[package]] @@ -3377,6 +3388,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + [[package]] name = "unicode-ident" version = "1.0.12" @@ -3420,6 +3437,17 @@ dependencies = [ "subtle", ] +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "utf8parse" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index a34afd7..abb68cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ boilerplate = { version = "1.0.0" } html-escaper = "0.2.0" tower-http = { version = "0.5.2", features = ["fs"] } readonly = "0.2.12" +url = "2.5.0" [patch.crates-io] diff --git a/src/html/page/mod.rs b/src/html/page/mod.rs index 765ec18..9384efe 100644 --- a/src/html/page/mod.rs +++ b/src/html/page/mod.rs @@ -1,4 +1,5 @@ pub mod block; pub mod not_found; +pub mod redirect_qs_to_path; pub mod root; pub mod utxo; diff --git a/src/html/page/redirect_qs_to_path.rs b/src/html/page/redirect_qs_to_path.rs new file mode 100644 index 0000000..f3265b8 --- /dev/null +++ b/src/html/page/redirect_qs_to_path.rs @@ -0,0 +1,116 @@ +use axum::extract::RawQuery; +use axum::extract::State; +use axum::response::Redirect; +use axum::response::Response; +use std::sync::Arc; +// use axum::routing::get; +// use axum::routing::Router; +use super::not_found::not_found_html_response; +use axum::response::IntoResponse; +use std::collections::HashSet; +// use super::root::root; +// use super::utxo::utxo_page; +use crate::model::app_state::AppState; +// use neptune_explorer::model::config::Config; + +/// This converts a query string into a path and redirects browser. +/// +/// Purpose: enable a javascript-free website. +/// +/// Problem being solved: +/// +/// Our axum routes are all paths, however an HTML form submits user input as a query-string. +/// We need to convert that query string into a path. +/// +/// We also want the end-user to see a nice path in the browser url +/// for purposes of copy/paste, etc. +/// +/// Browser form submits: We want: +/// /utxo?utxo=5&l=Submit /utxo/5 +/// /block?height=15&l=Submit /block/height/15 +/// +/// Solution: +/// +/// 1. We submit all browser forms to /rqs with method=get. +/// (note: rqs is short for redirect-query-string) +/// 2. /rqs calls this redirect_query_string_to_path() handler. +/// 3. query-string is transformed to path as follows: +/// a) if _ig key is present, the value is split by ',' +/// to obtain a list of query-string keys to ignore. +/// b. each key/val is converted to: +/// key (if val is empty) +/// key/val (if val is not empty) +/// c) any keys in the _ig list are ignored. +/// d) keys and vals are url encoded +/// e) each resulting /key or /key/val is appended to the path. +/// 4. a 301 redirect to the new path is sent to the browser. +/// +/// An html form might look like: +/// +///
+/// +/// note that the submit with name "l" is ignored because +/// of _ig=l. We could ignore a list of fields also +/// eg _ig=field1,field2,field3,etc. +/// +/// Order of keys in the query-string (and form) is important. +/// +/// Any keys that are not ignored are translated into a +/// path in the order received. Eg: +/// /rqs?block=&height_or_digest=10 --> /block/height_or_digest/10 +/// /rqs?height_or_digest=10&block= --> /height_or_digest/10/block +/// +/// A future enhancement could be to add an optional field for specifying the +/// path order. That would enable re-ordering of inputs in a form without +/// altering the resulting path. For now, our forms are so simple, that is not +/// needed. +#[axum::debug_handler] +pub async fn redirect_query_string_to_path( + RawQuery(raw_query_option): RawQuery, + State(state): State