Skip to content

Commit 09c3715

Browse files
committed
Auto merge of #1758 - smarnach:download-non-standard-name, r=sgrif
Return correct download link for requests for non-standard crate names. Fixes #1687. Crates are uploaded to S3 under the name they were first published as, but the download endpoint always uses the name as written in the request. If these names differ in their use of dashes vs. underscores, the download endpoint returns an invalid link. To avoid adding another database request for every single crate download, this fix changes the database request that updates the download count to also return the original crate name. We will need to confirm that the performance impact of this change is negligible.
2 parents 293a3fb + 076e7ce commit 09c3715

File tree

3 files changed

+32
-8
lines changed

3 files changed

+32
-8
lines changed

src/controllers/version/downloads.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ pub fn download(req: &mut dyn Request) -> CargoResult<Response> {
1818
let crate_name = &req.params()["crate_id"];
1919
let version = &req.params()["version"];
2020

21-
increment_download_counts(req, crate_name, version)?;
21+
let crate_name = increment_download_counts(req, crate_name, version)?;
2222

2323
let redirect_url = req
2424
.app()
2525
.config
2626
.uploader
27-
.crate_location(crate_name, version);
27+
.crate_location(&crate_name, version);
2828

2929
if req.wants_json() {
3030
#[derive(Serialize)]
@@ -39,7 +39,8 @@ pub fn download(req: &mut dyn Request) -> CargoResult<Response> {
3939

4040
/// Increment the download counts for a given crate version.
4141
///
42-
/// Returns an error if we could not load the version ID from the database.
42+
/// Returns the crate name as stored in the database, or an error if we could
43+
/// not load the version ID from the database.
4344
///
4445
/// This ignores any errors that occur updating the download count. Failure is
4546
/// expected if the application is in read only mode, or for API-only mirrors.
@@ -49,20 +50,21 @@ fn increment_download_counts(
4950
req: &dyn Request,
5051
crate_name: &str,
5152
version: &str,
52-
) -> CargoResult<()> {
53+
) -> CargoResult<String> {
5354
use self::versions::dsl::*;
5455

5556
let conn = req.db_conn()?;
56-
let version_id = versions
57-
.select(id)
58-
.filter(crate_id.eq_any(Crate::by_name(crate_name).select(crates::id)))
57+
let (version_id, crate_name) = versions
58+
.inner_join(crates::table)
59+
.select((id, crates::name))
60+
.filter(Crate::with_name(crate_name))
5961
.filter(num.eq(version))
6062
.first(&*conn)?;
6163

6264
// Wrap in a transaction so we don't poison the outer transaction if this
6365
// fails
6466
let _ = conn.transaction(|| VersionDownload::create_or_increment(version_id, &conn));
65-
Ok(())
67+
Ok(crate_name)
6668
}
6769

6870
/// Handles the `GET /crates/:crate_id/:version/downloads` route.

src/tests/krate.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1310,6 +1310,23 @@ fn download_nonexistent_version_of_existing_crate_404s() {
13101310
.assert_not_found();
13111311
}
13121312

1313+
#[test]
1314+
fn download_noncanonical_crate_name() {
1315+
let (app, anon, user) = TestApp::init().with_user();
1316+
let user = user.as_model();
1317+
1318+
app.db(|conn| {
1319+
CrateBuilder::new("foo_download", user.id)
1320+
.version(VersionBuilder::new("1.0.0"))
1321+
.expect_build(conn);
1322+
});
1323+
1324+
// Request download for "foo-download" with a dash instead of an underscore,
1325+
// and assert that the correct download link is returned.
1326+
anon.get::<()>("/api/v1/crates/foo-download/1.0.0/download")
1327+
.assert_redirect_ends_with("/crates/foo_download/foo_download-1.0.0.crate");
1328+
}
1329+
13131330
#[test]
13141331
fn dependencies() {
13151332
let (app, anon, user) = TestApp::init().with_user();

src/tests/util.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,11 @@ where
556556
assert_eq!(status, self.response.status.0);
557557
self
558558
}
559+
560+
pub fn assert_redirect_ends_with(&self, target: &str) -> &Self {
561+
assert!(self.response.headers["Location"][0].ends_with(target));
562+
self
563+
}
559564
}
560565

561566
impl Response<()> {

0 commit comments

Comments
 (0)