Skip to main content

cargo_test_support/
registry.rs

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