Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions crates/cargo-util-schemas/registry_config.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "RegistryConfig",
"description": "The [`config.json`] file stored in the index.\n\nThe config file may look like:\n\n```json\n{\n \"dl\": \"https://example.com/api/{crate}/{version}/download\",\n \"api\": \"https://example.com/api\",\n \"auth-required\": false\n}\n```\n\n[`config.json`]: https://doc.rust-lang.org/nightly/cargo/reference/registry-index.html#index-configuration",
"type": "object",
"properties": {
"dl": {
"description": "Download endpoint for all crates.\n\nThe string is a template which will generate the download URL for the\ntarball of a specific version of a crate. The substrings `{crate}` and\n`{version}` will be replaced with the crate's name and version\nrespectively. The substring `{prefix}` will be replaced with the\ncrate's prefix directory name, and the substring `{lowerprefix}` will\nbe replaced with the crate's prefix directory name converted to\nlowercase. The substring `{sha256-checksum}` will be replaced with the\ncrate's sha256 checksum.\n\nFor backwards compatibility, if the string does not contain any\nmarkers (`{crate}`, `{version}`, `{prefix}`, or `{lowerprefix}`), it\nwill be extended with `/{crate}/{version}/download` to\nsupport registries like crates.io which were created before the\ntemplating setup was created.\n\nFor more on the template of the download URL, see [Index Configuration](\nhttps://doc.rust-lang.org/nightly/cargo/reference/registry-index.html#index-configuration).",
"type": "string"
},
"api": {
"description": "API endpoint for the registry. This is what's actually hit to perform\noperations like yanks, owner modifications, publish new crates, etc.\nIf this is None, the registry does not support API commands.",
"type": [
"string",
"null"
]
},
"auth-required": {
"description": "Whether all operations require authentication. See [RFC 3139].\n\n[RFC 3139]: https://rust-lang.github.io/rfcs/3139-cargo-alternative-registry-auth.html",
"type": "boolean",
"default": false
}
},
"required": [
"dl"
]
}
63 changes: 63 additions & 0 deletions crates/cargo-util-schemas/src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,69 @@ fn dump_index_schema() {
snapbox::assert_data_eq!(dump, snapbox::file!("../index.schema.json").raw());
}

/// The [`config.json`] file stored in the index.
///
/// The config file may look like:
///
/// ```json
/// {
/// "dl": "https://example.com/api/{crate}/{version}/download",
/// "api": "https://example.com/api",
/// "auth-required": false
/// }
/// ```
///
/// [`config.json`]: https://doc.rust-lang.org/nightly/cargo/reference/registry-index.html#index-configuration
#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(rename_all = "kebab-case")]
#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
pub struct RegistryConfig {
/// Download endpoint for all crates.
///
/// The string is a template which will generate the download URL for the
/// tarball of a specific version of a crate. The substrings `{crate}` and
/// `{version}` will be replaced with the crate's name and version
/// respectively. The substring `{prefix}` will be replaced with the
/// crate's prefix directory name, and the substring `{lowerprefix}` will
/// be replaced with the crate's prefix directory name converted to
/// lowercase. The substring `{sha256-checksum}` will be replaced with the
/// crate's sha256 checksum.
///
/// For backwards compatibility, if the string does not contain any
/// markers (`{crate}`, `{version}`, `{prefix}`, or `{lowerprefix}`), it
/// will be extended with `/{crate}/{version}/download` to
/// support registries like crates.io which were created before the
/// templating setup was created.
///
/// For more on the template of the download URL, see [Index Configuration](
/// https://doc.rust-lang.org/nightly/cargo/reference/registry-index.html#index-configuration).
pub dl: String,

/// API endpoint for the registry. This is what's actually hit to perform
/// operations like yanks, owner modifications, publish new crates, etc.
/// If this is None, the registry does not support API commands.
pub api: Option<String>,

/// Whether all operations require authentication. See [RFC 3139].
///
/// [RFC 3139]: https://rust-lang.github.io/rfcs/3139-cargo-alternative-registry-auth.html
#[serde(default)]
pub auth_required: bool,
}

impl RegistryConfig {
/// File name of [`RegistryConfig`].
pub const NAME: &'static str = "config.json";
}

#[cfg(feature = "unstable-schema")]
#[test]
fn dump_registry_schema() {
let schema = schemars::schema_for!(crate::index::RegistryConfig);
let dump = serde_json::to_string_pretty(&schema).unwrap();
snapbox::assert_data_eq!(dump, snapbox::file!("../registry_config.schema.json").raw());
}

#[test]
fn pubtime_format() {
use snapbox::str;
Expand Down
29 changes: 29 additions & 0 deletions crates/cargo-util/src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,35 @@ pub fn make_dep_path(dep_name: &str, prefix_only: bool) -> String {
}
}

pub fn crate_url(dl_template: &str, krate: &str, version: &str, sha256_checksum: &str) -> String {
let url = if !dl_template.contains(CRATE_TEMPLATE)
&& !dl_template.contains(VERSION_TEMPLATE)
&& !dl_template.contains(PREFIX_TEMPLATE)
&& !dl_template.contains(LOWER_PREFIX_TEMPLATE)
&& !dl_template.contains(CHECKSUM_TEMPLATE)
{
// Original format before customizing the download URL was supported.
format!("{dl_template}/{krate}/{version}/download")
} else {
let prefix = make_dep_path(krate, true);
let lowerprefix = prefix.to_lowercase();
dl_template
.replace(CRATE_TEMPLATE, krate)
.replace(VERSION_TEMPLATE, version)
.replace(PREFIX_TEMPLATE, &prefix)
.replace(LOWER_PREFIX_TEMPLATE, &lowerprefix)
.replace(CHECKSUM_TEMPLATE, sha256_checksum)
};

url
}

const CRATE_TEMPLATE: &str = "{crate}";
const VERSION_TEMPLATE: &str = "{version}";
const PREFIX_TEMPLATE: &str = "{prefix}";
const LOWER_PREFIX_TEMPLATE: &str = "{lowerprefix}";
const CHECKSUM_TEMPLATE: &str = "{sha256-checksum}";

#[cfg(test)]
mod tests {
use super::make_dep_path;
Expand Down
33 changes: 7 additions & 26 deletions src/cargo/sources/registry/download.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::util::interning::InternedString;
use anyhow::Context as _;
use cargo_credential::Operation;
use cargo_util::Sha256;
use cargo_util::registry::make_dep_path;
use cargo_util::registry::crate_url;

use crate::core::PackageId;
use crate::core::global_cache_tracker;
Expand All @@ -17,18 +17,11 @@ use crate::util::auth;
use crate::util::cache_lock::CacheLockMode;
use crate::util::errors::CargoResult;
use crate::util::{Filesystem, GlobalContext};
use std::fmt::Write as FmtWrite;
use std::fs::{self, File, OpenOptions};
use std::io::SeekFrom;
use std::io::prelude::*;
use std::str;

const CRATE_TEMPLATE: &str = "{crate}";
const VERSION_TEMPLATE: &str = "{version}";
const PREFIX_TEMPLATE: &str = "{prefix}";
const LOWER_PREFIX_TEMPLATE: &str = "{lowerprefix}";
const CHECKSUM_TEMPLATE: &str = "{sha256-checksum}";

/// Checks if `pkg` is downloaded and ready under the directory at `cache_path`.
/// If not, returns a URL to download it from.
///
Expand Down Expand Up @@ -64,24 +57,12 @@ pub(super) fn download(
}
}

let mut url = registry_config.dl;
if !url.contains(CRATE_TEMPLATE)
&& !url.contains(VERSION_TEMPLATE)
&& !url.contains(PREFIX_TEMPLATE)
&& !url.contains(LOWER_PREFIX_TEMPLATE)
&& !url.contains(CHECKSUM_TEMPLATE)
{
// Original format before customizing the download URL was supported.
write!(url, "/{}/{}/download", pkg.name(), pkg.version()).unwrap();
} else {
let prefix = make_dep_path(&pkg.name(), true);
url = url
.replace(CRATE_TEMPLATE, &*pkg.name())
.replace(VERSION_TEMPLATE, &pkg.version().to_string())
.replace(PREFIX_TEMPLATE, &prefix)
.replace(LOWER_PREFIX_TEMPLATE, &prefix.to_lowercase())
.replace(CHECKSUM_TEMPLATE, checksum);
}
let url = crate_url(
&registry_config.dl,
&*pkg.name(),
&pkg.version().to_string(),
checksum,
);

let authorization = if registry_config.auth_required {
Some(auth::auth_token(
Expand Down
56 changes: 2 additions & 54 deletions src/cargo/sources/registry/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,8 @@ use crate::util::interning::InternedString;
use crate::util::{CargoResult, Filesystem, GlobalContext, LimitErrorReader, restricted_names};
use crate::util::{VersionExt, hex};

pub use cargo_util_schemas::index::RegistryConfig;

/// The `.cargo-ok` file is used to track if the source is already unpacked.
/// See [`RegistrySource::unpack_package`] for more.
///
Expand Down Expand Up @@ -270,55 +272,6 @@ pub struct RegistrySource<'gctx> {
selected_precise_yanked: RefCell<HashSet<(InternedString, semver::Version)>>,
}

/// The [`config.json`] file stored in the index.
///
/// The config file may look like:
///
/// ```json
/// {
/// "dl": "https://example.com/api/{crate}/{version}/download",
/// "api": "https://example.com/api",
/// "auth-required": false # unstable feature (RFC 3139)
/// }
/// ```
///
/// [`config.json`]: https://doc.rust-lang.org/nightly/cargo/reference/registry-index.html#index-configuration
#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct RegistryConfig {
/// Download endpoint for all crates.
///
/// The string is a template which will generate the download URL for the
/// tarball of a specific version of a crate. The substrings `{crate}` and
/// `{version}` will be replaced with the crate's name and version
/// respectively. The substring `{prefix}` will be replaced with the
/// crate's prefix directory name, and the substring `{lowerprefix}` will
/// be replaced with the crate's prefix directory name converted to
/// lowercase. The substring `{sha256-checksum}` will be replaced with the
/// crate's sha256 checksum.
///
/// For backwards compatibility, if the string does not contain any
/// markers (`{crate}`, `{version}`, `{prefix}`, or `{lowerprefix}`), it
/// will be extended with `/{crate}/{version}/download` to
/// support registries like crates.io which were created before the
/// templating setup was created.
///
/// For more on the template of the download URL, see [Index Configuration](
/// https://doc.rust-lang.org/nightly/cargo/reference/registry-index.html#index-configuration).
pub dl: String,

/// API endpoint for the registry. This is what's actually hit to perform
/// operations like yanks, owner modifications, publish new crates, etc.
/// If this is None, the registry does not support API commands.
pub api: Option<String>,

/// Whether all operations require authentication. See [RFC 3139].
///
/// [RFC 3139]: https://rust-lang.github.io/rfcs/3139-cargo-alternative-registry-auth.html
#[serde(default)]
pub auth_required: bool,
}

/// Result from loading data from a registry.
#[derive(Debug, Clone)]
pub enum LoadResponse {
Expand Down Expand Up @@ -965,11 +918,6 @@ impl<'gctx> Source for RegistrySource<'gctx> {
}
}

impl RegistryConfig {
/// File name of [`RegistryConfig`].
const NAME: &'static str = "config.json";
}

/// Get the maximum unpack size that Cargo permits
/// based on a given `size` of your compressed file.
///
Expand Down
Loading