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
159 changes: 3 additions & 156 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion ant-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ toml = { version = "0.8" }
tokio = { version = "1", features = ["sync", "macros"] }
tokio-util = { version = "0.7", features = ["rt"], optional = true }
utoipa = { version = "5", features = ["axum_extras"], optional = true }
zip = { version = "2", optional = true }
# Release .zip archives are Deflate-only. zip decodes them through flate2, on
# the backend the `flate2` dependency above selects.
zip = { version = "9", default-features = false, features = ["deflate-flate2"], optional = true }
tower-http = { version = "0.6.8", features = ["cors"], optional = true }

# Data operations
Expand Down
70 changes: 70 additions & 0 deletions ant-core/src/node/binary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,76 @@ mod tests {
);
}

/// Build an in-memory zip archive from `(entry name, contents)` pairs.
fn zip_archive(entries: &[(&str, &[u8])]) -> Vec<u8> {
let mut writer = zip::ZipWriter::new(std::io::Cursor::new(Vec::new()));
for (name, data) in entries {
writer
.start_file(*name, zip::write::SimpleFileOptions::default())
.unwrap();
std::io::Write::write_all(&mut writer, data).unwrap();
}
writer.finish().unwrap().into_inner()
}

#[test]
fn extract_zip_finds_binary_in_subdirectory() {
// Windows release archives carry `<binary>.exe`, possibly under a folder.
let tmp = tempfile::tempdir().unwrap();
let exe = format!("{BINARY_NAME}.exe");
let data = b"fake binary";
let zip_data = zip_archive(&[(&format!("release/{exe}"), data)]);

let result = extract_zip(&zip_data, tmp.path(), BINARY_NAME).unwrap();
assert_eq!(result.binary_path, tmp.path().join(&exe));
assert_eq!(std::fs::read(&result.binary_path).unwrap(), data);
assert!(result.bootstrap_peers_path.is_none());
}

#[test]
fn extract_zip_finds_bootstrap_peers() {
let tmp = tempfile::tempdir().unwrap();
let bp_data = b"[peers]\naddrs = [\"1.2.3.4:5000\"]\n";
let zip_data = zip_archive(&[
(BINARY_NAME, b"fake binary"),
(BOOTSTRAP_PEERS_FILE, bp_data),
]);

let result = extract_zip(&zip_data, tmp.path(), BINARY_NAME).unwrap();
assert!(result.binary_path.exists());
let bp_path = result.bootstrap_peers_path.unwrap();
assert_eq!(bp_path, tmp.path().join(BOOTSTRAP_PEERS_FILE));
assert_eq!(std::fs::read(&bp_path).unwrap(), bp_data);
}

#[test]
fn extract_zip_missing_binary_errors() {
let tmp = tempfile::tempdir().unwrap();
let zip_data = zip_archive(&[("README.md", b"no binary here")]);

let result = extract_zip(&zip_data, tmp.path(), BINARY_NAME);
assert!(result.is_err());
}

#[test]
fn extract_zip_skips_path_traversal_entry() {
// An entry that escapes the archive root has no enclosed name, so it
// is skipped rather than matched by its final path component.
let tmp = tempfile::tempdir().unwrap();
let install_dir = tmp.path().join("install");
std::fs::create_dir(&install_dir).unwrap();
let zip_data = zip_archive(&[(&format!("../{BINARY_NAME}"), b"malicious content")]);

let result = extract_zip(&zip_data, &install_dir, BINARY_NAME);
let err = result.unwrap_err().to_string();
assert!(
err.contains("not found"),
"expected not-found error, got: {err}"
);
assert!(!tmp.path().join(BINARY_NAME).exists());
assert!(!install_dir.join(BINARY_NAME).exists());
}

#[tokio::test]
async fn resolve_version_uses_cache() {
let tmp = tempfile::tempdir().unwrap();
Expand Down
Loading