mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
fix: correct scheme handling in router, and improve matching code by removing regexes (#569)
This commit is contained in:
parent
3d50ca32cd
commit
3648af0d9d
2 changed files with 130 additions and 179 deletions
|
@ -35,100 +35,52 @@ pub fn resolve_path<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
fn has_scheme(path: &str) -> bool {
|
||||
use regex::Regex;
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref HAS_SCHEME_RE: Regex =
|
||||
Regex::new(HAS_SCHEME).expect("couldn't compile HAS_SCHEME_RE");
|
||||
}
|
||||
|
||||
HAS_SCHEME_RE.is_match(path)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
fn has_scheme(path: &str) -> bool {
|
||||
let re = js_sys::RegExp::new(HAS_SCHEME, "");
|
||||
re.test(path)
|
||||
path.starts_with("//")
|
||||
|| path.starts_with("tel:")
|
||||
|| path.starts_with("mailto:")
|
||||
|| path
|
||||
.split_once("://")
|
||||
.map(|(prefix, _)| {
|
||||
prefix.chars().all(
|
||||
|c: char| matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9'),
|
||||
)
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn normalize(path: &str, omit_slash: bool) -> Cow<'_, str> {
|
||||
let s = replace_trim_path(path, "");
|
||||
if !s.is_empty() {
|
||||
if omit_slash || begins_with_query_or_hash(&s) {
|
||||
s
|
||||
} else {
|
||||
format!("/{s}").into()
|
||||
}
|
||||
fn normalize(path: &str, omit_slash: bool) -> Cow<'_, str> {
|
||||
let s = path.trim_start_matches('/').trim_end_matches('/');
|
||||
if s.is_empty() || omit_slash || begins_with_query_or_hash(&s) {
|
||||
s.into()
|
||||
} else {
|
||||
"".into()
|
||||
format!("/{s}").into()
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn join_paths<'a>(from: &'a str, to: &'a str) -> String {
|
||||
let from = replace_query(&normalize(from, false));
|
||||
let from = remove_wildcard(&normalize(from, false));
|
||||
from + &normalize(to, false)
|
||||
}
|
||||
|
||||
const TRIM_PATH: &str = r#"^/+|/+$"#;
|
||||
const BEGINS_WITH_QUERY_OR_HASH: &str = r#"^[?#]"#;
|
||||
const HAS_SCHEME: &str = r#"^(?:[a-z0-9]+:)?//"#;
|
||||
const QUERY: &str = r#"/*(\*.*)?$"#;
|
||||
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
fn replace_trim_path<'a>(text: &'a str, replace: &str) -> Cow<'a, str> {
|
||||
let re = js_sys::RegExp::new(TRIM_PATH, "g");
|
||||
js_sys::JsString::from(text)
|
||||
.replace_by_pattern(&re, replace)
|
||||
.as_string()
|
||||
.unwrap()
|
||||
.into()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
fn begins_with_query_or_hash(text: &str) -> bool {
|
||||
let re = js_sys::RegExp::new(BEGINS_WITH_QUERY_OR_HASH, "");
|
||||
re.test(text)
|
||||
matches!(text.chars().next(), Some('#') | Some('?'))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
fn replace_query(text: &str) -> String {
|
||||
let re = js_sys::RegExp::new(QUERY, "g");
|
||||
js_sys::JsString::from(text)
|
||||
.replace_by_pattern(&re, "")
|
||||
.as_string()
|
||||
.unwrap()
|
||||
fn remove_wildcard(text: &str) -> String {
|
||||
text.split_once('*')
|
||||
.map(|(prefix, _)| prefix.trim_end_matches('/'))
|
||||
.unwrap_or(text)
|
||||
.to_string()
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
fn replace_trim_path<'a>(text: &'a str, replace: &str) -> Cow<'a, str> {
|
||||
use regex::Regex;
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref TRIM_PATH_RE: Regex =
|
||||
Regex::new(TRIM_PATH).expect("couldn't compile TRIM_PATH_RE");
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn normalize_query_string_with_opening_slash() {
|
||||
assert_eq!(normalize("/?foo=bar", false), "?foo=bar");
|
||||
}
|
||||
|
||||
TRIM_PATH_RE.replace(text, replace)
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
fn begins_with_query_or_hash(text: &str) -> bool {
|
||||
use regex::Regex;
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref BEGINS_WITH_QUERY_OR_HASH_RE: Regex =
|
||||
Regex::new(BEGINS_WITH_QUERY_OR_HASH).expect("couldn't compile BEGINS_WITH_HASH_RE");
|
||||
}
|
||||
BEGINS_WITH_QUERY_OR_HASH_RE.is_match(text)
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
fn replace_query(text: &str) -> String {
|
||||
use regex::Regex;
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref QUERY_RE: Regex =
|
||||
Regex::new(QUERY).expect("couldn't compile QUERY_RE");
|
||||
}
|
||||
QUERY_RE.replace(text, "").into_owned()
|
||||
}
|
||||
|
|
|
@ -1,107 +1,106 @@
|
|||
use cfg_if::cfg_if;
|
||||
|
||||
// Test cases drawn from Solid Router
|
||||
// see https://github.com/solidjs/solid-router/blob/main/test/utils.spec.ts
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
use leptos_router::{normalize, resolve_path};
|
||||
use leptos_router::resolve_path;
|
||||
|
||||
#[test]
|
||||
fn normalize_query_string_with_opening_slash() {
|
||||
assert_eq!(normalize("/?foo=bar", false), "?foo=bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_path_should_normalize_base_arg() {
|
||||
assert_eq!(resolve_path("base", "", None), Some("/base".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_path_should_normalize_path_arg() {
|
||||
assert_eq!(resolve_path("", "path", None), Some("/path".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_path_should_normalize_from_arg() {
|
||||
assert_eq!(resolve_path("", "", Some("from")), Some("/from".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_path_should_return_default_when_all_empty() {
|
||||
assert_eq!(resolve_path("", "", None), Some("/".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_path_should_resolve_root_against_base_and_ignore_from() {
|
||||
assert_eq!(
|
||||
resolve_path("/base", "/", Some("/base/foo")),
|
||||
Some("/base".into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_path_should_resolve_rooted_paths_against_base_and_ignore_from() {
|
||||
assert_eq!(
|
||||
resolve_path("/base", "/bar", Some("/base/foo")),
|
||||
Some("/base/bar".into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_path_should_resolve_empty_path_against_from() {
|
||||
assert_eq!(
|
||||
resolve_path("/base", "", Some("/base/foo")),
|
||||
Some("/base/foo".into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_path_should_resolve_relative_paths_against_from() {
|
||||
assert_eq!(
|
||||
resolve_path("/base", "bar", Some("/base/foo")),
|
||||
Some("/base/foo/bar".into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_path_should_prepend_base_if_from_doesnt_start_with_it() {
|
||||
assert_eq!(
|
||||
resolve_path("/base", "bar", Some("/foo")),
|
||||
Some("/base/foo/bar".into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_path_should_test_start_of_from_against_base_case_insensitive() {
|
||||
assert_eq!(
|
||||
resolve_path("/base", "bar", Some("BASE/foo")),
|
||||
Some("/BASE/foo/bar".into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_path_should_work_with_rooted_search_and_base() {
|
||||
assert_eq!(
|
||||
resolve_path("/base", "/?foo=bar", Some("/base/page")),
|
||||
Some("/base?foo=bar".into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_path_should_work_with_rooted_search() {
|
||||
assert_eq!(
|
||||
resolve_path("", "/?foo=bar", None),
|
||||
Some("/?foo=bar".into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preserve_spaces() {
|
||||
assert_eq!(
|
||||
resolve_path(" foo ", " bar baz ", None),
|
||||
Some("/ foo / bar baz ".into())
|
||||
);
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn resolve_path_should_normalize_base_arg() {
|
||||
assert_eq!(resolve_path("base", "", None), Some("/base".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_path_should_normalize_path_arg() {
|
||||
assert_eq!(resolve_path("", "path", None), Some("/path".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_path_should_normalize_from_arg() {
|
||||
assert_eq!(resolve_path("", "", Some("from")), Some("/from".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_path_should_return_default_when_all_empty() {
|
||||
assert_eq!(resolve_path("", "", None), Some("/".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_path_should_resolve_root_against_base_and_ignore_from() {
|
||||
assert_eq!(
|
||||
resolve_path("/base", "/", Some("/base/foo")),
|
||||
Some("/base".into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_path_should_resolve_rooted_paths_against_base_and_ignore_from() {
|
||||
assert_eq!(
|
||||
resolve_path("/base", "/bar", Some("/base/foo")),
|
||||
Some("/base/bar".into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_path_should_resolve_empty_path_against_from() {
|
||||
assert_eq!(
|
||||
resolve_path("/base", "", Some("/base/foo")),
|
||||
Some("/base/foo".into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_path_should_resolve_relative_paths_against_from() {
|
||||
assert_eq!(
|
||||
resolve_path("/base", "bar", Some("/base/foo")),
|
||||
Some("/base/foo/bar".into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_path_should_prepend_base_if_from_doesnt_start_with_it() {
|
||||
assert_eq!(
|
||||
resolve_path("/base", "bar", Some("/foo")),
|
||||
Some("/base/foo/bar".into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_path_should_test_start_of_from_against_base_case_insensitive() {
|
||||
assert_eq!(
|
||||
resolve_path("/base", "bar", Some("BASE/foo")),
|
||||
Some("/BASE/foo/bar".into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_path_should_work_with_rooted_search_and_base() {
|
||||
assert_eq!(
|
||||
resolve_path("/base", "/?foo=bar", Some("/base/page")),
|
||||
Some("/base?foo=bar".into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_path_should_work_with_rooted_search() {
|
||||
assert_eq!(
|
||||
resolve_path("", "/?foo=bar", None),
|
||||
Some("/?foo=bar".into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preserve_spaces() {
|
||||
assert_eq!(
|
||||
resolve_path(" foo ", " bar baz ", None),
|
||||
Some("/ foo / bar baz ".into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cannot_resolve_if_path_has_scheme() {
|
||||
assert_eq!(resolve_path("", "http://example.com", None), None);
|
||||
assert_eq!(resolve_path("", "https://example.com", None), None);
|
||||
assert_eq!(resolve_path("", "example://google.com", None), None);
|
||||
assert_eq!(resolve_path("", "tel:+15555555555", None), None);
|
||||
assert_eq!(resolve_path("", "mailto:name@example.com", None), None);
|
||||
assert_eq!(resolve_path("", "//relative-protocol", None), None);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue