]> git.proxmox.com Git - rustc.git/blame - src/tools/cargo/crates/cargo-test-support/src/registry.rs
New upstream version 1.77.2+dfsg1
[rustc.git] / src / tools / cargo / crates / cargo-test-support / src / registry.rs
CommitLineData
0a29b90c
FG
1use crate::git::repo;
2use crate::paths;
3use crate::publish::{create_index_line, write_to_index};
4use cargo_util::paths::append;
5use cargo_util::Sha256;
6use flate2::write::GzEncoder;
7use flate2::Compression;
8use pasetors::keys::{AsymmetricPublicKey, AsymmetricSecretKey};
9use pasetors::paserk::FormatAsPaserk;
10use pasetors::token::UntrustedToken;
11use std::collections::{BTreeMap, HashMap};
12use std::fmt;
13use std::fs::{self, File};
14use std::io::{BufRead, BufReader, Read, Write};
15use std::net::{SocketAddr, TcpListener, TcpStream};
16use std::path::{Path, PathBuf};
17use std::thread::{self, JoinHandle};
18use tar::{Builder, Header};
19use time::format_description::well_known::Rfc3339;
20use time::{Duration, OffsetDateTime};
21use url::Url;
22
23/// Gets the path to the local index pretending to be crates.io. This is a Git repo
24/// initialized with a `config.json` file pointing to `dl_path` for downloads
25/// and `api_path` for uploads.
26pub fn registry_path() -> PathBuf {
27 generate_path("registry")
28}
29/// Gets the path for local web API uploads. Cargo will place the contents of a web API
30/// request here. For example, `api/v1/crates/new` is the result of publishing a crate.
31pub fn api_path() -> PathBuf {
32 generate_path("api")
33}
34/// Gets the path where crates can be downloaded using the web API endpoint. Crates
35/// should be organized as `{name}/{version}/download` to match the web API
36/// endpoint. This is rarely used and must be manually set up.
37fn dl_path() -> PathBuf {
38 generate_path("dl")
39}
40/// Gets the alternative-registry version of `registry_path`.
41fn alt_registry_path() -> PathBuf {
42 generate_path("alternative-registry")
43}
44/// Gets the alternative-registry version of `registry_url`.
45fn alt_registry_url() -> Url {
46 generate_url("alternative-registry")
47}
48/// Gets the alternative-registry version of `dl_path`.
49pub fn alt_dl_path() -> PathBuf {
50 generate_path("alternative-dl")
51}
52/// Gets the alternative-registry version of `api_path`.
53pub fn alt_api_path() -> PathBuf {
54 generate_path("alternative-api")
55}
56fn generate_path(name: &str) -> PathBuf {
57 paths::root().join(name)
58}
59fn generate_url(name: &str) -> Url {
60 Url::from_file_path(generate_path(name)).ok().unwrap()
61}
62
63#[derive(Clone)]
64pub enum Token {
65 Plaintext(String),
66 Keys(String, Option<String>),
67}
68
69impl Token {
70 /// This is a valid PASETO secret key.
71 /// This one is already publicly available as part of the text of the RFC so is safe to use for tests.
72 pub fn rfc_key() -> Token {
73 Token::Keys(
74 "k3.secret.fNYVuMvBgOlljt9TDohnaYLblghqaHoQquVZwgR6X12cBFHZLFsaU3q7X3k1Zn36"
75 .to_string(),
76 Some("sub".to_string()),
77 )
78 }
79}
80
81type RequestCallback = Box<dyn Send + Fn(&Request, &HttpServer) -> Response>;
82
83/// A builder for initializing registries.
84pub struct RegistryBuilder {
85 /// If set, configures an alternate registry with the given name.
86 alternative: Option<String>,
87 /// The authorization token for the registry.
88 token: Option<Token>,
89 /// If set, the registry requires authorization for all operations.
90 auth_required: bool,
91 /// If set, serves the index over http.
92 http_index: bool,
93 /// If set, serves the API over http.
94 http_api: bool,
95 /// If set, config.json includes 'api'
96 api: bool,
97 /// Write the token in the configuration.
98 configure_token: bool,
99 /// Write the registry in configuration.
100 configure_registry: bool,
101 /// API responders.
102 custom_responders: HashMap<String, RequestCallback>,
103 /// Handler for 404 responses.
104 not_found_handler: RequestCallback,
105 /// If nonzero, the git index update to be delayed by the given number of seconds.
106 delayed_index_update: usize,
add651ee
FG
107 /// Credential provider in configuration
108 credential_provider: Option<String>,
0a29b90c
FG
109}
110
111pub struct TestRegistry {
112 server: Option<HttpServerHandle>,
113 index_url: Url,
114 path: PathBuf,
115 api_url: Url,
116 dl_url: Url,
117 token: Token,
118}
119
120impl TestRegistry {
121 pub fn index_url(&self) -> &Url {
122 &self.index_url
123 }
124
125 pub fn api_url(&self) -> &Url {
126 &self.api_url
127 }
128
129 pub fn token(&self) -> &str {
130 match &self.token {
131 Token::Plaintext(s) => s,
132 Token::Keys(_, _) => panic!("registry was not configured with a plaintext token"),
133 }
134 }
135
136 pub fn key(&self) -> &str {
137 match &self.token {
138 Token::Plaintext(_) => panic!("registry was not configured with a secret key"),
139 Token::Keys(s, _) => s,
140 }
141 }
142
143 /// Shutdown the server thread and wait for it to stop.
144 /// `Drop` automatically stops the server, but this additionally
145 /// waits for the thread to stop.
146 pub fn join(self) {
147 if let Some(mut server) = self.server {
148 server.stop();
149 let handle = server.handle.take().unwrap();
150 handle.join().unwrap();
151 }
152 }
153}
154
155impl RegistryBuilder {
156 #[must_use]
157 pub fn new() -> RegistryBuilder {
158 let not_found = |_req: &Request, _server: &HttpServer| -> Response {
159 Response {
160 code: 404,
161 headers: vec![],
162 body: b"not found".to_vec(),
163 }
164 };
165 RegistryBuilder {
166 alternative: None,
167 token: None,
168 auth_required: false,
169 http_api: false,
170 http_index: false,
171 api: true,
172 configure_registry: true,
173 configure_token: true,
174 custom_responders: HashMap::new(),
175 not_found_handler: Box::new(not_found),
176 delayed_index_update: 0,
add651ee 177 credential_provider: None,
0a29b90c
FG
178 }
179 }
180
181 /// Adds a custom HTTP response for a specific url
182 #[must_use]
183 pub fn add_responder<R: 'static + Send + Fn(&Request, &HttpServer) -> Response>(
184 mut self,
185 url: impl Into<String>,
186 responder: R,
187 ) -> Self {
188 self.custom_responders
189 .insert(url.into(), Box::new(responder));
190 self
191 }
192
193 #[must_use]
194 pub fn not_found_handler<R: 'static + Send + Fn(&Request, &HttpServer) -> Response>(
195 mut self,
196 responder: R,
197 ) -> Self {
198 self.not_found_handler = Box::new(responder);
199 self
200 }
201
202 /// Configures the git index update to be delayed by the given number of seconds.
203 #[must_use]
204 pub fn delayed_index_update(mut self, delay: usize) -> Self {
205 self.delayed_index_update = delay;
206 self
207 }
208
209 /// Sets whether or not to initialize as an alternative registry.
210 #[must_use]
211 pub fn alternative_named(mut self, alt: &str) -> Self {
212 self.alternative = Some(alt.to_string());
213 self
214 }
215
216 /// Sets whether or not to initialize as an alternative registry.
217 #[must_use]
218 pub fn alternative(self) -> Self {
219 self.alternative_named("alternative")
220 }
221
222 /// Prevents placing a token in the configuration
223 #[must_use]
224 pub fn no_configure_token(mut self) -> Self {
225 self.configure_token = false;
226 self
227 }
228
229 /// Prevents adding the registry to the configuration.
230 #[must_use]
231 pub fn no_configure_registry(mut self) -> Self {
232 self.configure_registry = false;
233 self
234 }
235
236 /// Sets the token value
237 #[must_use]
238 pub fn token(mut self, token: Token) -> Self {
239 self.token = Some(token);
240 self
241 }
242
243 /// Sets this registry to require the authentication token for
244 /// all operations.
245 #[must_use]
246 pub fn auth_required(mut self) -> Self {
247 self.auth_required = true;
248 self
249 }
250
251 /// Operate the index over http
252 #[must_use]
253 pub fn http_index(mut self) -> Self {
254 self.http_index = true;
255 self
256 }
257
258 /// Operate the api over http
259 #[must_use]
260 pub fn http_api(mut self) -> Self {
261 self.http_api = true;
262 self
263 }
264
265 /// The registry has no api.
266 #[must_use]
267 pub fn no_api(mut self) -> Self {
268 self.api = false;
269 self
270 }
271
add651ee
FG
272 /// The credential provider to configure for this registry.
273 #[must_use]
274 pub fn credential_provider(mut self, provider: &[&str]) -> Self {
275 self.credential_provider = Some(format!("['{}']", provider.join("','")));
276 self
277 }
278
0a29b90c
FG
279 /// Initializes the registry.
280 #[must_use]
281 pub fn build(self) -> TestRegistry {
282 let config_path = paths::home().join(".cargo/config");
283 t!(fs::create_dir_all(config_path.parent().unwrap()));
284 let prefix = if let Some(alternative) = &self.alternative {
285 format!("{alternative}-")
286 } else {
287 String::new()
288 };
289 let registry_path = generate_path(&format!("{prefix}registry"));
290 let index_url = generate_url(&format!("{prefix}registry"));
291 let api_url = generate_url(&format!("{prefix}api"));
292 let dl_url = generate_url(&format!("{prefix}dl"));
293 let dl_path = generate_path(&format!("{prefix}dl"));
294 let api_path = generate_path(&format!("{prefix}api"));
295 let token = self
296 .token
297 .unwrap_or_else(|| Token::Plaintext(format!("{prefix}sekrit")));
298
299 let (server, index_url, api_url, dl_url) = if !self.http_index && !self.http_api {
300 // No need to start the HTTP server.
301 (None, index_url, api_url, dl_url)
302 } else {
303 let server = HttpServer::new(
304 registry_path.clone(),
305 dl_path,
306 api_path.clone(),
307 token.clone(),
308 self.auth_required,
309 self.custom_responders,
310 self.not_found_handler,
311 self.delayed_index_update,
312 );
313 let index_url = if self.http_index {
314 server.index_url()
315 } else {
316 index_url
317 };
318 let api_url = if self.http_api {
319 server.api_url()
320 } else {
321 api_url
322 };
323 let dl_url = server.dl_url();
324 (Some(server), index_url, api_url, dl_url)
325 };
326
327 let registry = TestRegistry {
328 api_url,
329 index_url,
330 server,
331 dl_url,
332 path: registry_path,
333 token,
334 };
335
336 if self.configure_registry {
337 if let Some(alternative) = &self.alternative {
338 append(
339 &config_path,
340 format!(
341 "
342 [registries.{alternative}]
343 index = '{}'",
344 registry.index_url
345 )
346 .as_bytes(),
347 )
348 .unwrap();
add651ee
FG
349 if let Some(p) = &self.credential_provider {
350 append(
351 &config_path,
352 &format!(
353 "
354 credential-provider = {p}
355 "
356 )
357 .as_bytes(),
358 )
359 .unwrap()
360 }
0a29b90c
FG
361 } else {
362 append(
363 &config_path,
364 format!(
365 "
366 [source.crates-io]
367 replace-with = 'dummy-registry'
368
369 [registries.dummy-registry]
370 index = '{}'",
371 registry.index_url
372 )
373 .as_bytes(),
374 )
375 .unwrap();
add651ee
FG
376
377 if let Some(p) = &self.credential_provider {
378 append(
379 &config_path,
380 &format!(
381 "
382 [registry]
383 credential-provider = {p}
384 "
385 )
386 .as_bytes(),
387 )
388 .unwrap()
389 }
0a29b90c
FG
390 }
391 }
392
393 if self.configure_token {
394 let credentials = paths::home().join(".cargo/credentials.toml");
395 match &registry.token {
396 Token::Plaintext(token) => {
397 if let Some(alternative) = &self.alternative {
398 append(
399 &credentials,
400 format!(
401 r#"
402 [registries.{alternative}]
403 token = "{token}"
404 "#
405 )
406 .as_bytes(),
407 )
408 .unwrap();
409 } else {
410 append(
411 &credentials,
412 format!(
413 r#"
414 [registry]
415 token = "{token}"
416 "#
417 )
418 .as_bytes(),
419 )
420 .unwrap();
421 }
422 }
423 Token::Keys(key, subject) => {
424 let mut out = if let Some(alternative) = &self.alternative {
425 format!("\n[registries.{alternative}]\n")
426 } else {
427 format!("\n[registry]\n")
428 };
429 out += &format!("secret-key = \"{key}\"\n");
430 if let Some(subject) = subject {
431 out += &format!("secret-key-subject = \"{subject}\"\n");
432 }
433
434 append(&credentials, out.as_bytes()).unwrap();
435 }
436 }
437 }
438
439 let auth = if self.auth_required {
440 r#","auth-required":true"#
441 } else {
442 ""
443 };
444 let api = if self.api {
445 format!(r#","api":"{}""#, registry.api_url)
446 } else {
447 String::new()
448 };
449 // Initialize a new registry.
450 repo(&registry.path)
451 .file(
452 "config.json",
453 &format!(r#"{{"dl":"{}"{api}{auth}}}"#, registry.dl_url),
454 )
455 .build();
456 fs::create_dir_all(api_path.join("api/v1/crates")).unwrap();
457
458 registry
459 }
460}
461
462/// A builder for creating a new package in a registry.
463///
464/// This uses "source replacement" using an automatically generated
465/// `.cargo/config` file to ensure that dependencies will use these packages
466/// instead of contacting crates.io. See `source-replacement.md` for more
467/// details on how source replacement works.
468///
469/// Call `publish` to finalize and create the package.
470///
471/// If no files are specified, an empty `lib.rs` file is automatically created.
472///
473/// The `Cargo.toml` file is automatically generated based on the methods
474/// called on `Package` (for example, calling `dep()` will add to the
475/// `[dependencies]` automatically). You may also specify a `Cargo.toml` file
476/// to override the generated one.
477///
478/// This supports different registry types:
479/// - Regular source replacement that replaces `crates.io` (the default).
480/// - A "local registry" which is a subset for vendoring (see
481/// `Package::local`).
482/// - An "alternative registry" which requires specifying the registry name
483/// (see `Package::alternative`).
484///
485/// This does not support "directory sources". See `directory.rs` for
486/// `VendorPackage` which implements directory sources.
487///
488/// # Example
49aad941
FG
489/// ```no_run
490/// use cargo_test_support::registry::Package;
491/// use cargo_test_support::project;
492///
0a29b90c
FG
493/// // Publish package "a" depending on "b".
494/// Package::new("a", "1.0.0")
495/// .dep("b", "1.0.0")
496/// .file("src/lib.rs", r#"
497/// extern crate b;
498/// pub fn f() -> i32 { b::f() * 2 }
499/// "#)
500/// .publish();
501///
502/// // Publish package "b".
503/// Package::new("b", "1.0.0")
504/// .file("src/lib.rs", r#"
505/// pub fn f() -> i32 { 12 }
506/// "#)
507/// .publish();
508///
509/// // Create a project that uses package "a".
510/// let p = project()
511/// .file("Cargo.toml", r#"
512/// [package]
513/// name = "foo"
514/// version = "0.0.1"
515///
516/// [dependencies]
517/// a = "1.0"
518/// "#)
519/// .file("src/main.rs", r#"
520/// extern crate a;
521/// fn main() { println!("{}", a::f()); }
522/// "#)
523/// .build();
524///
525/// p.cargo("run").with_stdout("24").run();
526/// ```
527#[must_use]
528pub struct Package {
529 name: String,
530 vers: String,
531 deps: Vec<Dependency>,
532 files: Vec<PackageFile>,
533 yanked: bool,
534 features: FeatureMap,
535 local: bool,
536 alternative: bool,
537 invalid_json: bool,
538 proc_macro: bool,
539 links: Option<String>,
540 rust_version: Option<String>,
541 cargo_features: Vec<String>,
542 v: Option<u32>,
543}
544
545pub(crate) type FeatureMap = BTreeMap<String, Vec<String>>;
546
547#[derive(Clone)]
548pub struct Dependency {
549 name: String,
550 vers: String,
551 kind: String,
781aab86
FG
552 artifact: Option<String>,
553 bindep_target: Option<String>,
554 lib: bool,
0a29b90c
FG
555 target: Option<String>,
556 features: Vec<String>,
557 registry: Option<String>,
558 package: Option<String>,
559 optional: bool,
4b012472 560 default_features: bool,
c0240ec0 561 public: bool,
0a29b90c
FG
562}
563
564/// Entry with data that corresponds to [`tar::EntryType`].
565#[non_exhaustive]
566enum EntryData {
567 Regular(String),
568 Symlink(PathBuf),
569}
570
571/// A file to be created in a package.
572struct PackageFile {
573 path: String,
574 contents: EntryData,
575 /// The Unix mode for the file. Note that when extracted on Windows, this
576 /// is mostly ignored since it doesn't have the same style of permissions.
577 mode: u32,
578 /// If `true`, the file is created in the root of the tarfile, used for
579 /// testing invalid packages.
580 extra: bool,
581}
582
583const DEFAULT_MODE: u32 = 0o644;
584
585/// Initializes the on-disk registry and sets up the config so that crates.io
586/// is replaced with the one on disk.
587pub fn init() -> TestRegistry {
588 RegistryBuilder::new().build()
589}
590
591/// Variant of `init` that initializes the "alternative" registry and crates.io
592/// replacement.
593pub fn alt_init() -> TestRegistry {
594 init();
595 RegistryBuilder::new().alternative().build()
596}
597
598pub struct HttpServerHandle {
599 addr: SocketAddr,
600 handle: Option<JoinHandle<()>>,
601}
602
603impl HttpServerHandle {
604 pub fn index_url(&self) -> Url {
605 Url::parse(&format!("sparse+http://{}/index/", self.addr.to_string())).unwrap()
606 }
607
608 pub fn api_url(&self) -> Url {
609 Url::parse(&format!("http://{}/", self.addr.to_string())).unwrap()
610 }
611
612 pub fn dl_url(&self) -> Url {
613 Url::parse(&format!("http://{}/dl", self.addr.to_string())).unwrap()
614 }
615
616 fn stop(&self) {
617 if let Ok(mut stream) = TcpStream::connect(self.addr) {
618 // shutdown the server
619 let _ = stream.write_all(b"stop");
620 let _ = stream.flush();
621 }
622 }
623}
624
625impl Drop for HttpServerHandle {
626 fn drop(&mut self) {
627 self.stop();
628 }
629}
630
631/// Request to the test http server
632#[derive(Clone)]
633pub struct Request {
634 pub url: Url,
635 pub method: String,
636 pub body: Option<Vec<u8>>,
637 pub authorization: Option<String>,
638 pub if_modified_since: Option<String>,
639 pub if_none_match: Option<String>,
640}
641
642impl fmt::Debug for Request {
643 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
644 // body is not included as it can produce long debug outputs
645 f.debug_struct("Request")
646 .field("url", &self.url)
647 .field("method", &self.method)
648 .field("authorization", &self.authorization)
649 .field("if_modified_since", &self.if_modified_since)
650 .field("if_none_match", &self.if_none_match)
651 .finish()
652 }
653}
654
655/// Response from the test http server
656pub struct Response {
657 pub code: u32,
658 pub headers: Vec<String>,
659 pub body: Vec<u8>,
660}
661
662pub struct HttpServer {
663 listener: TcpListener,
664 registry_path: PathBuf,
665 dl_path: PathBuf,
666 api_path: PathBuf,
667 addr: SocketAddr,
668 token: Token,
669 auth_required: bool,
670 custom_responders: HashMap<String, RequestCallback>,
671 not_found_handler: RequestCallback,
672 delayed_index_update: usize,
673}
674
675/// A helper struct that collects the arguments for [`HttpServer::check_authorized`].
676/// Based on looking at the request, these are the fields that the authentication header should attest to.
677struct Mutation<'a> {
678 mutation: &'a str,
679 name: Option<&'a str>,
680 vers: Option<&'a str>,
681 cksum: Option<&'a str>,
682}
683
684impl HttpServer {
685 pub fn new(
686 registry_path: PathBuf,
687 dl_path: PathBuf,
688 api_path: PathBuf,
689 token: Token,
690 auth_required: bool,
691 custom_responders: HashMap<String, RequestCallback>,
692 not_found_handler: RequestCallback,
693 delayed_index_update: usize,
694 ) -> HttpServerHandle {
695 let listener = TcpListener::bind("127.0.0.1:0").unwrap();
696 let addr = listener.local_addr().unwrap();
697 let server = HttpServer {
698 listener,
699 registry_path,
700 dl_path,
701 api_path,
702 addr,
703 token,
704 auth_required,
705 custom_responders,
706 not_found_handler,
707 delayed_index_update,
708 };
709 let handle = Some(thread::spawn(move || server.start()));
710 HttpServerHandle { addr, handle }
711 }
712
713 fn start(&self) {
714 let mut line = String::new();
715 'server: loop {
716 let (socket, _) = self.listener.accept().unwrap();
717 let mut buf = BufReader::new(socket);
718 line.clear();
719 if buf.read_line(&mut line).unwrap() == 0 {
720 // Connection terminated.
721 continue;
722 }
723 // Read the "GET path HTTP/1.1" line.
724 let mut parts = line.split_ascii_whitespace();
725 let method = parts.next().unwrap().to_ascii_lowercase();
726 if method == "stop" {
727 // Shutdown the server.
728 return;
729 }
730 let addr = self.listener.local_addr().unwrap();
731 let url = format!(
732 "http://{}/{}",
733 addr,
734 parts.next().unwrap().trim_start_matches('/')
735 );
736 let url = Url::parse(&url).unwrap();
737
738 // Grab headers we care about.
739 let mut if_modified_since = None;
740 let mut if_none_match = None;
741 let mut authorization = None;
742 let mut content_len = None;
743 loop {
744 line.clear();
745 if buf.read_line(&mut line).unwrap() == 0 {
746 continue 'server;
747 }
748 if line == "\r\n" {
749 // End of headers.
750 line.clear();
751 break;
752 }
753 let (name, value) = line.split_once(':').unwrap();
754 let name = name.trim().to_ascii_lowercase();
755 let value = value.trim().to_string();
756 match name.as_str() {
757 "if-modified-since" => if_modified_since = Some(value),
758 "if-none-match" => if_none_match = Some(value),
759 "authorization" => authorization = Some(value),
760 "content-length" => content_len = Some(value),
761 _ => {}
762 }
763 }
764
765 let mut body = None;
766 if let Some(con_len) = content_len {
767 let len = con_len.parse::<u64>().unwrap();
768 let mut content = vec![0u8; len as usize];
769 buf.read_exact(&mut content).unwrap();
770 body = Some(content)
771 }
772
773 let req = Request {
774 authorization,
775 if_modified_since,
776 if_none_match,
777 method,
778 url,
779 body,
780 };
781 println!("req: {:#?}", req);
782 let response = self.route(&req);
783 let buf = buf.get_mut();
784 write!(buf, "HTTP/1.1 {}\r\n", response.code).unwrap();
785 write!(buf, "Content-Length: {}\r\n", response.body.len()).unwrap();
781aab86 786 write!(buf, "Connection: close\r\n").unwrap();
0a29b90c
FG
787 for header in response.headers {
788 write!(buf, "{}\r\n", header).unwrap();
789 }
790 write!(buf, "\r\n").unwrap();
791 buf.write_all(&response.body).unwrap();
792 buf.flush().unwrap();
793 }
794 }
795
781aab86 796 fn check_authorized(&self, req: &Request, mutation: Option<Mutation<'_>>) -> bool {
0a29b90c
FG
797 let (private_key, private_key_subject) = if mutation.is_some() || self.auth_required {
798 match &self.token {
799 Token::Plaintext(token) => return Some(token) == req.authorization.as_ref(),
800 Token::Keys(private_key, private_key_subject) => {
801 (private_key.as_str(), private_key_subject)
802 }
803 }
804 } else {
805 assert!(req.authorization.is_none(), "unexpected token");
806 return true;
807 };
808
809 macro_rules! t {
810 ($e:expr) => {
811 match $e {
812 Some(e) => e,
813 None => return false,
814 }
815 };
816 }
817
818 let secret: AsymmetricSecretKey<pasetors::version3::V3> = private_key.try_into().unwrap();
819 let public: AsymmetricPublicKey<pasetors::version3::V3> = (&secret).try_into().unwrap();
820 let pub_key_id: pasetors::paserk::Id = (&public).into();
821 let mut paserk_pub_key_id = String::new();
822 FormatAsPaserk::fmt(&pub_key_id, &mut paserk_pub_key_id).unwrap();
823 // https://github.com/rust-lang/rfcs/blob/master/text/3231-cargo-asymmetric-tokens.md#how-the-registry-server-will-validate-an-asymmetric-token
824
825 // - The PASETO is in v3.public format.
826 let authorization = t!(&req.authorization);
827 let untrusted_token = t!(
828 UntrustedToken::<pasetors::Public, pasetors::version3::V3>::try_from(authorization)
829 .ok()
830 );
831
832 // - The PASETO validates using the public key it looked up based on the key ID.
833 #[derive(serde::Deserialize, Debug)]
834 struct Footer<'a> {
835 url: &'a str,
836 kip: &'a str,
837 }
781aab86
FG
838 let footer: Footer<'_> =
839 t!(serde_json::from_slice(untrusted_token.untrusted_footer()).ok());
0a29b90c
FG
840 if footer.kip != paserk_pub_key_id {
841 return false;
842 }
843 let trusted_token =
844 t!(
845 pasetors::version3::PublicToken::verify(&public, &untrusted_token, None, None,)
846 .ok()
847 );
848
849 // - The URL matches the registry base URL
850 if footer.url != "https://github.com/rust-lang/crates.io-index"
851 && footer.url != &format!("sparse+http://{}/index/", self.addr.to_string())
852 {
0a29b90c
FG
853 return false;
854 }
855
856 // - The PASETO is still within its valid time period.
857 #[derive(serde::Deserialize)]
858 struct Message<'a> {
859 iat: &'a str,
860 sub: Option<&'a str>,
861 mutation: Option<&'a str>,
862 name: Option<&'a str>,
863 vers: Option<&'a str>,
864 cksum: Option<&'a str>,
865 _challenge: Option<&'a str>, // todo: PASETO with challenges
866 v: Option<u8>,
867 }
781aab86 868 let message: Message<'_> = t!(serde_json::from_str(trusted_token.payload()).ok());
0a29b90c
FG
869 let token_time = t!(OffsetDateTime::parse(message.iat, &Rfc3339).ok());
870 let now = OffsetDateTime::now_utc();
871 if (now - token_time) > Duration::MINUTE {
872 return false;
873 }
874 if private_key_subject.as_deref() != message.sub {
0a29b90c
FG
875 return false;
876 }
877 // - If the claim v is set, that it has the value of 1.
878 if let Some(v) = message.v {
879 if v != 1 {
0a29b90c
FG
880 return false;
881 }
882 }
883 // - If the server issues challenges, that the challenge has not yet been answered.
884 // todo: PASETO with challenges
885 // - If the operation is a mutation:
886 if let Some(mutation) = mutation {
887 // - That the operation matches the mutation field and is one of publish, yank, or unyank.
888 if message.mutation != Some(mutation.mutation) {
0a29b90c
FG
889 return false;
890 }
891 // - That the package, and version match the request.
892 if message.name != mutation.name {
0a29b90c
FG
893 return false;
894 }
895 if message.vers != mutation.vers {
0a29b90c
FG
896 return false;
897 }
898 // - If the mutation is publish, that the version has not already been published, and that the hash matches the request.
899 if mutation.mutation == "publish" {
900 if message.cksum != mutation.cksum {
0a29b90c
FG
901 return false;
902 }
903 }
904 } else {
905 // - If the operation is a read, that the mutation field is not set.
906 if message.mutation.is_some()
907 || message.name.is_some()
908 || message.vers.is_some()
909 || message.cksum.is_some()
910 {
911 return false;
912 }
913 }
914 true
915 }
916
917 /// Route the request
918 fn route(&self, req: &Request) -> Response {
919 // Check for custom responder
920 if let Some(responder) = self.custom_responders.get(req.url.path()) {
921 return responder(&req, self);
922 }
923 let path: Vec<_> = req.url.path()[1..].split('/').collect();
924 match (req.method.as_str(), path.as_slice()) {
925 ("get", ["index", ..]) => {
926 if !self.check_authorized(req, None) {
927 self.unauthorized(req)
928 } else {
929 self.index(&req)
930 }
931 }
932 ("get", ["dl", ..]) => {
933 if !self.check_authorized(req, None) {
934 self.unauthorized(req)
935 } else {
936 self.dl(&req)
937 }
938 }
939 // publish
940 ("put", ["api", "v1", "crates", "new"]) => self.check_authorized_publish(req),
941 // The remainder of the operators in the test framework do nothing other than responding 'ok'.
942 //
943 // Note: We don't need to support anything real here because there are no tests that
944 // currently require anything other than publishing via the http api.
945
946 // yank / unyank
947 ("delete" | "put", ["api", "v1", "crates", crate_name, version, mutation]) => {
948 if !self.check_authorized(
949 req,
950 Some(Mutation {
951 mutation,
952 name: Some(crate_name),
953 vers: Some(version),
954 cksum: None,
955 }),
956 ) {
957 self.unauthorized(req)
958 } else {
959 self.ok(&req)
960 }
961 }
962 // owners
963 ("get" | "put" | "delete", ["api", "v1", "crates", crate_name, "owners"]) => {
964 if !self.check_authorized(
965 req,
966 Some(Mutation {
967 mutation: "owners",
968 name: Some(crate_name),
969 vers: None,
970 cksum: None,
971 }),
972 ) {
973 self.unauthorized(req)
974 } else {
975 self.ok(&req)
976 }
977 }
978 _ => self.not_found(&req),
979 }
980 }
981
982 /// Unauthorized response
983 pub fn unauthorized(&self, _req: &Request) -> Response {
984 Response {
985 code: 401,
986 headers: vec![
987 r#"WWW-Authenticate: Cargo login_url="https://test-registry-login/me""#.to_string(),
988 ],
989 body: b"Unauthorized message from server.".to_vec(),
990 }
991 }
992
993 /// Not found response
994 pub fn not_found(&self, req: &Request) -> Response {
995 (self.not_found_handler)(req, self)
996 }
997
998 /// Respond OK without doing anything
999 pub fn ok(&self, _req: &Request) -> Response {
1000 Response {
1001 code: 200,
1002 headers: vec![],
1003 body: br#"{"ok": true, "msg": "completed!"}"#.to_vec(),
1004 }
1005 }
1006
1007 /// Return an internal server error (HTTP 500)
1008 pub fn internal_server_error(&self, _req: &Request) -> Response {
1009 Response {
1010 code: 500,
1011 headers: vec![],
1012 body: br#"internal server error"#.to_vec(),
1013 }
1014 }
1015
1016 /// Serve the download endpoint
1017 pub fn dl(&self, req: &Request) -> Response {
1018 let file = self
1019 .dl_path
1020 .join(req.url.path().strip_prefix("/dl/").unwrap());
1021 println!("{}", file.display());
1022 if !file.exists() {
1023 return self.not_found(req);
1024 }
1025 return Response {
1026 body: fs::read(&file).unwrap(),
1027 code: 200,
1028 headers: vec![],
1029 };
1030 }
1031
1032 /// Serve the registry index
1033 pub fn index(&self, req: &Request) -> Response {
1034 let file = self
1035 .registry_path
1036 .join(req.url.path().strip_prefix("/index/").unwrap());
1037 if !file.exists() {
1038 return self.not_found(req);
1039 } else {
1040 // Now grab info about the file.
1041 let data = fs::read(&file).unwrap();
1042 let etag = Sha256::new().update(&data).finish_hex();
1043 let last_modified = format!("{:?}", file.metadata().unwrap().modified().unwrap());
1044
1045 // Start to construct our response:
1046 let mut any_match = false;
1047 let mut all_match = true;
1048 if let Some(expected) = &req.if_none_match {
1049 if &etag != expected {
1050 all_match = false;
1051 } else {
1052 any_match = true;
1053 }
1054 }
1055 if let Some(expected) = &req.if_modified_since {
1056 // NOTE: Equality comparison is good enough for tests.
1057 if &last_modified != expected {
1058 all_match = false;
1059 } else {
1060 any_match = true;
1061 }
1062 }
1063
1064 if any_match && all_match {
1065 return Response {
1066 body: Vec::new(),
1067 code: 304,
1068 headers: vec![],
1069 };
1070 } else {
1071 return Response {
1072 body: data,
1073 code: 200,
1074 headers: vec![
1075 format!("ETag: \"{}\"", etag),
1076 format!("Last-Modified: {}", last_modified),
1077 ],
1078 };
1079 }
1080 }
1081 }
1082
1083 pub fn check_authorized_publish(&self, req: &Request) -> Response {
1084 if let Some(body) = &req.body {
1085 // Mimic the publish behavior for local registries by writing out the request
1086 // so tests can verify publishes made to either registry type.
1087 let path = self.api_path.join("api/v1/crates/new");
1088 t!(fs::create_dir_all(path.parent().unwrap()));
1089 t!(fs::write(&path, body));
1090
1091 // Get the metadata of the package
1092 let (len, remaining) = body.split_at(4);
1093 let json_len = u32::from_le_bytes(len.try_into().unwrap());
1094 let (json, remaining) = remaining.split_at(json_len as usize);
1095 let new_crate = serde_json::from_slice::<crates_io::NewCrate>(json).unwrap();
1096 // Get the `.crate` file
1097 let (len, remaining) = remaining.split_at(4);
1098 let file_len = u32::from_le_bytes(len.try_into().unwrap());
1099 let (file, _remaining) = remaining.split_at(file_len as usize);
1100 let file_cksum = cksum(&file);
1101
1102 if !self.check_authorized(
1103 req,
1104 Some(Mutation {
1105 mutation: "publish",
1106 name: Some(&new_crate.name),
1107 vers: Some(&new_crate.vers),
1108 cksum: Some(&file_cksum),
1109 }),
1110 ) {
1111 return self.unauthorized(req);
1112 }
1113
1114 let dst = self
1115 .dl_path
1116 .join(&new_crate.name)
1117 .join(&new_crate.vers)
1118 .join("download");
1119
1120 if self.delayed_index_update == 0 {
1121 save_new_crate(dst, new_crate, file, file_cksum, &self.registry_path);
1122 } else {
1123 let delayed_index_update = self.delayed_index_update;
1124 let registry_path = self.registry_path.clone();
1125 let file = Vec::from(file);
1126 thread::spawn(move || {
1127 thread::sleep(std::time::Duration::new(delayed_index_update as u64, 0));
1128 save_new_crate(dst, new_crate, &file, file_cksum, &registry_path);
1129 });
1130 }
1131
1132 self.ok(&req)
1133 } else {
1134 Response {
1135 code: 400,
1136 headers: vec![],
1137 body: b"The request was missing a body".to_vec(),
1138 }
1139 }
1140 }
1141}
1142
1143fn save_new_crate(
1144 dst: PathBuf,
1145 new_crate: crates_io::NewCrate,
1146 file: &[u8],
1147 file_cksum: String,
1148 registry_path: &Path,
1149) {
1150 // Write the `.crate`
1151 t!(fs::create_dir_all(dst.parent().unwrap()));
1152 t!(fs::write(&dst, file));
1153
1154 let deps = new_crate
1155 .deps
1156 .iter()
1157 .map(|dep| {
1158 let (name, package) = match &dep.explicit_name_in_toml {
1159 Some(explicit) => (explicit.to_string(), Some(dep.name.to_string())),
1160 None => (dep.name.to_string(), None),
1161 };
1162 serde_json::json!({
1163 "name": name,
1164 "req": dep.version_req,
1165 "features": dep.features,
4b012472 1166 "default_features": dep.default_features,
0a29b90c
FG
1167 "target": dep.target,
1168 "optional": dep.optional,
1169 "kind": dep.kind,
1170 "registry": dep.registry,
1171 "package": package,
4b012472
FG
1172 "artifact": dep.artifact,
1173 "bindep_target": dep.bindep_target,
1174 "lib": dep.lib,
0a29b90c
FG
1175 })
1176 })
1177 .collect::<Vec<_>>();
1178
1179 let line = create_index_line(
1180 serde_json::json!(new_crate.name),
1181 &new_crate.vers,
1182 deps,
1183 &file_cksum,
1184 new_crate.features,
1185 false,
1186 new_crate.links,
4b012472 1187 new_crate.rust_version.as_deref(),
49aad941 1188 None,
0a29b90c
FG
1189 );
1190
1191 write_to_index(registry_path, &new_crate.name, line, false);
1192}
1193
1194impl Package {
1195 /// Creates a new package builder.
1196 /// Call `publish()` to finalize and build the package.
1197 pub fn new(name: &str, vers: &str) -> Package {
1198 let config = paths::home().join(".cargo/config");
1199 if !config.exists() {
1200 init();
1201 }
1202 Package {
1203 name: name.to_string(),
1204 vers: vers.to_string(),
1205 deps: Vec::new(),
1206 files: Vec::new(),
1207 yanked: false,
1208 features: BTreeMap::new(),
1209 local: false,
1210 alternative: false,
1211 invalid_json: false,
1212 proc_macro: false,
1213 links: None,
1214 rust_version: None,
1215 cargo_features: Vec::new(),
1216 v: None,
1217 }
1218 }
1219
1220 /// Call with `true` to publish in a "local registry".
1221 ///
1222 /// See `source-replacement.html#local-registry-sources` for more details
1223 /// on local registries. See `local_registry.rs` for the tests that use
1224 /// this.
1225 pub fn local(&mut self, local: bool) -> &mut Package {
1226 self.local = local;
1227 self
1228 }
1229
1230 /// Call with `true` to publish in an "alternative registry".
1231 ///
1232 /// The name of the alternative registry is called "alternative".
1233 ///
1234 /// See `src/doc/src/reference/registries.md` for more details on
1235 /// alternative registries. See `alt_registry.rs` for the tests that use
1236 /// this.
1237 pub fn alternative(&mut self, alternative: bool) -> &mut Package {
1238 self.alternative = alternative;
1239 self
1240 }
1241
1242 /// Adds a file to the package.
1243 pub fn file(&mut self, name: &str, contents: &str) -> &mut Package {
1244 self.file_with_mode(name, DEFAULT_MODE, contents)
1245 }
1246
1247 /// Adds a file with a specific Unix mode.
1248 pub fn file_with_mode(&mut self, path: &str, mode: u32, contents: &str) -> &mut Package {
1249 self.files.push(PackageFile {
1250 path: path.to_string(),
1251 contents: EntryData::Regular(contents.into()),
1252 mode,
1253 extra: false,
1254 });
1255 self
1256 }
1257
1258 /// Adds a symlink to a path to the package.
1259 pub fn symlink(&mut self, dst: &str, src: &str) -> &mut Package {
1260 self.files.push(PackageFile {
1261 path: dst.to_string(),
1262 contents: EntryData::Symlink(src.into()),
1263 mode: DEFAULT_MODE,
1264 extra: false,
1265 });
1266 self
1267 }
1268
1269 /// Adds an "extra" file that is not rooted within the package.
1270 ///
1271 /// Normal files are automatically placed within a directory named
1272 /// `$PACKAGE-$VERSION`. This allows you to override that behavior,
1273 /// typically for testing invalid behavior.
1274 pub fn extra_file(&mut self, path: &str, contents: &str) -> &mut Package {
1275 self.files.push(PackageFile {
1276 path: path.to_string(),
1277 contents: EntryData::Regular(contents.to_string()),
1278 mode: DEFAULT_MODE,
1279 extra: true,
1280 });
1281 self
1282 }
1283
1284 /// Adds a normal dependency. Example:
49aad941 1285 /// ```toml
0a29b90c
FG
1286 /// [dependencies]
1287 /// foo = {version = "1.0"}
1288 /// ```
1289 pub fn dep(&mut self, name: &str, vers: &str) -> &mut Package {
1290 self.add_dep(&Dependency::new(name, vers))
1291 }
1292
1293 /// Adds a dependency with the given feature. Example:
49aad941 1294 /// ```toml
0a29b90c
FG
1295 /// [dependencies]
1296 /// foo = {version = "1.0", "features": ["feat1", "feat2"]}
1297 /// ```
1298 pub fn feature_dep(&mut self, name: &str, vers: &str, features: &[&str]) -> &mut Package {
1299 self.add_dep(Dependency::new(name, vers).enable_features(features))
1300 }
1301
1302 /// Adds a platform-specific dependency. Example:
1303 /// ```toml
1304 /// [target.'cfg(windows)'.dependencies]
1305 /// foo = {version = "1.0"}
1306 /// ```
1307 pub fn target_dep(&mut self, name: &str, vers: &str, target: &str) -> &mut Package {
1308 self.add_dep(Dependency::new(name, vers).target(target))
1309 }
1310
1311 /// Adds a dependency to the alternative registry.
1312 pub fn registry_dep(&mut self, name: &str, vers: &str) -> &mut Package {
1313 self.add_dep(Dependency::new(name, vers).registry("alternative"))
1314 }
1315
1316 /// Adds a dev-dependency. Example:
49aad941 1317 /// ```toml
0a29b90c
FG
1318 /// [dev-dependencies]
1319 /// foo = {version = "1.0"}
1320 /// ```
1321 pub fn dev_dep(&mut self, name: &str, vers: &str) -> &mut Package {
1322 self.add_dep(Dependency::new(name, vers).dev())
1323 }
1324
1325 /// Adds a build-dependency. Example:
49aad941 1326 /// ```toml
0a29b90c
FG
1327 /// [build-dependencies]
1328 /// foo = {version = "1.0"}
1329 /// ```
1330 pub fn build_dep(&mut self, name: &str, vers: &str) -> &mut Package {
1331 self.add_dep(Dependency::new(name, vers).build())
1332 }
1333
1334 pub fn add_dep(&mut self, dep: &Dependency) -> &mut Package {
1335 self.deps.push(dep.clone());
1336 self
1337 }
1338
1339 /// Specifies whether or not the package is "yanked".
1340 pub fn yanked(&mut self, yanked: bool) -> &mut Package {
1341 self.yanked = yanked;
1342 self
1343 }
1344
1345 /// Specifies whether or not this is a proc macro.
1346 pub fn proc_macro(&mut self, proc_macro: bool) -> &mut Package {
1347 self.proc_macro = proc_macro;
1348 self
1349 }
1350
1351 /// Adds an entry in the `[features]` section.
1352 pub fn feature(&mut self, name: &str, deps: &[&str]) -> &mut Package {
1353 let deps = deps.iter().map(|s| s.to_string()).collect();
1354 self.features.insert(name.to_string(), deps);
1355 self
1356 }
1357
1358 /// Specify a minimal Rust version.
1359 pub fn rust_version(&mut self, rust_version: &str) -> &mut Package {
1360 self.rust_version = Some(rust_version.into());
1361 self
1362 }
1363
1364 /// Causes the JSON line emitted in the index to be invalid, presumably
1365 /// causing Cargo to skip over this version.
1366 pub fn invalid_json(&mut self, invalid: bool) -> &mut Package {
1367 self.invalid_json = invalid;
1368 self
1369 }
1370
1371 pub fn links(&mut self, links: &str) -> &mut Package {
1372 self.links = Some(links.to_string());
1373 self
1374 }
1375
1376 pub fn cargo_feature(&mut self, feature: &str) -> &mut Package {
1377 self.cargo_features.push(feature.to_owned());
1378 self
1379 }
1380
1381 /// Sets the index schema version for this package.
1382 ///
fe692bf9 1383 /// See `cargo::sources::registry::IndexPackage` for more information.
0a29b90c
FG
1384 pub fn schema_version(&mut self, version: u32) -> &mut Package {
1385 self.v = Some(version);
1386 self
1387 }
1388
1389 /// Creates the package and place it in the registry.
1390 ///
1391 /// This does not actually use Cargo's publishing system, but instead
1392 /// manually creates the entry in the registry on the filesystem.
1393 ///
1394 /// Returns the checksum for the package.
1395 pub fn publish(&self) -> String {
1396 self.make_archive();
1397
1398 // Figure out what we're going to write into the index.
1399 let deps = self
1400 .deps
1401 .iter()
1402 .map(|dep| {
1403 // In the index, the `registry` is null if it is from the same registry.
1404 // In Cargo.toml, it is None if it is from crates.io.
1405 let registry_url = match (self.alternative, dep.registry.as_deref()) {
1406 (false, None) => None,
1407 (false, Some("alternative")) => Some(alt_registry_url().to_string()),
1408 (true, None) => {
1409 Some("https://github.com/rust-lang/crates.io-index".to_string())
1410 }
1411 (true, Some("alternative")) => None,
1412 _ => panic!("registry_dep currently only supports `alternative`"),
1413 };
781aab86
FG
1414 let artifact = if let Some(artifact) = &dep.artifact {
1415 serde_json::json!([artifact])
1416 } else {
1417 serde_json::json!(null)
1418 };
0a29b90c
FG
1419 serde_json::json!({
1420 "name": dep.name,
1421 "req": dep.vers,
1422 "features": dep.features,
4b012472 1423 "default_features": dep.default_features,
0a29b90c 1424 "target": dep.target,
781aab86
FG
1425 "artifact": artifact,
1426 "bindep_target": dep.bindep_target,
1427 "lib": dep.lib,
0a29b90c
FG
1428 "optional": dep.optional,
1429 "kind": dep.kind,
1430 "registry": registry_url,
1431 "package": dep.package,
c0240ec0 1432 "public": dep.public,
0a29b90c
FG
1433 })
1434 })
1435 .collect::<Vec<_>>();
1436 let cksum = {
1437 let c = t!(fs::read(&self.archive_dst()));
1438 cksum(&c)
1439 };
1440 let name = if self.invalid_json {
1441 serde_json::json!(1)
1442 } else {
1443 serde_json::json!(self.name)
1444 };
1445 let line = create_index_line(
1446 name,
1447 &self.vers,
1448 deps,
1449 &cksum,
1450 self.features.clone(),
1451 self.yanked,
1452 self.links.clone(),
49aad941 1453 self.rust_version.as_deref(),
0a29b90c
FG
1454 self.v,
1455 );
1456
1457 let registry_path = if self.alternative {
1458 alt_registry_path()
1459 } else {
1460 registry_path()
1461 };
1462
1463 write_to_index(&registry_path, &self.name, line, self.local);
1464
1465 cksum
1466 }
1467
1468 fn make_archive(&self) {
1469 let dst = self.archive_dst();
1470 t!(fs::create_dir_all(dst.parent().unwrap()));
1471 let f = t!(File::create(&dst));
1472 let mut a = Builder::new(GzEncoder::new(f, Compression::default()));
1473
1474 if !self
1475 .files
1476 .iter()
1477 .any(|PackageFile { path, .. }| path == "Cargo.toml")
1478 {
1479 self.append_manifest(&mut a);
1480 }
1481 if self.files.is_empty() {
1482 self.append(
1483 &mut a,
1484 "src/lib.rs",
1485 DEFAULT_MODE,
1486 &EntryData::Regular("".into()),
1487 );
1488 } else {
1489 for PackageFile {
1490 path,
1491 contents,
1492 mode,
1493 extra,
1494 } in &self.files
1495 {
1496 if *extra {
1497 self.append_raw(&mut a, path, *mode, contents);
1498 } else {
1499 self.append(&mut a, path, *mode, contents);
1500 }
1501 }
1502 }
1503 }
1504
1505 fn append_manifest<W: Write>(&self, ar: &mut Builder<W>) {
1506 let mut manifest = String::new();
1507
1508 if !self.cargo_features.is_empty() {
1509 let mut features = String::new();
1510 serde::Serialize::serialize(
1511 &self.cargo_features,
1512 toml::ser::ValueSerializer::new(&mut features),
1513 )
1514 .unwrap();
1515 manifest.push_str(&format!("cargo-features = {}\n\n", features));
1516 }
1517
1518 manifest.push_str(&format!(
1519 r#"
1520 [package]
1521 name = "{}"
1522 version = "{}"
1523 authors = []
1524 "#,
1525 self.name, self.vers
1526 ));
1527
1528 if let Some(version) = &self.rust_version {
1529 manifest.push_str(&format!("rust-version = \"{}\"", version));
1530 }
1531
4b012472
FG
1532 if !self.features.is_empty() {
1533 let features: Vec<String> = self
1534 .features
1535 .iter()
1536 .map(|(feature, features)| {
1537 if features.is_empty() {
1538 format!("{} = []", feature)
1539 } else {
1540 format!(
1541 "{} = [{}]",
1542 feature,
1543 features
1544 .iter()
1545 .map(|s| format!("\"{}\"", s))
1546 .collect::<Vec<_>>()
1547 .join(", ")
1548 )
1549 }
1550 })
1551 .collect();
1552
1553 manifest.push_str(&format!("\n[features]\n{}", features.join("\n")));
1554 }
1555
0a29b90c
FG
1556 for dep in self.deps.iter() {
1557 let target = match dep.target {
1558 None => String::new(),
1559 Some(ref s) => format!("target.'{}'.", s),
1560 };
1561 let kind = match &dep.kind[..] {
1562 "build" => "build-",
1563 "dev" => "dev-",
1564 _ => "",
1565 };
1566 manifest.push_str(&format!(
1567 r#"
1568 [{}{}dependencies.{}]
1569 version = "{}"
1570 "#,
1571 target, kind, dep.name, dep.vers
1572 ));
4b012472
FG
1573 if dep.optional {
1574 manifest.push_str("optional = true\n");
1575 }
781aab86 1576 if let Some(artifact) = &dep.artifact {
0a29b90c 1577 manifest.push_str(&format!("artifact = \"{}\"\n", artifact));
781aab86
FG
1578 }
1579 if let Some(target) = &dep.bindep_target {
1580 manifest.push_str(&format!("target = \"{}\"\n", target));
1581 }
1582 if dep.lib {
1583 manifest.push_str("lib = true\n");
0a29b90c
FG
1584 }
1585 if let Some(registry) = &dep.registry {
1586 assert_eq!(registry, "alternative");
1587 manifest.push_str(&format!("registry-index = \"{}\"", alt_registry_url()));
1588 }
4b012472
FG
1589 if !dep.default_features {
1590 manifest.push_str("default-features = false\n");
1591 }
1592 if !dep.features.is_empty() {
1593 let mut features = String::new();
1594 serde::Serialize::serialize(
1595 &dep.features,
1596 toml::ser::ValueSerializer::new(&mut features),
1597 )
1598 .unwrap();
1599 manifest.push_str(&format!("features = {}\n", features));
1600 }
1601 if let Some(package) = &dep.package {
1602 manifest.push_str(&format!("package = \"{}\"\n", package));
1603 }
0a29b90c
FG
1604 }
1605 if self.proc_macro {
1606 manifest.push_str("[lib]\nproc-macro = true\n");
1607 }
1608
1609 self.append(
1610 ar,
1611 "Cargo.toml",
1612 DEFAULT_MODE,
1613 &EntryData::Regular(manifest.into()),
1614 );
1615 }
1616
1617 fn append<W: Write>(&self, ar: &mut Builder<W>, file: &str, mode: u32, contents: &EntryData) {
1618 self.append_raw(
1619 ar,
1620 &format!("{}-{}/{}", self.name, self.vers, file),
1621 mode,
1622 contents,
1623 );
1624 }
1625
1626 fn append_raw<W: Write>(
1627 &self,
1628 ar: &mut Builder<W>,
1629 path: &str,
1630 mode: u32,
1631 contents: &EntryData,
1632 ) {
1633 let mut header = Header::new_ustar();
1634 let contents = match contents {
1635 EntryData::Regular(contents) => contents.as_str(),
1636 EntryData::Symlink(src) => {
1637 header.set_entry_type(tar::EntryType::Symlink);
1638 t!(header.set_link_name(src));
1639 "" // Symlink has no contents.
1640 }
1641 };
1642 header.set_size(contents.len() as u64);
1643 t!(header.set_path(path));
1644 header.set_mode(mode);
1645 header.set_cksum();
1646 t!(ar.append(&header, contents.as_bytes()));
1647 }
1648
1649 /// Returns the path to the compressed package file.
1650 pub fn archive_dst(&self) -> PathBuf {
1651 if self.local {
1652 registry_path().join(format!("{}-{}.crate", self.name, self.vers))
1653 } else if self.alternative {
1654 alt_dl_path()
1655 .join(&self.name)
1656 .join(&self.vers)
1657 .join("download")
1658 } else {
1659 dl_path().join(&self.name).join(&self.vers).join("download")
1660 }
1661 }
1662}
1663
1664pub fn cksum(s: &[u8]) -> String {
1665 Sha256::new().update(s).finish_hex()
1666}
1667
1668impl Dependency {
1669 pub fn new(name: &str, vers: &str) -> Dependency {
1670 Dependency {
1671 name: name.to_string(),
1672 vers: vers.to_string(),
1673 kind: "normal".to_string(),
1674 artifact: None,
781aab86
FG
1675 bindep_target: None,
1676 lib: false,
0a29b90c
FG
1677 target: None,
1678 features: Vec::new(),
1679 package: None,
1680 optional: false,
1681 registry: None,
4b012472 1682 default_features: true,
c0240ec0 1683 public: false,
0a29b90c
FG
1684 }
1685 }
1686
1687 /// Changes this to `[build-dependencies]`.
1688 pub fn build(&mut self) -> &mut Self {
1689 self.kind = "build".to_string();
1690 self
1691 }
1692
1693 /// Changes this to `[dev-dependencies]`.
1694 pub fn dev(&mut self) -> &mut Self {
1695 self.kind = "dev".to_string();
1696 self
1697 }
1698
1699 /// Changes this to `[target.$target.dependencies]`.
1700 pub fn target(&mut self, target: &str) -> &mut Self {
1701 self.target = Some(target.to_string());
1702 self
1703 }
1704
1705 /// Change the artifact to be of the given kind, like "bin", or "staticlib",
1706 /// along with a specific target triple if provided.
1707 pub fn artifact(&mut self, kind: &str, target: Option<String>) -> &mut Self {
781aab86
FG
1708 self.artifact = Some(kind.to_string());
1709 self.bindep_target = target;
0a29b90c
FG
1710 self
1711 }
1712
1713 /// Adds `registry = $registry` to this dependency.
1714 pub fn registry(&mut self, registry: &str) -> &mut Self {
1715 self.registry = Some(registry.to_string());
1716 self
1717 }
1718
1719 /// Adds `features = [ ... ]` to this dependency.
1720 pub fn enable_features(&mut self, features: &[&str]) -> &mut Self {
1721 self.features.extend(features.iter().map(|s| s.to_string()));
1722 self
1723 }
1724
1725 /// Adds `package = ...` to this dependency.
1726 pub fn package(&mut self, pkg: &str) -> &mut Self {
1727 self.package = Some(pkg.to_string());
1728 self
1729 }
1730
1731 /// Changes this to an optional dependency.
1732 pub fn optional(&mut self, optional: bool) -> &mut Self {
1733 self.optional = optional;
1734 self
1735 }
4b012472 1736
c0240ec0
FG
1737 /// Changes this to an public dependency.
1738 pub fn public(&mut self, public: bool) -> &mut Self {
1739 self.public = public;
1740 self
1741 }
1742
4b012472
FG
1743 /// Adds `default-features = false` if the argument is `false`.
1744 pub fn default_features(&mut self, default_features: bool) -> &mut Self {
1745 self.default_features = default_features;
1746 self
1747 }
0a29b90c 1748}