Use Gitea Releases for installation and ship VS Code highlighting
This commit is contained in:
1 parent
a46bd6e132
commit
201bdee372
243 files changed
+3289
-12521
No files matched your search
@@ -108,15 +108,17 @@ fn signed_catalog_paths_and_total_size_are_bounded_before_extraction() {
|
||||
|
||||
#[test]
|
||||
fn official_redirects_reject_http_credentials_ports_and_foreign_hosts() {
|
||||
let current = "https://git.dever.cc/shemic/dever/dist/latest/a";
|
||||
let current = "https://git.dever.cc/attachments/a";
|
||||
let origin = url::Url::parse(OFFICIAL_RELEASES).unwrap();
|
||||
assert!(
|
||||
redirect(
|
||||
current,
|
||||
"https://git.dever.cc/shemic/dever/dist/1.2.3/a?token=opaque"
|
||||
"https://git.dever.cc/attachments/b?token=opaque",
|
||||
&origin,
|
||||
)
|
||||
.is_ok()
|
||||
);
|
||||
assert!(redirect(current, "/shemic/dever/dist/1.2.3/a").is_ok());
|
||||
assert!(redirect(current, "/attachments/b", &origin).is_ok());
|
||||
for address in [
|
||||
"http://git.dever.cc/a",
|
||||
"https://evil.example/a",
|
||||
@@ -127,34 +129,109 @@ fn official_redirects_reject_http_credentials_ports_and_foreign_hosts() {
|
||||
"https://github.com/a",
|
||||
"https://release-assets.githubusercontent.com/a",
|
||||
] {
|
||||
assert!(redirect(current, address).is_err(), "{address}");
|
||||
assert!(redirect(current, address, &origin).is_err(), "{address}");
|
||||
assert!(
|
||||
validate_download_url(address, &origin).is_err(),
|
||||
"{address}"
|
||||
);
|
||||
}
|
||||
assert!(validate_download_url("/attachments/b", &origin).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn official_assets_use_exact_platform_names_and_return_the_published_url() {
|
||||
let suffix = "ext-runtime-pip-linux-x86_64.tar.zst";
|
||||
let metadata = release_metadata("https://git.dever.cc", "v1.2.3", &[(suffix, b"archive")]);
|
||||
let release = ResolvedRelease::parse(&metadata, "latest").unwrap();
|
||||
assert_eq!(release.version.as_str(), "1.2.3");
|
||||
assert_eq!(
|
||||
release.asset(suffix).unwrap().browser_download_url,
|
||||
"https://git.dever.cc/attachments/0"
|
||||
);
|
||||
assert!(release.asset("manifest.json").is_err());
|
||||
assert!(
|
||||
release
|
||||
.asset("ext-runtime-pip-linux-aarch64.tar.zst")
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn release_metadata_rejects_unstable_tags_mismatched_versions_and_malformed_assets() {
|
||||
let good: serde_json::Value = serde_json::from_slice(&release_metadata(
|
||||
"https://git.dever.cc",
|
||||
"v1.2.3",
|
||||
&[("tar.zst", b"archive")],
|
||||
))
|
||||
.unwrap();
|
||||
for tag in [
|
||||
"1.2.3",
|
||||
"v01.2.3",
|
||||
"v1.2",
|
||||
"v1.2.3-rc.1",
|
||||
"v1.2.3+build",
|
||||
"v../escape",
|
||||
] {
|
||||
let mut metadata = good.clone();
|
||||
metadata["tag_name"] = tag.into();
|
||||
assert!(
|
||||
ResolvedRelease::parse(&serde_json::to_vec(&metadata).unwrap(), "latest").is_err(),
|
||||
"{tag}"
|
||||
);
|
||||
}
|
||||
assert!(ResolvedRelease::parse(&serde_json::to_vec(&good).unwrap(), "1.2.4").is_err());
|
||||
for field in ["draft", "prerelease"] {
|
||||
let mut metadata = good.clone();
|
||||
metadata[field] = true.into();
|
||||
assert!(ResolvedRelease::parse(&serde_json::to_vec(&metadata).unwrap(), "latest").is_err());
|
||||
metadata.as_object_mut().unwrap().remove(field);
|
||||
assert!(ResolvedRelease::parse(&serde_json::to_vec(&metadata).unwrap(), "latest").is_err());
|
||||
}
|
||||
let asset = good["assets"][0].clone();
|
||||
for assets in [
|
||||
serde_json::json!([]),
|
||||
serde_json::json!([asset, asset]),
|
||||
serde_json::json!(vec![asset.clone(); ASSET_LIMIT + 1]),
|
||||
] {
|
||||
let mut metadata = good.clone();
|
||||
metadata["assets"] = assets;
|
||||
assert!(ResolvedRelease::parse(&serde_json::to_vec(&metadata).unwrap(), "latest").is_err());
|
||||
}
|
||||
for size in [
|
||||
serde_json::json!(0),
|
||||
serde_json::json!(-1),
|
||||
serde_json::json!(1.5),
|
||||
serde_json::json!(RELEASE_LIMIT + 1),
|
||||
serde_json::json!("7"),
|
||||
] {
|
||||
let mut metadata = good.clone();
|
||||
metadata["assets"][0]["size"] = size;
|
||||
assert!(ResolvedRelease::parse(&serde_json::to_vec(&metadata).unwrap(), "latest").is_err());
|
||||
}
|
||||
for name in ["", "../asset", "a/b", "a\\b"] {
|
||||
let mut metadata = good.clone();
|
||||
metadata["assets"][0]["name"] = name.into();
|
||||
assert!(ResolvedRelease::parse(&serde_json::to_vec(&metadata).unwrap(), "latest").is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn official_asset_names_are_versioned_and_platform_specific() {
|
||||
let source = ReleaseSource::official();
|
||||
assert_eq!(
|
||||
source.asset("latest", "manifest.json"),
|
||||
format!(
|
||||
"https://git.dever.cc/shemic/dever/dist/latest/dever-{}.manifest.json",
|
||||
platform_identity()
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
source.asset("1.2.3", "tar.zst"),
|
||||
format!(
|
||||
"https://git.dever.cc/shemic/dever/dist/1.2.3/dever-{}.tar.zst",
|
||||
platform_identity()
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
source.asset("1.2.3", "ext-runtime-pip-linux-x86_64.tar.zst"),
|
||||
format!(
|
||||
"https://git.dever.cc/shemic/dever/dist/1.2.3/dever-{}.ext-runtime-pip-linux-x86_64.tar.zst",
|
||||
platform_identity()
|
||||
)
|
||||
);
|
||||
fn release_asset_reader_requires_the_exact_declared_size() {
|
||||
for size in [2, 4] {
|
||||
let mut reader = AssetReader {
|
||||
reader: io::Cursor::new(b"abc"),
|
||||
remaining: size,
|
||||
};
|
||||
assert!(reader.read_to_end(&mut Vec::new()).is_err());
|
||||
}
|
||||
let mut reader = AssetReader {
|
||||
reader: io::Cursor::new(b"abc"),
|
||||
remaining: 3,
|
||||
};
|
||||
assert_eq!(reader.read(&mut []).unwrap(), 0);
|
||||
let mut bytes = Vec::new();
|
||||
reader.read_to_end(&mut bytes).unwrap();
|
||||
assert_eq!(bytes, b"abc");
|
||||
}
|
||||
|
||||
fn signed_release() -> (ReleaseManifest, Vec<u8>, Vec<u8>, Vec<u8>) {
|
||||
@@ -177,13 +254,57 @@ fn signed_release() -> (ReleaseManifest, Vec<u8>, Vec<u8>, Vec<u8>) {
|
||||
}
|
||||
|
||||
fn server(responses: Vec<Vec<u8>>) -> (ReleaseSource, std::thread::JoinHandle<()>) {
|
||||
release_server("latest", "v1.2.3", responses)
|
||||
}
|
||||
|
||||
fn release_metadata(base: &str, tag: &str, assets: &[(&str, &[u8])]) -> Vec<u8> {
|
||||
serde_json::to_vec(&serde_json::json!({
|
||||
"tag_name": tag,
|
||||
"draft": false,
|
||||
"prerelease": false,
|
||||
"assets": assets.iter().enumerate().map(|(index, (suffix, bytes))| serde_json::json!({
|
||||
"name": format!("dever-{}.{suffix}", platform_identity()),
|
||||
"browser_download_url": format!("{base}/attachments/{index}"),
|
||||
"size": bytes.len(),
|
||||
})).collect::<Vec<_>>(),
|
||||
}))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn release_server(
|
||||
selection: &str,
|
||||
tag: &str,
|
||||
responses: Vec<Vec<u8>>,
|
||||
) -> (ReleaseSource, std::thread::JoinHandle<()>) {
|
||||
serve(|base| {
|
||||
let assets: Vec<_> = ["manifest.json", "manifest.sig", "tar.zst"]
|
||||
.into_iter()
|
||||
.zip(&responses)
|
||||
.map(|(suffix, bytes)| (suffix, bytes.as_slice()))
|
||||
.collect();
|
||||
let metadata = release_metadata(base, tag, &assets);
|
||||
std::iter::once((format!("/releases/{selection}"), metadata))
|
||||
.chain(
|
||||
responses
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(index, bytes)| (format!("/attachments/{index}"), bytes)),
|
||||
)
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
fn serve(
|
||||
responses: impl FnOnce(&str) -> Vec<(String, Vec<u8>)>,
|
||||
) -> (ReleaseSource, std::thread::JoinHandle<()>) {
|
||||
use std::io::BufRead;
|
||||
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
listener.set_nonblocking(true).unwrap();
|
||||
let base = format!("http://{}/releases", listener.local_addr().unwrap());
|
||||
let base = format!("http://{}", listener.local_addr().unwrap());
|
||||
let responses = responses(&base);
|
||||
let worker = std::thread::spawn(move || {
|
||||
let deadline = Instant::now() + Duration::from_secs(5);
|
||||
for (index, response) in responses.into_iter().enumerate() {
|
||||
for (path, response) in responses {
|
||||
let mut stream = loop {
|
||||
match listener.accept() {
|
||||
Ok((stream, _)) => break stream,
|
||||
@@ -203,18 +324,7 @@ fn server(responses: Vec<Vec<u8>>) -> (ReleaseSource, std::thread::JoinHandle<()
|
||||
let mut reader = io::BufReader::new(stream.try_clone().unwrap());
|
||||
let mut request = String::new();
|
||||
reader.read_line(&mut request).unwrap();
|
||||
let (selection, suffix) = [
|
||||
("latest", "manifest.json"),
|
||||
("latest", "manifest.sig"),
|
||||
("1.2.3", "tar.zst"),
|
||||
][index];
|
||||
assert_eq!(
|
||||
request,
|
||||
format!(
|
||||
"GET /releases/{selection}/dever-{}.{suffix} HTTP/1.1\r\n",
|
||||
platform_identity()
|
||||
)
|
||||
);
|
||||
assert_eq!(request, format!("GET {path} HTTP/1.1\r\n"));
|
||||
loop {
|
||||
let mut line = String::new();
|
||||
reader.read_line(&mut line).unwrap();
|
||||
@@ -231,7 +341,123 @@ fn server(responses: Vec<Vec<u8>>) -> (ReleaseSource, std::thread::JoinHandle<()
|
||||
stream.write_all(&response).unwrap();
|
||||
}
|
||||
});
|
||||
(ReleaseSource::new(base), worker)
|
||||
(ReleaseSource::new(format!("{base}/releases")), worker)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn latest_and_extensions_share_one_release_snapshot() {
|
||||
use super::super::{BuildTarget, ExtensionId, ExtensionKind, ExtensionSource};
|
||||
use crate::libs::Ecosystem;
|
||||
let extension = ExtensionId {
|
||||
kind: ExtensionKind::Runtime(Ecosystem::Pip),
|
||||
target: BuildTarget::LinuxX86_64,
|
||||
};
|
||||
let (source, worker) = serve(|base| {
|
||||
vec![
|
||||
(
|
||||
"/releases/latest".into(),
|
||||
release_metadata(base, "v1.2.3", &[(&extension.asset_suffix(), b"extension")]),
|
||||
),
|
||||
("/attachments/0".into(), b"extension".to_vec()),
|
||||
("/attachments/0".into(), b"extension".to_vec()),
|
||||
]
|
||||
});
|
||||
let selected = source.resolve("latest", Instant::now()).unwrap();
|
||||
assert!(Rc::ptr_eq(
|
||||
&selected,
|
||||
&source.resolve("latest", Instant::now()).unwrap()
|
||||
));
|
||||
assert!(Rc::ptr_eq(
|
||||
&selected,
|
||||
&source.resolve("1.2.3", Instant::now()).unwrap()
|
||||
));
|
||||
for _ in 0..2 {
|
||||
let mut contents = Vec::new();
|
||||
source
|
||||
.open(&selected.version, &extension)
|
||||
.unwrap()
|
||||
.read_to_end(&mut contents)
|
||||
.unwrap();
|
||||
assert_eq!(contents, b"extension");
|
||||
}
|
||||
worker.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exact_versions_resolve_the_tag_endpoint_and_missing_or_foreign_assets_fail() {
|
||||
let (source, worker) = serve(|base| {
|
||||
vec![(
|
||||
"/releases/tags/v1.2.3".into(),
|
||||
release_metadata(base, "v1.2.3", &[("tar.zst", b"archive")]),
|
||||
)]
|
||||
});
|
||||
let selected = source.resolve("1.2.3", Instant::now()).unwrap();
|
||||
worker.join().unwrap();
|
||||
assert!(selected.asset("manifest.sig").is_err());
|
||||
let foreign = ReleaseAsset {
|
||||
name: "asset".into(),
|
||||
browser_download_url: "https://evil.example/archive".into(),
|
||||
size: 7,
|
||||
};
|
||||
assert!(
|
||||
source
|
||||
.open_asset(&foreign, Instant::now())
|
||||
.err()
|
||||
.unwrap()
|
||||
.contains("origin")
|
||||
);
|
||||
for version in ["../latest", "v1.2.3", "1.2.3-rc.1"] {
|
||||
assert!(source.resolve(version, Instant::now()).is_err());
|
||||
}
|
||||
let (source, worker) = serve(|_| {
|
||||
vec![(
|
||||
"/releases/latest".into(),
|
||||
release_metadata("https://evil.example", "v1.2.3", &[("tar.zst", b"archive")]),
|
||||
)]
|
||||
});
|
||||
assert!(
|
||||
source
|
||||
.resolve("latest", Instant::now())
|
||||
.err()
|
||||
.unwrap()
|
||||
.contains("origin")
|
||||
);
|
||||
worker.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn release_metadata_downloads_are_bounded_and_asset_lengths_are_checked() {
|
||||
let (source, worker) = serve(|_| {
|
||||
vec![(
|
||||
"/releases/latest".into(),
|
||||
vec![b' '; METADATA_LIMIT as usize + 1],
|
||||
)]
|
||||
});
|
||||
assert!(
|
||||
source
|
||||
.resolve("latest", Instant::now())
|
||||
.err()
|
||||
.unwrap()
|
||||
.contains("size limit")
|
||||
);
|
||||
worker.join().unwrap();
|
||||
let root = Download::new(&std::env::temp_dir()).unwrap();
|
||||
for size in [2, 4] {
|
||||
let (source, worker) = serve(|_| vec![("/attachments/manifest".into(), b"abc".to_vec())]);
|
||||
let asset = ReleaseAsset {
|
||||
name: "asset".into(),
|
||||
browser_download_url: source.base.join("/attachments/manifest").unwrap().into(),
|
||||
size,
|
||||
};
|
||||
assert!(
|
||||
source
|
||||
.metadata(&asset, &root.0.join("manifest.json"), Instant::now())
|
||||
.unwrap_err()
|
||||
.contains("size")
|
||||
);
|
||||
worker.join().unwrap();
|
||||
assert!(!root.0.join("manifest.json").exists());
|
||||
}
|
||||
}
|
||||
|
||||
fn compressed_release(core: &[u8]) -> Vec<u8> {
|
||||
@@ -396,6 +622,31 @@ fn explicit_download_verifies_the_complete_bundle_before_publishing_catalog() {
|
||||
assert_eq!(fs::read_dir(layout.staging()).unwrap().count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn latest_manifest_must_match_the_selected_tag_before_publication() {
|
||||
let root = Download::new(&std::env::temp_dir()).unwrap();
|
||||
let layout = Layout::new(root.0.join("machine"));
|
||||
layout.initialize().unwrap();
|
||||
let (_, bytes, signature, key) = signed_release();
|
||||
fs::write(
|
||||
layout.state().join("trusted-release-key"),
|
||||
super::super::release::hex(&key),
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(layout.downloads().join("latest"), "0.9.0\n").unwrap();
|
||||
let (source, worker) = release_server("latest", "v1.2.4", vec![bytes, signature]);
|
||||
let error = prepare_from(&layout, "latest", &source).unwrap_err();
|
||||
worker.join().unwrap();
|
||||
assert!(error.contains("selected release tag"), "{error}");
|
||||
assert_eq!(
|
||||
fs::read_to_string(layout.downloads().join("latest")).unwrap(),
|
||||
"0.9.0\n"
|
||||
);
|
||||
assert!(!layout.downloads().join("1.2.3").exists());
|
||||
assert!(!layout.downloads().join("1.2.4").exists());
|
||||
assert_eq!(fs::read_dir(layout.staging()).unwrap().count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_signatures_and_corrupt_payloads_preserve_the_previous_catalog_and_active_version() {
|
||||
for bad_signature in [true, false] {
|
||||
|
||||
Reference in new issue
Block a user