]> git.proxmox.com Git - rustc.git/blame - src/tools/cargo/crates/cargo-test-support/src/registry.rs
New upstream version 1.74.1+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,
560}
561
562/// Entry with data that corresponds to [`tar::EntryType`].
563#[non_exhaustive]
564enum EntryData {
565 Regular(String),
566 Symlink(PathBuf),
567}
568
569/// A file to be created in a package.
570struct PackageFile {
571 path: String,
572 contents: EntryData,
573 /// The Unix mode for the file. Note that when extracted on Windows, this
574 /// is mostly ignored since it doesn't have the same style of permissions.
575 mode: u32,
576 /// If `true`, the file is created in the root of the tarfile, used for
577 /// testing invalid packages.
578 extra: bool,
579}
580
581const DEFAULT_MODE: u32 = 0o644;
582
583/// Initializes the on-disk registry and sets up the config so that crates.io
584/// is replaced with the one on disk.
585pub fn init() -> TestRegistry {
586 RegistryBuilder::new().build()
587}
588
589/// Variant of `init` that initializes the "alternative" registry and crates.io
590/// replacement.
591pub fn alt_init() -> TestRegistry {
592 init();
593 RegistryBuilder::new().alternative().build()
594}
595
596pub struct HttpServerHandle {
597 addr: SocketAddr,
598 handle: Option<JoinHandle<()>>,
599}
600
601impl HttpServerHandle {
602 pub fn index_url(&self) -> Url {
603 Url::parse(&format!("sparse+http://{}/index/", self.addr.to_string())).unwrap()
604 }
605
606 pub fn api_url(&self) -> Url {
607 Url::parse(&format!("http://{}/", self.addr.to_string())).unwrap()
608 }
609
610 pub fn dl_url(&self) -> Url {
611 Url::parse(&format!("http://{}/dl", self.addr.to_string())).unwrap()
612 }
613
614 fn stop(&self) {
615 if let Ok(mut stream) = TcpStream::connect(self.addr) {
616 // shutdown the server
617 let _ = stream.write_all(b"stop");
618 let _ = stream.flush();
619 }
620 }
621}
622
623impl Drop for HttpServerHandle {
624 fn drop(&mut self) {
625 self.stop();
626 }
627}
628
629/// Request to the test http server
630#[derive(Clone)]
631pub struct Request {
632 pub url: Url,
633 pub method: String,
634 pub body: Option<Vec<u8>>,
635 pub authorization: Option<String>,
636 pub if_modified_since: Option<String>,
637 pub if_none_match: Option<String>,
638}
639
640impl fmt::Debug for Request {
641 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
642 // body is not included as it can produce long debug outputs
643 f.debug_struct("Request")
644 .field("url", &self.url)
645 .field("method", &self.method)
646 .field("authorization", &self.authorization)
647 .field("if_modified_since", &self.if_modified_since)
648 .field("if_none_match", &self.if_none_match)
649 .finish()
650 }
651}
652
653/// Response from the test http server
654pub struct Response {
655 pub code: u32,
656 pub headers: Vec<String>,
657 pub body: Vec<u8>,
658}
659
660pub struct HttpServer {
661 listener: TcpListener,
662 registry_path: PathBuf,
663 dl_path: PathBuf,
664 api_path: PathBuf,
665 addr: SocketAddr,
666 token: Token,
667 auth_required: bool,
668 custom_responders: HashMap<String, RequestCallback>,
669 not_found_handler: RequestCallback,
670 delayed_index_update: usize,
671}
672
673/// A helper struct that collects the arguments for [`HttpServer::check_authorized`].
674/// Based on looking at the request, these are the fields that the authentication header should attest to.
675struct Mutation<'a> {
676 mutation: &'a str,
677 name: Option<&'a str>,
678 vers: Option<&'a str>,
679 cksum: Option<&'a str>,
680}
681
682impl HttpServer {
683 pub fn new(
684 registry_path: PathBuf,
685 dl_path: PathBuf,
686 api_path: PathBuf,
687 token: Token,
688 auth_required: bool,
689 custom_responders: HashMap<String, RequestCallback>,
690 not_found_handler: RequestCallback,
691 delayed_index_update: usize,
692 ) -> HttpServerHandle {
693 let listener = TcpListener::bind("127.0.0.1:0").unwrap();
694 let addr = listener.local_addr().unwrap();
695 let server = HttpServer {
696 listener,
697 registry_path,
698 dl_path,
699 api_path,
700 addr,
701 token,
702 auth_required,
703 custom_responders,
704 not_found_handler,
705 delayed_index_update,
706 };
707 let handle = Some(thread::spawn(move || server.start()));
708 HttpServerHandle { addr, handle }
709 }
710
711 fn start(&self) {
712 let mut line = String::new();
713 'server: loop {
714 let (socket, _) = self.listener.accept().unwrap();
715 let mut buf = BufReader::new(socket);
716 line.clear();
717 if buf.read_line(&mut line).unwrap() == 0 {
718 // Connection terminated.
719 continue;
720 }
721 // Read the "GET path HTTP/1.1" line.
722 let mut parts = line.split_ascii_whitespace();
723 let method = parts.next().unwrap().to_ascii_lowercase();
724 if method == "stop" {
725 // Shutdown the server.
726 return;
727 }
728 let addr = self.listener.local_addr().unwrap();
729 let url = format!(
730 "http://{}/{}",
731 addr,
732 parts.next().unwrap().trim_start_matches('/')
733 );
734 let url = Url::parse(&url).unwrap();
735
736 // Grab headers we care about.
737 let mut if_modified_since = None;
738 let mut if_none_match = None;
739 let mut authorization = None;
740 let mut content_len = None;
741 loop {
742 line.clear();
743 if buf.read_line(&mut line).unwrap() == 0 {
744 continue 'server;
745 }
746 if line == "\r\n" {
747 // End of headers.
748 line.clear();
749 break;
750 }
751 let (name, value) = line.split_once(':').unwrap();
752 let name = name.trim().to_ascii_lowercase();
753 let value = value.trim().to_string();
754 match name.as_str() {
755 "if-modified-since" => if_modified_since = Some(value),
756 "if-none-match" => if_none_match = Some(value),
757 "authorization" => authorization = Some(value),
758 "content-length" => content_len = Some(value),
759 _ => {}
760 }
761 }
762
763 let mut body = None;
764 if let Some(con_len) = content_len {
765 let len = con_len.parse::<u64>().unwrap();
766 let mut content = vec![0u8; len as usize];
767 buf.read_exact(&mut content).unwrap();
768 body = Some(content)
769 }
770
771 let req = Request {
772 authorization,
773 if_modified_since,
774 if_none_match,
775 method,
776 url,
777 body,
778 };
779 println!("req: {:#?}", req);
780 let response = self.route(&req);
781 let buf = buf.get_mut();
782 write!(buf, "HTTP/1.1 {}\r\n", response.code).unwrap();
783 write!(buf, "Content-Length: {}\r\n", response.body.len()).unwrap();
781aab86 784 write!(buf, "Connection: close\r\n").unwrap();
0a29b90c
FG
785 for header in response.headers {
786 write!(buf, "{}\r\n", header).unwrap();
787 }
788 write!(buf, "\r\n").unwrap();
789 buf.write_all(&response.body).unwrap();
790 buf.flush().unwrap();
791 }
792 }
793
781aab86 794 fn check_authorized(&self, req: &Request, mutation: Option<Mutation<'_>>) -> bool {
0a29b90c
FG
795 let (private_key, private_key_subject) = if mutation.is_some() || self.auth_required {
796 match &self.token {
797 Token::Plaintext(token) => return Some(token) == req.authorization.as_ref(),
798 Token::Keys(private_key, private_key_subject) => {
799 (private_key.as_str(), private_key_subject)
800 }
801 }
802 } else {
803 assert!(req.authorization.is_none(), "unexpected token");
804 return true;
805 };
806
807 macro_rules! t {
808 ($e:expr) => {
809 match $e {
810 Some(e) => e,
811 None => return false,
812 }
813 };
814 }
815
816 let secret: AsymmetricSecretKey<pasetors::version3::V3> = private_key.try_into().unwrap();
817 let public: AsymmetricPublicKey<pasetors::version3::V3> = (&secret).try_into().unwrap();
818 let pub_key_id: pasetors::paserk::Id = (&public).into();
819 let mut paserk_pub_key_id = String::new();
820 FormatAsPaserk::fmt(&pub_key_id, &mut paserk_pub_key_id).unwrap();
821 // https://github.com/rust-lang/rfcs/blob/master/text/3231-cargo-asymmetric-tokens.md#how-the-registry-server-will-validate-an-asymmetric-token
822
823 // - The PASETO is in v3.public format.
824 let authorization = t!(&req.authorization);
825 let untrusted_token = t!(
826 UntrustedToken::<pasetors::Public, pasetors::version3::V3>::try_from(authorization)
827 .ok()
828 );
829
830 // - The PASETO validates using the public key it looked up based on the key ID.
831 #[derive(serde::Deserialize, Debug)]
832 struct Footer<'a> {
833 url: &'a str,
834 kip: &'a str,
835 }
781aab86
FG
836 let footer: Footer<'_> =
837 t!(serde_json::from_slice(untrusted_token.untrusted_footer()).ok());
0a29b90c
FG
838 if footer.kip != paserk_pub_key_id {
839 return false;
840 }
841 let trusted_token =
842 t!(
843 pasetors::version3::PublicToken::verify(&public, &untrusted_token, None, None,)
844 .ok()
845 );
846
847 // - The URL matches the registry base URL
848 if footer.url != "https://github.com/rust-lang/crates.io-index"
849 && footer.url != &format!("sparse+http://{}/index/", self.addr.to_string())
850 {
0a29b90c
FG
851 return false;
852 }
853
854 // - The PASETO is still within its valid time period.
855 #[derive(serde::Deserialize)]
856 struct Message<'a> {
857 iat: &'a str,
858 sub: Option<&'a str>,
859 mutation: Option<&'a str>,
860 name: Option<&'a str>,
861 vers: Option<&'a str>,
862 cksum: Option<&'a str>,
863 _challenge: Option<&'a str>, // todo: PASETO with challenges
864 v: Option<u8>,
865 }
781aab86 866 let message: Message<'_> = t!(serde_json::from_str(trusted_token.payload()).ok());
0a29b90c
FG
867 let token_time = t!(OffsetDateTime::parse(message.iat, &Rfc3339).ok());
868 let now = OffsetDateTime::now_utc();
869 if (now - token_time) > Duration::MINUTE {
870 return false;
871 }
872 if private_key_subject.as_deref() != message.sub {
0a29b90c
FG
873 return false;
874 }
875 // - If the claim v is set, that it has the value of 1.
876 if let Some(v) = message.v {
877 if v != 1 {
0a29b90c
FG
878 return false;
879 }
880 }
881 // - If the server issues challenges, that the challenge has not yet been answered.
882 // todo: PASETO with challenges
883 // - If the operation is a mutation:
884 if let Some(mutation) = mutation {
885 // - That the operation matches the mutation field and is one of publish, yank, or unyank.
886 if message.mutation != Some(mutation.mutation) {
0a29b90c
FG
887 return false;
888 }
889 // - That the package, and version match the request.
890 if message.name != mutation.name {
0a29b90c
FG
891 return false;
892 }
893 if message.vers != mutation.vers {
0a29b90c
FG
894 return false;
895 }
896 // - If the mutation is publish, that the version has not already been published, and that the hash matches the request.
897 if mutation.mutation == "publish" {
898 if message.cksum != mutation.cksum {
0a29b90c
FG
899 return false;
900 }
901 }
902 } else {
903 // - If the operation is a read, that the mutation field is not set.
904 if message.mutation.is_some()
905 || message.name.is_some()
906 || message.vers.is_some()
907 || message.cksum.is_some()
908 {
909 return false;
910 }
911 }
912 true
913 }
914
915 /// Route the request
916 fn route(&self, req: &Request) -> Response {
917 // Check for custom responder
918 if let Some(responder) = self.custom_responders.get(req.url.path()) {
919 return responder(&req, self);
920 }
921 let path: Vec<_> = req.url.path()[1..].split('/').collect();
922 match (req.method.as_str(), path.as_slice()) {
923 ("get", ["index", ..]) => {
924 if !self.check_authorized(req, None) {
925 self.unauthorized(req)
926 } else {
927 self.index(&req)
928 }
929 }
930 ("get", ["dl", ..]) => {
931 if !self.check_authorized(req, None) {
932 self.unauthorized(req)
933 } else {
934 self.dl(&req)
935 }
936 }
937 // publish
938 ("put", ["api", "v1", "crates", "new"]) => self.check_authorized_publish(req),
939 // The remainder of the operators in the test framework do nothing other than responding 'ok'.
940 //
941 // Note: We don't need to support anything real here because there are no tests that
942 // currently require anything other than publishing via the http api.
943
944 // yank / unyank
945 ("delete" | "put", ["api", "v1", "crates", crate_name, version, mutation]) => {
946 if !self.check_authorized(
947 req,
948 Some(Mutation {
949 mutation,
950 name: Some(crate_name),
951 vers: Some(version),
952 cksum: None,
953 }),
954 ) {
955 self.unauthorized(req)
956 } else {
957 self.ok(&req)
958 }
959 }
960 // owners
961 ("get" | "put" | "delete", ["api", "v1", "crates", crate_name, "owners"]) => {
962 if !self.check_authorized(
963 req,
964 Some(Mutation {
965 mutation: "owners",
966 name: Some(crate_name),
967 vers: None,
968 cksum: None,
969 }),
970 ) {
971 self.unauthorized(req)
972 } else {
973 self.ok(&req)
974 }
975 }
976 _ => self.not_found(&req),
977 }
978 }
979
980 /// Unauthorized response
981 pub fn unauthorized(&self, _req: &Request) -> Response {
982 Response {
983 code: 401,
984 headers: vec![
985 r#"WWW-Authenticate: Cargo login_url="https://test-registry-login/me""#.to_string(),
986 ],
987 body: b"Unauthorized message from server.".to_vec(),
988 }
989 }
990
991 /// Not found response
992 pub fn not_found(&self, req: &Request) -> Response {
993 (self.not_found_handler)(req, self)
994 }
995
996 /// Respond OK without doing anything
997 pub fn ok(&self, _req: &Request) -> Response {
998 Response {
999 code: 200,
1000 headers: vec![],
1001 body: br#"{"ok": true, "msg": "completed!"}"#.to_vec(),
1002 }
1003 }
1004
1005 /// Return an internal server error (HTTP 500)
1006 pub fn internal_server_error(&self, _req: &Request) -> Response {
1007 Response {
1008 code: 500,
1009 headers: vec![],
1010 body: br#"internal server error"#.to_vec(),
1011 }
1012 }
1013
1014 /// Serve the download endpoint
1015 pub fn dl(&self, req: &Request) -> Response {
1016 let file = self
1017 .dl_path
1018 .join(req.url.path().strip_prefix("/dl/").unwrap());
1019 println!("{}", file.display());
1020 if !file.exists() {
1021 return self.not_found(req);
1022 }
1023 return Response {
1024 body: fs::read(&file).unwrap(),
1025 code: 200,
1026 headers: vec![],
1027 };
1028 }
1029
1030 /// Serve the registry index
1031 pub fn index(&self, req: &Request) -> Response {
1032 let file = self
1033 .registry_path
1034 .join(req.url.path().strip_prefix("/index/").unwrap());
1035 if !file.exists() {
1036 return self.not_found(req);
1037 } else {
1038 // Now grab info about the file.
1039 let data = fs::read(&file).unwrap();
1040 let etag = Sha256::new().update(&data).finish_hex();
1041 let last_modified = format!("{:?}", file.metadata().unwrap().modified().unwrap());
1042
1043 // Start to construct our response:
1044 let mut any_match = false;
1045 let mut all_match = true;
1046 if let Some(expected) = &req.if_none_match {
1047 if &etag != expected {
1048 all_match = false;
1049 } else {
1050 any_match = true;
1051 }
1052 }
1053 if let Some(expected) = &req.if_modified_since {
1054 // NOTE: Equality comparison is good enough for tests.
1055 if &last_modified != expected {
1056 all_match = false;
1057 } else {
1058 any_match = true;
1059 }
1060 }
1061
1062 if any_match && all_match {
1063 return Response {
1064 body: Vec::new(),
1065 code: 304,
1066 headers: vec![],
1067 };
1068 } else {
1069 return Response {
1070 body: data,
1071 code: 200,
1072 headers: vec![
1073 format!("ETag: \"{}\"", etag),
1074 format!("Last-Modified: {}", last_modified),
1075 ],
1076 };
1077 }
1078 }
1079 }
1080
1081 pub fn check_authorized_publish(&self, req: &Request) -> Response {
1082 if let Some(body) = &req.body {
1083 // Mimic the publish behavior for local registries by writing out the request
1084 // so tests can verify publishes made to either registry type.
1085 let path = self.api_path.join("api/v1/crates/new");
1086 t!(fs::create_dir_all(path.parent().unwrap()));
1087 t!(fs::write(&path, body));
1088
1089 // Get the metadata of the package
1090 let (len, remaining) = body.split_at(4);
1091 let json_len = u32::from_le_bytes(len.try_into().unwrap());
1092 let (json, remaining) = remaining.split_at(json_len as usize);
1093 let new_crate = serde_json::from_slice::<crates_io::NewCrate>(json).unwrap();
1094 // Get the `.crate` file
1095 let (len, remaining) = remaining.split_at(4);
1096 let file_len = u32::from_le_bytes(len.try_into().unwrap());
1097 let (file, _remaining) = remaining.split_at(file_len as usize);
1098 let file_cksum = cksum(&file);
1099
1100 if !self.check_authorized(
1101 req,
1102 Some(Mutation {
1103 mutation: "publish",
1104 name: Some(&new_crate.name),
1105 vers: Some(&new_crate.vers),
1106 cksum: Some(&file_cksum),
1107 }),
1108 ) {
1109 return self.unauthorized(req);
1110 }
1111
1112 let dst = self
1113 .dl_path
1114 .join(&new_crate.name)
1115 .join(&new_crate.vers)
1116 .join("download");
1117
1118 if self.delayed_index_update == 0 {
1119 save_new_crate(dst, new_crate, file, file_cksum, &self.registry_path);
1120 } else {
1121 let delayed_index_update = self.delayed_index_update;
1122 let registry_path = self.registry_path.clone();
1123 let file = Vec::from(file);
1124 thread::spawn(move || {
1125 thread::sleep(std::time::Duration::new(delayed_index_update as u64, 0));
1126 save_new_crate(dst, new_crate, &file, file_cksum, &registry_path);
1127 });
1128 }
1129
1130 self.ok(&req)
1131 } else {
1132 Response {
1133 code: 400,
1134 headers: vec![],
1135 body: b"The request was missing a body".to_vec(),
1136 }
1137 }
1138 }
1139}
1140
1141fn save_new_crate(
1142 dst: PathBuf,
1143 new_crate: crates_io::NewCrate,
1144 file: &[u8],
1145 file_cksum: String,
1146 registry_path: &Path,
1147) {
1148 // Write the `.crate`
1149 t!(fs::create_dir_all(dst.parent().unwrap()));
1150 t!(fs::write(&dst, file));
1151
1152 let deps = new_crate
1153 .deps
1154 .iter()
1155 .map(|dep| {
1156 let (name, package) = match &dep.explicit_name_in_toml {
1157 Some(explicit) => (explicit.to_string(), Some(dep.name.to_string())),
1158 None => (dep.name.to_string(), None),
1159 };
1160 serde_json::json!({
1161 "name": name,
1162 "req": dep.version_req,
1163 "features": dep.features,
1164 "default_features": true,
1165 "target": dep.target,
1166 "optional": dep.optional,
1167 "kind": dep.kind,
1168 "registry": dep.registry,
1169 "package": package,
1170 })
1171 })
1172 .collect::<Vec<_>>();
1173
1174 let line = create_index_line(
1175 serde_json::json!(new_crate.name),
1176 &new_crate.vers,
1177 deps,
1178 &file_cksum,
1179 new_crate.features,
1180 false,
1181 new_crate.links,
1182 None,
49aad941 1183 None,
0a29b90c
FG
1184 );
1185
1186 write_to_index(registry_path, &new_crate.name, line, false);
1187}
1188
1189impl Package {
1190 /// Creates a new package builder.
1191 /// Call `publish()` to finalize and build the package.
1192 pub fn new(name: &str, vers: &str) -> Package {
1193 let config = paths::home().join(".cargo/config");
1194 if !config.exists() {
1195 init();
1196 }
1197 Package {
1198 name: name.to_string(),
1199 vers: vers.to_string(),
1200 deps: Vec::new(),
1201 files: Vec::new(),
1202 yanked: false,
1203 features: BTreeMap::new(),
1204 local: false,
1205 alternative: false,
1206 invalid_json: false,
1207 proc_macro: false,
1208 links: None,
1209 rust_version: None,
1210 cargo_features: Vec::new(),
1211 v: None,
1212 }
1213 }
1214
1215 /// Call with `true` to publish in a "local registry".
1216 ///
1217 /// See `source-replacement.html#local-registry-sources` for more details
1218 /// on local registries. See `local_registry.rs` for the tests that use
1219 /// this.
1220 pub fn local(&mut self, local: bool) -> &mut Package {
1221 self.local = local;
1222 self
1223 }
1224
1225 /// Call with `true` to publish in an "alternative registry".
1226 ///
1227 /// The name of the alternative registry is called "alternative".
1228 ///
1229 /// See `src/doc/src/reference/registries.md` for more details on
1230 /// alternative registries. See `alt_registry.rs` for the tests that use
1231 /// this.
1232 pub fn alternative(&mut self, alternative: bool) -> &mut Package {
1233 self.alternative = alternative;
1234 self
1235 }
1236
1237 /// Adds a file to the package.
1238 pub fn file(&mut self, name: &str, contents: &str) -> &mut Package {
1239 self.file_with_mode(name, DEFAULT_MODE, contents)
1240 }
1241
1242 /// Adds a file with a specific Unix mode.
1243 pub fn file_with_mode(&mut self, path: &str, mode: u32, contents: &str) -> &mut Package {
1244 self.files.push(PackageFile {
1245 path: path.to_string(),
1246 contents: EntryData::Regular(contents.into()),
1247 mode,
1248 extra: false,
1249 });
1250 self
1251 }
1252
1253 /// Adds a symlink to a path to the package.
1254 pub fn symlink(&mut self, dst: &str, src: &str) -> &mut Package {
1255 self.files.push(PackageFile {
1256 path: dst.to_string(),
1257 contents: EntryData::Symlink(src.into()),
1258 mode: DEFAULT_MODE,
1259 extra: false,
1260 });
1261 self
1262 }
1263
1264 /// Adds an "extra" file that is not rooted within the package.
1265 ///
1266 /// Normal files are automatically placed within a directory named
1267 /// `$PACKAGE-$VERSION`. This allows you to override that behavior,
1268 /// typically for testing invalid behavior.
1269 pub fn extra_file(&mut self, path: &str, contents: &str) -> &mut Package {
1270 self.files.push(PackageFile {
1271 path: path.to_string(),
1272 contents: EntryData::Regular(contents.to_string()),
1273 mode: DEFAULT_MODE,
1274 extra: true,
1275 });
1276 self
1277 }
1278
1279 /// Adds a normal dependency. Example:
49aad941 1280 /// ```toml
0a29b90c
FG
1281 /// [dependencies]
1282 /// foo = {version = "1.0"}
1283 /// ```
1284 pub fn dep(&mut self, name: &str, vers: &str) -> &mut Package {
1285 self.add_dep(&Dependency::new(name, vers))
1286 }
1287
1288 /// Adds a dependency with the given feature. Example:
49aad941 1289 /// ```toml
0a29b90c
FG
1290 /// [dependencies]
1291 /// foo = {version = "1.0", "features": ["feat1", "feat2"]}
1292 /// ```
1293 pub fn feature_dep(&mut self, name: &str, vers: &str, features: &[&str]) -> &mut Package {
1294 self.add_dep(Dependency::new(name, vers).enable_features(features))
1295 }
1296
1297 /// Adds a platform-specific dependency. Example:
1298 /// ```toml
1299 /// [target.'cfg(windows)'.dependencies]
1300 /// foo = {version = "1.0"}
1301 /// ```
1302 pub fn target_dep(&mut self, name: &str, vers: &str, target: &str) -> &mut Package {
1303 self.add_dep(Dependency::new(name, vers).target(target))
1304 }
1305
1306 /// Adds a dependency to the alternative registry.
1307 pub fn registry_dep(&mut self, name: &str, vers: &str) -> &mut Package {
1308 self.add_dep(Dependency::new(name, vers).registry("alternative"))
1309 }
1310
1311 /// Adds a dev-dependency. Example:
49aad941 1312 /// ```toml
0a29b90c
FG
1313 /// [dev-dependencies]
1314 /// foo = {version = "1.0"}
1315 /// ```
1316 pub fn dev_dep(&mut self, name: &str, vers: &str) -> &mut Package {
1317 self.add_dep(Dependency::new(name, vers).dev())
1318 }
1319
1320 /// Adds a build-dependency. Example:
49aad941 1321 /// ```toml
0a29b90c
FG
1322 /// [build-dependencies]
1323 /// foo = {version = "1.0"}
1324 /// ```
1325 pub fn build_dep(&mut self, name: &str, vers: &str) -> &mut Package {
1326 self.add_dep(Dependency::new(name, vers).build())
1327 }
1328
1329 pub fn add_dep(&mut self, dep: &Dependency) -> &mut Package {
1330 self.deps.push(dep.clone());
1331 self
1332 }
1333
1334 /// Specifies whether or not the package is "yanked".
1335 pub fn yanked(&mut self, yanked: bool) -> &mut Package {
1336 self.yanked = yanked;
1337 self
1338 }
1339
1340 /// Specifies whether or not this is a proc macro.
1341 pub fn proc_macro(&mut self, proc_macro: bool) -> &mut Package {
1342 self.proc_macro = proc_macro;
1343 self
1344 }
1345
1346 /// Adds an entry in the `[features]` section.
1347 pub fn feature(&mut self, name: &str, deps: &[&str]) -> &mut Package {
1348 let deps = deps.iter().map(|s| s.to_string()).collect();
1349 self.features.insert(name.to_string(), deps);
1350 self
1351 }
1352
1353 /// Specify a minimal Rust version.
1354 pub fn rust_version(&mut self, rust_version: &str) -> &mut Package {
1355 self.rust_version = Some(rust_version.into());
1356 self
1357 }
1358
1359 /// Causes the JSON line emitted in the index to be invalid, presumably
1360 /// causing Cargo to skip over this version.
1361 pub fn invalid_json(&mut self, invalid: bool) -> &mut Package {
1362 self.invalid_json = invalid;
1363 self
1364 }
1365
1366 pub fn links(&mut self, links: &str) -> &mut Package {
1367 self.links = Some(links.to_string());
1368 self
1369 }
1370
1371 pub fn cargo_feature(&mut self, feature: &str) -> &mut Package {
1372 self.cargo_features.push(feature.to_owned());
1373 self
1374 }
1375
1376 /// Sets the index schema version for this package.
1377 ///
fe692bf9 1378 /// See `cargo::sources::registry::IndexPackage` for more information.
0a29b90c
FG
1379 pub fn schema_version(&mut self, version: u32) -> &mut Package {
1380 self.v = Some(version);
1381 self
1382 }
1383
1384 /// Creates the package and place it in the registry.
1385 ///
1386 /// This does not actually use Cargo's publishing system, but instead
1387 /// manually creates the entry in the registry on the filesystem.
1388 ///
1389 /// Returns the checksum for the package.
1390 pub fn publish(&self) -> String {
1391 self.make_archive();
1392
1393 // Figure out what we're going to write into the index.
1394 let deps = self
1395 .deps
1396 .iter()
1397 .map(|dep| {
1398 // In the index, the `registry` is null if it is from the same registry.
1399 // In Cargo.toml, it is None if it is from crates.io.
1400 let registry_url = match (self.alternative, dep.registry.as_deref()) {
1401 (false, None) => None,
1402 (false, Some("alternative")) => Some(alt_registry_url().to_string()),
1403 (true, None) => {
1404 Some("https://github.com/rust-lang/crates.io-index".to_string())
1405 }
1406 (true, Some("alternative")) => None,
1407 _ => panic!("registry_dep currently only supports `alternative`"),
1408 };
781aab86
FG
1409 let artifact = if let Some(artifact) = &dep.artifact {
1410 serde_json::json!([artifact])
1411 } else {
1412 serde_json::json!(null)
1413 };
0a29b90c
FG
1414 serde_json::json!({
1415 "name": dep.name,
1416 "req": dep.vers,
1417 "features": dep.features,
1418 "default_features": true,
1419 "target": dep.target,
781aab86
FG
1420 "artifact": artifact,
1421 "bindep_target": dep.bindep_target,
1422 "lib": dep.lib,
0a29b90c
FG
1423 "optional": dep.optional,
1424 "kind": dep.kind,
1425 "registry": registry_url,
1426 "package": dep.package,
1427 })
1428 })
1429 .collect::<Vec<_>>();
1430 let cksum = {
1431 let c = t!(fs::read(&self.archive_dst()));
1432 cksum(&c)
1433 };
1434 let name = if self.invalid_json {
1435 serde_json::json!(1)
1436 } else {
1437 serde_json::json!(self.name)
1438 };
1439 let line = create_index_line(
1440 name,
1441 &self.vers,
1442 deps,
1443 &cksum,
1444 self.features.clone(),
1445 self.yanked,
1446 self.links.clone(),
49aad941 1447 self.rust_version.as_deref(),
0a29b90c
FG
1448 self.v,
1449 );
1450
1451 let registry_path = if self.alternative {
1452 alt_registry_path()
1453 } else {
1454 registry_path()
1455 };
1456
1457 write_to_index(&registry_path, &self.name, line, self.local);
1458
1459 cksum
1460 }
1461
1462 fn make_archive(&self) {
1463 let dst = self.archive_dst();
1464 t!(fs::create_dir_all(dst.parent().unwrap()));
1465 let f = t!(File::create(&dst));
1466 let mut a = Builder::new(GzEncoder::new(f, Compression::default()));
1467
1468 if !self
1469 .files
1470 .iter()
1471 .any(|PackageFile { path, .. }| path == "Cargo.toml")
1472 {
1473 self.append_manifest(&mut a);
1474 }
1475 if self.files.is_empty() {
1476 self.append(
1477 &mut a,
1478 "src/lib.rs",
1479 DEFAULT_MODE,
1480 &EntryData::Regular("".into()),
1481 );
1482 } else {
1483 for PackageFile {
1484 path,
1485 contents,
1486 mode,
1487 extra,
1488 } in &self.files
1489 {
1490 if *extra {
1491 self.append_raw(&mut a, path, *mode, contents);
1492 } else {
1493 self.append(&mut a, path, *mode, contents);
1494 }
1495 }
1496 }
1497 }
1498
1499 fn append_manifest<W: Write>(&self, ar: &mut Builder<W>) {
1500 let mut manifest = String::new();
1501
1502 if !self.cargo_features.is_empty() {
1503 let mut features = String::new();
1504 serde::Serialize::serialize(
1505 &self.cargo_features,
1506 toml::ser::ValueSerializer::new(&mut features),
1507 )
1508 .unwrap();
1509 manifest.push_str(&format!("cargo-features = {}\n\n", features));
1510 }
1511
1512 manifest.push_str(&format!(
1513 r#"
1514 [package]
1515 name = "{}"
1516 version = "{}"
1517 authors = []
1518 "#,
1519 self.name, self.vers
1520 ));
1521
1522 if let Some(version) = &self.rust_version {
1523 manifest.push_str(&format!("rust-version = \"{}\"", version));
1524 }
1525
1526 for dep in self.deps.iter() {
1527 let target = match dep.target {
1528 None => String::new(),
1529 Some(ref s) => format!("target.'{}'.", s),
1530 };
1531 let kind = match &dep.kind[..] {
1532 "build" => "build-",
1533 "dev" => "dev-",
1534 _ => "",
1535 };
1536 manifest.push_str(&format!(
1537 r#"
1538 [{}{}dependencies.{}]
1539 version = "{}"
1540 "#,
1541 target, kind, dep.name, dep.vers
1542 ));
781aab86 1543 if let Some(artifact) = &dep.artifact {
0a29b90c 1544 manifest.push_str(&format!("artifact = \"{}\"\n", artifact));
781aab86
FG
1545 }
1546 if let Some(target) = &dep.bindep_target {
1547 manifest.push_str(&format!("target = \"{}\"\n", target));
1548 }
1549 if dep.lib {
1550 manifest.push_str("lib = true\n");
0a29b90c
FG
1551 }
1552 if let Some(registry) = &dep.registry {
1553 assert_eq!(registry, "alternative");
1554 manifest.push_str(&format!("registry-index = \"{}\"", alt_registry_url()));
1555 }
1556 }
1557 if self.proc_macro {
1558 manifest.push_str("[lib]\nproc-macro = true\n");
1559 }
1560
1561 self.append(
1562 ar,
1563 "Cargo.toml",
1564 DEFAULT_MODE,
1565 &EntryData::Regular(manifest.into()),
1566 );
1567 }
1568
1569 fn append<W: Write>(&self, ar: &mut Builder<W>, file: &str, mode: u32, contents: &EntryData) {
1570 self.append_raw(
1571 ar,
1572 &format!("{}-{}/{}", self.name, self.vers, file),
1573 mode,
1574 contents,
1575 );
1576 }
1577
1578 fn append_raw<W: Write>(
1579 &self,
1580 ar: &mut Builder<W>,
1581 path: &str,
1582 mode: u32,
1583 contents: &EntryData,
1584 ) {
1585 let mut header = Header::new_ustar();
1586 let contents = match contents {
1587 EntryData::Regular(contents) => contents.as_str(),
1588 EntryData::Symlink(src) => {
1589 header.set_entry_type(tar::EntryType::Symlink);
1590 t!(header.set_link_name(src));
1591 "" // Symlink has no contents.
1592 }
1593 };
1594 header.set_size(contents.len() as u64);
1595 t!(header.set_path(path));
1596 header.set_mode(mode);
1597 header.set_cksum();
1598 t!(ar.append(&header, contents.as_bytes()));
1599 }
1600
1601 /// Returns the path to the compressed package file.
1602 pub fn archive_dst(&self) -> PathBuf {
1603 if self.local {
1604 registry_path().join(format!("{}-{}.crate", self.name, self.vers))
1605 } else if self.alternative {
1606 alt_dl_path()
1607 .join(&self.name)
1608 .join(&self.vers)
1609 .join("download")
1610 } else {
1611 dl_path().join(&self.name).join(&self.vers).join("download")
1612 }
1613 }
1614}
1615
1616pub fn cksum(s: &[u8]) -> String {
1617 Sha256::new().update(s).finish_hex()
1618}
1619
1620impl Dependency {
1621 pub fn new(name: &str, vers: &str) -> Dependency {
1622 Dependency {
1623 name: name.to_string(),
1624 vers: vers.to_string(),
1625 kind: "normal".to_string(),
1626 artifact: None,
781aab86
FG
1627 bindep_target: None,
1628 lib: false,
0a29b90c
FG
1629 target: None,
1630 features: Vec::new(),
1631 package: None,
1632 optional: false,
1633 registry: None,
1634 }
1635 }
1636
1637 /// Changes this to `[build-dependencies]`.
1638 pub fn build(&mut self) -> &mut Self {
1639 self.kind = "build".to_string();
1640 self
1641 }
1642
1643 /// Changes this to `[dev-dependencies]`.
1644 pub fn dev(&mut self) -> &mut Self {
1645 self.kind = "dev".to_string();
1646 self
1647 }
1648
1649 /// Changes this to `[target.$target.dependencies]`.
1650 pub fn target(&mut self, target: &str) -> &mut Self {
1651 self.target = Some(target.to_string());
1652 self
1653 }
1654
1655 /// Change the artifact to be of the given kind, like "bin", or "staticlib",
1656 /// along with a specific target triple if provided.
1657 pub fn artifact(&mut self, kind: &str, target: Option<String>) -> &mut Self {
781aab86
FG
1658 self.artifact = Some(kind.to_string());
1659 self.bindep_target = target;
0a29b90c
FG
1660 self
1661 }
1662
1663 /// Adds `registry = $registry` to this dependency.
1664 pub fn registry(&mut self, registry: &str) -> &mut Self {
1665 self.registry = Some(registry.to_string());
1666 self
1667 }
1668
1669 /// Adds `features = [ ... ]` to this dependency.
1670 pub fn enable_features(&mut self, features: &[&str]) -> &mut Self {
1671 self.features.extend(features.iter().map(|s| s.to_string()));
1672 self
1673 }
1674
1675 /// Adds `package = ...` to this dependency.
1676 pub fn package(&mut self, pkg: &str) -> &mut Self {
1677 self.package = Some(pkg.to_string());
1678 self
1679 }
1680
1681 /// Changes this to an optional dependency.
1682 pub fn optional(&mut self, optional: bool) -> &mut Self {
1683 self.optional = optional;
1684 self
1685 }
1686}