use super::super::BROWSE_PREFIX;
use super::super::helpers::{object_location, percent_encode_path};
pub(super) fn directory_href(kb_slug: &str, prefix: &str) -> String {
format!(
"{BROWSE_PREFIX}/{}/{}",
percent_encode_path(kb_slug),
without_dot_segments(&percent_encode_path(prefix))
)
}
fn without_dot_segments(encoded_path: &str) -> String {
encoded_path
.split('/')
.map(|segment| match segment {
"." => "%2E",
".." => "%2E%2E",
other => other,
})
.collect::<Vec<_>>()
.join("/")
}
pub(super) fn object_href(kb_slug: &str, key: &str) -> String {
without_dot_segments(&object_location(kb_slug, key))
}
pub(super) fn root_href() -> String {
format!("{BROWSE_PREFIX}/")
}
pub(super) fn parent_href(kb_slug: &str, prefix: &str) -> String {
let trimmed = prefix.strip_suffix('/').unwrap_or(prefix);
match trimmed.rsplit_once('/') {
Some((parent, _)) => directory_href(kb_slug, &format!("{parent}/")),
None if trimmed.is_empty() => root_href(),
None => directory_href(kb_slug, ""),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_generated_link_leaves_the_selected_knowledge_base() {
let hostile = [
"..",
".",
"a b",
"a#b",
"a?b",
"a%2Fb",
"\\windows",
"naïve",
"a\"b",
"..evil",
];
for name in hostile {
let folder = directory_href("notes", &format!("{name}/"));
let object = object_href("notes", name);
for href in [&folder, &object] {
assert!(
href.starts_with("/browse/notes/")
|| href.starts_with("/api/v1/knowledgebases/notes/"),
"`{name}` produced `{href}`"
);
assert!(
!href.contains("/../") && !href.ends_with("/.."),
"`{name}` produced a dot-segment href: `{href}`"
);
assert!(!href.contains("/./"), "`{name}` -> `{href}`");
assert!(
!href.contains('#') && !href.contains('?'),
"`{name}` -> `{href}`"
);
assert!(!href.contains(' '), "`{name}` -> `{href}`");
}
}
}
#[test]
fn a_directory_href_is_always_the_canonical_trailing_slash_form() {
assert_eq!(directory_href("notes", ""), "/browse/notes/");
assert_eq!(directory_href("notes", "docs/"), "/browse/notes/docs/");
assert_eq!(
directory_href("notes", "docs/deep/"),
"/browse/notes/docs/deep/"
);
}
#[test]
fn an_object_href_matches_the_machine_api_url_for_the_same_key() {
assert_eq!(
object_href("notes", "docs/a b.md"),
crate::router::helpers::object_location("notes", "docs/a b.md")
);
assert_eq!(
object_href("notes", "a/../b"),
"/api/v1/knowledgebases/notes/a/%2E%2E/b"
);
}
#[test]
fn the_parent_of_a_knowledge_base_root_is_the_index() {
assert_eq!(parent_href("notes", ""), "/browse/");
}
#[test]
fn the_parent_of_a_nested_directory_is_one_level_up() {
assert_eq!(parent_href("notes", "docs/"), "/browse/notes/");
assert_eq!(parent_href("notes", "docs/deep/"), "/browse/notes/docs/");
}
}