App-MechaCPAN
view release on metacpan or search on metacpan
lib/App/MechaCPAN.pm view on Meta::CPAN
my ( $file, $keyring ) = @_;
run_qvf( "sq", "verify", "--signer-file", "$keyring", "$file" );
};
},
sub
{
return
if !eval { run(qw/rnp --version/); 1 };
return sub
{
my ( $file, $keyring ) = @_;
run_qvf( "rnp", "--keyfile", "$keyring", "--verify", "$file" );
};
},
);
sub _resolve_verifier
{
state $verify_fn;
if ( !defined $verify_fn )
{
foreach my $verify (@verifier)
{
my $fn = $verify->();
if ( defined $fn )
{
$verify_fn = $fn;
last;
}
}
}
return $verify_fn;
}
sub get_cpan_checksums
{
my $url = shift;
my $key = shift;
# If verify is off, don't check any part of the CHECKSUMS
return
if defined $CHKSIGS && !$CHKSIGS;
die "CHECKSUMS URL must be HTTPS, not '$url'"
unless $url =~ m{\Ahttps://}xmsi;
die "CHECKSUMS URL must end with CHECKSUMS, not '$url'"
unless $url =~ m{CHECKSUMS\z}xmsi;
# Even if verification is optional, not downloading the CHECKSUMS is fatal
my $checksums = '';
fetch_file( $url, \$checksums );
$checksums = App::MechaCPAN::_verify_checksums_body($checksums);
# If $CHKSIGS is true, we must verify that CHECKSUMS has a valid signature
# If $CHKSIGS is undef, we will verify that CHECKSUMS has a valid signature
# if possible, but won't throw errors if can't.
# In both cases, the CHECKSUMS is used to validate the checksums of the
# downloaded file
# If $CHKSIGS is otherwise false, we already did an early return above
my $verify_fn = _resolve_verifier();
if ( !defined $verify_fn )
{
die "Could not find verification program and verification was required"
if $CHKSIGS;
logmsg "CHECKSUMS signature not checked: PGP verifier not found";
}
else
{
my $keyring = cpan_keyring();
my $chk_file = humane_tmpfile('CHECKSUMS');
$chk_file->print($checksums);
$chk_file->flush;
$verify_fn->( "$chk_file", $keyring );
logmsg "VALID: $url";
}
my $result = do
{
require Safe;
my $safe = Safe->new("App::MechaCPAN::reval");
local $@;
my $chksum = $safe->reval($checksums);
die "Failed to parse CHECKSUMS: $@"
if $@;
die "Failed to get HASH from CHECKSUMS"
if ref $chksum ne 'HASH';
$chksum;
};
# If a module name is passed ($key), then we check if it is listed in the
# CHECKSUMS file. If it is missing, it depends on $CHKSIGS. If it is true
# (strict verify), an error will be produced. If it is undef (best-effort)
# then a log entry will be added, and nothing will be returned, whcih will
# match the no-verify path above.
if ( defined $key && !exists $result->{$key} )
{
die "Could not find module '$key' in CHECKSUMS"
if $CHKSIGS;
logmsg "Could not find '$key' in CHECKSUMS, not attempting to verify";
return;
}
return $result;
}
my @inflate = (
# System tar
sub
{
my $src = shift;
lib/App/MechaCPAN.pm view on Meta::CPAN
$tar = defined $tar_version_str;
}
return
unless $tar;
my $unzip
= $src =~ m/gz $humane_qr? $/xms ? 'gzip'
: $src =~ m/(bz2|bzip2) $humane_qr? $/xms ? 'bzip2'
: 'xz';
# Instead of relying on a shell to pipe contents from unzip to tar, both
# gnutar and bsdtar accept the use-compress-program option. bsdtar needs
# the -d option so it will decompress, while gnutar automatically adds it
# but it is harmless to add.
run( 'tar', '--use-compress-program', "$unzip -d", '-xf', $src );
return 1;
},
# Archive::Tar
sub
{
my $src = shift;
require Archive::Tar;
my $tar = Archive::Tar->new;
$tar->error(1);
my $ret = $tar->read( "$src", 1, { extract => 1 } );
die $tar->error
unless $ret;
},
);
sub _path_escapes_dir
{
my $path = shift;
return 0
if !defined $path;
return 0
if !length $path;
return 1
if File::Spec->file_name_is_absolute($path);
for my $part ( File::Spec->splitdir($path) )
{
return 1
if $part eq '..';
}
return 0;
}
# Inspect a tar archive's header entries (no extraction). Returns a
# human-readable reason if any entry would escape the destination dir
# (absolute paths, '..' traversal in names, or symlink/hardlink targets
# pointing outside). Returns undef if the archive is safe.
sub _validate_archive_is_safe
{
my $src = shift;
require Archive::Tar;
# Create a tar iterator. The magic 1 tells it to decompress
my $iter = Archive::Tar->iter( "$src", 1 );
return "could not read archive: $src"
if !defined $iter;
while ( my $entry = $iter->() )
{
my $name = $entry->full_path;
return "unsafe entry name: '$name'"
if _path_escapes_dir($name);
if ( $entry->is_symlink || $entry->is_hardlink )
{
my $linkname = $entry->linkname;
return "unsafe link target: '$name' -> '$linkname'"
if _path_escapes_dir($linkname);
}
}
return;
}
sub inflate_archive
{
my $src = shift;
my $dir = shift;
my $sha256;
if ( ref $src eq 'HASH' )
{
$sha256 = delete $src->{sha256};
# Technically the error is wrong, it can have an optional sha256 key,
# but it gets people here to see that the error is that there is a
# narrow requirement for the hashref version of inflate_archive
die "Hashref for inflate_archive must be only the 'src' key"
if scalar( keys %$src ) != 1 || !exists $src->{src};
$src = $src->{src};
}
# $src can be a file path or a URL.
if ( !-e "$src" )
{
$src = fetch_file($src);
}
if ($sha256)
{
file_digest_chk( $src, $sha256 );
logmsg "Digest passed: $src";
}
if ( !defined $dir )
{
my $descr = ( File::Spec->splitpath($src) )[2];
$dir = humane_tmpdir($descr);
}
die "Could not find destination directory: $dir"
if !-d $dir;
if ( my $reason = _validate_archive_is_safe($src) )
{
croak "Refusing to extract unsafe archive $src: $reason\n";
}
my $orig = cwd;
my $is_complete;
foreach my $inflate_sub (@inflate)
{
local $@;
my $success;
my $error_free = eval {
chdir $dir;
$success = $inflate_sub->($src);
1;
};
my $err = $@;
my @glob = glob('*');
chdir $orig;
logmsg $err
unless $error_free;
if ($success && @glob > 0)
{
$is_complete = 1;
last;
}
}
if ( !$is_complete )
{
croak "Could not unpack archive: $src\n";
}
# If there's only 1 file and it's a directory, go ahead and chdir into it
my @files = glob("$dir/*");
if ( @files == 1 && -d $files[0] )
{
$dir = $files[0];
}
return $dir;
}
sub _genio
{
state $iswin32 = $^O eq 'MSWin32';
my $write_hdl;
my $read_hdl;
if ($iswin32)
{
use Socket;
socketpair( $read_hdl, $write_hdl, AF_UNIX, SOCK_STREAM, PF_UNSPEC );
shutdown( $read_hdl, 1 );
shutdown( $write_hdl, 0 );
}
( run in 0.707 second using v1.01-cache-2.11-cpan-6aa56a78535 )