Skip to content

Commit 8350593

Browse files
nipunn1313Turbo87
authored andcommitted
controllers::krate::publish: Extract cargo vcs info from tarball
Supported in rust-lang/cargo#9866
1 parent b6525e4 commit 8350593

File tree

2 files changed

+110
-10
lines changed

2 files changed

+110
-10
lines changed

src/controllers/krate/publish.rs

+67-10
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use hex::ToHex;
55
use sha2::{Digest, Sha256};
66
use std::collections::HashMap;
77
use std::io::Read;
8+
use std::path::Path;
89
use std::sync::Arc;
910
use swirl::Job;
1011

@@ -18,7 +19,7 @@ use crate::worker;
1819

1920
use crate::schema::*;
2021
use crate::util::errors::{cargo_err, AppResult};
21-
use crate::util::{read_fill, read_le_u32, LimitErrorReader, Maximums};
22+
use crate::util::{read_fill, read_le_u32, CargoVcsInfo, LimitErrorReader, Maximums};
2223
use crate::views::{
2324
EncodableCrate, EncodableCrateDependency, EncodableCrateUpload, GoodCrate, PublishWarnings,
2425
};
@@ -195,9 +196,8 @@ pub fn publish(req: &mut dyn RequestExt) -> EndpointResult {
195196
LimitErrorReader::new(req.body(), maximums.max_upload_size).read_to_end(&mut tarball)?;
196197
let hex_cksum: String = Sha256::digest(&tarball).encode_hex();
197198
let pkg_name = format!("{}-{}", krate.name, vers);
198-
verify_tarball(&pkg_name, &tarball, maximums.max_unpack_size)?;
199-
200-
let pkg_path_in_vcs = None;
199+
let cargo_vcs_info = verify_tarball(&pkg_name, &tarball, maximums.max_unpack_size)?;
200+
let pkg_path_in_vcs = cargo_vcs_info.map(|info| info.path_in_vcs);
201201

202202
if let Some(readme) = new_crate.readme {
203203
worker::render_and_upload_readme(
@@ -382,7 +382,11 @@ pub fn add_dependencies(
382382
Ok(git_deps)
383383
}
384384

385-
fn verify_tarball(pkg_name: &str, tarball: &[u8], max_unpack: u64) -> AppResult<()> {
385+
fn verify_tarball(
386+
pkg_name: &str,
387+
tarball: &[u8],
388+
max_unpack: u64,
389+
) -> AppResult<Option<CargoVcsInfo>> {
386390
// All our data is currently encoded with gzip
387391
let decoder = GzDecoder::new(tarball);
388392

@@ -392,8 +396,12 @@ fn verify_tarball(pkg_name: &str, tarball: &[u8], max_unpack: u64) -> AppResult<
392396

393397
// Use this I/O object now to take a peek inside
394398
let mut archive = tar::Archive::new(decoder);
399+
400+
let vcs_info_path = Path::new(&pkg_name).join(".cargo_vcs_info.json");
401+
let mut vcs_info = None;
402+
395403
for entry in archive.entries()? {
396-
let entry = entry.map_err(|err| {
404+
let mut entry = entry.map_err(|err| {
397405
err.chain(cargo_err(
398406
"uploaded tarball is malformed or too large when decompressed",
399407
))
@@ -404,9 +412,15 @@ fn verify_tarball(pkg_name: &str, tarball: &[u8], max_unpack: u64) -> AppResult<
404412
// upload a tarball that contains both `foo-0.1.0/` source code as well
405413
// as `bar-0.1.0/` source code, and this could overwrite other crates in
406414
// the registry!
407-
if !entry.path()?.starts_with(&pkg_name) {
415+
let entry_path = entry.path()?;
416+
if !entry_path.starts_with(&pkg_name) {
408417
return Err(cargo_err("invalid tarball uploaded"));
409418
}
419+
if entry_path == vcs_info_path {
420+
let mut contents = String::new();
421+
entry.read_to_string(&mut contents)?;
422+
vcs_info = CargoVcsInfo::from_contents(&contents).ok();
423+
}
410424

411425
// Historical versions of the `tar` crate which Cargo uses internally
412426
// don't properly prevent hard links and symlinks from overwriting
@@ -418,7 +432,7 @@ fn verify_tarball(pkg_name: &str, tarball: &[u8], max_unpack: u64) -> AppResult<
418432
return Err(cargo_err("invalid tarball uploaded"));
419433
}
420434
}
421-
Ok(())
435+
Ok(vcs_info)
422436
}
423437

424438
#[cfg(test)]
@@ -438,14 +452,57 @@ mod tests {
438452
#[test]
439453
fn verify_tarball_test() {
440454
let mut pkg = tar::Builder::new(vec![]);
441-
add_file(&mut pkg, "foo-0.0.1/.cargo_vcs_info.json", br#"{}"#);
455+
add_file(&mut pkg, "foo-0.0.1/Cargo.toml", b"");
442456
let mut serialized_archive = vec![];
443457
GzEncoder::new(pkg.into_inner().unwrap().as_slice(), Default::default())
444458
.read_to_end(&mut serialized_archive)
445459
.unwrap();
446460

447461
let limit = 512 * 1024 * 1024;
448-
assert_ok!(verify_tarball("foo-0.0.1", &serialized_archive, limit));
462+
assert_eq!(
463+
verify_tarball("foo-0.0.1", &serialized_archive, limit).unwrap(),
464+
None
465+
);
449466
assert_err!(verify_tarball("bar-0.0.1", &serialized_archive, limit));
450467
}
468+
469+
#[test]
470+
fn verify_tarball_test_incomplete_vcs_info() {
471+
let mut pkg = tar::Builder::new(vec![]);
472+
add_file(&mut pkg, "foo-0.0.1/Cargo.toml", b"");
473+
add_file(
474+
&mut pkg,
475+
"foo-0.0.1/.cargo_vcs_info.json",
476+
br#"{"unknown": "field"}"#,
477+
);
478+
let mut serialized_archive = vec![];
479+
GzEncoder::new(pkg.into_inner().unwrap().as_slice(), Default::default())
480+
.read_to_end(&mut serialized_archive)
481+
.unwrap();
482+
let limit = 512 * 1024 * 1024;
483+
let vcs_info = verify_tarball("foo-0.0.1", &serialized_archive, limit)
484+
.unwrap()
485+
.unwrap();
486+
assert_eq!(vcs_info.path_in_vcs, "");
487+
}
488+
489+
#[test]
490+
fn verify_tarball_test_vcs_info() {
491+
let mut pkg = tar::Builder::new(vec![]);
492+
add_file(&mut pkg, "foo-0.0.1/Cargo.toml", b"");
493+
add_file(
494+
&mut pkg,
495+
"foo-0.0.1/.cargo_vcs_info.json",
496+
br#"{"path_in_vcs": "path/in/vcs"}"#,
497+
);
498+
let mut serialized_archive = vec![];
499+
GzEncoder::new(pkg.into_inner().unwrap().as_slice(), Default::default())
500+
.read_to_end(&mut serialized_archive)
501+
.unwrap();
502+
let limit = 512 * 1024 * 1024;
503+
let vcs_info = verify_tarball("foo-0.0.1", &serialized_archive, limit)
504+
.unwrap()
505+
.unwrap();
506+
assert_eq!(vcs_info.path_in_vcs, "path/in/vcs");
507+
}
451508
}

src/util.rs

+43
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,46 @@ impl Maximums {
5353
}
5454
}
5555
}
56+
57+
/// Represents relevant contents of .cargo_vcs_info.json file when uploaded from cargo
58+
/// or downloaded from crates.io
59+
#[derive(Debug, Deserialize, Eq, PartialEq)]
60+
pub struct CargoVcsInfo {
61+
/// Path to the package within repo (empty string if root). / not \
62+
#[serde(default)]
63+
pub path_in_vcs: String,
64+
}
65+
66+
impl CargoVcsInfo {
67+
pub fn from_contents(contents: &str) -> serde_json::Result<Self> {
68+
serde_json::from_str(contents)
69+
}
70+
}
71+
72+
#[cfg(test)]
73+
mod tests {
74+
use super::CargoVcsInfo;
75+
76+
#[test]
77+
fn test_cargo_vcs_info() {
78+
assert_eq!(CargoVcsInfo::from_contents("").ok(), None);
79+
assert_eq!(
80+
CargoVcsInfo::from_contents("{}").unwrap(),
81+
CargoVcsInfo {
82+
path_in_vcs: "".into()
83+
}
84+
);
85+
assert_eq!(
86+
CargoVcsInfo::from_contents(r#"{"path_in_vcs": "hi"}"#).unwrap(),
87+
CargoVcsInfo {
88+
path_in_vcs: "hi".into()
89+
}
90+
);
91+
assert_eq!(
92+
CargoVcsInfo::from_contents(r#"{"path_in_vcs": "hi", "future": "field"}"#).unwrap(),
93+
CargoVcsInfo {
94+
path_in_vcs: "hi".into()
95+
}
96+
);
97+
}
98+
}

0 commit comments

Comments
 (0)