~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/knotless

git that lives in your Atmosphere account

knotless.dev
1 collaborator

knotless 0.13.0 Latest 2 packages, 1 image

1
10

Cargo publish reads at most 1 MiB of Cargo.toml and succeeds once made

Merged chadtmiller.com asked to merge into main from chadtmiller.com/knotless:serve/cargo-publish
crates/knotless-serve/src/web/cargo.rs+22−10
@@ -42,6 +42,10 @@ const MOST_UNPACKED: u64 = 1 << 30;
/// The longest index line a release holds, as its lexicon has it.
const MOST_LINE: usize = 100_000;
/// The most of a crate's `Cargo.toml` read: what `cargo package` writes is
/// a few KiB.
const MOST_MANIFEST: u64 = 1 << 20;
/// The most characters of a crate's description a release holds.
const MOST_DESCRIPTION: usize = 1000;
@@ -153,6 +157,9 @@ fn manifest_of(crate_: &[u8]) -> Option<String> {
let (name, size) = (field(0, 100), field(124, 136));
let size = u64::from_str_radix(&size, 8).ok()?;
if name.split('/').count() == 2 && name.ends_with("/Cargo.toml") {
if size > MOST_MANIFEST {
return None;
}
let mut text = String::new();
tar.by_ref().take(size).read_to_string(&mut text).ok()?;
return Some(text);
@@ -342,7 +349,8 @@ pub(super) async fn publish(
let (name, version) = (said.name.as_str(), said.vers.as_str());
let file = format!("{name}-{version}.crate");
release::check_names(name, version, &[&file]).map_err(refused)?;
let manifest = manifest_of(crate_).ok_or_else(|| refused("the crate holds no Cargo.toml"))?;
let manifest = manifest_of(crate_)
.ok_or_else(|| refused("the crate holds no Cargo.toml of at most 1 MiB"))?;
if package_of(&manifest) != Some((name, version)) {
let says = format!("what was said is {name} {version}, which its Cargo.toml is not");
return Err(refused(says));
@@ -375,22 +383,21 @@ pub(super) async fn publish(
..Made::default()
};
target.create(&auth, files, made).await.map_err(upstream)?;
// It is published now, so what follows only hastens what the feed
// brings: a failure to read it back or index it is not the publish's,
// and answering one would have a retry told it is published already.
let rkey = keys::release_rkey(&key, name, version);
let got = target
.remote
.get_record::<Value>(nsid::RELEASE, &rkey)
.await;
let got = got.map_err(upstream)?;
let index = app.index.as_ref().expect("opened with an index");
let put = got.map(|record| index.put(&did, nsid::RELEASE, &rkey, &record.value));
let put = put.transpose().map_err(anyhow::Error::from);
put.map_err(upstream)?;
let got = got.ok().flatten();
let _ = got.map(|record| index.put(&did, nsid::RELEASE, &rkey, &record.value));
// Build metadata makes another record's key, so only now can a version
// be seen to have been published already under another's.
let all = index
.releases(&did, &key)
.map_err(anyhow::Error::from)
.map_err(upstream)?;
// be seen to have been published already under another's. Should the
// index not say, both stay, and it answers the oldest.
let all = index.releases(&did, &key).unwrap_or_default();
let found = crates(&all, name);
let first = found.iter().find(|r| base(&r.version) == base(version));
if let Some(first) = first.filter(|r| r.version != version) {
@@ -528,6 +535,11 @@ mod tests {
assert_eq!(package_of(&found), Some(("a", "1.0.0")));
assert_eq!(manifest_of(&tarball(&[("a-1.0.0/x", "x")])), None);
assert_eq!(manifest_of(b"not gzip"), None);
let large = "#".repeat(MOST_MANIFEST as usize + 1);
assert_eq!(
manifest_of(&tarball(&[("a-1.0.0/Cargo.toml", &large)])),
None
);
let bad = tarball(&[("a-1.0.0/x", "x")]);
let mut tar = Vec::new();
GzDecoder::new(&bad[..]).read_to_end(&mut tar).unwrap();
docs/design/packages.md+4−1
@@ -304,7 +304,10 @@ fewer. Only `yanked` is added, as it is read, from the
**What it reads is what the index has,** so a takedown holds and a private
repository has none. `cargo publish` waits up to a minute to find its crate,
and warns if it does not, so a publish and a yank put the record in the index
themselves, without waiting to hear of it.
themselves, without waiting to hear of it. Once its record is made, a publish
succeeds even if that fails: the version is out, the feed indexes it soon
after, and a failure answered then would have a retry told it is published
already.
**One version, however its build metadata differs.** Cargo's index holds one
of `1.0.0` and `1.0.0+b`, which have different record keys, so creating the
docs/reference/api.md+1−1
@@ -526,7 +526,7 @@ password>`, which logs in to the owner's PDS.
| `GET config.json` | `{dl, api}`, at the request's origin |
| `GET <index path>` | a crate's file of the index, by Cargo's layout (`1/a`, `2/ab`, `3/a/abc`, `ab/cd/abcd`): a line per version, the oldest first, `no-cache`, with an `ETag`; 304 to one seen, 404 for none |
| `GET dl/<name>/<version>/download` | the `.crate`, as a release's files are sent |
| `PUT api/v1/crates/new` | as Cargo publishes: the crate uploaded to the owner's PDS and its release made, at most 64 MiB. 400 for a body that is not one, a name or version a release does not take, a `Cargo.toml` that says otherwise, an index line over 100,000 bytes, or a version published already |
| `PUT api/v1/crates/new` | as Cargo publishes: the crate uploaded to the owner's PDS and its release made, at most 64 MiB. 400 for a body that is not one, a name or version a release does not take, a `Cargo.toml` missing, over 1 MiB or saying otherwise, an index line over 100,000 bytes, or a version published already. Once the release is made it answers 200, even should reading it back or indexing it fail, as the feed indexes it soon after |
| `DELETE api/v1/crates/<name>/<version>/yank`, `PUT .../unyank` | `{ok: true}`: [yanks](../design/packages.md#yanking) it, or takes that back |
| `GET api/v1/crates?q=&per_page=` | the crates whose names hold `q`, each at its latest: `{crates: [{name, max_version, description}], meta: {total}}`, 10 by default, 100 at most |