1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
use metadata::WheelMetadata;
use reqwest::{self, header::ContentType, multipart::Form, Client, StatusCode};
use sha2::{Digest, Sha256};
use std::fs::File;
use std::io;
use std::path::{Path, PathBuf};
use PythonInterpreter;
use Registry;
#[derive(Fail, Debug)]
#[fail(display = "Uploading to the registry failed")]
pub enum UploadError {
#[fail(display = "{}", _0)]
RewqestError(#[cause] reqwest::Error),
#[fail(display = "Username or password are incorrect")]
AuthenticationError,
#[fail(display = "{}", _0)]
IOError(#[cause] io::Error),
#[fail(display = "Failed to upload the wheel {}", _0)]
StatusCodeError(String),
}
impl From<io::Error> for UploadError {
fn from(error: io::Error) -> Self {
UploadError::IOError(error)
}
}
impl From<reqwest::Error> for UploadError {
fn from(error: reqwest::Error) -> Self {
UploadError::RewqestError(error)
}
}
pub fn upload_wheels(
regitry: &Registry,
wheels: &[(PathBuf, Option<PythonInterpreter>)],
metadata: &WheelMetadata,
) -> Result<(), UploadError> {
for (wheel_path, python_version) in wheels {
upload(®itry, &wheel_path, &metadata, &python_version)?;
}
Ok(())
}
pub fn upload(
registry: &Registry,
wheel_path: &Path,
metadata: &WheelMetadata,
python_version: &Option<PythonInterpreter>,
) -> Result<(), UploadError> {
let mut wheel = File::open(&wheel_path)?;
let hash = format!("{:x}", Sha256::digest_reader(&mut wheel)?);
let mut api_metadata = vec![
(":action".to_string(), "file_upload".to_string()),
("sha256_digest".to_string(), hash),
("protocol_version".to_string(), "1".to_string()),
];
if let Some(python_version) = python_version {
let pyversion = format!("cp{}{}", python_version.major, python_version.minor);
api_metadata.push(("pyversion".to_string(), pyversion));
api_metadata.push(("filetype".to_string(), "bdist_wheel".to_string()));
} else {
api_metadata.push(("pyversion".to_string(), "source".to_string()));
api_metadata.push(("filetype".to_string(), "sdist".to_string()));
}
let joined_metadata: Vec<(String, String)> = api_metadata
.into_iter()
.chain(metadata.metadata21.to_vec().clone().into_iter())
.map(|(key, value)| (key.to_lowercase().replace("-", "_"), value))
.collect();
let mut form = Form::new();
for (key, value) in joined_metadata {
form = form.text(key, value.to_owned())
}
form = form.file("content", &wheel_path)?;
let client = Client::new();
let mut response = client
.post(registry.url.clone())
.header(ContentType::json())
.multipart(form)
.basic_auth(registry.username.clone(), Some(registry.password.clone()))
.send()?;
if response.status().is_success() {
Ok(())
} else if response.status() == StatusCode::Forbidden {
Err(UploadError::AuthenticationError)
} else {
let err_text = response.text().unwrap_or_else(|e| {
format!(
"The registry should return some text, even in case of an error, but didn't ({})",
e
)
});
Err(UploadError::StatusCodeError(err_text))
}
}