diff --git a/dev-tools/src/main/resources/license-check/check_license_and_sha.pl b/dev-tools/src/main/resources/license-check/check_license_and_sha.pl
index 9a865afde53..824940ab2b5 100755
--- a/dev-tools/src/main/resources/license-check/check_license_and_sha.pl
+++ b/dev-tools/src/main/resources/license-check/check_license_and_sha.pl
@@ -3,26 +3,45 @@
use strict;
use warnings;
use v5.10;
+
+use FindBin qw($RealBin);
+use lib "$RealBin/lib";
+use Archive::Ar();
+use Cwd();
+use File::Spec();
use Digest::SHA qw(sha1);
use File::Temp();
use File::Basename qw(basename);
-use File::Find();
-my $mode = shift(@ARGV) || die usage();
-my $dir = shift(@ARGV) || die usage();
-$dir =~ s{/$}{};
+use Archive::Extract();
+$Archive::Extract::PREFER_BIN = 1;
-our $RELEASES_DIR = "$dir/target/releases/";
-our $LICENSE_DIR = "$dir/licenses/";
+our %Extract_Package = (
+ zip => \&extract_zip,
+ gz => \&extract_tar_gz,
+ rpm => \&extract_rpm,
+ deb => \&extract_deb
+);
-$mode eq '--check' ? check_shas_and_licenses($dir)
- : $mode eq '--update' ? write_shas($dir)
- : die usage();
+my $mode = shift(@ARGV) || "";
+die usage() unless $mode =~ /^--(check|update)$/;
+
+my $License_Dir = shift(@ARGV) || die usage();
+my $Package = shift(@ARGV) || die usage();
+$License_Dir = File::Spec->rel2abs($License_Dir) . '/';
+$Package = File::Spec->rel2abs($Package);
+
+die "License dir is not a directory: $License_Dir\n" . usage()
+ unless -d $License_Dir;
+
+my %shas = get_shas_from_package($Package);
+$mode eq '--check'
+ ? exit check_shas_and_licenses(%shas)
+ : exit write_shas(%shas);
#===================================
sub check_shas_and_licenses {
#===================================
- my %new = get_shas_from_zip();
- check_tar_has_same_shas(%new);
+ my %new = @_;
my %old = get_sha_files();
my %licenses = get_files_with('LICENSE');
@@ -41,7 +60,8 @@ sub check_shas_and_licenses {
}
unless ( $old_sha eq $new{$jar} ) {
- say STDERR "$jar: SHA has changed, expected $old_sha but found $new{$jar}";
+ say STDERR
+ "$jar: SHA has changed, expected $old_sha but found $new{$jar}";
$error++;
$sha_error++;
next;
@@ -101,18 +121,18 @@ sub check_shas_and_licenses {
You can update the SHA files by running:
- $0 --update core
+$0 --update $License_Dir $Package
SHAS
}
-
- exit $error;
+ say "All SHAs and licenses OK" unless $error;
+ return $error;
}
#===================================
sub write_shas {
#===================================
- my %new = get_shas_from_zip();
+ my %new = @_;
my %old = get_sha_files();
for my $jar ( sort keys %new ) {
@@ -123,7 +143,7 @@ sub write_shas {
else {
say "Adding $jar";
}
- open my $fh, '>', $LICENSE_DIR . $jar or die $!;
+ open my $fh, '>', $License_Dir . $jar or die $!;
say $fh $new{$jar} or die $!;
close $fh or die $!;
}
@@ -133,8 +153,10 @@ sub write_shas {
for my $jar ( sort keys %old ) {
say "Deleting $jar";
- unlink $LICENSE_DIR . $jar or die $!;
+ unlink $License_Dir . $jar or die $!;
}
+ say "SHAs updated";
+ return 0;
}
#===================================
@@ -142,7 +164,7 @@ sub get_files_with {
#===================================
my $pattern = shift;
my %files;
- for my $path ( grep {-f} glob("$LICENSE_DIR/*$pattern*") ) {
+ for my $path ( grep {-f} glob("$License_Dir/*$pattern*") ) {
my ($file) = ( $path =~ m{([^/]+)-${pattern}.*$} );
$files{$file} = 0;
}
@@ -154,10 +176,10 @@ sub get_sha_files {
#===================================
my %shas;
- die "Missing directory: $LICENSE_DIR\n"
- unless -d $LICENSE_DIR;
+ die "Missing directory: $License_Dir\n"
+ unless -d $License_Dir;
- for my $file ( grep {-f} glob("$LICENSE_DIR/*.sha1") ) {
+ for my $file ( grep {-f} glob("$License_Dir/*.sha1") ) {
my ($jar) = ( $file =~ m{([^/]+)$} );
open my $fh, '<', $file or die $!;
my $sha = <$fh>;
@@ -169,58 +191,82 @@ sub get_sha_files {
}
#===================================
-sub get_shas_from_zip {
+sub get_shas_from_package {
#===================================
- my ($zip) = glob("$RELEASES_DIR/elasticsearch*.zip")
- or die "No .zip file found in $RELEASES_DIR\n";
+ my $package = shift;
+ my ($type) = ( $package =~ /\.(\w+)$/ );
+ die "Unrecognised package type: $package"
+ unless $type && $Extract_Package{$type};
my $temp_dir = File::Temp->newdir;
- my $dir_name = $temp_dir->dirname;
- system( 'unzip', "-j", "-q", $zip, "*.jar", "-d" => $dir_name )
- && die "Error unzipping <$zip> to <" . $dir_name . ">: $!\n";
+ my $files
+ = eval { $Extract_Package{$type}->( $package, $temp_dir->dirname ) }
+ or die "Couldn't extract $package: $@";
- my @jars = grep { !/\/elasticsearch[^\/]+.jar$/ } glob "$dir_name/*.jar";
+ my @jars = map {"$temp_dir/$_"}
+ grep { /\.jar$/ && !/elasticsearch[^\/]*$/ } @$files;
return calculate_shas(@jars);
}
#===================================
-sub check_tar_has_same_shas {
+sub extract_zip {
#===================================
- my %zip_shas = @_;
- my ($tar) = glob("$RELEASES_DIR/elasticsearch*.tar.gz")
- or return;
+ my ( $package, $dir ) = @_;
+ my $archive = Archive::Extract->new( archive => $package, type => 'zip' );
+ $archive->extract( to => $dir ) || die $archive->error;
+ return $archive->files;
+}
- my $temp_dir = File::Temp->newdir;
- my $dir_name = $temp_dir->dirname;
- system( 'tar', "-xz", "-C" => $dir_name, "-f" => $tar )
- && die "Error unpacking <$tar> to <" . $dir_name . ">: $!\n";
+#===================================
+sub extract_tar_gz {
+#===================================
+ my ( $package, $dir ) = @_;
+ my $archive = Archive::Extract->new( archive => $package, type => 'tgz' );
+ $archive->extract( to => $dir ) || die $archive->error;
+ return $archive->files;
+}
- my @jars;
- File::Find::find(
- { wanted =>
- sub { push @jars, $_ if /\.jar$/ && !/elasticsearch[^\/]*$/ },
- no_chdir => 1
- },
- $dir_name
+#===================================
+sub extract_rpm {
+#===================================
+ my ( $package, $dir ) = @_;
+ my $cwd = Cwd::cwd();
+ my @files;
+ eval {
+ chdir $dir;
+ say "Trying with rpm2cpio";
+ my $out = eval {`rpm2cpio '$package' | cpio -idmv --quiet`};
+ unless ($out) {
+ say "Trying with rpm2cpio.pl";
+ $out = eval {`rpm2cpio.pl '$package' | cpio -idmv --quiet`};
+ }
+ @files = split "\n", $out if $out;
+ };
+ chdir $cwd;
+ die $@ if $@;
+ die "Couldn't extract $package\n" unless @files;
+ return \@files;
+}
+
+#===================================
+sub extract_deb {
+#===================================
+ my ( $package, $dir ) = @_;
+ my $archive = Archive::Ar->new;
+ $archive->read($package) || die $archive->error;
+ my $cwd = Cwd::cwd();
+ eval {
+ chdir $dir;
+ $archive->extract('data.tar.gz') || die $archive->error;
+ };
+ chdir $cwd;
+ die $@ if $@;
+ $archive = Archive::Extract->new(
+ archive => $dir . '/data.tar.gz',
+ type => 'tgz'
);
-
- my %tar_shas = calculate_shas(@jars);
- my @errors;
- for ( sort keys %zip_shas ) {
- my $sha = delete $tar_shas{$_};
- if ( !$sha ) {
- push @errors, "$_: JAR present in zip but not in tar.gz";
- }
- elsif ( $sha ne $zip_shas{$_} ) {
- push @errors, "$_: JAR in zip and tar.gz are different";
- }
- }
- for ( sort keys %tar_shas ) {
- push @errors, "$_: JAR present in tar.gz but not in zip";
- }
- if (@errors) {
- die join "\n", @errors;
- }
+ $archive->extract( to => $dir ) || die $archive->error;
+ return $archive->files;
}
#===================================
@@ -242,11 +288,13 @@ sub usage {
USAGE:
- $0 --check dir # check the sha1 and LICENSE files for each jar
- $0 --update dir # update the sha1 files for each jar
+ # check the sha1 and LICENSE files for each jar in the zip|gz|deb|rpm
+ $0 --check path/to/licenses/ path/to/package.zip
-The
can be set to e.g. 'core' or 'plugins/analysis-icu/'
+ # updates the sha1s for each jar in the zip|gz|deb|rpm
+ $0 --update path/to/licenses/ path/to/package.zip
USAGE
}
+
diff --git a/dev-tools/src/main/resources/license-check/lib/Archive/Ar.pm b/dev-tools/src/main/resources/license-check/lib/Archive/Ar.pm
new file mode 100644
index 00000000000..6d6439b37b4
--- /dev/null
+++ b/dev-tools/src/main/resources/license-check/lib/Archive/Ar.pm
@@ -0,0 +1,806 @@
+###########################################################
+# Archive::Ar - Pure perl module to handle ar achives
+#
+# Copyright 2003 - Jay Bonci
+# Copyright 2014 - John Bazik
+# Licensed under the same terms as perl itself
+#
+###########################################################
+package Archive::Ar;
+
+use base qw(Exporter);
+our @EXPORT_OK = qw(COMMON BSD GNU);
+
+use strict;
+use File::Spec;
+use Time::Local;
+use Carp qw(carp longmess);
+
+use vars qw($VERSION);
+$VERSION = '2.02';
+
+use constant CAN_CHOWN => ($> == 0 and $^O ne 'MacOS' and $^O ne 'MSWin32');
+
+use constant ARMAG => "!\n";
+use constant SARMAG => length(ARMAG);
+use constant ARFMAG => "`\n";
+use constant AR_EFMT1 => "#1/";
+
+use constant COMMON => 1;
+use constant BSD => 2;
+use constant GNU => 3;
+
+my $has_io_string;
+BEGIN {
+ $has_io_string = eval {
+ require IO::String;
+ IO::String->import();
+ 1;
+ } || 0;
+}
+
+sub new {
+ my $class = shift;
+ my $file = shift;
+ my $opts = shift || 0;
+ my $self = bless {}, $class;
+ my $defopts = {
+ chmod => 1,
+ chown => 1,
+ same_perms => ($> == 0) ? 1:0,
+ symbols => undef,
+ };
+ $opts = {warn => $opts} unless ref $opts;
+
+ $self->clear();
+ $self->{opts} = {(%$defopts, %{$opts})};
+ if ($file) {
+ return unless $self->read($file);
+ }
+ return $self;
+}
+
+sub set_opt {
+ my $self = shift;
+ my $name = shift;
+ my $val = shift;
+
+ $self->{opts}->{$name} = $val;
+}
+
+sub get_opt {
+ my $self = shift;
+ my $name = shift;
+
+ return $self->{opts}->{$name};
+}
+
+sub type {
+ return shift->{type};
+}
+
+sub clear {
+ my $self = shift;
+
+ $self->{names} = [];
+ $self->{files} = {};
+ $self->{type} = undef;
+}
+
+sub read {
+ my $self = shift;
+ my $file = shift;
+
+ my $fh = $self->_get_handle($file);
+ local $/ = undef;
+ my $data = <$fh>;
+ close $fh;
+
+ return $self->read_memory($data);
+}
+
+sub read_memory {
+ my $self = shift;
+ my $data = shift;
+
+ $self->clear();
+ return unless $self->_parse($data);
+ return length($data);
+}
+
+sub contains_file {
+ my $self = shift;
+ my $filename = shift;
+
+ return unless defined $filename;
+ return exists $self->{files}->{$filename};
+}
+
+sub extract {
+ my $self = shift;
+
+ for my $filename (@_ ? @_ : @{$self->{names}}) {
+ $self->extract_file($filename) or return;
+ }
+ return 1;
+}
+
+sub extract_file {
+ my $self = shift;
+ my $filename = shift;
+ my $target = shift || $filename;
+
+ my $meta = $self->{files}->{$filename};
+ return $self->_error("$filename: not in archive") unless $meta;
+ open my $fh, '>', $target or return $self->_error("$target: $!");
+ binmode $fh;
+ syswrite $fh, $meta->{data} or return $self->_error("$filename: $!");
+ close $fh or return $self->_error("$filename: $!");
+ if (CAN_CHOWN && $self->{opts}->{chown}) {
+ chown $meta->{uid}, $meta->{gid}, $filename or
+ return $self->_error("$filename: $!");
+ }
+ if ($self->{opts}->{chmod}) {
+ my $mode = $meta->{mode};
+ unless ($self->{opts}->{same_perms}) {
+ $mode &= ~(oct(7000) | (umask | 0));
+ }
+ chmod $mode, $filename or return $self->_error("$filename: $!");
+ }
+ utime $meta->{date}, $meta->{date}, $filename or
+ return $self->_error("$filename: $!");
+ return 1;
+}
+
+sub rename {
+ my $self = shift;
+ my $filename = shift;
+ my $target = shift;
+
+ if ($self->{files}->{$filename}) {
+ $self->{files}->{$target} = $self->{files}->{$filename};
+ delete $self->{files}->{$filename};
+ for (@{$self->{names}}) {
+ if ($_ eq $filename) {
+ $_ = $target;
+ last;
+ }
+ }
+ }
+}
+
+sub chmod {
+ my $self = shift;
+ my $filename = shift;
+ my $mode = shift; # octal string or numeric
+
+ return unless $self->{files}->{$filename};
+ $self->{files}->{$filename}->{mode} =
+ $mode + 0 eq $mode ? $mode : oct($mode);
+ return 1;
+}
+
+sub chown {
+ my $self = shift;
+ my $filename = shift;
+ my $uid = shift;
+ my $gid = shift;
+
+ return unless $self->{files}->{$filename};
+ $self->{files}->{$filename}->{uid} = $uid if $uid >= 0;
+ $self->{files}->{$filename}->{gid} = $gid if defined $gid && $gid >= 0;
+ return 1;
+}
+
+sub remove {
+ my $self = shift;
+ my $files = ref $_[0] ? shift : \@_;
+
+ my $nfiles_orig = scalar @{$self->{names}};
+
+ for my $file (@$files) {
+ next unless $file;
+ if (exists($self->{files}->{$file})) {
+ delete $self->{files}->{$file};
+ }
+ else {
+ $self->_error("$file: no such member")
+ }
+ }
+ @{$self->{names}} = grep($self->{files}->{$_}, @{$self->{names}});
+
+ return $nfiles_orig - scalar @{$self->{names}};
+}
+
+sub list_files {
+ my $self = shift;
+
+ return wantarray ? @{$self->{names}} : $self->{names};
+}
+
+sub add_files {
+ my $self = shift;
+ my $files = ref $_[0] ? shift : \@_;
+
+ for my $path (@$files) {
+ if (open my $fd, $path) {
+ my @st = stat $fd or return $self->_error("$path: $!");
+ local $/ = undef;
+ binmode $fd;
+ my $content = <$fd>;
+ close $fd;
+
+ my $filename = (File::Spec->splitpath($path))[2];
+
+ $self->_add_data($filename, $content, @st[9,4,5,2,7]);
+ }
+ else {
+ $self->_error("$path: $!");
+ }
+ }
+ return scalar @{$self->{names}};
+}
+
+sub add_data {
+ my $self = shift;
+ my $path = shift;
+ my $content = shift;
+ my $params = shift || {};
+
+ return $self->_error("No filename given") unless $path;
+
+ my $filename = (File::Spec->splitpath($path))[2];
+
+ $self->_add_data($filename, $content,
+ $params->{date} || timelocal(localtime()),
+ $params->{uid} || 0,
+ $params->{gid} || 0,
+ $params->{mode} || 0100644) or return;
+
+ return $self->{files}->{$filename}->{size};
+}
+
+sub write {
+ my $self = shift;
+ my $filename = shift;
+ my $opts = {(%{$self->{opts}}, %{shift || {}})};
+ my $type = $opts->{type} || $self->{type} || COMMON;
+
+ my @body = ( ARMAG );
+
+ my %gnuindex;
+ my @filenames = @{$self->{names}};
+ if ($type eq GNU) {
+ #
+ # construct extended filename index, if needed
+ #
+ if (my @longs = grep(length($_) > 15, @filenames)) {
+ my $ptr = 0;
+ for my $long (@longs) {
+ $gnuindex{$long} = $ptr;
+ $ptr += length($long) + 2;
+ }
+ push @body, pack('A16A32A10A2', '//', '', $ptr, ARFMAG),
+ join("/\n", @longs, '');
+ push @body, "\n" if $ptr % 2; # padding
+ }
+ }
+ for my $fn (@filenames) {
+ my $meta = $self->{files}->{$fn};
+ my $mode = sprintf('%o', $meta->{mode});
+ my $size = $meta->{size};
+ my $name;
+
+ if ($type eq GNU) {
+ $fn = '' if defined $opts->{symbols} && $fn eq $opts->{symbols};
+ $name = $fn . '/';
+ }
+ else {
+ $name = $fn;
+ }
+ if (length($name) <= 16 || $type eq COMMON) {
+ push @body, pack('A16A12A6A6A8A10A2', $name,
+ @$meta{qw/date uid gid/}, $mode, $size, ARFMAG);
+ }
+ elsif ($type eq GNU) {
+ push @body, pack('A1A15A12A6A6A8A10A2', '/', $gnuindex{$fn},
+ @$meta{qw/date uid gid/}, $mode, $size, ARFMAG);
+ }
+ elsif ($type eq BSD) {
+ $size += length($name);
+ push @body, pack('A3A13A12A6A6A8A10A2', AR_EFMT1, length($name),
+ @$meta{qw/date uid gid/}, $mode, $size, ARFMAG),
+ $name;
+ }
+ else {
+ return $self->_error("$type: unexpected ar type");
+ }
+ push @body, $meta->{data};
+ push @body, "\n" if $size % 2; # padding
+ }
+ if ($filename) {
+ my $fh = $self->_get_handle($filename, '>');
+ print $fh @body;
+ close $fh;
+ my $len = 0;
+ $len += length($_) for @body;
+ return $len;
+ }
+ else {
+ return join '', @body;
+ }
+}
+
+sub get_content {
+ my $self = shift;
+ my ($filename) = @_;
+
+ unless ($filename) {
+ $self->_error("get_content can't continue without a filename");
+ return;
+ }
+
+ unless (exists($self->{files}->{$filename})) {
+ $self->_error(
+ "get_content failed because there is not a file named $filename");
+ return;
+ }
+
+ return $self->{files}->{$filename};
+}
+
+sub get_data {
+ my $self = shift;
+ my $filename = shift;
+
+ return $self->_error("$filename: no such member")
+ unless exists $self->{files}->{$filename};
+ return $self->{files}->{$filename}->{data};
+}
+
+sub get_handle {
+ my $self = shift;
+ my $filename = shift;
+ my $fh;
+
+ return $self->_error("$filename: no such member")
+ unless exists $self->{files}->{$filename};
+ if ($has_io_string) {
+ $fh = IO::String->new($self->{files}->{$filename}->{data});
+ }
+ else {
+ my $data = $self->{files}->{$filename}->{data};
+ open $fh, '<', \$data or return $self->_error("in-memory file: $!");
+ }
+ return $fh;
+}
+
+sub error {
+ my $self = shift;
+
+ return shift() ? $self->{longmess} : $self->{error};
+}
+
+#
+# deprecated
+#
+sub DEBUG {
+ my $self = shift;
+ my $debug = shift;
+
+ $self->{opts}->{warn} = 1 unless (defined($debug) and int($debug) == 0);
+}
+
+sub _parse {
+ my $self = shift;
+ my $data = shift;
+
+ unless (substr($data, 0, SARMAG, '') eq ARMAG) {
+ return $self->_error("Bad magic number - not an ar archive");
+ }
+ my $type;
+ my $names;
+ while ($data =~ /\S/) {
+ my ($name, $date, $uid, $gid, $mode, $size, $magic) =
+ unpack('A16A12A6A6A8A10a2', substr($data, 0, 60, ''));
+ unless ($magic eq "`\n") {
+ return $self->_error("Bad file header");
+ }
+ if ($name =~ m|^/|) {
+ $type = GNU;
+ if ($name eq '//') {
+ $names = substr($data, 0, $size, '');
+ substr($data, 0, $size % 2, '');
+ next;
+ }
+ elsif ($name eq '/') {
+ $name = $self->{opts}->{symbols};
+ unless (defined $name && $name) {
+ substr($data, 0, $size + $size % 2, '');
+ next;
+ }
+ }
+ else {
+ $name = substr($names, int(substr($name, 1)));
+ $name =~ s/\n.*//;
+ chop $name;
+ }
+ }
+ elsif ($name =~ m|^#1/|) {
+ $type = BSD;
+ $name = substr($data, 0, int(substr($name, 3)), '');
+ $size -= length($name);
+ }
+ else {
+ if ($name =~ m|/$|) {
+ $type ||= GNU; # only gnu has trailing slashes
+ chop $name;
+ }
+ }
+ $uid = int($uid);
+ $gid = int($gid);
+ $mode = oct($mode);
+ my $content = substr($data, 0, $size, '');
+ substr($data, 0, $size % 2, '');
+
+ $self->_add_data($name, $content, $date, $uid, $gid, $mode, $size);
+ }
+ $self->{type} = $type || COMMON;
+ return scalar @{$self->{names}};
+}
+
+sub _add_data {
+ my $self = shift;
+ my $filename = shift;
+ my $content = shift || '';
+ my $date = shift;
+ my $uid = shift;
+ my $gid = shift;
+ my $mode = shift;
+ my $size = shift;
+
+ if (exists($self->{files}->{$filename})) {
+ return $self->_error("$filename: entry already exists");
+ }
+ $self->{files}->{$filename} = {
+ name => $filename,
+ date => defined $date ? $date : timelocal(localtime()),
+ uid => defined $uid ? $uid : 0,
+ gid => defined $gid ? $gid : 0,
+ mode => defined $mode ? $mode : 0100644,
+ size => defined $size ? $size : length($content),
+ data => $content,
+ };
+ push @{$self->{names}}, $filename;
+ return 1;
+}
+
+sub _get_handle {
+ my $self = shift;
+ my $file = shift;
+ my $mode = shift || '<';
+
+ if (ref $file) {
+ return $file if eval{*$file{IO}} or $file->isa('IO::Handle');
+ return $self->_error("Not a filehandle");
+ }
+ else {
+ open my $fh, $mode, $file or return $self->_error("$file: $!");
+ binmode $fh;
+ return $fh;
+ }
+}
+
+sub _error {
+ my $self = shift;
+ my $msg = shift;
+
+ $self->{error} = $msg;
+ $self->{longerror} = longmess($msg);
+ if ($self->{opts}->{warn} > 1) {
+ carp $self->{longerror};
+ }
+ elsif ($self->{opts}->{warn}) {
+ carp $self->{error};
+ }
+ return;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Archive::Ar - Interface for manipulating ar archives
+
+=head1 SYNOPSIS
+
+ use Archive::Ar;
+
+ my $ar = Archive::Ar->new;
+
+ $ar->read('./foo.ar');
+ $ar->extract;
+
+ $ar->add_files('./bar.tar.gz', 'bat.pl')
+ $ar->add_data('newfile.txt','Some contents');
+
+ $ar->chmod('file1', 0644);
+ $ar->chown('file1', $uid, $gid);
+
+ $ar->remove('file1', 'file2');
+
+ my $filehash = $ar->get_content('bar.tar.gz');
+ my $data = $ar->get_data('bar.tar.gz');
+ my $handle = $ar->get_handle('bar.tar.gz');
+
+ my @files = $ar->list_files();
+
+ my $archive = $ar->write;
+ my $size = $ar->write('outbound.ar');
+
+ $ar->error();
+
+
+=head1 DESCRIPTION
+
+Archive::Ar is a pure-perl way to handle standard ar archives.
+
+This is useful if you have those types of archives on the system, but it
+is also useful because .deb packages for the Debian GNU/Linux distribution are
+ar archives. This is one building block in a future chain of modules to build,
+manipulate, extract, and test debian modules with no platform or architecture
+dependence.
+
+You may notice that the API to Archive::Ar is similar to Archive::Tar, and
+this was done intentionally to keep similarity between the Archive::*
+modules.
+
+=head1 METHODS
+
+=head2 new
+
+ $ar = Archive::Ar->new()
+ $ar = Archive::Ar->new($filename)
+ $ar = Archive::Ar->new($filehandle)
+
+Returns a new Archive::Ar object. Without an argument, it returns
+an empty object. If passed a filename or an open filehandle, it will
+read the referenced archive into memory. If the read fails for any
+reason, returns undef.
+
+=head2 set_opt
+
+ $ar->set_opt($name, $val)
+
+Assign option $name value $val. Possible options are:
+
+=over 4
+
+=item * warn
+
+Warning level. Levels are zero for no warnings, 1 for brief warnings,
+and 2 for warnings with a stack trace. Default is zero.
+
+=item * chmod
+
+Change the file permissions of files created when extracting. Default
+is true (non-zero).
+
+=item * same_perms
+
+When setting file permissions, use the values in the archive unchanged.
+If false, removes setuid bits and applies the user's umask. Default is
+true for the root user, false otherwise.
+
+=item * chown
+
+Change the owners of extracted files, if possible. Default is true.
+
+=item * type
+
+Archive type. May be GNU, BSD or COMMON, or undef if no archive has
+been read. Defaults to the type of the archive read, or undef.
+
+=item * symbols
+
+Provide a filename for the symbol table, if present. If set, the symbol
+table is treated as a file that can be read from or written to an archive.
+It is an error if the filename provided matches the name of a file in the
+archive. If undefined, the symbol table is ignored. Defaults to undef.
+
+=back
+
+=head2 get_opt
+
+ $val = $ar->get_opt($name)
+
+Returns the value of option $name.
+
+=head2 type
+
+ $type = $ar->type()
+
+Returns the type of the ar archive. The type is undefined until an
+archive is loaded. If the archive displays characteristics of a gnu-style
+archive, GNU is returned. If it looks like a bsd-style archive, BSD
+is returned. Otherwise, COMMON is returned. Note that unless filenames
+exceed 16 characters in length, bsd archives look like the common format.
+
+=head2 clear
+
+ $ar->clear()
+
+Clears the current in-memory archive.
+
+=head2 read
+
+ $len = $ar->read($filename)
+ $len = $ar->read($filehandle)
+
+This reads a new file into the object, removing any ar archive already
+represented in the object. The argument may be a filename, filehandle
+or IO::Handle object. Returns the size of the file contents or undef
+if it fails.
+
+=head2 read_memory
+
+ $len = $ar->read_memory($data)
+
+Parses the string argument as an archive, reading it into memory. Replaces
+any previously loaded archive. Returns the number of bytes read, or undef
+if it fails.
+
+=head2 contains_file
+
+ $bool = $ar->contains_file($filename)
+
+Returns true if the archive contains a file with $filename. Returns
+undef otherwise.
+
+=head2 extract
+
+ $ar->extract()
+ $ar->extract_file($filename)
+
+Extracts files from the archive. The first form extracts all files, the
+latter extracts just the named file. Extracted files are assigned the
+permissions and modification time stored in the archive, and, if possible,
+the user and group ownership. Returns non-zero upon success, or undef if
+failure.
+
+=head2 rename
+
+ $ar->rename($filename, $newname)
+
+Changes the name of a file in the in-memory archive.
+
+=head2 chmod
+
+ $ar->chmod($filename, $mode);
+
+Change the mode of the member to C<$mode>.
+
+=head2 chown
+
+ $ar->chown($filename, $uid, $gid);
+ $ar->chown($filename, $uid);
+
+Change the ownership of the member to user id C<$uid> and (optionally)
+group id C<$gid>. Negative id values are ignored.
+
+=head2 remove
+
+ $ar->remove(@filenames)
+ $ar->remove($arrayref)
+
+Removes files from the in-memory archive. Returns the number of files
+removed.
+
+=head2 list_files
+
+ @filenames = $ar->list_files()
+
+Returns a list of the names of all the files in the archive.
+If called in a scalar context, returns a reference to an array.
+
+=head2 add_files
+
+ $ar->add_files(@filenames)
+ $ar->add_files($arrayref)
+
+Adds files to the archive. The arguments can be paths, but only the
+filenames are stored in the archive. Stores the uid, gid, mode, size,
+and modification timestamp of the file as returned by C.
+
+Returns the number of files successfully added, or undef if failure.
+
+=head2 add_data
+
+ $ar->add_data("filename", $data)
+ $ar->add_data("filename", $data, $options)
+
+Adds a file to the in-memory archive with name $filename and content
+$data. File properties can be set with $optional_hashref:
+
+ $options = {
+ 'data' => $data,
+ 'uid' => $uid, #defaults to zero
+ 'gid' => $gid, #defaults to zero
+ 'date' => $date, #date in epoch seconds. Defaults to now.
+ 'mode' => $mode, #defaults to 0100644;
+ }
+
+You cannot add_data over another file however. This returns the file length in
+bytes if it is successful, undef otherwise.
+
+=head2 write
+
+ $data = $ar->write()
+ $len = $ar->write($filename)
+
+Returns the archive as a string, or writes it to disk as $filename.
+Returns the archive size upon success when writing to disk. Returns
+undef if failure.
+
+=head2 get_content
+
+ $content = $ar->get_content($filename)
+
+This returns a hash with the file content in it, including the data
+that the file would contain. If the file does not exist or no filename
+is given, this returns undef. On success, a hash is returned:
+
+ $content = {
+ 'name' => $filename,
+ 'date' => $mtime,
+ 'uid' => $uid,
+ 'gid' => $gid,
+ 'mode' => $mode,
+ 'size' => $size,
+ 'data' => $file_contents,
+ }
+
+=head2 get_data
+
+ $data = $ar->get_data("filename")
+
+Returns a scalar containing the file data of the given archive
+member. Upon error, returns undef.
+
+=head2 get_handle
+
+ $handle = $ar->get_handle("filename")>
+
+Returns a file handle to the in-memory file data of the given archive member.
+Upon error, returns undef. This can be useful for unpacking nested archives.
+Uses IO::String if it's loaded.
+
+=head2 error
+
+ $errstr = $ar->error($trace)
+
+Returns the current error string, which is usually the last error reported.
+If a true value is provided, returns the error message and stack trace.
+
+=head1 BUGS
+
+See https://github.com/jbazik/Archive-Ar/issues/ to report and view bugs.
+
+=head1 SOURCE
+
+The source code repository for Archive::Ar can be found at http://github.com/jbazik/Archive-Ar/.
+
+=head1 COPYRIGHT
+
+Copyright 2009-2014 John Bazik Ejbazik@cpan.orgE.
+
+Copyright 2003 Jay Bonci Ejaybonci@cpan.orgE.
+
+This program is free software; you can redistribute it and/or modify it under
+the same terms as Perl itself.
+
+See http://www.perl.com/perl/misc/Artistic.html
+
+=cut
diff --git a/dev-tools/src/main/resources/license-check/lib/Archive/Extract.pm b/dev-tools/src/main/resources/license-check/lib/Archive/Extract.pm
new file mode 100644
index 00000000000..e88cf11f037
--- /dev/null
+++ b/dev-tools/src/main/resources/license-check/lib/Archive/Extract.pm
@@ -0,0 +1,1694 @@
+package Archive::Extract;
+use if $] > 5.017, 'deprecate';
+
+use strict;
+
+use Cwd qw[cwd chdir];
+use Carp qw[carp];
+use IPC::Cmd qw[run can_run];
+use FileHandle;
+use File::Path qw[mkpath];
+use File::Spec;
+use File::Basename qw[dirname basename];
+use Params::Check qw[check];
+use Module::Load::Conditional qw[can_load check_install];
+use Locale::Maketext::Simple Style => 'gettext';
+
+### solaris has silly /bin/tar output ###
+use constant ON_SOLARIS => $^O eq 'solaris' ? 1 : 0;
+use constant ON_NETBSD => $^O eq 'netbsd' ? 1 : 0;
+use constant ON_OPENBSD => $^O =~ m!^(openbsd|bitrig)$! ? 1 : 0;
+use constant ON_FREEBSD => $^O =~ m!^(free|midnight|dragonfly)(bsd)?$! ? 1 : 0;
+use constant ON_LINUX => $^O eq 'linux' ? 1 : 0;
+use constant FILE_EXISTS => sub { -e $_[0] ? 1 : 0 };
+
+### VMS may require quoting upper case command options
+use constant ON_VMS => $^O eq 'VMS' ? 1 : 0;
+
+### Windows needs special treatment of Tar options
+use constant ON_WIN32 => $^O eq 'MSWin32' ? 1 : 0;
+
+### we can't use this extraction method, because of missing
+### modules/binaries:
+use constant METHOD_NA => [];
+
+### If these are changed, update @TYPES and the new() POD
+use constant TGZ => 'tgz';
+use constant TAR => 'tar';
+use constant GZ => 'gz';
+use constant ZIP => 'zip';
+use constant BZ2 => 'bz2';
+use constant TBZ => 'tbz';
+use constant Z => 'Z';
+use constant LZMA => 'lzma';
+use constant XZ => 'xz';
+use constant TXZ => 'txz';
+
+use vars qw[$VERSION $PREFER_BIN $PROGRAMS $WARN $DEBUG
+ $_ALLOW_BIN $_ALLOW_PURE_PERL $_ALLOW_TAR_ITER
+ ];
+
+$VERSION = '0.76';
+$PREFER_BIN = 0;
+$WARN = 1;
+$DEBUG = 0;
+$_ALLOW_PURE_PERL = 1; # allow pure perl extractors
+$_ALLOW_BIN = 1; # allow binary extractors
+$_ALLOW_TAR_ITER = 1; # try to use Archive::Tar->iter if available
+
+# same as all constants
+my @Types = ( TGZ, TAR, GZ, ZIP, BZ2, TBZ, Z, LZMA, XZ, TXZ );
+
+local $Params::Check::VERBOSE = $Params::Check::VERBOSE = 1;
+
+=pod
+
+=head1 NAME
+
+Archive::Extract - A generic archive extracting mechanism
+
+=head1 SYNOPSIS
+
+ use Archive::Extract;
+
+ ### build an Archive::Extract object ###
+ my $ae = Archive::Extract->new( archive => 'foo.tgz' );
+
+ ### extract to cwd() ###
+ my $ok = $ae->extract;
+
+ ### extract to /tmp ###
+ my $ok = $ae->extract( to => '/tmp' );
+
+ ### what if something went wrong?
+ my $ok = $ae->extract or die $ae->error;
+
+ ### files from the archive ###
+ my $files = $ae->files;
+
+ ### dir that was extracted to ###
+ my $outdir = $ae->extract_path;
+
+
+ ### quick check methods ###
+ $ae->is_tar # is it a .tar file?
+ $ae->is_tgz # is it a .tar.gz or .tgz file?
+ $ae->is_gz; # is it a .gz file?
+ $ae->is_zip; # is it a .zip file?
+ $ae->is_bz2; # is it a .bz2 file?
+ $ae->is_tbz; # is it a .tar.bz2 or .tbz file?
+ $ae->is_lzma; # is it a .lzma file?
+ $ae->is_xz; # is it a .xz file?
+ $ae->is_txz; # is it a .tar.xz or .txz file?
+
+ ### absolute path to the archive you provided ###
+ $ae->archive;
+
+ ### commandline tools, if found ###
+ $ae->bin_tar # path to /bin/tar, if found
+ $ae->bin_gzip # path to /bin/gzip, if found
+ $ae->bin_unzip # path to /bin/unzip, if found
+ $ae->bin_bunzip2 # path to /bin/bunzip2 if found
+ $ae->bin_unlzma # path to /bin/unlzma if found
+ $ae->bin_unxz # path to /bin/unxz if found
+
+=head1 DESCRIPTION
+
+Archive::Extract is a generic archive extraction mechanism.
+
+It allows you to extract any archive file of the type .tar, .tar.gz,
+.gz, .Z, tar.bz2, .tbz, .bz2, .zip, .xz,, .txz, .tar.xz or .lzma
+without having to worry how it
+does so, or use different interfaces for each type by using either
+perl modules, or commandline tools on your system.
+
+See the C section further down for details.
+
+=cut
+
+
+### see what /bin/programs are available ###
+$PROGRAMS = {};
+CMD: for my $pgm (qw[tar unzip gzip bunzip2 uncompress unlzma unxz]) {
+ if ( $pgm eq 'unzip' and ON_FREEBSD and my $unzip = can_run('info-unzip') ) {
+ $PROGRAMS->{$pgm} = $unzip;
+ next CMD;
+ }
+ if ( $pgm eq 'unzip' and ( ON_NETBSD or ON_FREEBSD ) ) {
+ local $IPC::Cmd::INSTANCES = 1;
+ ($PROGRAMS->{$pgm}) = grep { ON_NETBSD ? m!/usr/pkg/! : m!/usr/local! } can_run($pgm);
+ next CMD;
+ }
+ if ( $pgm eq 'unzip' and ON_LINUX ) {
+ # Check if 'unzip' is busybox masquerading
+ local $IPC::Cmd::INSTANCES = 1;
+ my $opt = ON_VMS ? '"-Z"' : '-Z';
+ ($PROGRAMS->{$pgm}) = grep { scalar run(command=> [ $_, $opt, '-1' ]) } can_run($pgm);
+ next CMD;
+ }
+ if ( $pgm eq 'tar' and ( ON_OPENBSD || ON_SOLARIS || ON_NETBSD ) ) {
+ # try gtar first
+ next CMD if $PROGRAMS->{$pgm} = can_run('gtar');
+ }
+ $PROGRAMS->{$pgm} = can_run($pgm);
+}
+
+### mapping from types to extractor methods ###
+my $Mapping = { # binary program # pure perl module
+ is_tgz => { bin => '_untar_bin', pp => '_untar_at' },
+ is_tar => { bin => '_untar_bin', pp => '_untar_at' },
+ is_gz => { bin => '_gunzip_bin', pp => '_gunzip_cz' },
+ is_zip => { bin => '_unzip_bin', pp => '_unzip_az' },
+ is_tbz => { bin => '_untar_bin', pp => '_untar_at' },
+ is_bz2 => { bin => '_bunzip2_bin', pp => '_bunzip2_bz2'},
+ is_Z => { bin => '_uncompress_bin', pp => '_gunzip_cz' },
+ is_lzma => { bin => '_unlzma_bin', pp => '_unlzma_cz' },
+ is_xz => { bin => '_unxz_bin', pp => '_unxz_cz' },
+ is_txz => { bin => '_untar_bin', pp => '_untar_at' },
+};
+
+{ ### use subs so we re-generate array refs etc for the no-override flags
+ ### if we don't, then we reuse the same arrayref, meaning objects store
+ ### previous errors
+ my $tmpl = {
+ archive => sub { { required => 1, allow => FILE_EXISTS } },
+ type => sub { { default => '', allow => [ @Types ] } },
+ _error_msg => sub { { no_override => 1, default => [] } },
+ _error_msg_long => sub { { no_override => 1, default => [] } },
+ };
+
+ ### build accessors ###
+ for my $method( keys %$tmpl,
+ qw[_extractor _gunzip_to files extract_path],
+ ) {
+ no strict 'refs';
+ *$method = sub {
+ my $self = shift;
+ $self->{$method} = $_[0] if @_;
+ return $self->{$method};
+ }
+ }
+
+=head1 METHODS
+
+=head2 $ae = Archive::Extract->new(archive => '/path/to/archive',[type => TYPE])
+
+Creates a new C object based on the archive file you
+passed it. Automatically determines the type of archive based on the
+extension, but you can override that by explicitly providing the
+C argument.
+
+Valid values for C are:
+
+=over 4
+
+=item tar
+
+Standard tar files, as produced by, for example, C.
+Corresponds to a C<.tar> suffix.
+
+=item tgz
+
+Gzip compressed tar files, as produced by, for example C.
+Corresponds to a C<.tgz> or C<.tar.gz> suffix.
+
+=item gz
+
+Gzip compressed file, as produced by, for example C.
+Corresponds to a C<.gz> suffix.
+
+=item Z
+
+Lempel-Ziv compressed file, as produced by, for example C.
+Corresponds to a C<.Z> suffix.
+
+=item zip
+
+Zip compressed file, as produced by, for example C.
+Corresponds to a C<.zip>, C<.jar> or C<.par> suffix.
+
+=item bz2
+
+Bzip2 compressed file, as produced by, for example, C.
+Corresponds to a C<.bz2> suffix.
+
+=item tbz
+
+Bzip2 compressed tar file, as produced by, for example C.
+Corresponds to a C<.tbz> or C<.tar.bz2> suffix.
+
+=item lzma
+
+Lzma compressed file, as produced by C.
+Corresponds to a C<.lzma> suffix.
+
+=item xz
+
+Xz compressed file, as produced by C.
+Corresponds to a C<.xz> suffix.
+
+=item txz
+
+Xz compressed tar file, as produced by, for example C.
+Corresponds to a C<.txz> or C<.tar.xz> suffix.
+
+=back
+
+Returns a C object on success, or false on failure.
+
+=cut
+
+ ### constructor ###
+ sub new {
+ my $class = shift;
+ my %hash = @_;
+
+ ### see above why we use subs here and generate the template;
+ ### it's basically to not re-use arrayrefs
+ my %utmpl = map { $_ => $tmpl->{$_}->() } keys %$tmpl;
+
+ my $parsed = check( \%utmpl, \%hash ) or return;
+
+ ### make sure we have an absolute path ###
+ my $ar = $parsed->{archive} = File::Spec->rel2abs( $parsed->{archive} );
+
+ ### figure out the type, if it wasn't already specified ###
+ unless ( $parsed->{type} ) {
+ $parsed->{type} =
+ $ar =~ /.+?\.(?:tar\.gz|tgz)$/i ? TGZ :
+ $ar =~ /.+?\.gz$/i ? GZ :
+ $ar =~ /.+?\.tar$/i ? TAR :
+ $ar =~ /.+?\.(zip|jar|ear|war|par)$/i ? ZIP :
+ $ar =~ /.+?\.(?:tbz2?|tar\.bz2?)$/i ? TBZ :
+ $ar =~ /.+?\.bz2$/i ? BZ2 :
+ $ar =~ /.+?\.Z$/ ? Z :
+ $ar =~ /.+?\.lzma$/ ? LZMA :
+ $ar =~ /.+?\.(?:txz|tar\.xz)$/i ? TXZ :
+ $ar =~ /.+?\.xz$/ ? XZ :
+ '';
+
+ }
+
+ bless $parsed, $class;
+
+ ### don't know what type of file it is
+ ### XXX this *has* to be an object call, not a package call
+ return $parsed->_error(loc("Cannot determine file type for '%1'",
+ $parsed->{archive} )) unless $parsed->{type};
+ return $parsed;
+ }
+}
+
+=head2 $ae->extract( [to => '/output/path'] )
+
+Extracts the archive represented by the C object to
+the path of your choice as specified by the C argument. Defaults to
+C.
+
+Since C<.gz> files never hold a directory, but only a single file; if
+the C argument is an existing directory, the file is extracted
+there, with its C<.gz> suffix stripped.
+If the C argument is not an existing directory, the C argument
+is understood to be a filename, if the archive type is C.
+In the case that you did not specify a C argument, the output
+file will be the name of the archive file, stripped from its C<.gz>
+suffix, in the current working directory.
+
+C will try a pure perl solution first, and then fall back to
+commandline tools if they are available. See the C
+section below on how to alter this behaviour.
+
+It will return true on success, and false on failure.
+
+On success, it will also set the follow attributes in the object:
+
+=over 4
+
+=item $ae->extract_path
+
+This is the directory that the files where extracted to.
+
+=item $ae->files
+
+This is an array ref with the paths of all the files in the archive,
+relative to the C argument you specified.
+To get the full path to an extracted file, you would use:
+
+ File::Spec->catfile( $to, $ae->files->[0] );
+
+Note that all files from a tar archive will be in unix format, as per
+the tar specification.
+
+=back
+
+=cut
+
+sub extract {
+ my $self = shift;
+ my %hash = @_;
+
+ ### reset error messages
+ $self->_error_msg( [] );
+ $self->_error_msg_long( [] );
+
+ my $to;
+ my $tmpl = {
+ to => { default => '.', store => \$to }
+ };
+
+ check( $tmpl, \%hash ) or return;
+
+ ### so 'to' could be a file or a dir, depending on whether it's a .gz
+ ### file, or basically anything else.
+ ### so, check that, then act accordingly.
+ ### set an accessor specifically so _gunzip can know what file to extract
+ ### to.
+ my $dir;
+ { ### a foo.gz file
+ if( $self->is_gz or $self->is_bz2 or $self->is_Z or $self->is_lzma or $self->is_xz ) {
+
+ my $cp = $self->archive; $cp =~ s/\.(?:gz|bz2?|Z|lzma|xz)$//i;
+
+ ### to is a dir?
+ if ( -d $to ) {
+ $dir = $to;
+ $self->_gunzip_to( basename($cp) );
+
+ ### then it's a filename
+ } else {
+ $dir = dirname($to);
+ $self->_gunzip_to( basename($to) );
+ }
+
+ ### not a foo.gz file
+ } else {
+ $dir = $to;
+ }
+ }
+
+ ### make the dir if it doesn't exist ###
+ unless( -d $dir ) {
+ eval { mkpath( $dir ) };
+
+ return $self->_error(loc("Could not create path '%1': %2", $dir, $@))
+ if $@;
+ }
+
+ ### get the current dir, to restore later ###
+ my $cwd = cwd();
+
+ my $ok = 1;
+ EXTRACT: {
+
+ ### chdir to the target dir ###
+ unless( chdir $dir ) {
+ $self->_error(loc("Could not chdir to '%1': %2", $dir, $!));
+ $ok = 0; last EXTRACT;
+ }
+
+ ### set files to an empty array ref, so there's always an array
+ ### ref IN the accessor, to avoid errors like:
+ ### Can't use an undefined value as an ARRAY reference at
+ ### ../lib/Archive/Extract.pm line 742. (rt #19815)
+ $self->files( [] );
+
+ ### find out the dispatch methods needed for this type of
+ ### archive. Do a $self->is_XXX to figure out the type, then
+ ### get the hashref with bin + pure perl dispatchers.
+ my ($map) = map { $Mapping->{$_} } grep { $self->$_ } keys %$Mapping;
+
+ ### add pure perl extractor if allowed & add bin extractor if allowed
+ my @methods;
+ push @methods, $map->{'pp'} if $_ALLOW_PURE_PERL;
+ push @methods, $map->{'bin'} if $_ALLOW_BIN;
+
+ ### reverse it if we prefer bin extractors
+ @methods = reverse @methods if $PREFER_BIN;
+
+ my($na, $fail);
+ for my $method (@methods) {
+ $self->debug( "# Extracting with ->$method\n" );
+
+ my $rv = $self->$method;
+
+ ### a positive extraction
+ if( $rv and $rv ne METHOD_NA ) {
+ $self->debug( "# Extraction succeeded\n" );
+ $self->_extractor($method);
+ last;
+
+ ### method is not available
+ } elsif ( $rv and $rv eq METHOD_NA ) {
+ $self->debug( "# Extraction method not available\n" );
+ $na++;
+ } else {
+ $self->debug( "# Extraction method failed\n" );
+ $fail++;
+ }
+ }
+
+ ### warn something went wrong if we didn't get an extractor
+ unless( $self->_extractor ) {
+ my $diag = $fail ? loc("Extract failed due to errors") :
+ $na ? loc("Extract failed; no extractors available") :
+ '';
+
+ $self->_error($diag);
+ $ok = 0;
+ }
+ }
+
+ ### and chdir back ###
+ unless( chdir $cwd ) {
+ $self->_error(loc("Could not chdir back to start dir '%1': %2'",
+ $cwd, $!));
+ }
+
+ return $ok;
+}
+
+=pod
+
+=head1 ACCESSORS
+
+=head2 $ae->error([BOOL])
+
+Returns the last encountered error as string.
+Pass it a true value to get the C output instead.
+
+=head2 $ae->extract_path
+
+This is the directory the archive got extracted to.
+See C for details.
+
+=head2 $ae->files
+
+This is an array ref holding all the paths from the archive.
+See C for details.
+
+=head2 $ae->archive
+
+This is the full path to the archive file represented by this
+C object.
+
+=head2 $ae->type
+
+This is the type of archive represented by this C
+object. See accessors below for an easier way to use this.
+See the C method for details.
+
+=head2 $ae->types
+
+Returns a list of all known C for C's
+C method.
+
+=cut
+
+sub types { return @Types }
+
+=head2 $ae->is_tgz
+
+Returns true if the file is of type C<.tar.gz>.
+See the C method for details.
+
+=head2 $ae->is_tar
+
+Returns true if the file is of type C<.tar>.
+See the C method for details.
+
+=head2 $ae->is_gz
+
+Returns true if the file is of type C<.gz>.
+See the C method for details.
+
+=head2 $ae->is_Z
+
+Returns true if the file is of type C<.Z>.
+See the C method for details.
+
+=head2 $ae->is_zip
+
+Returns true if the file is of type C<.zip>.
+See the C method for details.
+
+=head2 $ae->is_lzma
+
+Returns true if the file is of type C<.lzma>.
+See the C method for details.
+
+=head2 $ae->is_xz
+
+Returns true if the file is of type C<.xz>.
+See the C method for details.
+
+=cut
+
+### quick check methods ###
+sub is_tgz { return $_[0]->type eq TGZ }
+sub is_tar { return $_[0]->type eq TAR }
+sub is_gz { return $_[0]->type eq GZ }
+sub is_zip { return $_[0]->type eq ZIP }
+sub is_tbz { return $_[0]->type eq TBZ }
+sub is_bz2 { return $_[0]->type eq BZ2 }
+sub is_Z { return $_[0]->type eq Z }
+sub is_lzma { return $_[0]->type eq LZMA }
+sub is_xz { return $_[0]->type eq XZ }
+sub is_txz { return $_[0]->type eq TXZ }
+
+=pod
+
+=head2 $ae->bin_tar
+
+Returns the full path to your tar binary, if found.
+
+=head2 $ae->bin_gzip
+
+Returns the full path to your gzip binary, if found
+
+=head2 $ae->bin_unzip
+
+Returns the full path to your unzip binary, if found
+
+=head2 $ae->bin_unlzma
+
+Returns the full path to your unlzma binary, if found
+
+=head2 $ae->bin_unxz
+
+Returns the full path to your unxz binary, if found
+
+=cut
+
+### paths to commandline tools ###
+sub bin_gzip { return $PROGRAMS->{'gzip'} if $PROGRAMS->{'gzip'} }
+sub bin_unzip { return $PROGRAMS->{'unzip'} if $PROGRAMS->{'unzip'} }
+sub bin_tar { return $PROGRAMS->{'tar'} if $PROGRAMS->{'tar'} }
+sub bin_bunzip2 { return $PROGRAMS->{'bunzip2'} if $PROGRAMS->{'bunzip2'} }
+sub bin_uncompress { return $PROGRAMS->{'uncompress'}
+ if $PROGRAMS->{'uncompress'} }
+sub bin_unlzma { return $PROGRAMS->{'unlzma'} if $PROGRAMS->{'unlzma'} }
+sub bin_unxz { return $PROGRAMS->{'unxz'} if $PROGRAMS->{'unxz'} }
+
+=head2 $bool = $ae->have_old_bunzip2
+
+Older versions of C, from before the C release,
+require all archive names to end in C<.bz2> or it will not extract
+them. This method checks if you have a recent version of C
+that allows any extension, or an older one that doesn't.
+
+=cut
+
+sub have_old_bunzip2 {
+ my $self = shift;
+
+ ### no bunzip2? no old bunzip2 either :)
+ return unless $self->bin_bunzip2;
+
+ ### if we can't run this, we can't be sure if it's too old or not
+ ### XXX stupid stupid stupid bunzip2 doesn't understand --version
+ ### is not a request to extract data:
+ ### $ bunzip2 --version
+ ### bzip2, a block-sorting file compressor. Version 1.0.2, 30-Dec-2001.
+ ### [...]
+ ### bunzip2: I won't read compressed data from a terminal.
+ ### bunzip2: For help, type: `bunzip2 --help'.
+ ### $ echo $?
+ ### 1
+ ### HATEFUL!
+
+ ### double hateful: bunzip2 --version also hangs if input is a pipe
+ ### See #32370: Archive::Extract will hang if stdin is a pipe [+PATCH]
+ ### So, we have to provide *another* argument which is a fake filename,
+ ### just so it wont try to read from stdin to print its version..
+ ### *sigh*
+ ### Even if the file exists, it won't clobber or change it.
+ my $buffer;
+ scalar run(
+ command => [$self->bin_bunzip2, '--version', 'NoSuchFile'],
+ verbose => 0,
+ buffer => \$buffer
+ );
+
+ ### no output
+ return unless $buffer;
+
+ my ($version) = $buffer =~ /version \s+ (\d+)/ix;
+
+ return 1 if $version < 1;
+ return;
+}
+
+#################################
+#
+# Untar code
+#
+#################################
+
+### annoying issue with (gnu) tar on win32, as illustrated by this
+### bug: https://rt.cpan.org/Ticket/Display.html?id=40138
+### which shows that (gnu) tar will interpret a file name with a :
+### in it as a remote file name, so C:\tmp\foo.txt is interpreted
+### as a remote shell, and the extract fails.
+{ my @ExtraTarFlags;
+ if( ON_WIN32 and my $cmd = __PACKAGE__->bin_tar ) {
+
+ ### if this is gnu tar we are running, we need to use --force-local
+ push @ExtraTarFlags, '--force-local' if `$cmd --version` =~ /gnu tar/i;
+ }
+
+
+ ### use /bin/tar to extract ###
+ sub _untar_bin {
+ my $self = shift;
+
+ ### check for /bin/tar ###
+ ### check for /bin/gzip if we need it ###
+ ### if any of the binaries are not available, return NA
+ { my $diag = !$self->bin_tar ?
+ loc("No '%1' program found", '/bin/tar') :
+ $self->is_tgz && !$self->bin_gzip ?
+ loc("No '%1' program found", '/bin/gzip') :
+ $self->is_tbz && !$self->bin_bunzip2 ?
+ loc("No '%1' program found", '/bin/bunzip2') :
+ $self->is_txz && !$self->bin_unxz ?
+ loc("No '%1' program found", '/bin/unxz') :
+ '';
+
+ if( $diag ) {
+ $self->_error( $diag );
+ return METHOD_NA;
+ }
+ }
+
+ ### XXX figure out how to make IPC::Run do this in one call --
+ ### currently i don't know how to get output of a command after a pipe
+ ### trapped in a scalar. Mailed barries about this 5th of june 2004.
+
+ ### see what command we should run, based on whether
+ ### it's a .tgz or .tar
+
+ ### GNU tar can't handled VMS filespecs, but VMSTAR can handle Unix filespecs.
+ my $archive = $self->archive;
+ $archive = VMS::Filespec::unixify($archive) if ON_VMS;
+
+ ### XXX solaris tar and bsdtar are having different outputs
+ ### depending whether you run with -x or -t
+ ### compensate for this insanity by running -t first, then -x
+ { my $cmd =
+ $self->is_tgz ? [$self->bin_gzip, '-c', '-d', '-f', $archive, '|',
+ $self->bin_tar, '-tf', '-'] :
+ $self->is_tbz ? [$self->bin_bunzip2, '-cd', $archive, '|',
+ $self->bin_tar, '-tf', '-'] :
+ $self->is_txz ? [$self->bin_unxz, '-cd', $archive, '|',
+ $self->bin_tar, '-tf', '-'] :
+ [$self->bin_tar, @ExtraTarFlags, '-tf', $archive];
+
+ ### run the command
+ ### newer versions of 'tar' (1.21 and up) now print record size
+ ### to STDERR as well if v OR t is given (used to be both). This
+ ### is a 'feature' according to the changelog, so we must now only
+ ### inspect STDOUT, otherwise, failures like these occur:
+ ### http://www.cpantesters.org/cpan/report/3230366
+ my $buffer = '';
+ my @out = run( command => $cmd,
+ buffer => \$buffer,
+ verbose => $DEBUG );
+
+ ### command was unsuccessful
+ unless( $out[0] ) {
+ return $self->_error(loc(
+ "Error listing contents of archive '%1': %2",
+ $archive, $buffer ));
+ }
+
+ ### no buffers available?
+ if( !IPC::Cmd->can_capture_buffer and !$buffer ) {
+ $self->_error( $self->_no_buffer_files( $archive ) );
+
+ } else {
+ ### if we're on solaris we /might/ be using /bin/tar, which has
+ ### a weird output format... we might also be using
+ ### /usr/local/bin/tar, which is gnu tar, which is perfectly
+ ### fine... so we have to do some guessing here =/
+ my @files = map { chomp;
+ !ON_SOLARIS ? $_
+ : (m|^ x \s+ # 'xtract' -- sigh
+ (.+?), # the actual file name
+ \s+ [\d,.]+ \s bytes,
+ \s+ [\d,.]+ \s tape \s blocks
+ |x ? $1 : $_);
+
+ ### only STDOUT, see above. Sometimes, extra whitespace
+ ### is present, so make sure we only pick lines with
+ ### a length
+ } grep { length } map { split $/, $_ } join '', @{$out[3]};
+
+ ### store the files that are in the archive ###
+ $self->files(\@files);
+ }
+ }
+
+ ### now actually extract it ###
+ { my $cmd =
+ $self->is_tgz ? [$self->bin_gzip, '-c', '-d', '-f', $archive, '|',
+ $self->bin_tar, '-xf', '-'] :
+ $self->is_tbz ? [$self->bin_bunzip2, '-cd', $archive, '|',
+ $self->bin_tar, '-xf', '-'] :
+ $self->is_txz ? [$self->bin_unxz, '-cd', $archive, '|',
+ $self->bin_tar, '-xf', '-'] :
+ [$self->bin_tar, @ExtraTarFlags, '-xf', $archive];
+
+ my $buffer = '';
+ unless( scalar run( command => $cmd,
+ buffer => \$buffer,
+ verbose => $DEBUG )
+ ) {
+ return $self->_error(loc("Error extracting archive '%1': %2",
+ $archive, $buffer ));
+ }
+
+ ### we might not have them, due to lack of buffers
+ if( $self->files ) {
+ ### now that we've extracted, figure out where we extracted to
+ my $dir = $self->__get_extract_dir( $self->files );
+
+ ### store the extraction dir ###
+ $self->extract_path( $dir );
+ }
+ }
+
+ ### we got here, no error happened
+ return 1;
+ }
+}
+
+
+### use archive::tar to extract ###
+sub _untar_at {
+ my $self = shift;
+
+ ### Loading Archive::Tar is going to set it to 1, so make it local
+ ### within this block, starting with its initial value. Whatever
+ ### Achive::Tar does will be undone when we return.
+ ###
+ ### Also, later, set $Archive::Tar::WARN to $Archive::Extract::WARN
+ ### so users don't have to even think about this variable. If they
+ ### do, they still get their set value outside of this call.
+ local $Archive::Tar::WARN = $Archive::Tar::WARN;
+
+ ### we definitely need Archive::Tar, so load that first
+ { my $use_list = { 'Archive::Tar' => '0.0' };
+
+ unless( can_load( modules => $use_list ) ) {
+
+ $self->_error(loc("You do not have '%1' installed - " .
+ "Please install it as soon as possible.",
+ 'Archive::Tar'));
+
+ return METHOD_NA;
+ }
+ }
+
+ ### we might pass it a filehandle if it's a .tbz file..
+ my $fh_to_read = $self->archive;
+
+ ### we will need Compress::Zlib too, if it's a tgz... and IO::Zlib
+ ### if A::T's version is 0.99 or higher
+ if( $self->is_tgz ) {
+ my $use_list = { 'Compress::Zlib' => '0.0' };
+ $use_list->{ 'IO::Zlib' } = '0.0'
+ if $Archive::Tar::VERSION >= '0.99';
+
+ unless( can_load( modules => $use_list ) ) {
+ my $which = join '/', sort keys %$use_list;
+
+ $self->_error(loc(
+ "You do not have '%1' installed - Please ".
+ "install it as soon as possible.", $which)
+ );
+
+ return METHOD_NA;
+ }
+
+ } elsif ( $self->is_tbz ) {
+ my $use_list = { 'IO::Uncompress::Bunzip2' => '0.0' };
+ unless( can_load( modules => $use_list ) ) {
+ $self->_error(loc(
+ "You do not have '%1' installed - Please " .
+ "install it as soon as possible.",
+ 'IO::Uncompress::Bunzip2')
+ );
+
+ return METHOD_NA;
+ }
+
+ my $bz = IO::Uncompress::Bunzip2->new( $self->archive ) or
+ return $self->_error(loc("Unable to open '%1': %2",
+ $self->archive,
+ $IO::Uncompress::Bunzip2::Bunzip2Error));
+
+ $fh_to_read = $bz;
+ } elsif ( $self->is_txz ) {
+ my $use_list = { 'IO::Uncompress::UnXz' => '0.0' };
+ unless( can_load( modules => $use_list ) ) {
+ $self->_error(loc(
+ "You do not have '%1' installed - Please " .
+ "install it as soon as possible.",
+ 'IO::Uncompress::UnXz')
+ );
+
+ return METHOD_NA;
+ }
+
+ my $xz = IO::Uncompress::UnXz->new( $self->archive ) or
+ return $self->_error(loc("Unable to open '%1': %2",
+ $self->archive,
+ $IO::Uncompress::UnXz::UnXzError));
+
+ $fh_to_read = $xz;
+ }
+
+ my @files;
+ {
+ ### $Archive::Tar::WARN is 1 by default in Archive::Tar, but we've
+ ### localized $Archive::Tar::WARN already.
+ $Archive::Tar::WARN = $Archive::Extract::WARN;
+
+ ### only tell it it's compressed if it's a .tgz, as we give it a file
+ ### handle if it's a .tbz
+ my @read = ( $fh_to_read, ( $self->is_tgz ? 1 : 0 ) );
+
+ ### for version of Archive::Tar > 1.04
+ local $Archive::Tar::CHOWN = 0;
+
+ ### use the iterator if we can. it's a feature of A::T 1.40 and up
+ if ( $_ALLOW_TAR_ITER && Archive::Tar->can( 'iter' ) ) {
+
+ my $next;
+ unless ( $next = Archive::Tar->iter( @read ) ) {
+ return $self->_error(loc(
+ "Unable to read '%1': %2", $self->archive,
+ $Archive::Tar::error));
+ }
+
+ while ( my $file = $next->() ) {
+ push @files, $file->full_path;
+
+ $file->extract or return $self->_error(loc(
+ "Unable to read '%1': %2",
+ $self->archive,
+ $Archive::Tar::error));
+ }
+
+ ### older version, read the archive into memory
+ } else {
+
+ my $tar = Archive::Tar->new();
+
+ unless( $tar->read( @read ) ) {
+ return $self->_error(loc("Unable to read '%1': %2",
+ $self->archive, $Archive::Tar::error));
+ }
+
+ ### workaround to prevent Archive::Tar from setting uid, which
+ ### is a potential security hole. -autrijus
+ ### have to do it here, since A::T needs to be /loaded/ first ###
+ { no strict 'refs'; local $^W;
+
+ ### older versions of archive::tar <= 0.23
+ *Archive::Tar::chown = sub {};
+ }
+
+ { local $^W; # quell 'splice() offset past end of array' warnings
+ # on older versions of A::T
+
+ ### older archive::tar always returns $self, return value
+ ### slightly fux0r3d because of it.
+ $tar->extract or return $self->_error(loc(
+ "Unable to extract '%1': %2",
+ $self->archive, $Archive::Tar::error ));
+ }
+
+ @files = $tar->list_files;
+ }
+ }
+
+ my $dir = $self->__get_extract_dir( \@files );
+
+ ### store the files that are in the archive ###
+ $self->files(\@files);
+
+ ### store the extraction dir ###
+ $self->extract_path( $dir );
+
+ ### check if the dir actually appeared ###
+ return 1 if -d $self->extract_path;
+
+ ### no dir, we failed ###
+ return $self->_error(loc("Unable to extract '%1': %2",
+ $self->archive, $Archive::Tar::error ));
+}
+
+#################################
+#
+# Gunzip code
+#
+#################################
+
+sub _gunzip_bin {
+ my $self = shift;
+
+ ### check for /bin/gzip -- we need it ###
+ unless( $self->bin_gzip ) {
+ $self->_error(loc("No '%1' program found", '/bin/gzip'));
+ return METHOD_NA;
+ }
+
+ my $fh = FileHandle->new('>'. $self->_gunzip_to) or
+ return $self->_error(loc("Could not open '%1' for writing: %2",
+ $self->_gunzip_to, $! ));
+
+ my $cmd = [ $self->bin_gzip, '-c', '-d', '-f', $self->archive ];
+
+ my $buffer;
+ unless( scalar run( command => $cmd,
+ verbose => $DEBUG,
+ buffer => \$buffer )
+ ) {
+ return $self->_error(loc("Unable to gunzip '%1': %2",
+ $self->archive, $buffer));
+ }
+
+ ### no buffers available?
+ if( !IPC::Cmd->can_capture_buffer and !$buffer ) {
+ $self->_error( $self->_no_buffer_content( $self->archive ) );
+ }
+
+ $self->_print($fh, $buffer) if defined $buffer;
+
+ close $fh;
+
+ ### set what files where extract, and where they went ###
+ $self->files( [$self->_gunzip_to] );
+ $self->extract_path( File::Spec->rel2abs(cwd()) );
+
+ return 1;
+}
+
+sub _gunzip_cz {
+ my $self = shift;
+
+ my $use_list = { 'Compress::Zlib' => '0.0' };
+ unless( can_load( modules => $use_list ) ) {
+ $self->_error(loc("You do not have '%1' installed - Please " .
+ "install it as soon as possible.", 'Compress::Zlib'));
+ return METHOD_NA;
+ }
+
+ my $gz = Compress::Zlib::gzopen( $self->archive, "rb" ) or
+ return $self->_error(loc("Unable to open '%1': %2",
+ $self->archive, $Compress::Zlib::gzerrno));
+
+ my $fh = FileHandle->new('>'. $self->_gunzip_to) or
+ return $self->_error(loc("Could not open '%1' for writing: %2",
+ $self->_gunzip_to, $! ));
+
+ my $buffer;
+ $self->_print($fh, $buffer) while $gz->gzread($buffer) > 0;
+ $fh->close;
+
+ ### set what files where extract, and where they went ###
+ $self->files( [$self->_gunzip_to] );
+ $self->extract_path( File::Spec->rel2abs(cwd()) );
+
+ return 1;
+}
+
+#################################
+#
+# Uncompress code
+#
+#################################
+
+sub _uncompress_bin {
+ my $self = shift;
+
+ ### check for /bin/gzip -- we need it ###
+ unless( $self->bin_uncompress ) {
+ $self->_error(loc("No '%1' program found", '/bin/uncompress'));
+ return METHOD_NA;
+ }
+
+ my $fh = FileHandle->new('>'. $self->_gunzip_to) or
+ return $self->_error(loc("Could not open '%1' for writing: %2",
+ $self->_gunzip_to, $! ));
+
+ my $cmd = [ $self->bin_uncompress, '-c', $self->archive ];
+
+ my $buffer;
+ unless( scalar run( command => $cmd,
+ verbose => $DEBUG,
+ buffer => \$buffer )
+ ) {
+ return $self->_error(loc("Unable to uncompress '%1': %2",
+ $self->archive, $buffer));
+ }
+
+ ### no buffers available?
+ if( !IPC::Cmd->can_capture_buffer and !$buffer ) {
+ $self->_error( $self->_no_buffer_content( $self->archive ) );
+ }
+
+ $self->_print($fh, $buffer) if defined $buffer;
+
+ close $fh;
+
+ ### set what files where extract, and where they went ###
+ $self->files( [$self->_gunzip_to] );
+ $self->extract_path( File::Spec->rel2abs(cwd()) );
+
+ return 1;
+}
+
+
+#################################
+#
+# Unzip code
+#
+#################################
+
+
+sub _unzip_bin {
+ my $self = shift;
+
+ ### check for /bin/gzip if we need it ###
+ unless( $self->bin_unzip ) {
+ $self->_error(loc("No '%1' program found", '/bin/unzip'));
+ return METHOD_NA;
+ }
+
+ ### first, get the files.. it must be 2 different commands with 'unzip' :(
+ { ### on VMS, capital letter options have to be quoted. This is
+ ### reported by John Malmberg on P5P Tue 21 Aug 2007 05:05:11
+ ### Subject: [patch@31735]Archive Extract fix on VMS.
+ my $opt = ON_VMS ? '"-Z"' : '-Z';
+ my $cmd = [ $self->bin_unzip, $opt, '-1', $self->archive ];
+
+ my $buffer = '';
+ unless( scalar run( command => $cmd,
+ verbose => $DEBUG,
+ buffer => \$buffer )
+ ) {
+ return $self->_error(loc("Unable to unzip '%1': %2",
+ $self->archive, $buffer));
+ }
+
+ ### no buffers available?
+ if( !IPC::Cmd->can_capture_buffer and !$buffer ) {
+ $self->_error( $self->_no_buffer_files( $self->archive ) );
+
+ } else {
+ ### Annoyingly, pesky MSWin32 can either have 'native' tools
+ ### which have \r\n line endings or Cygwin-based tools which
+ ### have \n line endings. Jan Dubois suggested using this fix
+ my $split = ON_WIN32 ? qr/\r?\n/ : "\n";
+ $self->files( [split $split, $buffer] );
+ }
+ }
+
+ ### now, extract the archive ###
+ { my $cmd = [ $self->bin_unzip, '-qq', '-o', $self->archive ];
+
+ my $buffer;
+ unless( scalar run( command => $cmd,
+ verbose => $DEBUG,
+ buffer => \$buffer )
+ ) {
+ return $self->_error(loc("Unable to unzip '%1': %2",
+ $self->archive, $buffer));
+ }
+
+ if( scalar @{$self->files} ) {
+ my $files = $self->files;
+ my $dir = $self->__get_extract_dir( $files );
+
+ $self->extract_path( $dir );
+ }
+ }
+
+ return 1;
+}
+
+sub _unzip_az {
+ my $self = shift;
+
+ my $use_list = { 'Archive::Zip' => '0.0' };
+ unless( can_load( modules => $use_list ) ) {
+ $self->_error(loc("You do not have '%1' installed - Please " .
+ "install it as soon as possible.", 'Archive::Zip'));
+ return METHOD_NA;
+ }
+
+ my $zip = Archive::Zip->new();
+
+ unless( $zip->read( $self->archive ) == &Archive::Zip::AZ_OK ) {
+ return $self->_error(loc("Unable to read '%1'", $self->archive));
+ }
+
+ my @files;
+
+
+ ### Address: #43278: Explicitly tell Archive::Zip where to put the files:
+ ### "In my BackPAN indexing, Archive::Zip was extracting things
+ ### in my script's directory instead of the current working directory.
+ ### I traced this back through Archive::Zip::_asLocalName which
+ ### eventually calls File::Spec::Win32::rel2abs which on Windows might
+ ### call Cwd::getdcwd. getdcwd returns the wrong directory in my
+ ### case, even though I think I'm on the same drive.
+ ###
+ ### To fix this, I pass the optional second argument to
+ ### extractMember using the cwd from Archive::Extract." --bdfoy
+
+ ## store cwd() before looping; calls to cwd() can be expensive, and
+ ### it won't change during the loop
+ my $extract_dir = cwd();
+
+ ### have to extract every member individually ###
+ for my $member ($zip->members) {
+ push @files, $member->{fileName};
+
+ ### file to extract to, to avoid the above problem
+ my $to = File::Spec->catfile( $extract_dir, $member->{fileName} );
+
+ unless( $zip->extractMember($member, $to) == &Archive::Zip::AZ_OK ) {
+ return $self->_error(loc("Extraction of '%1' from '%2' failed",
+ $member->{fileName}, $self->archive ));
+ }
+ }
+
+ my $dir = $self->__get_extract_dir( \@files );
+
+ ### set what files where extract, and where they went ###
+ $self->files( \@files );
+ $self->extract_path( File::Spec->rel2abs($dir) );
+
+ return 1;
+}
+
+sub __get_extract_dir {
+ my $self = shift;
+ my $files = shift || [];
+
+ return unless scalar @$files;
+
+ my($dir1, $dir2);
+ for my $aref ( [ \$dir1, 0 ], [ \$dir2, -1 ] ) {
+ my($dir,$pos) = @$aref;
+
+ ### add a catdir(), so that any trailing slashes get
+ ### take care of (removed)
+ ### also, a catdir() normalises './dir/foo' to 'dir/foo';
+ ### which was the problem in bug #23999
+ my $res = -d $files->[$pos]
+ ? File::Spec->catdir( $files->[$pos], '' )
+ : File::Spec->catdir( dirname( $files->[$pos] ) );
+
+ $$dir = $res;
+ }
+
+ ### if the first and last dir don't match, make sure the
+ ### dirname is not set wrongly
+ my $dir;
+
+ ### dirs are the same, so we know for sure what the extract dir is
+ if( $dir1 eq $dir2 ) {
+ $dir = $dir1;
+
+ ### dirs are different.. do they share the base dir?
+ ### if so, use that, if not, fall back to '.'
+ } else {
+ my $base1 = [ File::Spec->splitdir( $dir1 ) ]->[0];
+ my $base2 = [ File::Spec->splitdir( $dir2 ) ]->[0];
+
+ $dir = File::Spec->rel2abs( $base1 eq $base2 ? $base1 : '.' );
+ }
+
+ return File::Spec->rel2abs( $dir );
+}
+
+#################################
+#
+# Bunzip2 code
+#
+#################################
+
+sub _bunzip2_bin {
+ my $self = shift;
+
+ ### check for /bin/gzip -- we need it ###
+ unless( $self->bin_bunzip2 ) {
+ $self->_error(loc("No '%1' program found", '/bin/bunzip2'));
+ return METHOD_NA;
+ }
+
+ my $fh = FileHandle->new('>'. $self->_gunzip_to) or
+ return $self->_error(loc("Could not open '%1' for writing: %2",
+ $self->_gunzip_to, $! ));
+
+ ### guard against broken bunzip2. See ->have_old_bunzip2()
+ ### for details
+ if( $self->have_old_bunzip2 and $self->archive !~ /\.bz2$/i ) {
+ return $self->_error(loc("Your bunzip2 version is too old and ".
+ "can only extract files ending in '%1'",
+ '.bz2'));
+ }
+
+ my $cmd = [ $self->bin_bunzip2, '-cd', $self->archive ];
+
+ my $buffer;
+ unless( scalar run( command => $cmd,
+ verbose => $DEBUG,
+ buffer => \$buffer )
+ ) {
+ return $self->_error(loc("Unable to bunzip2 '%1': %2",
+ $self->archive, $buffer));
+ }
+
+ ### no buffers available?
+ if( !IPC::Cmd->can_capture_buffer and !$buffer ) {
+ $self->_error( $self->_no_buffer_content( $self->archive ) );
+ }
+
+ $self->_print($fh, $buffer) if defined $buffer;
+
+ close $fh;
+
+ ### set what files where extract, and where they went ###
+ $self->files( [$self->_gunzip_to] );
+ $self->extract_path( File::Spec->rel2abs(cwd()) );
+
+ return 1;
+}
+
+### using cz2, the compact versions... this we use mainly in archive::tar
+### extractor..
+# sub _bunzip2_cz1 {
+# my $self = shift;
+#
+# my $use_list = { 'IO::Uncompress::Bunzip2' => '0.0' };
+# unless( can_load( modules => $use_list ) ) {
+# return $self->_error(loc("You do not have '%1' installed - Please " .
+# "install it as soon as possible.",
+# 'IO::Uncompress::Bunzip2'));
+# }
+#
+# my $bz = IO::Uncompress::Bunzip2->new( $self->archive ) or
+# return $self->_error(loc("Unable to open '%1': %2",
+# $self->archive,
+# $IO::Uncompress::Bunzip2::Bunzip2Error));
+#
+# my $fh = FileHandle->new('>'. $self->_gunzip_to) or
+# return $self->_error(loc("Could not open '%1' for writing: %2",
+# $self->_gunzip_to, $! ));
+#
+# my $buffer;
+# $fh->print($buffer) while $bz->read($buffer) > 0;
+# $fh->close;
+#
+# ### set what files where extract, and where they went ###
+# $self->files( [$self->_gunzip_to] );
+# $self->extract_path( File::Spec->rel2abs(cwd()) );
+#
+# return 1;
+# }
+
+sub _bunzip2_bz2 {
+ my $self = shift;
+
+ my $use_list = { 'IO::Uncompress::Bunzip2' => '0.0' };
+ unless( can_load( modules => $use_list ) ) {
+ $self->_error(loc("You do not have '%1' installed - Please " .
+ "install it as soon as possible.",
+ 'IO::Uncompress::Bunzip2'));
+ return METHOD_NA;
+ }
+
+ IO::Uncompress::Bunzip2::bunzip2($self->archive => $self->_gunzip_to)
+ or return $self->_error(loc("Unable to uncompress '%1': %2",
+ $self->archive,
+ $IO::Uncompress::Bunzip2::Bunzip2Error));
+
+ ### set what files where extract, and where they went ###
+ $self->files( [$self->_gunzip_to] );
+ $self->extract_path( File::Spec->rel2abs(cwd()) );
+
+ return 1;
+}
+
+#################################
+#
+# UnXz code
+#
+#################################
+
+sub _unxz_bin {
+ my $self = shift;
+
+ ### check for /bin/unxz -- we need it ###
+ unless( $self->bin_unxz ) {
+ $self->_error(loc("No '%1' program found", '/bin/unxz'));
+ return METHOD_NA;
+ }
+
+ my $fh = FileHandle->new('>'. $self->_gunzip_to) or
+ return $self->_error(loc("Could not open '%1' for writing: %2",
+ $self->_gunzip_to, $! ));
+
+ my $cmd = [ $self->bin_unxz, '-c', '-d', '-f', $self->archive ];
+
+ my $buffer;
+ unless( scalar run( command => $cmd,
+ verbose => $DEBUG,
+ buffer => \$buffer )
+ ) {
+ return $self->_error(loc("Unable to unxz '%1': %2",
+ $self->archive, $buffer));
+ }
+
+ ### no buffers available?
+ if( !IPC::Cmd->can_capture_buffer and !$buffer ) {
+ $self->_error( $self->_no_buffer_content( $self->archive ) );
+ }
+
+ $self->_print($fh, $buffer) if defined $buffer;
+
+ close $fh;
+
+ ### set what files where extract, and where they went ###
+ $self->files( [$self->_gunzip_to] );
+ $self->extract_path( File::Spec->rel2abs(cwd()) );
+
+ return 1;
+}
+
+sub _unxz_cz {
+ my $self = shift;
+
+ my $use_list = { 'IO::Uncompress::UnXz' => '0.0' };
+ unless( can_load( modules => $use_list ) ) {
+ $self->_error(loc("You do not have '%1' installed - Please " .
+ "install it as soon as possible.",
+ 'IO::Uncompress::UnXz'));
+ return METHOD_NA;
+ }
+
+ IO::Uncompress::UnXz::unxz($self->archive => $self->_gunzip_to)
+ or return $self->_error(loc("Unable to uncompress '%1': %2",
+ $self->archive,
+ $IO::Uncompress::UnXz::UnXzError));
+
+ ### set what files where extract, and where they went ###
+ $self->files( [$self->_gunzip_to] );
+ $self->extract_path( File::Spec->rel2abs(cwd()) );
+
+ return 1;
+}
+
+
+#################################
+#
+# unlzma code
+#
+#################################
+
+sub _unlzma_bin {
+ my $self = shift;
+
+ ### check for /bin/unlzma -- we need it ###
+ unless( $self->bin_unlzma ) {
+ $self->_error(loc("No '%1' program found", '/bin/unlzma'));
+ return METHOD_NA;
+ }
+
+ my $fh = FileHandle->new('>'. $self->_gunzip_to) or
+ return $self->_error(loc("Could not open '%1' for writing: %2",
+ $self->_gunzip_to, $! ));
+
+ my $cmd = [ $self->bin_unlzma, '-c', $self->archive ];
+
+ my $buffer;
+ unless( scalar run( command => $cmd,
+ verbose => $DEBUG,
+ buffer => \$buffer )
+ ) {
+ return $self->_error(loc("Unable to unlzma '%1': %2",
+ $self->archive, $buffer));
+ }
+
+ ### no buffers available?
+ if( !IPC::Cmd->can_capture_buffer and !$buffer ) {
+ $self->_error( $self->_no_buffer_content( $self->archive ) );
+ }
+
+ $self->_print($fh, $buffer) if defined $buffer;
+
+ close $fh;
+
+ ### set what files where extract, and where they went ###
+ $self->files( [$self->_gunzip_to] );
+ $self->extract_path( File::Spec->rel2abs(cwd()) );
+
+ return 1;
+}
+
+sub _unlzma_cz {
+ my $self = shift;
+
+ my $use_list1 = { 'IO::Uncompress::UnLzma' => '0.0' };
+ my $use_list2 = { 'Compress::unLZMA' => '0.0' };
+
+ if (can_load( modules => $use_list1 ) ) {
+ IO::Uncompress::UnLzma::unlzma($self->archive => $self->_gunzip_to)
+ or return $self->_error(loc("Unable to uncompress '%1': %2",
+ $self->archive,
+ $IO::Uncompress::UnLzma::UnLzmaError));
+ }
+ elsif (can_load( modules => $use_list2 ) ) {
+
+ my $fh = FileHandle->new('>'. $self->_gunzip_to) or
+ return $self->_error(loc("Could not open '%1' for writing: %2",
+ $self->_gunzip_to, $! ));
+
+ my $buffer;
+ $buffer = Compress::unLZMA::uncompressfile( $self->archive );
+ unless ( defined $buffer ) {
+ return $self->_error(loc("Could not unlzma '%1': %2",
+ $self->archive, $@));
+ }
+
+ $self->_print($fh, $buffer) if defined $buffer;
+
+ close $fh;
+ }
+ else {
+ $self->_error(loc("You do not have '%1' or '%2' installed - Please " .
+ "install it as soon as possible.", 'Compress::unLZMA', 'IO::Uncompress::UnLzma'));
+ return METHOD_NA;
+ }
+
+ ### set what files where extract, and where they went ###
+ $self->files( [$self->_gunzip_to] );
+ $self->extract_path( File::Spec->rel2abs(cwd()) );
+
+ return 1;
+}
+
+#################################
+#
+# Error code
+#
+#################################
+
+# For printing binaries that avoids interfering globals
+sub _print {
+ my $self = shift;
+ my $fh = shift;
+
+ local( $\, $", $, ) = ( undef, ' ', '' );
+ return print $fh @_;
+}
+
+sub _error {
+ my $self = shift;
+ my $error = shift;
+ my $lerror = Carp::longmess($error);
+
+ push @{$self->_error_msg}, $error;
+ push @{$self->_error_msg_long}, $lerror;
+
+ ### set $Archive::Extract::WARN to 0 to disable printing
+ ### of errors
+ if( $WARN ) {
+ carp $DEBUG ? $lerror : $error;
+ }
+
+ return;
+}
+
+sub error {
+ my $self = shift;
+
+ ### make sure we have a fallback aref
+ my $aref = do {
+ shift()
+ ? $self->_error_msg_long
+ : $self->_error_msg
+ } || [];
+
+ return join $/, @$aref;
+}
+
+=head2 debug( MESSAGE )
+
+This method outputs MESSAGE to the default filehandle if C<$DEBUG> is
+true. It's a small method, but it's here if you'd like to subclass it
+so you can so something else with any debugging output.
+
+=cut
+
+### this is really a stub for subclassing
+sub debug {
+ return unless $DEBUG;
+
+ print $_[1];
+}
+
+sub _no_buffer_files {
+ my $self = shift;
+ my $file = shift or return;
+ return loc("No buffer captured, unable to tell ".
+ "extracted files or extraction dir for '%1'", $file);
+}
+
+sub _no_buffer_content {
+ my $self = shift;
+ my $file = shift or return;
+ return loc("No buffer captured, unable to get content for '%1'", $file);
+}
+1;
+
+=pod
+
+=head1 HOW IT WORKS
+
+C tries first to determine what type of archive you
+are passing it, by inspecting its suffix. It does not do this by using
+Mime magic, or something related. See C below.
+
+Once it has determined the file type, it knows which extraction methods
+it can use on the archive. It will try a perl solution first, then fall
+back to a commandline tool if that fails. If that also fails, it will
+return false, indicating it was unable to extract the archive.
+See the section on C to see how to alter this order.
+
+=head1 CAVEATS
+
+=head2 File Extensions
+
+C trusts on the extension of the archive to determine
+what type it is, and what extractor methods therefore can be used. If
+your archives do not have any of the extensions as described in the
+C method, you will have to specify the type explicitly, or
+C will not be able to extract the archive for you.
+
+=head2 Supporting Very Large Files
+
+C can use either pure perl modules or command line
+programs under the hood. Some of the pure perl modules (like
+C and Compress::unLZMA) take the entire contents of the archive into memory,
+which may not be feasible on your system. Consider setting the global
+variable C<$Archive::Extract::PREFER_BIN> to C<1>, which will prefer
+the use of command line programs and won't consume so much memory.
+
+See the C section below for details.
+
+=head2 Bunzip2 support of arbitrary extensions.
+
+Older versions of C do not support arbitrary file
+extensions and insist on a C<.bz2> suffix. Although we do our best
+to guard against this, if you experience a bunzip2 error, it may
+be related to this. For details, please see the C
+method.
+
+=head1 GLOBAL VARIABLES
+
+=head2 $Archive::Extract::DEBUG
+
+Set this variable to C to have all calls to command line tools
+be printed out, including all their output.
+This also enables C errors, instead of the regular
+C errors.
+
+Good for tracking down why things don't work with your particular
+setup.
+
+Defaults to C.
+
+=head2 $Archive::Extract::WARN
+
+This variable controls whether errors encountered internally by
+C should be C'd or not.
+
+Set to false to silence warnings. Inspect the output of the C
+method manually to see what went wrong.
+
+Defaults to C.
+
+=head2 $Archive::Extract::PREFER_BIN
+
+This variables controls whether C should prefer the
+use of perl modules, or commandline tools to extract archives.
+
+Set to C to have C prefer commandline tools.
+
+Defaults to C.
+
+=head1 TODO / CAVEATS
+
+=over 4
+
+=item Mime magic support
+
+Maybe this module should use something like C to determine
+the type, rather than blindly trust the suffix.
+
+=item Thread safety
+
+Currently, C does a C to the extraction dir before
+extraction, and a C back again after. This is not necessarily
+thread safe. See C bug C<#45671> for details.
+
+=back
+
+=head1 BUG REPORTS
+
+Please report bugs or other issues to Ebug-archive-extract@rt.cpan.orgE.
+
+=head1 AUTHOR
+
+This module by Jos Boumans Ekane@cpan.orgE.
+
+=head1 COPYRIGHT
+
+This library is free software; you may redistribute and/or modify it
+under the same terms as Perl itself.
+
+=cut
+
+# Local variables:
+# c-indentation-style: bsd
+# c-basic-offset: 4
+# indent-tabs-mode: nil
+# End:
+# vim: expandtab shiftwidth=4:
+
diff --git a/dev-tools/src/main/resources/license-check/lib/Archive/Zip.pm b/dev-tools/src/main/resources/license-check/lib/Archive/Zip.pm
new file mode 100644
index 00000000000..0fdbf17bb39
--- /dev/null
+++ b/dev-tools/src/main/resources/license-check/lib/Archive/Zip.pm
@@ -0,0 +1,2136 @@
+package Archive::Zip;
+
+use 5.006;
+use strict;
+use Carp ();
+use Cwd ();
+use IO::File ();
+use IO::Seekable ();
+use Compress::Raw::Zlib ();
+use File::Spec ();
+use File::Temp ();
+use FileHandle ();
+
+use vars qw( $VERSION @ISA );
+
+BEGIN {
+ $VERSION = '1.48';
+
+ require Exporter;
+ @ISA = qw( Exporter );
+}
+
+use vars qw( $ChunkSize $ErrorHandler );
+
+BEGIN {
+ # This is the size we'll try to read, write, and (de)compress.
+ # You could set it to something different if you had lots of memory
+ # and needed more speed.
+ $ChunkSize ||= 32768;
+
+ $ErrorHandler = \&Carp::carp;
+}
+
+# BEGIN block is necessary here so that other modules can use the constants.
+use vars qw( @EXPORT_OK %EXPORT_TAGS );
+
+BEGIN {
+ @EXPORT_OK = ('computeCRC32');
+ %EXPORT_TAGS = (
+ CONSTANTS => [
+ qw(
+ FA_MSDOS
+ FA_UNIX
+ GPBF_ENCRYPTED_MASK
+ GPBF_DEFLATING_COMPRESSION_MASK
+ GPBF_HAS_DATA_DESCRIPTOR_MASK
+ COMPRESSION_STORED
+ COMPRESSION_DEFLATED
+ COMPRESSION_LEVEL_NONE
+ COMPRESSION_LEVEL_DEFAULT
+ COMPRESSION_LEVEL_FASTEST
+ COMPRESSION_LEVEL_BEST_COMPRESSION
+ IFA_TEXT_FILE_MASK
+ IFA_TEXT_FILE
+ IFA_BINARY_FILE
+ )
+ ],
+
+ MISC_CONSTANTS => [
+ qw(
+ FA_AMIGA
+ FA_VAX_VMS
+ FA_VM_CMS
+ FA_ATARI_ST
+ FA_OS2_HPFS
+ FA_MACINTOSH
+ FA_Z_SYSTEM
+ FA_CPM
+ FA_TOPS20
+ FA_WINDOWS_NTFS
+ FA_QDOS
+ FA_ACORN
+ FA_VFAT
+ FA_MVS
+ FA_BEOS
+ FA_TANDEM
+ FA_THEOS
+ GPBF_IMPLODING_8K_SLIDING_DICTIONARY_MASK
+ GPBF_IMPLODING_3_SHANNON_FANO_TREES_MASK
+ GPBF_IS_COMPRESSED_PATCHED_DATA_MASK
+ COMPRESSION_SHRUNK
+ DEFLATING_COMPRESSION_NORMAL
+ DEFLATING_COMPRESSION_MAXIMUM
+ DEFLATING_COMPRESSION_FAST
+ DEFLATING_COMPRESSION_SUPER_FAST
+ COMPRESSION_REDUCED_1
+ COMPRESSION_REDUCED_2
+ COMPRESSION_REDUCED_3
+ COMPRESSION_REDUCED_4
+ COMPRESSION_IMPLODED
+ COMPRESSION_TOKENIZED
+ COMPRESSION_DEFLATED_ENHANCED
+ COMPRESSION_PKWARE_DATA_COMPRESSION_LIBRARY_IMPLODED
+ )
+ ],
+
+ ERROR_CODES => [
+ qw(
+ AZ_OK
+ AZ_STREAM_END
+ AZ_ERROR
+ AZ_FORMAT_ERROR
+ AZ_IO_ERROR
+ )
+ ],
+
+ # For Internal Use Only
+ PKZIP_CONSTANTS => [
+ qw(
+ SIGNATURE_FORMAT
+ SIGNATURE_LENGTH
+ LOCAL_FILE_HEADER_SIGNATURE
+ LOCAL_FILE_HEADER_FORMAT
+ LOCAL_FILE_HEADER_LENGTH
+ CENTRAL_DIRECTORY_FILE_HEADER_SIGNATURE
+ DATA_DESCRIPTOR_FORMAT
+ DATA_DESCRIPTOR_LENGTH
+ DATA_DESCRIPTOR_SIGNATURE
+ DATA_DESCRIPTOR_FORMAT_NO_SIG
+ DATA_DESCRIPTOR_LENGTH_NO_SIG
+ CENTRAL_DIRECTORY_FILE_HEADER_FORMAT
+ CENTRAL_DIRECTORY_FILE_HEADER_LENGTH
+ END_OF_CENTRAL_DIRECTORY_SIGNATURE
+ END_OF_CENTRAL_DIRECTORY_SIGNATURE_STRING
+ END_OF_CENTRAL_DIRECTORY_FORMAT
+ END_OF_CENTRAL_DIRECTORY_LENGTH
+ )
+ ],
+
+ # For Internal Use Only
+ UTILITY_METHODS => [
+ qw(
+ _error
+ _printError
+ _ioError
+ _formatError
+ _subclassResponsibility
+ _binmode
+ _isSeekable
+ _newFileHandle
+ _readSignature
+ _asZipDirName
+ )
+ ],
+ );
+
+ # Add all the constant names and error code names to @EXPORT_OK
+ Exporter::export_ok_tags(
+ qw(
+ CONSTANTS
+ ERROR_CODES
+ PKZIP_CONSTANTS
+ UTILITY_METHODS
+ MISC_CONSTANTS
+ ));
+
+}
+
+# Error codes
+use constant AZ_OK => 0;
+use constant AZ_STREAM_END => 1;
+use constant AZ_ERROR => 2;
+use constant AZ_FORMAT_ERROR => 3;
+use constant AZ_IO_ERROR => 4;
+
+# File types
+# Values of Archive::Zip::Member->fileAttributeFormat()
+
+use constant FA_MSDOS => 0;
+use constant FA_AMIGA => 1;
+use constant FA_VAX_VMS => 2;
+use constant FA_UNIX => 3;
+use constant FA_VM_CMS => 4;
+use constant FA_ATARI_ST => 5;
+use constant FA_OS2_HPFS => 6;
+use constant FA_MACINTOSH => 7;
+use constant FA_Z_SYSTEM => 8;
+use constant FA_CPM => 9;
+use constant FA_TOPS20 => 10;
+use constant FA_WINDOWS_NTFS => 11;
+use constant FA_QDOS => 12;
+use constant FA_ACORN => 13;
+use constant FA_VFAT => 14;
+use constant FA_MVS => 15;
+use constant FA_BEOS => 16;
+use constant FA_TANDEM => 17;
+use constant FA_THEOS => 18;
+
+# general-purpose bit flag masks
+# Found in Archive::Zip::Member->bitFlag()
+
+use constant GPBF_ENCRYPTED_MASK => 1 << 0;
+use constant GPBF_DEFLATING_COMPRESSION_MASK => 3 << 1;
+use constant GPBF_HAS_DATA_DESCRIPTOR_MASK => 1 << 3;
+
+# deflating compression types, if compressionMethod == COMPRESSION_DEFLATED
+# ( Archive::Zip::Member->bitFlag() & GPBF_DEFLATING_COMPRESSION_MASK )
+
+use constant DEFLATING_COMPRESSION_NORMAL => 0 << 1;
+use constant DEFLATING_COMPRESSION_MAXIMUM => 1 << 1;
+use constant DEFLATING_COMPRESSION_FAST => 2 << 1;
+use constant DEFLATING_COMPRESSION_SUPER_FAST => 3 << 1;
+
+# compression method
+
+# these two are the only ones supported in this module
+use constant COMPRESSION_STORED => 0; # file is stored (no compression)
+use constant COMPRESSION_DEFLATED => 8; # file is Deflated
+use constant COMPRESSION_LEVEL_NONE => 0;
+use constant COMPRESSION_LEVEL_DEFAULT => -1;
+use constant COMPRESSION_LEVEL_FASTEST => 1;
+use constant COMPRESSION_LEVEL_BEST_COMPRESSION => 9;
+
+# internal file attribute bits
+# Found in Archive::Zip::Member::internalFileAttributes()
+
+use constant IFA_TEXT_FILE_MASK => 1;
+use constant IFA_TEXT_FILE => 1;
+use constant IFA_BINARY_FILE => 0;
+
+# PKZIP file format miscellaneous constants (for internal use only)
+use constant SIGNATURE_FORMAT => "V";
+use constant SIGNATURE_LENGTH => 4;
+
+# these lengths are without the signature.
+use constant LOCAL_FILE_HEADER_SIGNATURE => 0x04034b50;
+use constant LOCAL_FILE_HEADER_FORMAT => "v3 V4 v2";
+use constant LOCAL_FILE_HEADER_LENGTH => 26;
+
+# PKZIP docs don't mention the signature, but Info-Zip writes it.
+use constant DATA_DESCRIPTOR_SIGNATURE => 0x08074b50;
+use constant DATA_DESCRIPTOR_FORMAT => "V3";
+use constant DATA_DESCRIPTOR_LENGTH => 12;
+
+# but the signature is apparently optional.
+use constant DATA_DESCRIPTOR_FORMAT_NO_SIG => "V2";
+use constant DATA_DESCRIPTOR_LENGTH_NO_SIG => 8;
+
+use constant CENTRAL_DIRECTORY_FILE_HEADER_SIGNATURE => 0x02014b50;
+use constant CENTRAL_DIRECTORY_FILE_HEADER_FORMAT => "C2 v3 V4 v5 V2";
+use constant CENTRAL_DIRECTORY_FILE_HEADER_LENGTH => 42;
+
+use constant END_OF_CENTRAL_DIRECTORY_SIGNATURE => 0x06054b50;
+use constant END_OF_CENTRAL_DIRECTORY_SIGNATURE_STRING =>
+ pack("V", END_OF_CENTRAL_DIRECTORY_SIGNATURE);
+use constant END_OF_CENTRAL_DIRECTORY_FORMAT => "v4 V2 v";
+use constant END_OF_CENTRAL_DIRECTORY_LENGTH => 18;
+
+use constant GPBF_IMPLODING_8K_SLIDING_DICTIONARY_MASK => 1 << 1;
+use constant GPBF_IMPLODING_3_SHANNON_FANO_TREES_MASK => 1 << 2;
+use constant GPBF_IS_COMPRESSED_PATCHED_DATA_MASK => 1 << 5;
+
+# the rest of these are not supported in this module
+use constant COMPRESSION_SHRUNK => 1; # file is Shrunk
+use constant COMPRESSION_REDUCED_1 => 2; # file is Reduced CF=1
+use constant COMPRESSION_REDUCED_2 => 3; # file is Reduced CF=2
+use constant COMPRESSION_REDUCED_3 => 4; # file is Reduced CF=3
+use constant COMPRESSION_REDUCED_4 => 5; # file is Reduced CF=4
+use constant COMPRESSION_IMPLODED => 6; # file is Imploded
+use constant COMPRESSION_TOKENIZED => 7; # reserved for Tokenizing compr.
+use constant COMPRESSION_DEFLATED_ENHANCED => 9; # reserved for enh. Deflating
+use constant COMPRESSION_PKWARE_DATA_COMPRESSION_LIBRARY_IMPLODED => 10;
+
+# Load the various required classes
+require Archive::Zip::Archive;
+require Archive::Zip::Member;
+require Archive::Zip::FileMember;
+require Archive::Zip::DirectoryMember;
+require Archive::Zip::ZipFileMember;
+require Archive::Zip::NewFileMember;
+require Archive::Zip::StringMember;
+
+# Convenience functions
+
+sub _ISA ($$) {
+
+ # Can't rely on Scalar::Util, so use the next best way
+ local $@;
+ !!eval { ref $_[0] and $_[0]->isa($_[1]) };
+}
+
+sub _CAN ($$) {
+ local $@;
+ !!eval { ref $_[0] and $_[0]->can($_[1]) };
+}
+
+#####################################################################
+# Methods
+
+sub new {
+ my $class = shift;
+ return Archive::Zip::Archive->new(@_);
+}
+
+sub computeCRC32 {
+ my ($data, $crc);
+
+ if (ref($_[0]) eq 'HASH') {
+ $data = $_[0]->{string};
+ $crc = $_[0]->{checksum};
+ } else {
+ $data = shift;
+ $data = shift if ref($data);
+ $crc = shift;
+ }
+
+ return Compress::Raw::Zlib::crc32($data, $crc);
+}
+
+# Report or change chunk size used for reading and writing.
+# Also sets Zlib's default buffer size (eventually).
+sub setChunkSize {
+ shift if ref($_[0]) eq 'Archive::Zip::Archive';
+ my $chunkSize = (ref($_[0]) eq 'HASH') ? shift->{chunkSize} : shift;
+ my $oldChunkSize = $Archive::Zip::ChunkSize;
+ $Archive::Zip::ChunkSize = $chunkSize if ($chunkSize);
+ return $oldChunkSize;
+}
+
+sub chunkSize {
+ return $Archive::Zip::ChunkSize;
+}
+
+sub setErrorHandler {
+ my $errorHandler = (ref($_[0]) eq 'HASH') ? shift->{subroutine} : shift;
+ $errorHandler = \&Carp::carp unless defined($errorHandler);
+ my $oldErrorHandler = $Archive::Zip::ErrorHandler;
+ $Archive::Zip::ErrorHandler = $errorHandler;
+ return $oldErrorHandler;
+}
+
+######################################################################
+# Private utility functions (not methods).
+
+sub _printError {
+ my $string = join(' ', @_, "\n");
+ my $oldCarpLevel = $Carp::CarpLevel;
+ $Carp::CarpLevel += 2;
+ &{$ErrorHandler}($string);
+ $Carp::CarpLevel = $oldCarpLevel;
+}
+
+# This is called on format errors.
+sub _formatError {
+ shift if ref($_[0]);
+ _printError('format error:', @_);
+ return AZ_FORMAT_ERROR;
+}
+
+# This is called on IO errors.
+sub _ioError {
+ shift if ref($_[0]);
+ _printError('IO error:', @_, ':', $!);
+ return AZ_IO_ERROR;
+}
+
+# This is called on generic errors.
+sub _error {
+ shift if ref($_[0]);
+ _printError('error:', @_);
+ return AZ_ERROR;
+}
+
+# Called when a subclass should have implemented
+# something but didn't
+sub _subclassResponsibility {
+ Carp::croak("subclass Responsibility\n");
+}
+
+# Try to set the given file handle or object into binary mode.
+sub _binmode {
+ my $fh = shift;
+ return _CAN($fh, 'binmode') ? $fh->binmode() : binmode($fh);
+}
+
+# Attempt to guess whether file handle is seekable.
+# Because of problems with Windows, this only returns true when
+# the file handle is a real file.
+sub _isSeekable {
+ my $fh = shift;
+ return 0 unless ref $fh;
+ _ISA($fh, "IO::Scalar") # IO::Scalar objects are brokenly-seekable
+ and return 0;
+ _ISA($fh, "IO::String")
+ and return 1;
+ if (_ISA($fh, "IO::Seekable")) {
+
+ # Unfortunately, some things like FileHandle objects
+ # return true for Seekable, but AREN'T!!!!!
+ _ISA($fh, "FileHandle")
+ and return 0;
+ return 1;
+ }
+
+ # open my $fh, "+<", \$data;
+ ref $fh eq "GLOB" && eval { seek $fh, 0, 1 } and return 1;
+ _CAN($fh, "stat")
+ and return -f $fh;
+ return (_CAN($fh, "seek") and _CAN($fh, "tell")) ? 1 : 0;
+}
+
+# Print to the filehandle, while making sure the pesky Perl special global
+# variables don't interfere.
+sub _print {
+ my ($self, $fh, @data) = @_;
+
+ local $\;
+
+ return $fh->print(@data);
+}
+
+# Return an opened IO::Handle
+# my ( $status, fh ) = _newFileHandle( 'fileName', 'w' );
+# Can take a filename, file handle, or ref to GLOB
+# Or, if given something that is a ref but not an IO::Handle,
+# passes back the same thing.
+sub _newFileHandle {
+ my $fd = shift;
+ my $status = 1;
+ my $handle;
+
+ if (ref($fd)) {
+ if (_ISA($fd, 'IO::Scalar') or _ISA($fd, 'IO::String')) {
+ $handle = $fd;
+ } elsif (_ISA($fd, 'IO::Handle') or ref($fd) eq 'GLOB') {
+ $handle = IO::File->new;
+ $status = $handle->fdopen($fd, @_);
+ } else {
+ $handle = $fd;
+ }
+ } else {
+ $handle = IO::File->new;
+ $status = $handle->open($fd, @_);
+ }
+
+ return ($status, $handle);
+}
+
+# Returns next signature from given file handle, leaves
+# file handle positioned afterwards.
+# In list context, returns ($status, $signature)
+# ( $status, $signature) = _readSignature( $fh, $fileName );
+
+sub _readSignature {
+ my $fh = shift;
+ my $fileName = shift;
+ my $expectedSignature = shift; # optional
+
+ my $signatureData;
+ my $bytesRead = $fh->read($signatureData, SIGNATURE_LENGTH);
+ if ($bytesRead != SIGNATURE_LENGTH) {
+ return _ioError("reading header signature");
+ }
+ my $signature = unpack(SIGNATURE_FORMAT, $signatureData);
+ my $status = AZ_OK;
+
+ # compare with expected signature, if any, or any known signature.
+ if (
+ (defined($expectedSignature) && $signature != $expectedSignature)
+ || ( !defined($expectedSignature)
+ && $signature != CENTRAL_DIRECTORY_FILE_HEADER_SIGNATURE
+ && $signature != LOCAL_FILE_HEADER_SIGNATURE
+ && $signature != END_OF_CENTRAL_DIRECTORY_SIGNATURE
+ && $signature != DATA_DESCRIPTOR_SIGNATURE)
+ ) {
+ my $errmsg = sprintf("bad signature: 0x%08x", $signature);
+ if (_isSeekable($fh)) {
+ $errmsg .= sprintf(" at offset %d", $fh->tell() - SIGNATURE_LENGTH);
+ }
+
+ $status = _formatError("$errmsg in file $fileName");
+ }
+
+ return ($status, $signature);
+}
+
+# Utility method to make and open a temp file.
+# Will create $temp_dir if it does not exist.
+# Returns file handle and name:
+#
+# my ($fh, $name) = Archive::Zip::tempFile();
+# my ($fh, $name) = Archive::Zip::tempFile('mytempdir');
+#
+
+sub tempFile {
+ my $dir = (ref($_[0]) eq 'HASH') ? shift->{tempDir} : shift;
+ my ($fh, $filename) = File::Temp::tempfile(
+ SUFFIX => '.zip',
+ UNLINK => 1,
+ $dir ? (DIR => $dir) : ());
+ return (undef, undef) unless $fh;
+ my ($status, $newfh) = _newFileHandle($fh, 'w+');
+ return ($newfh, $filename);
+}
+
+# Return the normalized directory name as used in a zip file (path
+# separators become slashes, etc.).
+# Will translate internal slashes in path components (i.e. on Macs) to
+# underscores. Discards volume names.
+# When $forceDir is set, returns paths with trailing slashes (or arrays
+# with trailing blank members).
+#
+# If third argument is a reference, returns volume information there.
+#
+# input output
+# . ('.') '.'
+# ./a ('a') a
+# ./a/b ('a','b') a/b
+# ./a/b/ ('a','b') a/b
+# a/b/ ('a','b') a/b
+# /a/b/ ('','a','b') a/b
+# c:\a\b\c.doc ('','a','b','c.doc') a/b/c.doc # on Windows
+# "i/o maps:whatever" ('i_o maps', 'whatever') "i_o maps/whatever" # on Macs
+sub _asZipDirName {
+ my $name = shift;
+ my $forceDir = shift;
+ my $volReturn = shift;
+ my ($volume, $directories, $file) =
+ File::Spec->splitpath(File::Spec->canonpath($name), $forceDir);
+ $$volReturn = $volume if (ref($volReturn));
+ my @dirs = map { $_ =~ s{/}{_}g; $_ } File::Spec->splitdir($directories);
+ if (@dirs > 0) { pop(@dirs) unless $dirs[-1] } # remove empty component
+ push(@dirs, defined($file) ? $file : '');
+
+ #return wantarray ? @dirs : join ( '/', @dirs );
+
+ my $normalised_path = join '/', @dirs;
+
+ # Leading directory separators should not be stored in zip archives.
+ # Example:
+ # C:\a\b\c\ a/b/c
+ # C:\a\b\c.txt a/b/c.txt
+ # /a/b/c/ a/b/c
+ # /a/b/c.txt a/b/c.txt
+ $normalised_path =~ s{^/}{}; # remove leading separator
+
+ return $normalised_path;
+}
+
+# Return an absolute local name for a zip name.
+# Assume a directory if zip name has trailing slash.
+# Takes an optional volume name in FS format (like 'a:').
+#
+sub _asLocalName {
+ my $name = shift; # zip format
+ my $volume = shift;
+ $volume = '' unless defined($volume); # local FS format
+
+ my @paths = split(/\//, $name);
+ my $filename = pop(@paths);
+ $filename = '' unless defined($filename);
+ my $localDirs = @paths ? File::Spec->catdir(@paths) : '';
+ my $localName = File::Spec->catpath($volume, $localDirs, $filename);
+ unless ($volume) {
+ $localName = File::Spec->rel2abs($localName, Cwd::getcwd());
+ }
+ return $localName;
+}
+
+1;
+
+__END__
+
+=pod
+
+=encoding utf8
+
+=head1 NAME
+
+Archive::Zip - Provide an interface to ZIP archive files.
+
+=head1 SYNOPSIS
+
+ # Create a Zip file
+ use Archive::Zip qw( :ERROR_CODES :CONSTANTS );
+ my $zip = Archive::Zip->new();
+
+ # Add a directory
+ my $dir_member = $zip->addDirectory( 'dirname/' );
+
+ # Add a file from a string with compression
+ my $string_member = $zip->addString( 'This is a test', 'stringMember.txt' );
+ $string_member->desiredCompressionMethod( COMPRESSION_DEFLATED );
+
+ # Add a file from disk
+ my $file_member = $zip->addFile( 'xyz.pl', 'AnotherName.pl' );
+
+ # Save the Zip file
+ unless ( $zip->writeToFileNamed('someZip.zip') == AZ_OK ) {
+ die 'write error';
+ }
+
+ # Read a Zip file
+ my $somezip = Archive::Zip->new();
+ unless ( $somezip->read( 'someZip.zip' ) == AZ_OK ) {
+ die 'read error';
+ }
+
+ # Change the compression type for a file in the Zip
+ my $member = $somezip->memberNamed( 'stringMember.txt' );
+ $member->desiredCompressionMethod( COMPRESSION_STORED );
+ unless ( $zip->writeToFileNamed( 'someOtherZip.zip' ) == AZ_OK ) {
+ die 'write error';
+ }
+
+=head1 DESCRIPTION
+
+The Archive::Zip module allows a Perl program to create, manipulate, read,
+and write Zip archive files.
+
+Zip archives can be created, or you can read from existing zip files.
+
+Once created, they can be written to files, streams, or strings. Members
+can be added, removed, extracted, replaced, rearranged, and enumerated.
+They can also be renamed or have their dates, comments, or other attributes
+queried or modified. Their data can be compressed or uncompressed as needed.
+
+Members can be created from members in existing Zip files, or from existing
+directories, files, or strings.
+
+This module uses the L library to read and write the
+compressed streams inside the files.
+
+One can use L to read the zip file archive members
+as if they were files.
+
+=head2 File Naming
+
+Regardless of what your local file system uses for file naming, names in a
+Zip file are in Unix format (I slashes (/) separating directory
+names, etc.).
+
+C tries to be consistent with file naming conventions, and will
+translate back and forth between native and Zip file names.
+
+However, it can't guess which format names are in. So two rules control what
+kind of file name you must pass various routines:
+
+=over 4
+
+=item Names of files are in local format.
+
+C and C are used for various file
+operations. When you're referring to a file on your system, use its
+file naming conventions.
+
+=item Names of archive members are in Unix format.
+
+This applies to every method that refers to an archive member, or
+provides a name for new archive members. The C methods
+that can take one or two names will convert from local to zip names
+if you call them with a single name.
+
+=back
+
+=head2 Archive::Zip Object Model
+
+=head3 Overview
+
+Archive::Zip::Archive objects are what you ordinarily deal with.
+These maintain the structure of a zip file, without necessarily
+holding data. When a zip is read from a disk file, the (possibly
+compressed) data still lives in the file, not in memory. Archive
+members hold information about the individual members, but not
+(usually) the actual member data. When the zip is written to a
+(different) file, the member data is compressed or copied as needed.
+It is possible to make archive members whose data is held in a string
+in memory, but this is not done when a zip file is read. Directory
+members don't have any data.
+
+=head2 Inheritance
+
+ Exporter
+ Archive::Zip Common base class, has defs.
+ Archive::Zip::Archive A Zip archive.
+ Archive::Zip::Member Abstract superclass for all members.
+ Archive::Zip::StringMember Member made from a string
+ Archive::Zip::FileMember Member made from an external file
+ Archive::Zip::ZipFileMember Member that lives in a zip file
+ Archive::Zip::NewFileMember Member whose data is in a file
+ Archive::Zip::DirectoryMember Member that is a directory
+
+=head1 EXPORTS
+
+=over 4
+
+=item :CONSTANTS
+
+Exports the following constants:
+
+FA_MSDOS FA_UNIX GPBF_ENCRYPTED_MASK
+GPBF_DEFLATING_COMPRESSION_MASK GPBF_HAS_DATA_DESCRIPTOR_MASK
+COMPRESSION_STORED COMPRESSION_DEFLATED IFA_TEXT_FILE_MASK
+IFA_TEXT_FILE IFA_BINARY_FILE COMPRESSION_LEVEL_NONE
+COMPRESSION_LEVEL_DEFAULT COMPRESSION_LEVEL_FASTEST
+COMPRESSION_LEVEL_BEST_COMPRESSION
+
+=item :MISC_CONSTANTS
+
+Exports the following constants (only necessary for extending the
+module):
+
+FA_AMIGA FA_VAX_VMS FA_VM_CMS FA_ATARI_ST FA_OS2_HPFS
+FA_MACINTOSH FA_Z_SYSTEM FA_CPM FA_WINDOWS_NTFS
+GPBF_IMPLODING_8K_SLIDING_DICTIONARY_MASK
+GPBF_IMPLODING_3_SHANNON_FANO_TREES_MASK
+GPBF_IS_COMPRESSED_PATCHED_DATA_MASK COMPRESSION_SHRUNK
+DEFLATING_COMPRESSION_NORMAL DEFLATING_COMPRESSION_MAXIMUM
+DEFLATING_COMPRESSION_FAST DEFLATING_COMPRESSION_SUPER_FAST
+COMPRESSION_REDUCED_1 COMPRESSION_REDUCED_2 COMPRESSION_REDUCED_3
+COMPRESSION_REDUCED_4 COMPRESSION_IMPLODED COMPRESSION_TOKENIZED
+COMPRESSION_DEFLATED_ENHANCED
+COMPRESSION_PKWARE_DATA_COMPRESSION_LIBRARY_IMPLODED
+
+=item :ERROR_CODES
+
+Explained below. Returned from most methods.
+
+AZ_OK AZ_STREAM_END AZ_ERROR AZ_FORMAT_ERROR AZ_IO_ERROR
+
+=back
+
+=head1 ERROR CODES
+
+Many of the methods in Archive::Zip return error codes. These are implemented
+as inline subroutines, using the C