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