1#![allow(unknown_lints)]
2#![allow(clippy::identity_op)] use std::collections::BTreeMap;
5use std::fs::File;
6use std::io::prelude::*;
7use std::io::Cursor;
8
9use curl::easy::{Easy, List};
10use failure::bail;
11use serde::{Deserialize, Serialize};
12use serde_json;
13use url::percent_encoding::{percent_encode, QUERY_ENCODE_SET};
14
15pub type Result<T> = std::result::Result<T, failure::Error>;
16
17pub struct Registry {
18 host: String,
20 token: Option<String>,
23 handle: Easy,
25}
26
27#[derive(PartialEq, Clone, Copy)]
28pub enum Auth {
29 Authorized,
30 Unauthorized,
31}
32
33#[derive(Deserialize)]
34pub struct Crate {
35 pub name: String,
36 pub description: Option<String>,
37 pub max_version: String,
38}
39
40#[derive(Serialize)]
41pub struct NewCrate {
42 pub name: String,
43 pub vers: String,
44 pub deps: Vec<NewCrateDependency>,
45 pub features: BTreeMap<String, Vec<String>>,
46 pub authors: Vec<String>,
47 pub description: Option<String>,
48 pub documentation: Option<String>,
49 pub homepage: Option<String>,
50 pub readme: Option<String>,
51 pub readme_file: Option<String>,
52 pub keywords: Vec<String>,
53 pub categories: Vec<String>,
54 pub license: Option<String>,
55 pub license_file: Option<String>,
56 pub repository: Option<String>,
57 pub badges: BTreeMap<String, BTreeMap<String, String>>,
58 #[serde(default)]
59 pub links: Option<String>,
60}
61
62#[derive(Serialize)]
63pub struct NewCrateDependency {
64 pub optional: bool,
65 pub default_features: bool,
66 pub name: String,
67 pub features: Vec<String>,
68 pub version_req: String,
69 pub target: Option<String>,
70 pub kind: String,
71 #[serde(skip_serializing_if = "Option::is_none")]
72 pub registry: Option<String>,
73 #[serde(skip_serializing_if = "Option::is_none")]
74 pub explicit_name_in_toml: Option<String>,
75}
76
77#[derive(Deserialize)]
78pub struct User {
79 pub id: u32,
80 pub login: String,
81 pub avatar: Option<String>,
82 pub email: Option<String>,
83 pub name: Option<String>,
84}
85
86pub struct Warnings {
87 pub invalid_categories: Vec<String>,
88 pub invalid_badges: Vec<String>,
89 pub other: Vec<String>,
90}
91
92#[derive(Deserialize)]
93struct R {
94 ok: bool,
95}
96#[derive(Deserialize)]
97struct OwnerResponse {
98 ok: bool,
99 msg: String,
100}
101#[derive(Deserialize)]
102struct ApiErrorList {
103 errors: Vec<ApiError>,
104}
105#[derive(Deserialize)]
106struct ApiError {
107 detail: String,
108}
109#[derive(Serialize)]
110struct OwnersReq<'a> {
111 users: &'a [&'a str],
112}
113#[derive(Deserialize)]
114struct Users {
115 users: Vec<User>,
116}
117#[derive(Deserialize)]
118struct TotalCrates {
119 total: u32,
120}
121#[derive(Deserialize)]
122struct Crates {
123 crates: Vec<Crate>,
124 meta: TotalCrates,
125}
126impl Registry {
127 pub fn new(host: String, token: Option<String>) -> Registry {
128 Registry::new_handle(host, token, Easy::new())
129 }
130
131 pub fn new_handle(host: String, token: Option<String>, handle: Easy) -> Registry {
132 Registry {
133 host,
134 token,
135 handle,
136 }
137 }
138
139 pub fn host(&self) -> &str {
140 &self.host
141 }
142
143 pub fn add_owners(&mut self, krate: &str, owners: &[&str]) -> Result<String> {
144 let body = serde_json::to_string(&OwnersReq { users: owners })?;
145 let body = self.put(&format!("/crates/{}/owners", krate), body.as_bytes())?;
146 assert!(serde_json::from_str::<OwnerResponse>(&body)?.ok);
147 Ok(serde_json::from_str::<OwnerResponse>(&body)?.msg)
148 }
149
150 pub fn remove_owners(&mut self, krate: &str, owners: &[&str]) -> Result<()> {
151 let body = serde_json::to_string(&OwnersReq { users: owners })?;
152 let body = self.delete(&format!("/crates/{}/owners", krate), Some(body.as_bytes()))?;
153 assert!(serde_json::from_str::<OwnerResponse>(&body)?.ok);
154 Ok(())
155 }
156
157 pub fn list_owners(&mut self, krate: &str) -> Result<Vec<User>> {
158 let body = self.get(&format!("/crates/{}/owners", krate))?;
159 Ok(serde_json::from_str::<Users>(&body)?.users)
160 }
161
162 pub fn publish(&mut self, krate: &NewCrate, tarball: &File) -> Result<Warnings> {
163 let json = serde_json::to_string(krate)?;
164 let stat = tarball.metadata()?;
171 let header = {
172 let mut w = Vec::new();
173 w.extend(
174 [
175 (json.len() >> 0) as u8,
176 (json.len() >> 8) as u8,
177 (json.len() >> 16) as u8,
178 (json.len() >> 24) as u8,
179 ].iter().cloned(),
180 );
181 w.extend(json.as_bytes().iter().cloned());
182 w.extend(
183 [
184 (stat.len() >> 0) as u8,
185 (stat.len() >> 8) as u8,
186 (stat.len() >> 16) as u8,
187 (stat.len() >> 24) as u8,
188 ].iter().cloned(),
189 );
190 w
191 };
192 let size = stat.len() as usize + header.len();
193 let mut body = Cursor::new(header).chain(tarball);
194
195 let url = format!("{}/api/v1/crates/new", self.host);
196
197 let token = match self.token.as_ref() {
198 Some(s) => s,
199 None => bail!("no upload token found, please run `cargo login`"),
200 };
201 self.handle.put(true)?;
202 self.handle.url(&url)?;
203 self.handle.in_filesize(size as u64)?;
204 let mut headers = List::new();
205 headers.append("Accept: application/json")?;
206 headers.append(&format!("Authorization: {}", token))?;
207 self.handle.http_headers(headers)?;
208
209 let body = handle(&mut self.handle, &mut |buf| body.read(buf).unwrap_or(0))?;
210
211 let response = if body.is_empty() {
212 "{}".parse()?
213 } else {
214 body.parse::<serde_json::Value>()?
215 };
216
217 let invalid_categories: Vec<String> = response
218 .get("warnings")
219 .and_then(|j| j.get("invalid_categories"))
220 .and_then(|j| j.as_array())
221 .map(|x| x.iter().flat_map(|j| j.as_str()).map(Into::into).collect())
222 .unwrap_or_else(Vec::new);
223
224 let invalid_badges: Vec<String> = response
225 .get("warnings")
226 .and_then(|j| j.get("invalid_badges"))
227 .and_then(|j| j.as_array())
228 .map(|x| x.iter().flat_map(|j| j.as_str()).map(Into::into).collect())
229 .unwrap_or_else(Vec::new);
230
231 let other: Vec<String> = response
232 .get("warnings")
233 .and_then(|j| j.get("other"))
234 .and_then(|j| j.as_array())
235 .map(|x| x.iter().flat_map(|j| j.as_str()).map(Into::into).collect())
236 .unwrap_or_else(Vec::new);
237
238 Ok(Warnings {
239 invalid_categories,
240 invalid_badges,
241 other,
242 })
243 }
244
245 pub fn search(&mut self, query: &str, limit: u32) -> Result<(Vec<Crate>, u32)> {
246 let formatted_query = percent_encode(query.as_bytes(), QUERY_ENCODE_SET);
247 let body = self.req(
248 &format!("/crates?q={}&per_page={}", formatted_query, limit),
249 None,
250 Auth::Unauthorized,
251 )?;
252
253 let crates = serde_json::from_str::<Crates>(&body)?;
254 Ok((crates.crates, crates.meta.total))
255 }
256
257 pub fn yank(&mut self, krate: &str, version: &str) -> Result<()> {
258 let body = self.delete(&format!("/crates/{}/{}/yank", krate, version), None)?;
259 assert!(serde_json::from_str::<R>(&body)?.ok);
260 Ok(())
261 }
262
263 pub fn unyank(&mut self, krate: &str, version: &str) -> Result<()> {
264 let body = self.put(&format!("/crates/{}/{}/unyank", krate, version), &[])?;
265 assert!(serde_json::from_str::<R>(&body)?.ok);
266 Ok(())
267 }
268
269 fn put(&mut self, path: &str, b: &[u8]) -> Result<String> {
270 self.handle.put(true)?;
271 self.req(path, Some(b), Auth::Authorized)
272 }
273
274 fn get(&mut self, path: &str) -> Result<String> {
275 self.handle.get(true)?;
276 self.req(path, None, Auth::Authorized)
277 }
278
279 fn delete(&mut self, path: &str, b: Option<&[u8]>) -> Result<String> {
280 self.handle.custom_request("DELETE")?;
281 self.req(path, b, Auth::Authorized)
282 }
283
284 fn req(&mut self, path: &str, body: Option<&[u8]>, authorized: Auth) -> Result<String> {
285 self.handle.url(&format!("{}/api/v1{}", self.host, path))?;
286 let mut headers = List::new();
287 headers.append("Accept: application/json")?;
288 headers.append("Content-Type: application/json")?;
289
290 if authorized == Auth::Authorized {
291 let token = match self.token.as_ref() {
292 Some(s) => s,
293 None => bail!("no upload token found, please run `cargo login`"),
294 };
295 headers.append(&format!("Authorization: {}", token))?;
296 }
297 self.handle.http_headers(headers)?;
298 match body {
299 Some(mut body) => {
300 self.handle.upload(true)?;
301 self.handle.in_filesize(body.len() as u64)?;
302 handle(&mut self.handle, &mut |buf| body.read(buf).unwrap_or(0))
303 }
304 None => handle(&mut self.handle, &mut |_| 0),
305 }
306 }
307}
308
309fn handle(handle: &mut Easy, read: &mut dyn FnMut(&mut [u8]) -> usize) -> Result<String> {
310 let mut headers = Vec::new();
311 let mut body = Vec::new();
312 {
313 let mut handle = handle.transfer();
314 handle.read_function(|buf| Ok(read(buf)))?;
315 handle.write_function(|data| {
316 body.extend_from_slice(data);
317 Ok(data.len())
318 })?;
319 handle.header_function(|data| {
320 headers.push(String::from_utf8_lossy(data).into_owned());
321 true
322 })?;
323 handle.perform()?;
324 }
325
326 match handle.response_code()? {
327 0 => {} 200 => {}
329 403 => bail!("received 403 unauthorized response code"),
330 404 => bail!("received 404 not found response code"),
331 code => bail!(
332 "failed to get a 200 OK response, got {}\n\
333 headers:\n\
334 \t{}\n\
335 body:\n\
336 {}",
337 code,
338 headers.join("\n\t"),
339 String::from_utf8_lossy(&body)
340 ),
341 }
342
343 let body = match String::from_utf8(body) {
344 Ok(body) => body,
345 Err(..) => bail!("response body was not valid utf-8"),
346 };
347 if let Ok(errors) = serde_json::from_str::<ApiErrorList>(&body) {
348 let errors = errors.errors.into_iter().map(|s| s.detail);
349 bail!("api errors: {}", errors.collect::<Vec<_>>().join(", "));
350 }
351 Ok(body)
352}