///|
/// Exact-version Mooncakes metadata used to resolve the source repository.
/// `checksum` authenticates the Mooncakes source archive only.
pub struct RegistryRelease {
module_path : String
version : String
repository : String
checksum : String
}
///|
pub fn mooncakes_manifest_url(
module_path : String,
version : String?,
) -> String raise {
let parts = module_path.split("/").collect()
guard parts.length() == 2 &&
valid_id(parts[0].to_owned()) &&
valid_id(parts[1].to_owned()) else {
fail("Mooncakes module must be owner/module")
}
let version_suffix = match version {
Some(v) => {
guard valid_registry_version(v) else { fail("unsafe Mooncakes version") }
"@" + v
}
None => ""
}
"https://mooncakes.io/api/v0/manifest/\{parts[0]}/\{parts[1]}\{version_suffix}"
}
///|
pub fn mooncakes_source_url(
module_path : String,
version : String,
) -> String raise {
let parts = module_path.split("/").collect()
guard parts.length() == 2 &&
valid_id(parts[0].to_owned()) &&
valid_id(parts[1].to_owned()) &&
valid_registry_version(version) else {
fail("unsafe Mooncakes source coordinate")
}
"https://download.mooncakes.io/user/\{parts[0]}/\{parts[1]}/\{version}.zip"
}
///|
/// Parse the response from the Mooncakes exact-release manifest endpoint.
/// The response's repository and source checksum are never treated as binary
/// artifact metadata.
pub fn resolve_registry_release(
payload : String,
expected_module : String,
expected_version : String?,
) -> RegistryRelease raise {
let document = @json.parse(payload)
guard document
is {
"module": String(actual_module),
"version": String(version),
"metadata": {
"repository": String(repository),
"checksum": String(checksum),
..
},
..
} else {
fail("invalid Mooncakes registry response")
}
guard actual_module == expected_module else {
fail("Mooncakes returned a different module: \{actual_module}")
}
guard valid_registry_version(version) else {
fail("Mooncakes returned an unsafe release version")
}
if expected_version is Some(expected) && version != expected {
fail("Mooncakes returned version \{version}, expected \{expected}")
}
if document is { "yanked": true, .. } {
fail("Mooncakes release is yanked: \{actual_module}@\{version}")
}
guard valid_sha256_hex(checksum) else {
fail("Mooncakes release has no valid source checksum")
}
let (owner, repo) = parse_github_repository(repository)
{
module_path: actual_module,
version,
repository: "https://github.com/\{owner}/\{repo}",
checksum,
}
}
///|
pub fn parse_github_repository(repository : String) -> (String, String) raise {
let prefix = "https://github.com/"
guard repository.has_prefix(prefix) else {
fail("Mooncakes repository must be an HTTPS GitHub URL")
}
let suffix = repository[prefix.length():].to_owned()
guard !suffix.has_suffix("/") &&
!suffix.has_prefix("/") &&
!suffix.contains("?") &&
!suffix.contains("#") else {
fail("unsafe GitHub repository URL")
}
let path = if suffix.has_suffix(".git") {
suffix[:suffix.length() - 4].to_owned()
} else {
suffix
}
let parts = path.split("/").collect()
guard parts.length() == 2 &&
valid_id(parts[0].to_owned()) &&
valid_id(parts[1].to_owned()) else {
fail("unsafe GitHub repository URL")
}
(parts[0].to_owned(), parts[1].to_owned())
}
///|
pub fn valid_sha256_hex(value : String) -> Bool {
if value.length() != 64 {
return false
}
for c in value {
if !((c >= '0' && c <= '9') ||
(c >= 'a' && c <= 'f') ||
(c >= 'A' && c <= 'F')) {
return false
}
}
true
}
///|
pub async fn verify_sha256_hex(path : String, expected : String) -> Unit {
guard valid_sha256_hex(expected) else { fail("invalid SHA-256 checksum") }
let actual = @crypto.bytes_to_hex_string(
@crypto.sha256(@fs.read_file(path).binary()),
)
guard actual == expected.to_lower() else {
fail("SHA-256 verification failed: expected \{expected}, got \{actual}")
}
}