Created
April 11, 2025 22:21
-
-
Save manwar/c6ff473380f5e85622a128dbec60d015 to your computer and use it in GitHub Desktop.
Verify CHECKSUMS of given module and optionally version.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env perl | |
use v5.30; | |
use Safe; | |
use JSON; | |
use LWP::UserAgent; | |
use Digest::SHA qw(sha256_hex); | |
our $VERBOSE = 0; | |
my ($module, $version) = @ARGV; | |
die "Usage: $0 Module::Name [version]" unless $module; | |
my $download_url = get_download_url($module, $version); | |
my ($tarball) = $download_url =~ m|/([^/]+\.tar\.gz)$|; | |
die "Failed to extract tarball name from URL" unless $tarball; | |
my $local_sha256 = get_local_sha256($download_url); | |
print "SHA256 from downloaded tarball: $local_sha256\n" if $VERBOSE; | |
my $cpan_sha256 = get_cpan_sha256($module, $tarball); | |
print "SHA256 from CPAN CHECKSUMS : $cpan_sha256\n" if $VERBOSE; | |
if ($cpan_sha256 eq $local_sha256) { | |
print "Checksums: PASS\n"; | |
} else { | |
print "Checksums: FAIL\n"; | |
} | |
# | |
# | |
# SUBROUTINES | |
sub get_local_sha256 { | |
my ($url) = @_; | |
my $ua = LWP::UserAgent->new; | |
my $res = $ua->get($url); | |
die "Download failed: " . $res->status_line | |
unless $res->is_success; | |
return sha256_hex($res->decoded_content); | |
} | |
sub get_cpan_sha256 { | |
my ($module, $tarball) = @_; | |
my $mod_info = get_module_info($module); | |
my $author = $mod_info->{author}; | |
my $checksums = fetch_checksums_data($author); | |
if (exists $checksums->{$tarball}{'sha256'}) { | |
return $checksums->{$tarball}{'sha256'}; | |
} else { | |
die "No SHA256 found for $tarball in CHECKSUMS\n"; | |
} | |
} | |
sub get_module_info { | |
my ($module) = @_; | |
my $ua = LWP::UserAgent->new; | |
my $url = "https://fastapi.metacpan.org/v1/module/$module"; | |
my $res = $ua->get($url); | |
die "Module fetch error: " . $res->status_line | |
unless $res->is_success; | |
return decode_json($res->decoded_content); | |
} | |
sub get_download_url { | |
my ($module, $version) = @_; | |
# Step 1: Get distribution name | |
my $ua = LWP::UserAgent->new; | |
my $mod_url = "https://fastapi.metacpan.org/v1/module/$module"; | |
my $mod_res = $ua->get($mod_url); | |
die "Error fetching module info: " . $mod_res->status_line | |
unless $mod_res->is_success; | |
my $mod_data = decode_json($mod_res->decoded_content); | |
my $dist = $mod_data->{distribution}; | |
# Step 2: Search for release | |
my $query_url = "https://fastapi.metacpan.org/v1/release/_search"; | |
my $query = { | |
query => { | |
bool => { | |
must => [ | |
{ term => { distribution => $dist } }, | |
($version ? ({ term => { version => $version } }) : ()), | |
] | |
} | |
}, | |
size => 1, | |
sort => [ { date => "desc" } ], | |
}; | |
my $res = $ua->post( | |
$query_url, | |
'Content-Type' => 'application/json', | |
Content => encode_json($query) | |
); | |
die "Error searching release: " . $res->status_line | |
unless $res->is_success; | |
my $data = decode_json($res->decoded_content); | |
if (ref $data->{hits} eq 'HASH' && | |
ref $data->{hits}{hits} eq 'ARRAY' && | |
@{$data->{hits}{hits}}) { | |
my $release = $data->{hits}{hits}[0]{_source}; | |
return $release->{download_url}; | |
} else { | |
die "No matching release found for $dist" . | |
($version ? " version $version" : ""); | |
} | |
} | |
sub fetch_checksums_data { | |
my ($author) = @_; | |
my @chars = split //, $author; | |
my $url = sprintf( | |
"https://cpan.metacpan.org/authors/id/%s/%s/%s/CHECKSUMS", | |
$chars[0], $chars[0] . $chars[1], $author | |
); | |
my $ua = LWP::UserAgent->new; | |
my $res = $ua->get($url); | |
die "Failed to fetch CHECKSUMS: " . $res->status_line | |
unless $res->is_success; | |
my $safe = Safe->new; | |
my $data = $safe->reval($res->decoded_content); | |
die "Failed to eval CHECKSUMS" | |
unless ref $data eq 'HASH'; | |
return $data; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment