Wave 4: Full CLI command implementations - fetch, list, show, search, tags, aliases, doctor, cache lifecycle (bd-16o, bd-3km, bd-1dj, bd-acf, bd-3bl, bd-30a, bd-2s6, bd-1d4)
This commit is contained in:
@@ -72,19 +72,18 @@ fn is_blocked_mapped_v4(v6: &std::net::Ipv6Addr) -> bool {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn validate_url(url: &str, allow_insecure_http: bool) -> Result<Url, SwaggerCliError> {
|
||||
let parsed = Url::parse(url).map_err(|e| {
|
||||
SwaggerCliError::InvalidSpec(format!("invalid URL '{url}': {e}"))
|
||||
})?;
|
||||
let parsed = Url::parse(url)
|
||||
.map_err(|e| SwaggerCliError::InvalidSpec(format!("invalid URL '{url}': {e}")))?;
|
||||
|
||||
match parsed.scheme() {
|
||||
"https" => Ok(parsed),
|
||||
"http" if allow_insecure_http => Ok(parsed),
|
||||
"http" => Err(SwaggerCliError::PolicyBlocked(
|
||||
format!("HTTP is not allowed for '{url}'. Use --allow-insecure-http to override."),
|
||||
)),
|
||||
other => Err(SwaggerCliError::InvalidSpec(
|
||||
format!("unsupported scheme '{other}' in URL '{url}'"),
|
||||
)),
|
||||
"http" => Err(SwaggerCliError::PolicyBlocked(format!(
|
||||
"HTTP is not allowed for '{url}'. Use --allow-insecure-http to override."
|
||||
))),
|
||||
other => Err(SwaggerCliError::InvalidSpec(format!(
|
||||
"unsupported scheme '{other}' in URL '{url}'"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,16 +104,16 @@ async fn resolve_and_check(
|
||||
let addrs: Vec<_> = match lookup_host(&addr).await {
|
||||
Ok(iter) => iter.collect(),
|
||||
Err(e) => {
|
||||
return Err(SwaggerCliError::InvalidSpec(
|
||||
format!("DNS resolution failed for '{host}': {e}"),
|
||||
));
|
||||
return Err(SwaggerCliError::InvalidSpec(format!(
|
||||
"DNS resolution failed for '{host}': {e}"
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
if addrs.is_empty() {
|
||||
return Err(SwaggerCliError::InvalidSpec(
|
||||
format!("DNS resolution returned no addresses for '{host}'"),
|
||||
));
|
||||
return Err(SwaggerCliError::InvalidSpec(format!(
|
||||
"DNS resolution returned no addresses for '{host}'"
|
||||
)));
|
||||
}
|
||||
|
||||
for socket_addr in &addrs {
|
||||
@@ -178,9 +177,9 @@ impl AsyncHttpClient {
|
||||
pub async fn fetch_spec(&self, url: &str) -> Result<FetchResult, SwaggerCliError> {
|
||||
let parsed = validate_url(url, self.allow_insecure_http)?;
|
||||
|
||||
let host = parsed.host_str().ok_or_else(|| {
|
||||
SwaggerCliError::InvalidSpec(format!("URL '{url}' has no host"))
|
||||
})?;
|
||||
let host = parsed
|
||||
.host_str()
|
||||
.ok_or_else(|| SwaggerCliError::InvalidSpec(format!("URL '{url}' has no host")))?;
|
||||
let port = parsed.port_or_known_default().unwrap_or(443);
|
||||
|
||||
resolve_and_check(host, port, &self.allowed_private_hosts).await?;
|
||||
@@ -215,11 +214,7 @@ impl AsyncHttpClient {
|
||||
attempts += 1;
|
||||
if attempts > self.max_retries {
|
||||
return Err(SwaggerCliError::Network(
|
||||
client
|
||||
.get(url)
|
||||
.send()
|
||||
.await
|
||||
.unwrap_err(),
|
||||
client.get(url).send().await.unwrap_err(),
|
||||
));
|
||||
}
|
||||
let delay = self.retry_delay(&response, attempts);
|
||||
@@ -370,7 +365,9 @@ mod tests {
|
||||
#[test]
|
||||
fn test_ssrf_blocks_loopback() {
|
||||
assert!(is_ip_blocked(&IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))));
|
||||
assert!(is_ip_blocked(&IpAddr::V4(Ipv4Addr::new(127, 255, 255, 254))));
|
||||
assert!(is_ip_blocked(&IpAddr::V4(Ipv4Addr::new(
|
||||
127, 255, 255, 254
|
||||
))));
|
||||
assert!(is_ip_blocked(&IpAddr::V6(Ipv6Addr::LOCALHOST)));
|
||||
}
|
||||
|
||||
@@ -392,7 +389,9 @@ mod tests {
|
||||
#[test]
|
||||
fn test_ssrf_blocks_link_local() {
|
||||
// IPv4 link-local (169.254.x.x) -- includes the AWS metadata endpoint
|
||||
assert!(is_ip_blocked(&IpAddr::V4(Ipv4Addr::new(169, 254, 169, 254))));
|
||||
assert!(is_ip_blocked(&IpAddr::V4(Ipv4Addr::new(
|
||||
169, 254, 169, 254
|
||||
))));
|
||||
assert!(is_ip_blocked(&IpAddr::V4(Ipv4Addr::new(169, 254, 0, 1))));
|
||||
|
||||
// IPv6 link-local (fe80::/10)
|
||||
@@ -441,10 +440,7 @@ mod tests {
|
||||
fn test_url_allows_https() {
|
||||
let result = validate_url("https://example.com/spec.json", false);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(
|
||||
result.unwrap().as_str(),
|
||||
"https://example.com/spec.json"
|
||||
);
|
||||
assert_eq!(result.unwrap().as_str(), "https://example.com/spec.json");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -457,7 +453,10 @@ mod tests {
|
||||
fn test_url_rejects_unsupported_scheme() {
|
||||
let result = validate_url("ftp://example.com/spec.json", false);
|
||||
assert!(result.is_err());
|
||||
assert!(matches!(result.unwrap_err(), SwaggerCliError::InvalidSpec(_)));
|
||||
assert!(matches!(
|
||||
result.unwrap_err(),
|
||||
SwaggerCliError::InvalidSpec(_)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -505,8 +504,7 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_resolve_and_check_skips_allowed_host() {
|
||||
let result =
|
||||
resolve_and_check("localhost", 80, &["localhost".into()]).await;
|
||||
let result = resolve_and_check("localhost", 80, &["localhost".into()]).await;
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user