1 use crate::compare::{assert_match_exact, find_json_mismatch};
2 use crate::registry::{self, alt_api_path};
3 use flate2::read::GzDecoder;
4 use std::collections::{HashMap, HashSet};
5 use std::fs::File;
6 use std::io::{self, prelude::*, SeekFrom};
7 use std::path::{Path, PathBuf};
8 use tar::Archive;
9 
read_le_u32<R>(mut reader: R) -> io::Result<u32> where R: Read,10 fn read_le_u32<R>(mut reader: R) -> io::Result<u32>
11 where
12     R: Read,
13 {
14     let mut buf = [0; 4];
15     reader.read_exact(&mut buf)?;
16     Ok(u32::from_le_bytes(buf))
17 }
18 
19 /// Checks the result of a crate publish.
validate_upload(expected_json: &str, expected_crate_name: &str, expected_files: &[&str])20 pub fn validate_upload(expected_json: &str, expected_crate_name: &str, expected_files: &[&str]) {
21     let new_path = registry::api_path().join("api/v1/crates/new");
22     _validate_upload(
23         &new_path,
24         expected_json,
25         expected_crate_name,
26         expected_files,
27         &[],
28     );
29 }
30 
31 /// Checks the result of a crate publish, along with the contents of the files.
validate_upload_with_contents( expected_json: &str, expected_crate_name: &str, expected_files: &[&str], expected_contents: &[(&str, &str)], )32 pub fn validate_upload_with_contents(
33     expected_json: &str,
34     expected_crate_name: &str,
35     expected_files: &[&str],
36     expected_contents: &[(&str, &str)],
37 ) {
38     let new_path = registry::api_path().join("api/v1/crates/new");
39     _validate_upload(
40         &new_path,
41         expected_json,
42         expected_crate_name,
43         expected_files,
44         expected_contents,
45     );
46 }
47 
48 /// Checks the result of a crate publish to an alternative registry.
validate_alt_upload( expected_json: &str, expected_crate_name: &str, expected_files: &[&str], )49 pub fn validate_alt_upload(
50     expected_json: &str,
51     expected_crate_name: &str,
52     expected_files: &[&str],
53 ) {
54     let new_path = alt_api_path().join("api/v1/crates/new");
55     _validate_upload(
56         &new_path,
57         expected_json,
58         expected_crate_name,
59         expected_files,
60         &[],
61     );
62 }
63 
_validate_upload( new_path: &Path, expected_json: &str, expected_crate_name: &str, expected_files: &[&str], expected_contents: &[(&str, &str)], )64 fn _validate_upload(
65     new_path: &Path,
66     expected_json: &str,
67     expected_crate_name: &str,
68     expected_files: &[&str],
69     expected_contents: &[(&str, &str)],
70 ) {
71     let mut f = File::open(new_path).unwrap();
72     // 32-bit little-endian integer of length of JSON data.
73     let json_sz = read_le_u32(&mut f).expect("read json length");
74     let mut json_bytes = vec![0; json_sz as usize];
75     f.read_exact(&mut json_bytes).expect("read JSON data");
76     let actual_json = serde_json::from_slice(&json_bytes).expect("uploaded JSON should be valid");
77     let expected_json = serde_json::from_str(expected_json).expect("expected JSON does not parse");
78 
79     if let Err(e) = find_json_mismatch(&expected_json, &actual_json, None) {
80         panic!("{}", e);
81     }
82 
83     // 32-bit little-endian integer of length of crate file.
84     let crate_sz = read_le_u32(&mut f).expect("read crate length");
85     let mut krate_bytes = vec![0; crate_sz as usize];
86     f.read_exact(&mut krate_bytes).expect("read crate data");
87     // Check at end.
88     let current = f.seek(SeekFrom::Current(0)).unwrap();
89     assert_eq!(f.seek(SeekFrom::End(0)).unwrap(), current);
90 
91     // Verify the tarball.
92     validate_crate_contents(
93         &krate_bytes[..],
94         expected_crate_name,
95         expected_files,
96         expected_contents,
97     );
98 }
99 
100 /// Checks the contents of a `.crate` file.
101 ///
102 /// - `expected_crate_name` should be something like `foo-0.0.1.crate`.
103 /// - `expected_files` should be a complete list of files in the crate
104 ///   (relative to expected_crate_name).
105 /// - `expected_contents` should be a list of `(file_name, contents)` tuples
106 ///   to validate the contents of the given file. Only the listed files will
107 ///   be checked (others will be ignored).
validate_crate_contents( reader: impl Read, expected_crate_name: &str, expected_files: &[&str], expected_contents: &[(&str, &str)], )108 pub fn validate_crate_contents(
109     reader: impl Read,
110     expected_crate_name: &str,
111     expected_files: &[&str],
112     expected_contents: &[(&str, &str)],
113 ) {
114     let mut rdr = GzDecoder::new(reader);
115     assert_eq!(
116         rdr.header().unwrap().filename().unwrap(),
117         expected_crate_name.as_bytes()
118     );
119     let mut contents = Vec::new();
120     rdr.read_to_end(&mut contents).unwrap();
121     let mut ar = Archive::new(&contents[..]);
122     let files: HashMap<PathBuf, String> = ar
123         .entries()
124         .unwrap()
125         .map(|entry| {
126             let mut entry = entry.unwrap();
127             let name = entry.path().unwrap().into_owned();
128             let mut contents = String::new();
129             entry.read_to_string(&mut contents).unwrap();
130             (name, contents)
131         })
132         .collect();
133     assert!(expected_crate_name.ends_with(".crate"));
134     let base_crate_name = Path::new(&expected_crate_name[..expected_crate_name.len() - 6]);
135     let actual_files: HashSet<PathBuf> = files.keys().cloned().collect();
136     let expected_files: HashSet<PathBuf> = expected_files
137         .iter()
138         .map(|name| base_crate_name.join(name))
139         .collect();
140     let missing: Vec<&PathBuf> = expected_files.difference(&actual_files).collect();
141     let extra: Vec<&PathBuf> = actual_files.difference(&expected_files).collect();
142     if !missing.is_empty() || !extra.is_empty() {
143         panic!(
144             "uploaded archive does not match.\nMissing: {:?}\nExtra: {:?}\n",
145             missing, extra
146         );
147     }
148     if !expected_contents.is_empty() {
149         for (e_file_name, e_file_contents) in expected_contents {
150             let full_e_name = base_crate_name.join(e_file_name);
151             let actual_contents = files
152                 .get(&full_e_name)
153                 .unwrap_or_else(|| panic!("file `{}` missing in archive", e_file_name));
154             assert_match_exact(e_file_contents, actual_contents);
155         }
156     }
157 }
158