Skip to content

Commit d06ceff

Browse files
committed
Fix redirect loop with one-component path for crate without trailing slash
1 parent 3b3d9a0 commit d06ceff

File tree

2 files changed

+92
-5
lines changed

2 files changed

+92
-5
lines changed

src/web/extractors/rustdoc.rs

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -723,10 +723,24 @@ fn generate_rustdoc_path_for_url(
723723
&& !path.is_empty()
724724
&& path != INDEX_HTML
725725
{
726-
// ust juse the given inner to start, if:
727-
// * it's not empty
728-
// * it's not just "index.html"
729-
path.to_string()
726+
if let Some(target_name) = target_name
727+
&& path == target_name
728+
{
729+
// Sometimes people add a path that is just the target name.
730+
// This matches our `rustdoc_redirector_handler` route,
731+
// without trailing slash (`/{name}/{version}/{target}`).
732+
//
733+
// Even though the rustdoc html handler would also be capable
734+
// handling this input, we want to redirect to the trailing
735+
// slash version (`/{name}/{version}/{target}/`),
736+
// so they are separate.
737+
format!("{}/", target_name)
738+
} else {
739+
// just use the given inner to start, if:
740+
// * it's not empty
741+
// * it's not just "index.html"
742+
path.to_string()
743+
}
730744
} else if let Some(target_name) = target_name {
731745
// after having no usable given path, we generate one with the
732746
// target name, if we have one.
@@ -1600,4 +1614,48 @@ mod tests {
16001614
assert_eq!(params.doc_target(), Some(DEFAULT_TARGET));
16011615
assert_eq!(params.inner_path(), "dummy/struct.Dummy.html");
16021616
}
1617+
1618+
#[test]
1619+
fn test_parse_something() {
1620+
// test for https://github.com/rust-lang/docs.rs/issues/2989
1621+
let params = dbg!(
1622+
RustdocParams::new(KRATE)
1623+
.with_page_kind(PageKind::Rustdoc)
1624+
.try_with_original_uri(format!("/{KRATE}/latest/{KRATE}"))
1625+
.unwrap()
1626+
.with_req_version(ReqVersion::Latest)
1627+
.with_doc_target(KRATE)
1628+
);
1629+
1630+
assert_eq!(params.rustdoc_url(), "/krate/latest/krate/");
1631+
1632+
let params = dbg!(
1633+
params
1634+
.with_target_name(KRATE)
1635+
.with_default_target(DEFAULT_TARGET)
1636+
.with_doc_targets(TARGETS.iter().cloned())
1637+
);
1638+
1639+
assert_eq!(params.rustdoc_url(), "/krate/latest/krate/");
1640+
}
1641+
1642+
#[test_case(Some(PageKind::Rustdoc))]
1643+
#[test_case(None)]
1644+
fn test_only_target_name_without_slash(page_kind: Option<PageKind>) {
1645+
// test for https://github.com/rust-lang/docs.rs/issues/2989
1646+
let params = RustdocParams::new(KRATE)
1647+
.with_maybe_page_kind(page_kind)
1648+
.try_with_original_uri("/dummy/latest/dummy")
1649+
.unwrap()
1650+
.with_req_version(ReqVersion::Latest)
1651+
.with_maybe_doc_target(None::<String>)
1652+
.with_inner_path(KRATE)
1653+
.with_default_target(DEFAULT_TARGET)
1654+
.with_target_name(KRATE)
1655+
.with_doc_targets(TARGETS.iter().cloned());
1656+
1657+
dbg!(&params);
1658+
1659+
assert_eq!(params.rustdoc_url(), "/krate/latest/krate/");
1660+
}
16031661
}

src/web/rustdoc.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ pub(crate) async fn rustdoc_redirector_handler(
229229
url
230230
};
231231

232-
trace!("redirect to doc");
232+
trace!(%url, ?cache_policy, path_in_crate, "redirect to doc");
233233
Ok(axum_cached_redirect(url, cache_policy)?)
234234
}
235235

@@ -3341,4 +3341,33 @@ mod test {
33413341
assert_eq!(response.status(), StatusCode::NOT_FOUND);
33423342
Ok(())
33433343
}
3344+
3345+
#[tokio::test(flavor = "multi_thread")]
3346+
#[test_case("/dummy/"; "only krate")]
3347+
#[test_case("/dummy/latest/"; "with version")]
3348+
#[test_case("/dummy/latest/dummy"; "full without trailing slash")]
3349+
#[test_case("/dummy/latest/dummy/"; "final target")]
3350+
async fn test_full_latest_url_without_trailing_slash(path: &str) -> Result<()> {
3351+
// test for https://github.com/rust-lang/docs.rs/issues/2989
3352+
3353+
let env = TestEnvironment::new().await?;
3354+
3355+
env.fake_release()
3356+
.await
3357+
.name("dummy")
3358+
.version("1.0.0")
3359+
.create()
3360+
.await?;
3361+
3362+
let web = env.web_app().await;
3363+
const TARGET: &str = "/dummy/latest/dummy/";
3364+
if path == TARGET {
3365+
web.get(path).await?.status().is_success();
3366+
} else {
3367+
web.assert_redirect_unchecked(path, "/dummy/latest/dummy/")
3368+
.await?;
3369+
}
3370+
3371+
Ok(())
3372+
}
33443373
}

0 commit comments

Comments
 (0)