mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-24 10:09:01 -06:00
Merge PR #2406: Remove dot segments when resolving redirect Locations
crates/fetch/src/http.rs: tests keep #2404's join_query_only_keeps_file_name and #2406's join_removes_dot_segments
This commit is contained in:
@@ -58,6 +58,37 @@ pub struct Url {
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
/// Remove `.` and `..` segments from the path part of `path` (RFC 3986 section 5.2.4); the query is kept as is.
|
||||
fn remove_dot_segments(path: &str) -> String {
|
||||
let (p, query) = match path.find('?') {
|
||||
Some(i) => (path.get(..i).unwrap_or_default(), path.get(i..).unwrap_or_default()),
|
||||
None => (path, ""),
|
||||
};
|
||||
let mut out: Vec<&str> = Vec::new();
|
||||
let mut segments = p.split('/').skip(1).peekable();
|
||||
let mut trailing_slash = false;
|
||||
while let Some(seg) = segments.next() {
|
||||
let last = segments.peek().is_none();
|
||||
match seg {
|
||||
"." => trailing_slash = last,
|
||||
".." => {
|
||||
out.pop();
|
||||
trailing_slash = last;
|
||||
}
|
||||
_ => {
|
||||
out.push(seg);
|
||||
trailing_slash = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut result = format!("/{}", out.join("/"));
|
||||
if trailing_slash && !result.ends_with('/') {
|
||||
result.push('/');
|
||||
}
|
||||
result.push_str(query);
|
||||
result
|
||||
}
|
||||
|
||||
impl Url {
|
||||
pub fn parse(s: &str) -> Result<Url, HttpError> {
|
||||
let bad = |why: &str| HttpError::BadUrl(why.to_string());
|
||||
@@ -81,6 +112,7 @@ impl Url {
|
||||
None => (rest, ""),
|
||||
};
|
||||
let path = if path.starts_with('/') { path.to_string() } else { format!("/{path}") };
|
||||
let path = remove_dot_segments(&path);
|
||||
if authority.contains('@') {
|
||||
return Err(bad("user names and passwords in URLs are not supported"));
|
||||
}
|
||||
@@ -628,6 +660,16 @@ mod tests {
|
||||
assert_eq!(u.join("?page=2").unwrap().path, "/x/y/file?page=2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn join_removes_dot_segments() {
|
||||
let base = Url::parse("https://a.example/x/y/file").unwrap();
|
||||
for (loc, want) in
|
||||
[("../z", "/x/z"), ("./z", "/x/y/z"), ("../../../z", "/z"), ("/a/./b/../c", "/a/c"), ("../z?q=1", "/x/z?q=1"), ("..", "/x/")]
|
||||
{
|
||||
assert_eq!(base.join(loc).unwrap().path, want, "{loc}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_urls() {
|
||||
let u = Url::parse("https://cdn.example.com/models/sam3/model.safetensors?sig=1#x").unwrap();
|
||||
|
||||
Reference in New Issue
Block a user