Check a single cloud URL for accessibility
(client: &Client, url: &str)
| 538 | |
| 539 | /// Check a single cloud URL for accessibility |
| 540 | async fn check_cloud_url(client: &Client, url: &str) -> Option<CloudAsset> { |
| 541 | // Determine provider |
| 542 | let provider = if url.contains("s3.amazonaws.com") || url.contains("s3-") { |
| 543 | "AWS S3" |
| 544 | } else if url.contains("blob.core.windows.net") { |
| 545 | "Azure Blob" |
| 546 | } else if url.contains("storage.googleapis.com") { |
| 547 | "Google Cloud Storage" |
| 548 | } else if url.contains("digitaloceanspaces.com") { |
| 549 | "DigitalOcean Spaces" |
| 550 | } else if url.contains("backblazeb2.com") { |
| 551 | "Backblaze B2" |
| 552 | } else if url.contains("wasabisys.com") { |
| 553 | "Wasabi" |
| 554 | } else if url.contains("cloudfront.net") { |
| 555 | "CloudFront" |
| 556 | } else { |
| 557 | "Unknown" |
| 558 | }; |
| 559 | |
| 560 | // Extract bucket name |
| 561 | let bucket_name = url |
| 562 | .split('/') |
| 563 | .find(|s| !s.is_empty() && !s.contains("http") && !s.contains(".")) |
| 564 | .unwrap_or("unknown") |
| 565 | .to_string(); |
| 566 | |
| 567 | // Try to access the URL |
| 568 | let resp = client.get(url).send().await.ok()?; |
| 569 | let status = resp.status().as_u16(); |
| 570 | |
| 571 | // Check if listable (common indicator of misconfiguration) |
| 572 | let body = resp.text().await.unwrap_or_default(); |
| 573 | let allows_listing = body.contains("<ListBucketResult") || |
| 574 | body.contains("<EnumerationResults") || |
| 575 | body.contains("Contents") || |
| 576 | body.contains("<Key>"); |
| 577 | |
| 578 | let is_public = status == 200 || status == 403; // 403 means exists but no access |
| 579 | |
| 580 | Some(CloudAsset { |
| 581 | provider: provider.to_string(), |
| 582 | url: url.to_string(), |
| 583 | bucket_name, |
| 584 | is_public: status == 200, |
| 585 | allows_listing, |
| 586 | status_code: status, |
| 587 | }) |
| 588 | } |
| 589 | |
| 590 | /// Save cloud assets to file |
| 591 | pub fn save_to_file(&self, assets: &[CloudAsset], output_path: &Path) -> Result<()> { |
nothing calls this directly
no outgoing calls
no test coverage detected