summaryrefslogtreecommitdiffstats
path: root/lib/Lintian/Check/DebFormat.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Lintian/Check/DebFormat.pm')
-rw-r--r--lib/Lintian/Check/DebFormat.pm227
1 files changed, 227 insertions, 0 deletions
diff --git a/lib/Lintian/Check/DebFormat.pm b/lib/Lintian/Check/DebFormat.pm
new file mode 100644
index 0000000..57c57a4
--- /dev/null
+++ b/lib/Lintian/Check/DebFormat.pm
@@ -0,0 +1,227 @@
+# deb-format -- lintian check script -*- perl -*-
+
+# Copyright (C) 2009 Russ Allbery
+# Copyright (C) 2018 Chris Lamb <lamby@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation; either version 2 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+
+package Lintian::Check::DebFormat;
+
+use v5.20;
+use warnings;
+use utf8;
+
+use Const::Fast;
+use IPC::Run3;
+use List::SomeUtils qw(first_index none);
+use Path::Tiny;
+use Unicode::UTF8 qw(decode_utf8);
+
+use Lintian::IPC::Run3 qw(safe_qx);
+
+use Moo;
+use namespace::clean;
+
+with 'Lintian::Check';
+
+const my $SPACE => q{ };
+
+const my $MINIMUM_DEB_ARCHIVE_MEMBERS => 3;
+const my $INDEX_NOT_FOUND => -1;
+
+sub installable {
+ my ($self) = @_;
+
+ my $EXTRA_MEMBERS = $self->data->load('deb-format/extra-members');
+
+ my $deb_path = $self->processable->path;
+
+ # set to one when something is so bad that we can't continue
+ my $failed;
+
+ my @command = ('ar', 't', $deb_path);
+
+ my $stdout;
+ my $stderr;
+
+ run3(\@command, \undef, \$stdout, \$stderr);
+
+ unless ($?) {
+ my @members = split(/\n/, $stdout);
+ my $count = scalar(@members);
+ my ($ctrl_member, $data_member);
+
+ if ($count < $MINIMUM_DEB_ARCHIVE_MEMBERS) {
+ $self->hint('malformed-deb-archive',
+"found only $count members instead of $MINIMUM_DEB_ARCHIVE_MEMBERS"
+ );
+
+ } elsif ($members[0] ne 'debian-binary') {
+ $self->hint('malformed-deb-archive',
+ "first member $members[0] not debian-binary");
+
+ } elsif ($count == $MINIMUM_DEB_ARCHIVE_MEMBERS
+ && none {substr($_, 0, 1) eq '_';}@members) {
+ # Fairly common case - if there are only 3 members without
+ # "_", we can trivially determine their (expected)
+ # positions. We only use this case when there are no
+ # "extra" members, because they can trigger more tags
+ # (see below)
+ (undef, $ctrl_member, $data_member) = @members;
+
+ } else {
+ my $ctrl_index
+ = first_index { substr($_, 0, 1) ne '_' } @members[1..$#members];
+ my $data_index;
+
+ if ($ctrl_index != $INDEX_NOT_FOUND) {
+ # Since we searched only a sublist of @members, we have to
+ # add 1 to $ctrl_index
+ $ctrl_index++;
+ $ctrl_member = $members[$ctrl_index];
+ $data_index = first_index { substr($_, 0, 1) ne '_' }
+ @members[$ctrl_index+1..$#members];
+ if ($data_index != $INDEX_NOT_FOUND) {
+ # Since we searched only a sublist of @members, we
+ # have to adjust $data_index
+ $data_index += $ctrl_index + 1;
+ $data_member = $members[$data_index];
+ }
+ }
+
+ # Extra members
+ # NB: We deliberately do not allow _extra member,
+ # since various tools seems to be unable to cope
+ # with them particularly dak
+ # see https://wiki.debian.org/Teams/Dpkg/DebSupport
+ for my $i (1..$#members) {
+ my $member = $members[$i];
+ my $actual_index = $i;
+ my ($expected, $text);
+ next if $i == $ctrl_index or $i == $data_index;
+ $expected = $EXTRA_MEMBERS->value($member);
+ if (defined($expected)) {
+ next if $expected eq 'ANYWHERE';
+ next if $expected == $actual_index;
+ $text = "expected at position $expected, but appeared";
+ } elsif (substr($member,0,1) eq '_') {
+ $text = 'unexpected _member';
+ } else {
+ $text = 'unexpected member';
+ }
+ $self->hint('misplaced-extra-member-in-deb',
+ "$member ($text at position $actual_index)");
+ }
+ }
+
+ if (not defined($ctrl_member)) {
+ # Somehow I doubt we will ever get this far without a control
+ # file... :)
+ $self->hint('malformed-deb-archive', 'Missing control.tar member');
+ $failed = 1;
+ } else {
+ if (
+ $ctrl_member !~ m{\A
+ control\.tar(?:\.(?:gz|xz))? \Z}xsm
+ ) {
+ $self->hint(
+ 'malformed-deb-archive',
+ join($SPACE,
+ "second (official) member $ctrl_member",
+ 'not control.tar.(gz|xz)')
+ );
+ $failed = 1;
+ } elsif ($ctrl_member eq 'control.tar') {
+ $self->hint('uses-no-compression-for-control-tarball');
+ }
+ $self->hint('control-tarball-compression-format',
+ $ctrl_member =~ s/^control\.tar\.?//r || '(none)');
+ }
+
+ if (not defined($data_member)) {
+ # Somehow I doubt we will ever get this far without a data
+ # member (i.e. I suspect unpacked and index will fail), but
+ # mah
+ $self->hint('malformed-deb-archive', 'Missing data.tar member');
+ $failed = 1;
+ } else {
+ if (
+ $data_member !~ m{\A
+ data\.tar(?:\.(?:gz|bz2|xz|lzma))? \Z}xsm
+ ) {
+ # wasn't okay after all
+ $self->hint(
+ 'malformed-deb-archive',
+ join($SPACE,
+ "third (official) member $data_member",
+ 'not data.tar.(gz|xz|bz2|lzma)')
+ );
+ $failed = 1;
+ } elsif ($self->processable->type eq 'udeb'
+ && $data_member !~ m/^data\.tar\.[gx]z$/) {
+ $self->hint(
+ 'udeb-uses-unsupported-compression-for-data-tarball');
+ } elsif ($data_member eq 'data.tar.lzma') {
+ $self->hint('uses-deprecated-compression-for-data-tarball',
+ 'lzma');
+ # Ubuntu's archive allows lzma packages.
+ $self->hint('lzma-deb-archive');
+ } elsif ($data_member eq 'data.tar.bz2') {
+ $self->hint('uses-deprecated-compression-for-data-tarball',
+ 'bzip2');
+ } elsif ($data_member eq 'data.tar') {
+ $self->hint('uses-no-compression-for-data-tarball');
+ }
+ $self->hint('data-tarball-compression-format',
+ $data_member =~ s/^data\.tar\.?//r || '(none)');
+ }
+ } else {
+ # unpack will probably fail so we'll never get here, but may as well be
+ # complete just in case.
+ $stderr =~ s/\n.*//s;
+ $stderr =~ s/^ar:\s*//;
+ $stderr =~ s/^deb:\s*//;
+ $self->hint('malformed-deb-archive', "ar error: $stderr");
+ }
+
+ # Check the debian-binary version number. We probably won't get
+ # here because dpkg-deb will decline to unpack the deb, but be
+ # thorough just in case. We may eventually have a case where dpkg
+ # supports a newer format but it's not permitted in the archive
+ # yet.
+ if (not defined($failed)) {
+ my $bytes = safe_qx('ar', 'p', $deb_path, 'debian-binary');
+ if ($? != 0) {
+ $self->hint('malformed-deb-archive',
+ 'cannot read debian-binary member');
+ } else {
+ my $output = decode_utf8($bytes);
+ if ($output !~ /^2\.\d+\n/) {
+ my ($version) = split(m/\n/, $output);
+ $self->hint('malformed-deb-archive',
+ "version $version not 2.0");
+ }
+ }
+ }
+
+ return;
+}
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et