use regex::Regex;
use std::sync::LazyLock;
static BLOB_URL_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"^(.*)://(.*)/v2/(.*)/blobs/([^?]+)(?:\?.*)?$").unwrap());
pub fn is_blob_url(url: &str) -> bool {
BLOB_URL_REGEX.is_match(url)
}
pub fn extract_encoded_from_blob_url(url: &str) -> Option<String> {
let digest = BLOB_URL_REGEX
.captures(url)
.and_then(|caps| caps.get(4))
.map(|m| m.as_str())?;
let (algorithm, encoded) = digest.split_once(':')?;
let expected_len = match algorithm {
"crc32" => 10,
"sha256" => 64,
"sha512" => 128,
_ => return None,
};
if encoded.len() != expected_len {
return None;
}
Some(encoded.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_blob_url() {
assert!(is_blob_url(
"https://registry.example.com/v2/library/ubuntu/blobs/sha256:b2c366cce7e68013d5441c6326d5a3e1b12aeb5ed58564d0fd3fa089bc29cb6e"
));
assert!(is_blob_url(
"http://registry.example.com/v2/myorg/myrepo/blobs/sha256:abcdef?ns=docker.io"
));
assert!(!is_blob_url(
"https://registry.example.com/v2/library/ubuntu/manifests/latest"
));
assert!(!is_blob_url("https://example.com/file.txt"));
}
#[test]
fn test_extract_encoded_from_blob_url() {
assert_eq!(
extract_encoded_from_blob_url(
"https://registry.example.com/v2/library/ubuntu/blobs/sha256:b2c366cce7e68013d5441c6326d5a3e1b12aeb5ed58564d0fd3fa089bc29cb6e"
),
Some("b2c366cce7e68013d5441c6326d5a3e1b12aeb5ed58564d0fd3fa089bc29cb6e".to_string())
);
assert_eq!(
extract_encoded_from_blob_url(
"https://registry.example.com/v2/library/ubuntu/blobs/sha256:b2c366cce7e68013d5441c6326d5a3e1b12aeb5ed58564d0fd3fa089bc29cb6e?ns=docker.io"
),
Some("b2c366cce7e68013d5441c6326d5a3e1b12aeb5ed58564d0fd3fa089bc29cb6e".to_string())
);
assert_eq!(
extract_encoded_from_blob_url(
"https://registry.example.com/v2/library/ubuntu/blobs/md5:abcdef"
),
None
);
assert_eq!(
extract_encoded_from_blob_url(
"https://registry.example.com/v2/library/ubuntu/blobs/sha256:abcdef"
),
None
);
assert_eq!(
extract_encoded_from_blob_url("https://example.com/file.txt"),
None
);
}
}