summaryrefslogtreecommitdiffstats
path: root/lib/Lintian/Processable
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--lib/Lintian/Processable.pm302
-rw-r--r--lib/Lintian/Processable/Buildinfo.pm133
-rw-r--r--lib/Lintian/Processable/Buildinfo/Overrides.pm94
-rw-r--r--lib/Lintian/Processable/Changelog/Version.pm108
-rw-r--r--lib/Lintian/Processable/Changes.pm145
-rw-r--r--lib/Lintian/Processable/Changes/Overrides.pm94
-rw-r--r--lib/Lintian/Processable/Debian/Control.pm90
-rw-r--r--lib/Lintian/Processable/Diffstat.pm162
-rw-r--r--lib/Lintian/Processable/Fields/Files.pm181
-rw-r--r--lib/Lintian/Processable/Hardening.pm105
-rw-r--r--lib/Lintian/Processable/Installable.pm201
-rw-r--r--lib/Lintian/Processable/Installable/Changelog.pm151
-rw-r--r--lib/Lintian/Processable/Installable/Class.pm139
-rw-r--r--lib/Lintian/Processable/Installable/Conffiles.pm97
-rw-r--r--lib/Lintian/Processable/Installable/Control.pm99
-rw-r--r--lib/Lintian/Processable/Installable/Installed.pm103
-rw-r--r--lib/Lintian/Processable/Installable/Overrides.pm131
-rw-r--r--lib/Lintian/Processable/Installable/Relation.pm154
-rw-r--r--lib/Lintian/Processable/IsNonFree.pm109
-rw-r--r--lib/Lintian/Processable/NotJustDocs.pm112
-rw-r--r--lib/Lintian/Processable/Overrides.pm219
-rw-r--r--lib/Lintian/Processable/Source.pm142
-rw-r--r--lib/Lintian/Processable/Source/Changelog.pm109
-rw-r--r--lib/Lintian/Processable/Source/Components.pm126
-rw-r--r--lib/Lintian/Processable/Source/Format.pm136
-rw-r--r--lib/Lintian/Processable/Source/Orig.pm200
-rw-r--r--lib/Lintian/Processable/Source/Overrides.pm109
-rw-r--r--lib/Lintian/Processable/Source/Patched.pm161
-rw-r--r--lib/Lintian/Processable/Source/Relation.pm267
-rw-r--r--lib/Lintian/Processable/Source/Repacked.pm99
30 files changed, 4278 insertions, 0 deletions
diff --git a/lib/Lintian/Processable.pm b/lib/Lintian/Processable.pm
new file mode 100644
index 0000000..c89a1fc
--- /dev/null
+++ b/lib/Lintian/Processable.pm
@@ -0,0 +1,302 @@
+# Copyright (C) 2011 Niels Thykier <niels@thykier.net>
+# Copyright (C) 2019-2020 Felix Lechner
+#
+# 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, you can find it on the World Wide
+# Web at https://www.gnu.org/copyleft/gpl.html, or write to the Free
+# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+
+package Lintian::Processable;
+
+use v5.20;
+use warnings;
+use utf8;
+use warnings::register;
+
+use Const::Fast;
+use File::Basename;
+use Path::Tiny;
+
+use Moo::Role;
+use MooX::Aliases;
+use namespace::clean;
+
+const my $EMPTY => q{};
+const my $COLON => q{:};
+const my $SLASH => q{/};
+const my $UNDERSCORE => q{_};
+const my $EVIL_CHARACTERS => qr{[/&|;\$"'<>]};
+
+=encoding utf-8
+
+=head1 NAME
+
+Lintian::Processable -- An (abstract) object that Lintian can process
+
+=head1 SYNOPSIS
+
+ use Lintian::Processable;
+
+=head1 DESCRIPTION
+
+Instances of this perl class are objects that Lintian can process (e.g.
+deb files). Multiple objects can then be combined into
+L<groups|Lintian::Group>, which Lintian will process
+together.
+
+=head1 INSTANCE METHODS
+
+=over 4
+
+=item name
+
+Returns the name of the package.
+
+=item type
+
+Returns the type of package (e.g. binary, source, udeb ...)
+
+=item hints
+
+=item $proc->version
+
+Returns the version of the package.
+
+=item $proc->path
+
+Returns the path to the packaged version of actual package. This path
+is used in case the data needs to be extracted from the package.
+
+=item basename
+
+Returns the basename of the package path.
+
+=item $proc->architecture
+
+Returns the architecture(s) of the package. May return multiple values
+from changes processables. For source processables it is "source".
+
+=item $proc->source_name
+
+Returns the name of the source package.
+
+=item $proc->source_version
+
+Returns the version of the source package.
+
+=item $proc->tainted
+
+Returns a truth value if one or more fields in this Processable is
+tainted. On a best effort basis tainted fields will be sanitized
+to less dangerous (but possibly invalid) values.
+
+=item fields
+
+Lintian::Deb822::Section with primary field values.
+
+=item $proc->pooldir
+
+Returns a reference to lab this Processable is in.
+
+=item $proc->basedir
+
+Returns the base directory of this package inside the lab.
+
+=cut
+
+has path => (
+ is => 'rw',
+ default => $EMPTY,
+ trigger => sub {
+ my ($self, $path) = @_;
+
+ my $basename = basename($path);
+ $self->basename($basename);
+ }
+);
+has basename => (is => 'rw', default => $EMPTY);
+has type => (is => 'rw', default => $EMPTY);
+
+has hints => (is => 'rw', default => sub { [] });
+
+has architecture => (
+ is => 'rw',
+ coerce => sub {
+ my ($value) = @_;
+ return clean_field($value);
+ },
+ default => $EMPTY
+);
+has name => (
+ is => 'rw',
+ coerce => sub {
+ my ($value) = @_;
+ return clean_field($value);
+ },
+ default => $EMPTY
+);
+has source_name => (
+ is => 'rw',
+ coerce => sub {
+ my ($value) = @_;
+ return clean_field($value);
+ },
+ default => $EMPTY
+);
+has source_version =>(
+ is => 'rw',
+ coerce => sub {
+ my ($value) = @_;
+ return clean_field($value);
+ },
+ default => $EMPTY
+);
+has version => (
+ is => 'rw',
+ coerce => sub {
+ my ($value) = @_;
+ return clean_field($value);
+ },
+ default => $EMPTY
+);
+
+has tainted => (is => 'rw', default => 0);
+
+has fields => (is => 'rw', default => sub { Lintian::Deb822::Section->new; });
+
+has pooldir => (is => 'rw', default => $EMPTY);
+has basedir => (
+ is => 'rw',
+ lazy => 1,
+ trigger => sub {
+ my ($self, $folder) = @_;
+
+ return
+ unless length $folder;
+
+ # create directory
+ path($folder)->mkpath
+ unless -e $folder;
+ },
+ default => sub {
+ my ($self) = @_;
+
+ my $path
+ = $self->source_name
+ . $SLASH
+ . $self->name
+ . $UNDERSCORE
+ . $self->version;
+ $path .= $UNDERSCORE . $self->architecture
+ unless $self->type eq 'source';
+ $path .= $UNDERSCORE . $self->type;
+
+ # architectures can contain spaces in changes files
+ $path =~ s/\s/-/g;
+
+ # colon can be a path separator
+ $path =~ s/:/_/g;
+
+ my $basedir = $self->pooldir . "/$path";
+
+ return $basedir;
+ }
+);
+
+=item C<identifier>
+
+Produces an identifier for this processable. The identifier is
+based on the type, name, version and architecture of the package.
+
+=cut
+
+sub identifier {
+ my ($self) = @_;
+
+ my $id = $self->type . $COLON . $self->name . $UNDERSCORE . $self->version;
+
+ # add architecture unless it is source
+ $id .= $UNDERSCORE . $self->architecture
+ unless $self->type eq 'source';
+
+ $id =~ s/\s+/_/g;
+
+ return $id;
+}
+
+=item clean_field
+
+Cleans a field of evil characters to prevent traversal or worse.
+
+=cut
+
+sub clean_field {
+ my ($value) = @_;
+
+ # make sure none of the fields can cause traversal
+ my $clean = $value;
+ $clean =~ s/${$EVIL_CHARACTERS}/_/g;
+
+ return $clean;
+}
+
+=item guess_name
+
+=cut
+
+sub guess_name {
+ my ($self, $path) = @_;
+
+ my $guess = path($path)->basename;
+
+ # drop extension, to catch fields-general-missing.deb
+ $guess =~ s/\.[^.]*$//;
+
+ # drop everything after the first underscore, if any
+ $guess =~ s/_.*$//;
+
+ # 'path/lintian_2.5.2_amd64.changes' became 'lintian'
+ return $guess;
+}
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Niels Thykier <niels@thykier.net> for Lintian.
+Substantial portions written by Russ Allbery <rra@debian.org> for Lintian.
+
+=head1 SEE ALSO
+
+lintian(1)
+
+L<Lintian::Processable::Installable>
+
+L<Lintian::Processable::Buildinfo>
+
+L<Lintian::Processable::Changes>
+
+L<Lintian::Processable::Source>
+
+L<Lintian::Group>
+
+=cut
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/lib/Lintian/Processable/Buildinfo.pm b/lib/Lintian/Processable/Buildinfo.pm
new file mode 100644
index 0000000..f5983fa
--- /dev/null
+++ b/lib/Lintian/Processable/Buildinfo.pm
@@ -0,0 +1,133 @@
+# Copyright (C) 2019-2020 Felix Lechner
+#
+# 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, you can find it on the World Wide
+# Web at https://www.gnu.org/copyleft/gpl.html, or write to the Free
+# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+
+package Lintian::Processable::Buildinfo;
+
+use v5.20;
+use warnings;
+use utf8;
+
+use Carp qw(croak);
+use Unicode::UTF8 qw(encode_utf8);
+
+use Lintian::Deb822;
+
+use Moo;
+use namespace::clean;
+
+with
+ 'Lintian::Processable',
+ 'Lintian::Processable::Fields::Files',
+ 'Lintian::Processable::Buildinfo::Overrides';
+
+=for Pod::Coverage BUILDARGS
+
+=head1 NAME
+
+Lintian::Processable::Buildinfo -- A buildinfo file Lintian can process
+
+=head1 SYNOPSIS
+
+ use Lintian::Processable::Buildinfo;
+
+ my $processable = Lintian::Processable::Buildinfo->new;
+ $processable->init_from_file('path');
+
+=head1 DESCRIPTION
+
+This class represents a 'buildinfo' file that Lintian can process. Objects
+of this kind are often part of a L<Lintian::Group>, which
+represents all the files in a changes or buildinfo file.
+
+=head1 INSTANCE METHODS
+
+=over 4
+
+=item init_from_file (PATH)
+
+Initializes a new object from PATH.
+
+=cut
+
+sub init_from_file {
+ my ($self, $file) = @_;
+
+ croak encode_utf8("File $file does not exist")
+ unless -e $file;
+
+ $self->path($file);
+ $self->type('buildinfo');
+
+ my $primary = Lintian::Deb822->new;
+ my @sections = $primary->read_file($self->path)
+ or croak encode_utf8(
+ $self->path. ' is not a valid '. $self->type . ' file');
+
+ $self->fields($sections[0]);
+
+ my $name = $self->fields->value('Source');
+ my $version = $self->fields->value('Version');
+ my $architecture = $self->fields->value('Architecture');
+
+ unless (length $name) {
+ $name = $self->guess_name($self->path);
+ croak encode_utf8('Cannot determine the name from '. $self->path)
+ unless length $name;
+ }
+
+ my $source_name = $name;
+ my $source_version = $version;
+
+ $self->name($name);
+ $self->version($version);
+ $self->architecture($architecture);
+ $self->source_name($source_name);
+ $self->source_version($source_version);
+
+ # make sure none of these fields can cause traversal
+ $self->tainted(1)
+ if $self->name ne $name
+ || $self->version ne $version
+ || $self->architecture ne $architecture
+ || $self->source_name ne $source_name
+ || $self->source_version ne $source_version;
+
+ return;
+}
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Felix Lechner <felix.lechner@lease-up.com> for Lintian.
+
+=head1 SEE ALSO
+
+lintian(1)
+
+L<Lintian::Processable>
+
+=cut
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/lib/Lintian/Processable/Buildinfo/Overrides.pm b/lib/Lintian/Processable/Buildinfo/Overrides.pm
new file mode 100644
index 0000000..136c01c
--- /dev/null
+++ b/lib/Lintian/Processable/Buildinfo/Overrides.pm
@@ -0,0 +1,94 @@
+# -*- perl -*- Lintian::Processable::Buildinfo::Overrides
+#
+# Copyright (C) 2021 Felix Lechner
+#
+# 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::Processable::Buildinfo::Overrides;
+
+use v5.20;
+use warnings;
+use utf8;
+
+use Moo::Role;
+use namespace::clean;
+
+with 'Lintian::Processable::Overrides';
+
+=head1 NAME
+
+Lintian::Processable::Buildinfo::Overrides - access to override data
+
+=head1 SYNOPSIS
+
+ use Lintian::Processable;
+
+=head1 DESCRIPTION
+
+Lintian::Processable::Buildinfo::Overrides provides an interface for overrides.
+
+=head1 INSTANCE METHODS
+
+=over 4
+
+=item override_file
+
+=cut
+
+has override_file => (
+ is => 'rw',
+ lazy => 1,
+ default => sub {
+ my ($self) = @_;
+
+ return undef;
+ }
+);
+
+=item overrides
+
+=cut
+
+has overrides => (
+ is => 'rw',
+ lazy => 1,
+ default => sub {
+ my ($self) = @_;
+
+ my @overrides;
+
+ return \@overrides;
+ }
+);
+
+1;
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Felix Lechner <felix.lechner@lease-up.com> for
+Lintian.
+
+=head1 SEE ALSO
+
+lintian(1)
+
+=cut
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/lib/Lintian/Processable/Changelog/Version.pm b/lib/Lintian/Processable/Changelog/Version.pm
new file mode 100644
index 0000000..7721b17
--- /dev/null
+++ b/lib/Lintian/Processable/Changelog/Version.pm
@@ -0,0 +1,108 @@
+# -*- perl -*-
+# Lintian::Processable::Changelog::Version -- interface to source package data collection
+
+# Copyright (C) 2008 Russ Allbery
+# Copyright (C) 2009 Raphael Geissert
+# Copyright (C) 2020 Felix Lechner
+#
+# 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::Processable::Changelog::Version;
+
+use v5.20;
+use warnings;
+use utf8;
+
+use Syntax::Keyword::Try;
+
+use Lintian::Changelog::Version;
+
+use Moo::Role;
+use namespace::clean;
+
+=head1 NAME
+
+Lintian::Processable::Changelog::Version - Lintian interface to source package data collection
+
+=head1 SYNOPSIS
+
+ my ($name, $type, $dir) = ('foobar', 'source', '/path/to/lab-entry');
+ my $collect = Lintian::Processable::Changelog::Version->new($name);
+ if ($collect->native) {
+ print "Package is native\n";
+ }
+
+=head1 DESCRIPTION
+
+Lintian::Processable::Changelog::Version provides an interface to package data for source
+packages. It implements data collection methods specific to source
+packages.
+
+This module is in its infancy. Most of Lintian still reads all data from
+files in the laboratory whenever that data is needed and generates that
+data via collect scripts. The goal is to eventually access all data about
+source packages via this module so that the module can cache data where
+appropriate and possibly retire collect scripts in favor of caching that
+data in memory.
+
+=head1 INSTANCE METHODS
+
+=over 4
+
+=item changelog_version
+
+Returns a fully parsed Lintian::Changelog::Version for the
+source package's version string.
+
+=cut
+
+has changelog_version => (
+ is => 'rw',
+ lazy => 1,
+ default => sub {
+ my ($self) = @_;
+
+ my $versionstring = $self->fields->value('Version');
+
+ my $version = Lintian::Changelog::Version->new;
+ try {
+ $version->assign($versionstring, $self->native);
+
+ } catch {
+ }
+
+ return $version;
+ }
+);
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Russ Allbery <rra@debian.org> for Lintian.
+Amended by Felix Lechner <felix.lechner@lease-up.com> for Lintian.
+
+=head1 SEE ALSO
+
+lintian(1)
+
+=cut
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/lib/Lintian/Processable/Changes.pm b/lib/Lintian/Processable/Changes.pm
new file mode 100644
index 0000000..65eb8e4
--- /dev/null
+++ b/lib/Lintian/Processable/Changes.pm
@@ -0,0 +1,145 @@
+# Copyright (C) 2019-2020 Felix Lechner
+#
+# 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, you can find it on the World Wide
+# Web at https://www.gnu.org/copyleft/gpl.html, or write to the Free
+# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+
+package Lintian::Processable::Changes;
+
+use v5.20;
+use warnings;
+use utf8;
+
+use Carp qw(croak);
+use Path::Tiny;
+use Unicode::UTF8 qw(valid_utf8 decode_utf8 encode_utf8);
+
+use Lintian::Deb822;
+
+use Moo;
+use namespace::clean;
+
+with
+ 'Lintian::Processable',
+ 'Lintian::Processable::Fields::Files',
+ 'Lintian::Processable::Changes::Overrides';
+
+=for Pod::Coverage BUILDARGS
+
+=head1 NAME
+
+Lintian::Processable::Changes -- A changes file Lintian can process
+
+=head1 SYNOPSIS
+
+ use Lintian::Processable::Changes;
+
+ my $processable = Lintian::Processable::Changes->new;
+ $processable->init_from_file('path');
+
+=head1 DESCRIPTION
+
+This class represents a 'changes' file that Lintian can process. Objects
+of this kind are often part of a L<Lintian::Group>, which
+represents all the files in a changes or buildinfo file.
+
+=head1 INSTANCE METHODS
+
+=over 4
+
+=item init_from_file (PATH)
+
+Initializes a new object from PATH.
+
+=cut
+
+sub init_from_file {
+ my ($self, $file) = @_;
+
+ croak encode_utf8("File $file does not exist")
+ unless -e $file;
+
+ $self->path($file);
+ $self->type('changes');
+
+ # dpkg will include news items in national encoding
+ my $bytes = path($self->path)->slurp;
+
+ my $contents;
+ if (valid_utf8($bytes)) {
+ $contents = decode_utf8($bytes);
+ } else {
+ # try to proceed with nat'l encoding; stopping here breaks tests
+ $contents = $bytes;
+ }
+
+ my $primary = Lintian::Deb822->new;
+ my @sections = $primary->parse_string($contents)
+ or croak encode_utf8(
+ $self->path. ' is not a valid '. $self->type . ' file');
+
+ $self->fields($sections[0]);
+
+ my $name = $self->fields->value('Source');
+ my $version = $self->fields->value('Version');
+ my $architecture = $self->fields->value('Architecture');
+
+ unless (length $name) {
+ $name = $self->guess_name($self->path);
+ croak encode_utf8('Cannot determine the name from ' . $self->path)
+ unless length $name;
+ }
+
+ my $source_name = $name;
+ my $source_version = $version;
+
+ $self->name($name);
+ $self->version($version);
+ $self->architecture($architecture);
+ $self->source_name($source_name);
+ $self->source_version($source_version);
+
+ # make sure none of these fields can cause traversal
+ $self->tainted(1)
+ if $self->name ne $name
+ || $self->version ne $version
+ || $self->architecture ne $architecture
+ || $self->source_name ne $source_name
+ || $self->source_version ne $source_version;
+
+ return;
+}
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Felix Lechner <felix.lechner@lease-up.com> for Lintian.
+
+=head1 SEE ALSO
+
+lintian(1)
+
+L<Lintian::Processable>
+
+=cut
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/lib/Lintian/Processable/Changes/Overrides.pm b/lib/Lintian/Processable/Changes/Overrides.pm
new file mode 100644
index 0000000..78bd04d
--- /dev/null
+++ b/lib/Lintian/Processable/Changes/Overrides.pm
@@ -0,0 +1,94 @@
+# -*- perl -*- Lintian::Processable::Changes::Overrides
+#
+# Copyright (C) 2021 Felix Lechner
+#
+# 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::Processable::Changes::Overrides;
+
+use v5.20;
+use warnings;
+use utf8;
+
+use Moo::Role;
+use namespace::clean;
+
+with 'Lintian::Processable::Overrides';
+
+=head1 NAME
+
+Lintian::Processable::Changes::Overrides - access to override data
+
+=head1 SYNOPSIS
+
+ use Lintian::Processable;
+
+=head1 DESCRIPTION
+
+Lintian::Processable::Changes::Overrides provides an interface for overrides.
+
+=head1 INSTANCE METHODS
+
+=over 4
+
+=item override_file
+
+=cut
+
+has override_file => (
+ is => 'rw',
+ lazy => 1,
+ default => sub {
+ my ($self) = @_;
+
+ return undef;
+ }
+);
+
+=item overrides
+
+=cut
+
+has overrides => (
+ is => 'rw',
+ lazy => 1,
+ default => sub {
+ my ($self) = @_;
+
+ my @overrides;
+
+ return \@overrides;
+ }
+);
+
+1;
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Felix Lechner <felix.lechner@lease-up.com> for
+Lintian.
+
+=head1 SEE ALSO
+
+lintian(1)
+
+=cut
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/lib/Lintian/Processable/Debian/Control.pm b/lib/Lintian/Processable/Debian/Control.pm
new file mode 100644
index 0000000..a4c1cf9
--- /dev/null
+++ b/lib/Lintian/Processable/Debian/Control.pm
@@ -0,0 +1,90 @@
+# -*- perl -*-
+# Lintian::Processable::Debian::Control -- interface to source package data collection
+
+# Copyright (C) 2008 Russ Allbery
+# Copyright (C) 2009 Raphael Geissert
+# Copyright (C) 2020 Felix Lechner
+#
+# 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::Processable::Debian::Control;
+
+use v5.20;
+use warnings;
+use utf8;
+
+use Lintian::Debian::Control;
+
+use Moo::Role;
+use namespace::clean;
+
+=head1 NAME
+
+Lintian::Processable::Debian::Control - Lintian interface to d/control fields
+
+=head1 SYNOPSIS
+
+ use Lintian::Processable;
+
+=head1 DESCRIPTION
+
+Lintian::Processable::Debian::Control provides an interface to package data
+from d/control.
+
+=head1 INSTANCE METHODS
+
+=over 4
+
+=item debian_control
+
+=cut
+
+has debian_control => (
+ is => 'rw',
+ lazy => 1,
+ default => sub {
+ my ($self) = @_;
+
+ my $control = Lintian::Debian::Control->new;
+
+ my $item = $self->patched->resolve_path('debian/control');
+ return $control
+ unless defined $item;
+
+ $control->load($item);
+
+ return $control;
+ }
+);
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Russ Allbery <rra@debian.org> for Lintian.
+Amended by Felix Lechner <felix.lechner@lease-up.com> for Lintian.
+
+=head1 SEE ALSO
+
+lintian(1)
+
+=cut
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/lib/Lintian/Processable/Diffstat.pm b/lib/Lintian/Processable/Diffstat.pm
new file mode 100644
index 0000000..82a3b28
--- /dev/null
+++ b/lib/Lintian/Processable/Diffstat.pm
@@ -0,0 +1,162 @@
+# -*- perl -*- Lintian::Processable::Diffstat -- access to collected diffstat data
+#
+# Copyright (C) 1998 Richard Braakman
+# Copyright (C) 2019-2020 Felix Lechner
+#
+# 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::Processable::Diffstat;
+
+use v5.20;
+use warnings;
+use utf8;
+
+use Const::Fast;
+use IPC::Run3;
+use Path::Tiny;
+use Unicode::UTF8 qw(encode_utf8 decode_utf8);
+
+use Moo::Role;
+use namespace::clean;
+
+const my $COLON => q{:};
+const my $UNDERSCORE => q{_};
+const my $NEWLINE => qq{\n};
+
+const my $OPEN_PIPE => q{-|};
+const my $WAIT_STATUS_SHIFT => 8;
+
+=head1 NAME
+
+Lintian::Processable::Diffstat - access to collected diffstat data
+
+=head1 SYNOPSIS
+
+ use Lintian::Processable;
+
+=head1 DESCRIPTION
+
+Lintian::Processable::Diffstat provides an interface to diffstat data.
+
+=head1 INSTANCE METHODS
+
+=over 4
+
+=item diffstat
+
+Returns the path to diffstat output run on the Debian packaging diff
+(a.k.a. the "diff.gz") for 1.0 non-native packages. For source
+packages without a "diff.gz" component, this returns the path to an
+empty file (this may be a device like /dev/null).
+
+=cut
+
+has diffstat => (
+ is => 'rw',
+ lazy => 1,
+ default => sub {
+ my ($self) = @_;
+
+ my $noepoch = $self->fields->value('Version');
+
+ # strip epoch
+ $noepoch =~ s/^\d://;
+
+ # look for a format 1.0 diff.gz near the input file
+ my $diffname = $self->name . $UNDERSCORE . $noepoch . '.diff.gz';
+ return {}
+ unless exists $self->files->{$diffname};
+
+ my $diffpath = path($self->path)->parent->child($diffname)->stringify;
+ return {}
+ unless -e $diffpath;
+
+ my @gunzip_command = ('gunzip', '--stdout', $diffpath);
+ my $gunzip_pid = open(my $from_gunzip, $OPEN_PIPE, @gunzip_command)
+ or die encode_utf8("Cannot run @gunzip_command: $!");
+
+ my $stdout;
+ my $stderr;
+ my @diffstat_command = qw(diffstat -p1);
+ run3(\@diffstat_command, $from_gunzip, \$stdout, \$stderr);
+ my $status = ($? >> $WAIT_STATUS_SHIFT);
+
+ $stdout = decode_utf8($stdout)
+ if length $stdout;
+ $stderr = decode_utf8($stderr)
+ if length $stderr;
+
+ if ($status) {
+
+ my $message= "Non-zero status $status from @diffstat_command";
+ $message .= $COLON . $NEWLINE . $stderr
+ if length $stderr;
+
+ die encode_utf8($message);
+ }
+
+ close $from_gunzip
+ or
+ warn encode_utf8("close failed for handle from @gunzip_command: $!");
+
+ waitpid($gunzip_pid, 0);
+
+ # remove summary in last line
+ chomp $stdout;
+ $stdout =~ s/.*\Z//;
+
+ my %diffstat;
+
+ my @lines = split(/\n/, $stdout);
+ for my $line (@lines) {
+
+ next
+ unless $line =~ s/\|\s*([^|]*)\s*$//;
+
+ my $stats = $1;
+ my $file = $line;
+
+ # trim both ends
+ $file =~ s/^\s+|\s+$//g;
+
+ die encode_utf8("syntax error in diffstat file: $line")
+ unless length $file;
+
+ $diffstat{$file} = $stats;
+ }
+
+ return \%diffstat;
+ }
+);
+
+1;
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Felix Lechner <felix.lechner@lease-up.com> for
+Lintian.
+
+=head1 SEE ALSO
+
+lintian(1)
+
+=cut
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/lib/Lintian/Processable/Fields/Files.pm b/lib/Lintian/Processable/Fields/Files.pm
new file mode 100644
index 0000000..df21352
--- /dev/null
+++ b/lib/Lintian/Processable/Fields/Files.pm
@@ -0,0 +1,181 @@
+# -*- perl -*-
+# Lintian::Processable::Fields::Files -- interface to .buildinfo file data collection
+#
+# Copyright (C) 2010 Adam D. Barratt
+# Copyright (C) 2018 Chris Lamb
+# Copyright (C) 2019-2020 Felix Lechner
+#
+# 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::Processable::Fields::Files;
+
+use v5.20;
+use warnings;
+use utf8;
+
+use Moo::Role;
+use namespace::clean;
+
+=head1 NAME
+
+Lintian::Processable::Fields::Files - Lintian interface to .buildinfo or changes file data collection
+
+=head1 SYNOPSIS
+
+ use Moo;
+
+ with 'Lintian::Processable::Fields::Files';
+
+=head1 DESCRIPTION
+
+Lintian::Processable::Fields::Files provides an interface to data for .buildinfo
+and changes files. It implements data collection methods specific to .buildinfo
+and changes files.
+
+=head1 INSTANCE METHODS
+
+=over 4
+
+=item files
+
+Returns a reference to a hash containing information about files listed
+in the .buildinfo file. Each hash may have the following keys:
+
+=over 4
+
+=item name
+
+Name of the file.
+
+=item size
+
+The size of the file in bytes.
+
+=item section
+
+The archive section to which the file belongs.
+
+=item priority
+
+The priority of the file.
+
+=item checksums
+
+A hash with the keys being checksum algorithms and the values themselves being
+hashes containing
+
+=over 4
+
+=item sum
+
+The result of applying the given algorithm to the file.
+
+=item filesize
+
+The size of the file as given in the .buildinfo section relating to the given
+checksum.
+
+=back
+
+=back
+
+=cut
+
+has files => (
+ is => 'rw',
+ lazy => 1,
+ default =>
+
+ sub {
+ my ($self) = @_;
+
+ my %files;
+
+ my @files_lines = split(/\n/, $self->fields->value('Files'));
+
+ # trim both ends of each line
+ s/^\s+|\s+$//g for @files_lines;
+
+ for my $line (grep { length } @files_lines) {
+
+ my @fields = split(/\s+/, $line);
+ my $basename = $fields[-1];
+
+ # ignore traversals
+ next
+ if $basename =~ m{/};
+
+ my ($md5sum, $size, $section, $priority) = @fields;
+
+ $files{$basename}{checksums}{Md5} = {
+ 'sum' => $md5sum,
+ 'filesize' => $size,
+ };
+
+ $files{$basename}{name} = $basename;
+ $files{$basename}{size} = $size;
+
+ unless ($self->type eq 'source') {
+
+ $files{$basename}{section} = $section;
+ $files{$basename}{priority} = $priority;
+ }
+ }
+
+ for my $algorithm (qw(Sha1 Sha256)) {
+
+ my @lines
+ = split(/\n/, $self->fields->value("Checksums-$algorithm"));
+
+ # trim both ends of each line
+ s/^\s+|\s+$//g for @lines;
+
+ for my $line (grep { length } @lines) {
+
+ my ($checksum, $size, $basename) = split(/\s+/, $line);
+
+ # ignore traversals
+ next
+ if $basename =~ m{/};
+
+ $files{$basename}{checksums}{$algorithm} = {
+ 'sum' => $checksum,
+ 'filesize' => $size
+ };
+ }
+ }
+
+ return \%files;
+ }
+);
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Adam D. Barratt <adsb@debian.org> for Lintian.
+
+=head1 SEE ALSO
+
+lintian(1), L<Lintian::Processable>
+
+=cut
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/lib/Lintian/Processable/Hardening.pm b/lib/Lintian/Processable/Hardening.pm
new file mode 100644
index 0000000..4bf24bd
--- /dev/null
+++ b/lib/Lintian/Processable/Hardening.pm
@@ -0,0 +1,105 @@
+# -*- perl -*- Lintian::Processable::Hardening
+#
+# Copyright (C) 2019 Felix Lechner
+#
+# 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::Processable::Hardening;
+
+use v5.20;
+use warnings;
+use utf8;
+
+use Path::Tiny;
+use Unicode::UTF8 qw(encode_utf8);
+
+use Moo::Role;
+use namespace::clean;
+
+=head1 NAME
+
+Lintian::Processable::Hardening - access to collected hardening data
+
+=head1 SYNOPSIS
+
+ use Lintian::Processable;
+
+=head1 DESCRIPTION
+
+Lintian::Processable::Hardening provides an interface to collected hardening data.
+
+=head1 INSTANCE METHODS
+
+=over 4
+
+=item hardening_info
+
+Returns a hashref mapping a FILE to its hardening issues.
+
+NB: This is generally only useful for checks/binaries to emit the
+hardening-no-* tags.
+
+=cut
+
+sub hardening_info {
+ my ($self) = @_;
+
+ return $self->{hardening_info}
+ if exists $self->{hardening_info};
+
+ my $hardf = path($self->basedir)->child('hardening-info')->stringify;
+
+ my %hardening_info;
+
+ if (-e $hardf) {
+ open(my $idx, '<:utf8_strict', $hardf)
+ or die encode_utf8("Cannot open $hardf");
+
+ while (my $line = <$idx>) {
+ chomp($line);
+
+ if ($line =~ m{^([^:]+):(?:\./)?(.*)$}) {
+ my ($tag, $file) = ($1, $2);
+
+ push(@{$hardening_info{$file}}, $tag);
+ }
+ }
+ close($idx);
+ }
+
+ $self->{hardening_info} = \%hardening_info;
+
+ return $self->{hardening_info};
+}
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Felix Lechner <felix.lechner@lease-up.com> for
+Lintian.
+
+=head1 SEE ALSO
+
+lintian(1)
+
+=cut
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/lib/Lintian/Processable/Installable.pm b/lib/Lintian/Processable/Installable.pm
new file mode 100644
index 0000000..54ae406
--- /dev/null
+++ b/lib/Lintian/Processable/Installable.pm
@@ -0,0 +1,201 @@
+# Copyright (C) 2019-2020 Felix Lechner
+#
+# 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, you can find it on the World Wide
+# Web at https://www.gnu.org/copyleft/gpl.html, or write to the Free
+# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+
+package Lintian::Processable::Installable;
+
+use v5.20;
+use warnings;
+use utf8;
+
+use Carp qw(croak);
+use Const::Fast;
+use IPC::Run3;
+use Unicode::UTF8 qw(encode_utf8 decode_utf8 valid_utf8);
+
+use Lintian::Deb822;
+
+use Moo;
+use namespace::clean;
+
+with
+ 'Lintian::Processable',
+ 'Lintian::Processable::Installable::Changelog',
+ 'Lintian::Processable::Installable::Class',
+ 'Lintian::Processable::Installable::Conffiles',
+ 'Lintian::Processable::Installable::Control',
+ 'Lintian::Processable::Installable::Installed',
+ 'Lintian::Processable::Installable::Overrides',
+ 'Lintian::Processable::Installable::Relation',
+ 'Lintian::Processable::IsNonFree',
+ 'Lintian::Processable::Hardening',
+ 'Lintian::Processable::NotJustDocs';
+
+# read up to 40kB at a time. this happens to be 4096 "tar records"
+# (with a block-size of 512 and a block factor of 20, which appear to
+# be the defaults). when we do full reads and writes of READ_SIZE (the
+# OS willing), the receiving end will never be with an incomplete
+# record.
+const my $TAR_RECORD_SIZE => 20 * 512;
+
+const my $COLON => q{:};
+const my $NEWLINE => qq{\n};
+const my $OPEN_PIPE => q{-|};
+
+const my $WAIT_STATUS_SHIFT => 8;
+
+=for Pod::Coverage BUILDARGS
+
+=head1 NAME
+
+Lintian::Processable::Installable -- An installation package Lintian can process
+
+=head1 SYNOPSIS
+
+ use Lintian::Processable::Installable;
+
+ my $processable = Lintian::Processable::Installable->new;
+ $processable->init_from_file('path');
+
+=head1 DESCRIPTION
+
+This class represents a 'deb' or 'udeb' file that Lintian can process. Objects
+of this kind are often part of a L<Lintian::Group>, which
+represents all the files in a changes or buildinfo file.
+
+=head1 INSTANCE METHODS
+
+=over 4
+
+=item init_from_file (PATH)
+
+Initializes a new object from PATH.
+
+=cut
+
+sub init_from_file {
+ my ($self, $file) = @_;
+
+ croak encode_utf8("File $file does not exist")
+ unless -e $file;
+
+ $self->path($file);
+
+ # get control.tar.gz; dpkg-deb -f $file is slow; use tar instead
+ my @dpkg_command = ('dpkg-deb', '--ctrl-tarfile', $self->path);
+
+ my $dpkg_pid = open(my $from_dpkg, $OPEN_PIPE, @dpkg_command)
+ or die encode_utf8("Cannot run @dpkg_command: $!");
+
+ # would like to set buffer size to 4096 & $TAR_RECORD_SIZE
+
+ # get binary control file
+ my $stdout_bytes;
+ my $stderr_bytes;
+ my @tar_command = qw{tar --wildcards -xO -f - *control};
+ run3(\@tar_command, $from_dpkg, \$stdout_bytes, \$stderr_bytes);
+ my $status = ($? >> $WAIT_STATUS_SHIFT);
+
+ if ($status) {
+
+ my $message= "Non-zero status $status from @tar_command";
+ $message .= $COLON . $NEWLINE . decode_utf8($stderr_bytes)
+ if length $stderr_bytes;
+
+ croak encode_utf8($message);
+ }
+
+ close $from_dpkg
+ or warn encode_utf8("close failed for handle from @dpkg_command: $!");
+
+ waitpid($dpkg_pid, 0);
+
+ croak encode_utf8('Nationally encoded control data in ' . $self->path)
+ unless valid_utf8($stdout_bytes);
+
+ my $stdout = decode_utf8($stdout_bytes);
+
+ my $deb822 = Lintian::Deb822->new;
+ my @sections = $deb822->parse_string($stdout);
+ croak encode_utf8(
+ 'Not exactly one section with installable control data in '
+ . $self->path)
+ unless @sections == 1;
+
+ $self->fields($sections[0]);
+
+ my $name = $self->fields->value('Package');
+ my $version = $self->fields->value('Version');
+ my $architecture = $self->fields->value('Architecture');
+ my $source_name = $self->fields->value('Source');
+
+ my $source_version = $version;
+
+ unless (length $name) {
+ $name = $self->guess_name($self->path);
+ croak encode_utf8('Cannot determine the name from ' . $self->path)
+ unless length $name;
+ }
+
+ # source may be left out if same as $name
+ $source_name = $name
+ unless length $source_name;
+
+ # source probably contains the version in parentheses
+ if ($source_name =~ m/(\S++)\s*\(([^\)]+)\)/){
+ $source_name = $1;
+ $source_version = $2;
+ }
+
+ $self->name($name);
+ $self->version($version);
+ $self->architecture($architecture);
+ $self->source_name($source_name);
+ $self->source_version($source_version);
+
+ # make sure none of these fields can cause traversal
+ $self->tainted(1)
+ if $self->name ne $name
+ || $self->version ne $version
+ || $self->architecture ne $architecture
+ || $self->source_name ne $source_name
+ || $self->source_version ne $source_version;
+
+ return;
+}
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Felix Lechner <felix.lechner@lease-up.com> for Lintian.
+
+=head1 SEE ALSO
+
+lintian(1)
+
+L<Lintian::Processable>
+
+=cut
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/lib/Lintian/Processable/Installable/Changelog.pm b/lib/Lintian/Processable/Installable/Changelog.pm
new file mode 100644
index 0000000..c43a17b
--- /dev/null
+++ b/lib/Lintian/Processable/Installable/Changelog.pm
@@ -0,0 +1,151 @@
+# -*- perl -*- Lintian::Processable::Installable::Changelog -- access to collected changelog data
+#
+# Copyright (C) 1998 Richard Braakman
+# Copyright (C) 2019-2021 Felix Lechner
+#
+# 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::Processable::Installable::Changelog;
+
+use v5.20;
+use warnings;
+use utf8;
+
+use File::Copy qw(copy);
+use List::SomeUtils qw(first_value);
+use Path::Tiny;
+use Unicode::UTF8 qw(valid_utf8 decode_utf8 encode_utf8);
+
+use Lintian::IPC::Run3 qw(safe_qx);
+
+use Moo::Role;
+use namespace::clean;
+
+=head1 NAME
+
+Lintian::Processable::Installable::Changelog - access to collected changelog data
+
+=head1 SYNOPSIS
+
+ use Lintian::Processable;
+
+=head1 DESCRIPTION
+
+Lintian::Processable::Installable::Changelog provides an interface to changelog data.
+
+=head1 INSTANCE METHODS
+
+=over 4
+
+=item changelog_item
+
+=cut
+
+has changelog_item => (
+ is => 'rw',
+ lazy => 1,
+ default => sub {
+ my ($self) = @_;
+
+ my @candidate_names = (
+ 'changelog.Debian.gz','changelog.Debian',
+ 'changelog.debian.gz','changelog.debian',
+ 'changelog.gz','changelog',
+ );
+
+ my $package_path = 'usr/share/doc/' . $self->name;
+ my @candidate_items = grep { defined }
+ map { $self->installed->lookup("$package_path/$_") }@candidate_names;
+
+ # pick the first existing file
+ my $item
+ = first_value { $_->is_file || length $_->link } @candidate_items;
+
+ return $item;
+ }
+);
+
+=item changelog
+
+For binary:
+
+Returns the changelog of the binary package as a Parse::DebianChangelog
+object, or an empty object if the changelog doesn't exist. The changelog-file
+collection script must have been run to create the changelog file, which
+this method expects to find in F<changelog>.
+
+=cut
+
+has changelog => (
+ is => 'rw',
+ lazy => 1,
+ default => sub {
+ my ($self) = @_;
+
+ my $changelog = Lintian::Changelog->new;
+
+ my $unresolved = $self->changelog_item;
+ return $changelog
+ unless defined $unresolved;
+
+ # stop for dangling symbolic link
+ my $item = $unresolved->resolve_path;
+ return $changelog
+ unless defined $item;
+
+ # return empty changelog
+ return $changelog
+ unless $item->is_file && $item->is_open_ok;
+
+ if ($item->basename =~ m{ [.]gz $}x) {
+
+ my $bytes = safe_qx('gunzip', '-c', $item->unpacked_path);
+
+ return $changelog
+ unless valid_utf8($bytes);
+
+ $changelog->parse(decode_utf8($bytes));
+
+ return $changelog;
+ }
+
+ return $changelog
+ unless $item->is_valid_utf8;
+
+ $changelog->parse($item->decoded_utf8);
+
+ return $changelog;
+ }
+);
+
+1;
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Felix Lechner <felix.lechner@lease-up.com> for
+Lintian.
+
+=head1 SEE ALSO
+
+lintian(1)
+
+=cut
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/lib/Lintian/Processable/Installable/Class.pm b/lib/Lintian/Processable/Installable/Class.pm
new file mode 100644
index 0000000..00520be
--- /dev/null
+++ b/lib/Lintian/Processable/Installable/Class.pm
@@ -0,0 +1,139 @@
+# -*- perl -*-
+# Lintian::Processable::Installable::Class -- interface to binary package data collection
+
+# Copyright (C) 2008, 2009 Russ Allbery
+# Copyright (C) 2008 Frank Lichtenheld
+# Copyright (C) 2012 Kees Cook
+# Copyright (C) 2020-2021 Felix Lechner
+#
+# 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::Processable::Installable::Class;
+
+use v5.20;
+use warnings;
+use utf8;
+
+use Moo::Role;
+use namespace::clean;
+
+=head1 NAME
+
+Lintian::Processable::Installable::Class - Lintian interface to binary package data collection
+
+=head1 SYNOPSIS
+
+ my ($name, $type, $dir) = ('foobar', 'binary', '/path/to/lab-entry');
+ my $collect = Lintian::Processable::Installable::Class->new($name);
+
+=head1 DESCRIPTION
+
+Lintian::Processable::Installable::Class provides an interface to package data for binary
+packages.
+
+=head1 INSTANCE METHODS
+
+=over 4
+
+=item is_debug_package
+
+The package probably contains only debug symbols.
+
+=cut
+
+sub is_debug_package {
+ my ($self) = @_;
+
+ return 1
+ if $self->name =~ /-dbg(?:sym)?/;
+
+ return 0;
+}
+
+=item is_auto_generated
+
+The package was probably generated automatically.
+
+=cut
+
+sub is_auto_generated {
+ my ($self) = @_;
+
+ return 1
+ if $self->fields->declares('Auto-Built-Package');
+
+ return 0;
+}
+
+=item is_transitional
+
+The package is probably transitional, i.e. it probably depends
+ on stuff will eventually disappear.
+
+=cut
+
+sub is_transitional {
+ my ($self) = @_;
+
+ return 1
+ if $self->fields->value('Description') =~ /transitional package/i;
+
+ return 0;
+}
+
+=item is_meta_package
+
+This package is probably some kind of meta or task package. A meta
+package is usually empty and just depend on stuff. It also returns
+a true value for "tasks" (i.e. tasksel "tasks").
+
+=cut
+
+sub is_meta_package {
+ my ($self) = @_;
+
+ return 1
+ if $self->fields->value('Description')
+ =~ /meta[ -]?package|(?:dependency|dummy|empty) package/i;
+
+ # section "tasks" or "metapackages" qualifies too
+ return 1
+ if $self->fields->value('Section') =~ m{(?:^|/)(?:tasks|metapackages)$};
+
+ return 1
+ if $self->name =~ /^task-/;
+
+ return 0;
+}
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Frank Lichtenheld <djpig@debian.org> for Lintian.
+Amended by Felix Lechner <felix.lechner@lease-up.com> for Lintian.
+
+=head1 SEE ALSO
+
+lintian(1)
+
+=cut
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/lib/Lintian/Processable/Installable/Conffiles.pm b/lib/Lintian/Processable/Installable/Conffiles.pm
new file mode 100644
index 0000000..50db7f7
--- /dev/null
+++ b/lib/Lintian/Processable/Installable/Conffiles.pm
@@ -0,0 +1,97 @@
+# -*- perl -*- Lintian::Processable::Installable::Conffiles
+#
+# Copyright (C) 2019-2021 Felix Lechner
+#
+# 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::Processable::Installable::Conffiles;
+
+use v5.20;
+use warnings;
+use utf8;
+
+use Lintian::Conffiles;
+
+use Moo::Role;
+use namespace::clean;
+
+=head1 NAME
+
+Lintian::Processable::Installable::Conffiles - access to collected control data for conffiles
+
+=head1 SYNOPSIS
+
+ use Lintian::Processable;
+
+=head1 DESCRIPTION
+
+Lintian::Processable::Installable::Conffiles provides an interface to control data for conffiles.
+
+=head1 INSTANCE METHODS
+
+=over 4
+
+=item conffiles_item
+
+=cut
+
+has conffiles_item => (
+ is => 'rw',
+ lazy => 1,
+ default => sub {
+ my ($self) = @_;
+
+ return $self->control->resolve_path('conffiles');
+ }
+);
+
+=item declared_conffiles
+
+=cut
+
+has declared_conffiles => (
+ is => 'rw',
+ lazy => 1,
+ default => sub {
+ my ($self) = @_;
+
+ my $item = $self->conffiles_item;
+
+ my $conffiles = Lintian::Conffiles->new;
+ $conffiles->parse($item, $self);
+
+ return $conffiles;
+ }
+);
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Felix Lechner <felix.lechner@lease-up.com> for
+Lintian.
+
+=head1 SEE ALSO
+
+lintian(1)
+
+=cut
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/lib/Lintian/Processable/Installable/Control.pm b/lib/Lintian/Processable/Installable/Control.pm
new file mode 100644
index 0000000..b6a72d8
--- /dev/null
+++ b/lib/Lintian/Processable/Installable/Control.pm
@@ -0,0 +1,99 @@
+# -*- perl -*- Lintian::Processable::Installable::Control
+#
+# Copyright (C) 2020 Felix Lechner
+#
+# 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::Processable::Installable::Control;
+
+use v5.20;
+use warnings;
+use utf8;
+
+use Const::Fast;
+use List::SomeUtils qw(uniq);
+
+use Lintian::Index;
+
+const my $SLASH => q{/};
+
+use Moo::Role;
+use namespace::clean;
+
+=head1 NAME
+
+Lintian::Processable::Installable::Control - access to collected control file data
+
+=head1 SYNOPSIS
+
+ use Lintian::Processable;
+
+=head1 DESCRIPTION
+
+Lintian::Processable::Installable::Control provides an interface to control file data.
+
+=head1 INSTANCE METHODS
+
+=over 4
+
+=item control
+
+Returns the index for a binary control file.
+
+=cut
+
+has control => (
+ is => 'rw',
+ lazy => 1,
+ default => sub {
+ my ($self) = @_;
+
+ my $index = Lintian::Index->new;
+ my $archive = $self->basename;
+ $index->identifier("$archive (control)");
+ $index->basedir($self->basedir . $SLASH . 'control');
+
+ # control files are not installed relative to the system root
+ # disallow absolute paths and symbolic links
+
+ my @command = (qw(dpkg-deb --ctrl-tarfile), $self->path);
+ my $errors = $index->create_from_piped_tar(\@command);
+
+ my @messages = uniq split(/\n/, $errors);
+ push(@{$index->unpack_messages}, @messages);
+
+ return $index;
+ }
+);
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Felix Lechner <felix.lechner@lease-up.com> for
+Lintian.
+
+=head1 SEE ALSO
+
+lintian(1)
+
+=cut
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/lib/Lintian/Processable/Installable/Installed.pm b/lib/Lintian/Processable/Installable/Installed.pm
new file mode 100644
index 0000000..61444ac
--- /dev/null
+++ b/lib/Lintian/Processable/Installable/Installed.pm
@@ -0,0 +1,103 @@
+# -*- perl -*- Lintian::Processable::Installable::Installed
+#
+# Copyright (C) 2008, 2009 Russ Allbery
+# Copyright (C) 2008 Frank Lichtenheld
+# Copyright (C) 2012 Kees Cook
+# Copyright (C) 2020 Felix Lechner
+#
+# 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::Processable::Installable::Installed;
+
+use v5.20;
+use warnings;
+use utf8;
+
+use Const::Fast;
+use List::SomeUtils qw(uniq);
+
+use Lintian::Index;
+
+const my $SLASH => q{/};
+
+use Moo::Role;
+use namespace::clean;
+
+=head1 NAME
+
+Lintian::Processable::Installable::Installed - access to collected data about the upstream (orig) sources
+
+=head1 SYNOPSIS
+
+ use Lintian::Processable;
+
+=head1 DESCRIPTION
+
+Lintian::Processable::Installable::Installed provides an interface to collected data about the upstream (orig) sources.
+
+=head1 INSTANCE METHODS
+
+=over 4
+
+=item installed
+
+Returns a index object representing installed files from a binary package.
+
+=cut
+
+has installed => (
+ is => 'rw',
+ lazy => 1,
+ default => sub {
+ my ($self) = @_;
+
+ my $index = Lintian::Index->new;
+ my $archive = $self->basename;
+ $index->identifier("$archive (installed)");
+ $index->basedir($self->basedir . $SLASH . 'unpacked');
+
+ # binary packages are anchored to the system root
+ # allow absolute paths and symbolic links
+ $index->anchored(1);
+
+ my @command = (qw(dpkg-deb --fsys-tarfile), $self->path);
+ my $errors = $index->create_from_piped_tar(\@command);
+
+ my @messages = uniq split(/\n/, $errors);
+ push(@{$index->unpack_messages}, @messages);
+
+ return $index;
+ }
+);
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Felix Lechner <felix.lechner@lease-up.com> for
+Lintian.
+
+=head1 SEE ALSO
+
+lintian(1)
+
+=cut
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/lib/Lintian/Processable/Installable/Overrides.pm b/lib/Lintian/Processable/Installable/Overrides.pm
new file mode 100644
index 0000000..0da551f
--- /dev/null
+++ b/lib/Lintian/Processable/Installable/Overrides.pm
@@ -0,0 +1,131 @@
+# -*- perl -*- Lintian::Processable::Installable::Overrides
+#
+# Copyright (C) 2019-2021 Felix Lechner
+#
+# 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::Processable::Installable::Overrides;
+
+use v5.20;
+use warnings;
+use utf8;
+
+use Const::Fast;
+use PerlIO::gzip;
+use List::SomeUtils qw(first_value);
+use Unicode::UTF8 qw(valid_utf8 decode_utf8 encode_utf8);
+
+use Moo::Role;
+use namespace::clean;
+
+with 'Lintian::Processable::Overrides';
+
+const my $EMPTY => q{};
+
+=head1 NAME
+
+Lintian::Processable::Installable::Overrides - access to override data
+
+=head1 SYNOPSIS
+
+ use Lintian::Processable;
+
+=head1 DESCRIPTION
+
+Lintian::Processable::Installable::Overrides provides an interface for overrides.
+
+=head1 INSTANCE METHODS
+
+=over 4
+
+=item override_file
+
+=cut
+
+has override_file => (
+ is => 'rw',
+ lazy => 1,
+ default => sub {
+ my ($self) = @_;
+
+ my $unzipped = 'usr/share/lintian/overrides/' . $self->name;
+
+ my @candidates = map { $unzipped . $_ } ($EMPTY, '.gz');
+
+ # pick the first
+ my $override_item= first_value { defined }
+ map { $self->installed->lookup($_) } @candidates;
+
+ return $override_item;
+ }
+);
+
+=item overrides
+
+=cut
+
+has overrides => (
+ is => 'rw',
+ lazy => 1,
+ default => sub {
+ my ($self) = @_;
+
+ return []
+ unless defined $self->override_file;
+
+ my $contents = $EMPTY;
+
+ if ($self->override_file->name =~ m{ [.]gz $}x) {
+
+ my $local_path = $self->override_file->unpacked_path;
+
+ open(my $fd, '<:gzip', $local_path)
+ or die encode_utf8("Cannot open $local_path.");
+
+ local $/ = undef;
+ my $bytes = <$fd>;
+
+ $contents = decode_utf8($bytes)
+ if valid_utf8($bytes);
+
+ close $fd;
+
+ } else {
+ $contents = $self->override_file->decoded_utf8;
+ }
+
+ return $self->parse_overrides($contents);
+ }
+);
+
+1;
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Felix Lechner <felix.lechner@lease-up.com> for
+Lintian.
+
+=head1 SEE ALSO
+
+lintian(1)
+
+=cut
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/lib/Lintian/Processable/Installable/Relation.pm b/lib/Lintian/Processable/Installable/Relation.pm
new file mode 100644
index 0000000..ac94489
--- /dev/null
+++ b/lib/Lintian/Processable/Installable/Relation.pm
@@ -0,0 +1,154 @@
+# -*- perl -*-
+# Lintian::Processable::Installable::Relation -- interface to binary package data collection
+
+# Copyright (C) 2008, 2009 Russ Allbery
+# Copyright (C) 2008 Frank Lichtenheld
+# Copyright (C) 2012 Kees Cook
+# Copyright (C) 2020 Felix Lechner
+#
+# 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::Processable::Installable::Relation;
+
+use v5.20;
+use warnings;
+use utf8;
+
+use Carp qw(croak);
+use Unicode::UTF8 qw(encode_utf8);
+
+use Lintian::Relation;
+
+use Moo::Role;
+use namespace::clean;
+
+=head1 NAME
+
+Lintian::Processable::Installable::Relation - Lintian interface to binary package data collection
+
+=head1 SYNOPSIS
+
+ my ($name, $type, $dir) = ('foobar', 'binary', '/path/to/lab-entry');
+ my $collect = Lintian::Processable::Installable::Relation->new($name);
+
+=head1 DESCRIPTION
+
+Lintian::Processable::Installable::Relation provides an interface to package data for binary
+packages. It implements data collection methods specific to binary
+packages.
+
+This module is in its infancy. Most of Lintian still reads all data from
+files in the laboratory whenever that data is needed and generates that
+data via collect scripts. The goal is to eventually access all data about
+binary packages via this module so that the module can cache data where
+appropriate and possibly retire collect scripts in favor of caching that
+data in memory.
+
+Native heuristics are only available in source packages.
+
+=head1 INSTANCE METHODS
+
+=over 4
+
+=item relation (FIELD)
+
+Returns a L<Lintian::Relation> object for the specified FIELD, which should
+be one of the possible relationship fields of a Debian package or one of
+the following special values:
+
+=over 4
+
+=item All
+
+The concatenation of Pre-Depends, Depends, Recommends, and Suggests.
+
+=item Strong
+
+The concatenation of Pre-Depends and Depends.
+
+=item Weak
+
+The concatenation of Recommends and Suggests.
+
+=back
+
+If FIELD isn't present in the package, the returned Lintian::Relation
+object will be empty (always present and satisfies nothing).
+
+=item saved_relations
+
+=cut
+
+has saved_relations => (
+ is => 'rw',
+ coerce => sub { my ($hashref) = @_; return ($hashref // {}); },
+ default => sub { {} }
+);
+
+my %alias = (
+ all => [qw(Pre-Depends Depends Recommends Suggests)],
+ strong => [qw(Pre-Depends Depends)],
+ weak => [qw(Recommends Suggests)]
+);
+
+my %known = map { $_ => 1 }
+ qw(pre-depends depends recommends suggests enhances breaks
+ conflicts provides replaces);
+
+sub relation {
+ my ($self, $name) = @_;
+
+ my $lowercase = lc $name;
+
+ my $relation = $self->saved_relations->{$lowercase};
+ unless (defined $relation) {
+
+ if (exists $alias{$lowercase}) {
+ $relation
+ = Lintian::Relation->new->logical_and(map { $self->relation($_) }
+ @{ $alias{$lowercase} });
+ } else {
+ croak encode_utf8("unknown relation field $name")
+ unless $known{$lowercase};
+
+ my $value = $self->fields->value($name);
+ $relation = Lintian::Relation->new->load($value);
+ }
+
+ $self->saved_relations->{$lowercase} = $relation;
+ }
+
+ return $relation;
+}
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Frank Lichtenheld <djpig@debian.org> for Lintian.
+Amended by Felix Lechner <felix.lechner@lease-up.com> for Lintian.
+
+=head1 SEE ALSO
+
+lintian(1)
+
+=cut
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/lib/Lintian/Processable/IsNonFree.pm b/lib/Lintian/Processable/IsNonFree.pm
new file mode 100644
index 0000000..bd6f246
--- /dev/null
+++ b/lib/Lintian/Processable/IsNonFree.pm
@@ -0,0 +1,109 @@
+# -*- perl -*-
+# Lintian::Processable::IsNonFree -- interface to source package data collection
+
+# Copyright (C) 2008 Russ Allbery
+# Copyright (C) 2009 Raphael Geissert
+# Copyright (C) 2020 Felix Lechner
+#
+# 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::Processable::IsNonFree;
+
+use v5.20;
+use warnings;
+use utf8;
+
+use Moo::Role;
+use namespace::clean;
+
+=head1 NAME
+
+Lintian::Processable::IsNonFree - Lintian interface to source package data collection
+
+=head1 SYNOPSIS
+
+ my ($name, $type, $dir) = ('foobar', 'source', '/path/to/lab-entry');
+ my $collect = Lintian::Processable::IsNonFree->new($name);
+ if ($collect->native) {
+ print encode_utf8("Package is native\n");
+ }
+
+=head1 DESCRIPTION
+
+Lintian::Processable::IsNonFree provides an interface to package data for source
+packages. It implements data collection methods specific to source
+packages.
+
+This module is in its infancy. Most of Lintian still reads all data from
+files in the laboratory whenever that data is needed and generates that
+data via collect scripts. The goal is to eventually access all data about
+source packages via this module so that the module can cache data where
+appropriate and possibly retire collect scripts in favor of caching that
+data in memory.
+
+=head1 INSTANCE METHODS
+
+=over 4
+
+=item is_non_free
+
+Returns a truth value if the package appears to be non-free (based on
+the section field; "non-free/*" and "restricted/*")
+
+=cut
+
+has is_non_free => (
+ is => 'rw',
+ lazy => 1,
+ default => sub {
+ my ($self) = @_;
+
+ my $section;
+
+ if ($self->type eq 'source') {
+ $section = $self->debian_control->source_fields->value('Section');
+ } else {
+ $section = $self->fields->value('Section');
+ }
+
+ $section ||= 'main';
+
+ return 1
+ if $section
+ =~ m{^(?:non-free|non-free-firmware|restricted|multiverse)/};
+
+ return 0;
+ }
+);
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Russ Allbery <rra@debian.org> for Lintian.
+Amended by Felix Lechner <felix.lechner@lease-up.com> for Lintian.
+
+=head1 SEE ALSO
+
+lintian(1)
+
+=cut
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/lib/Lintian/Processable/NotJustDocs.pm b/lib/Lintian/Processable/NotJustDocs.pm
new file mode 100644
index 0000000..1e08760
--- /dev/null
+++ b/lib/Lintian/Processable/NotJustDocs.pm
@@ -0,0 +1,112 @@
+# -*- perl -*-
+# Lintian::Processable::NotJustDocs
+
+# Copyright (C) 2020 Felix Lechner
+#
+# 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::Processable::NotJustDocs;
+
+use v5.20;
+use warnings;
+use utf8;
+
+use Moo::Role;
+use namespace::clean;
+
+=head1 NAME
+
+Lintian::Processable::NotJustDocs - Lintian interface to installable package data collection
+
+=head1 SYNOPSIS
+
+ my $processable = Lintian::Processable::Installable->new;
+
+ my $is_empty = $processable->not_just_docs;
+
+=head1 DESCRIPTION
+
+Lintian::Processable::NotJustDocs provides an interface to package data for installation
+packages.
+
+=head1 INSTANCE METHODS
+
+=over 4
+
+=item not_just_docs
+
+Returns a truth value if the package appears to be empty.
+
+=cut
+
+has not_just_docs => (
+ is => 'rw',
+ lazy => 1,
+ default => sub {
+ my ($self) = @_;
+
+ my $quoted_name = quotemeta($self->name);
+
+ my $empty = 1;
+ for my $item (@{$self->installed->sorted_list}) {
+
+ # ignore directories
+ next
+ if $item->is_dir;
+
+ # skip /usr/share/doc/$name symlinks.
+ next
+ if $item->name eq 'usr/share/doc/' . $self->name;
+
+ # only look outside /usr/share/doc/$name directory
+ next
+ if $item->name =~ m{^usr/share/doc/$quoted_name};
+
+ # except if it is a lintian override.
+ next
+ if $item->name =~ m{\A
+ # Except for:
+ usr/share/ (?:
+ # lintian overrides
+ lintian/overrides/$quoted_name(?:\.gz)?
+ # reportbug scripts/utilities
+ | bug/$quoted_name(?:/(?:control|presubj|script))?
+ )\Z}xsm;
+
+ return 0;
+ }
+
+ return 1;
+ }
+);
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Felix Lechner <felix.lechner@lease-up.com> for Lintian.
+
+=head1 SEE ALSO
+
+lintian(1)
+
+=cut
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/lib/Lintian/Processable/Overrides.pm b/lib/Lintian/Processable/Overrides.pm
new file mode 100644
index 0000000..0de05a4
--- /dev/null
+++ b/lib/Lintian/Processable/Overrides.pm
@@ -0,0 +1,219 @@
+# -*- perl -*- Lintian::Processable::Overrides
+#
+# Copyright (C) 2019-2021 Felix Lechner
+#
+# 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::Processable::Overrides;
+
+use v5.20;
+use warnings;
+use utf8;
+
+use Const::Fast;
+
+use Lintian::Override;
+
+const my $EMPTY => q{};
+const my $SPACE => q{ };
+
+use Moo::Role;
+use namespace::clean;
+
+=head1 NAME
+
+Lintian::Processable::Overrides - access to override data
+
+=head1 SYNOPSIS
+
+ use Lintian::Processable;
+
+=head1 DESCRIPTION
+
+Lintian::Processable::Overrides provides an interface to overrides.
+
+=head1 INSTANCE METHODS
+
+=over 4
+
+=item override_errors
+
+=cut
+
+has override_errors => (is => 'rw', default => sub { [] });
+
+=item parse_overrides
+
+=cut
+
+sub parse_overrides {
+ my ($self, $contents) = @_;
+
+ $contents //= $EMPTY;
+
+ my @declared_overrides;
+
+ my $justification = $EMPTY;
+ my $previous = Lintian::Override->new;
+
+ my @lines = split(/\n/, $contents);
+
+ my $position = 1;
+ for my $line (@lines) {
+
+ my $remaining = $line;
+
+ # trim both ends
+ $remaining =~ s/^\s+|\s+$//g;
+
+ if ($remaining eq $EMPTY) {
+ # Throw away comments, as they are not attached to a tag
+ # also throw away the option of "carrying over" the last
+ # comment
+ $justification = $EMPTY;
+ $previous = Lintian::Override->new;
+ next;
+ }
+
+ if ($remaining =~ s{^ [#] \s* }{}x) {
+
+ if (length $remaining) {
+
+ $justification .= $SPACE
+ if length $justification;
+
+ $justification .= $remaining;
+ }
+
+ next;
+ }
+
+ # reduce white space
+ $remaining =~ s/\s+/ /g;
+
+ # [[pkg-name] [arch-list] [pkg-type]:] <tag> [context]
+ my $require_colon = 0;
+ my @architectures;
+
+ # strip package name, if present; require name
+ # parsing overrides is ambiguous (see #699628)
+ my $package = $self->name;
+ if ($remaining =~ s/^\Q$package\E(?=\s|:)//) {
+
+ # both spaces or colon were unmatched lookhead
+ $remaining =~ s/^\s+//;
+ $require_colon = 1;
+ }
+
+ # remove architecture list
+ if ($remaining =~ s{^ \[ ([^\]]*) \] (?=\s|:)}{}x) {
+
+ my $list = $1;
+
+ @architectures = split($SPACE, $list);
+
+ # both spaces or colon were unmatched lookhead
+ $remaining =~ s/^\s+//;
+ $require_colon = 1;
+ }
+
+ # remove package type
+ my $type = $self->type;
+ if ($remaining =~ s/^\Q$type\E(?=\s|:)//) {
+
+ # both spaces or colon were unmatched lookhead
+ $remaining =~ s/^\s+//;
+ $require_colon = 1;
+ }
+
+ my $pointer = $self->override_file->pointer($position);
+
+ # require and remove colon when any package details are present
+ if ($require_colon && $remaining !~ s/^\s*:\s*//) {
+
+ my %error;
+ $error{message} = 'Expected a colon';
+ $error{pointer} = $pointer;
+ push(@{$self->override_errors}, \%error);
+
+ next;
+ }
+
+ my $hint_like = $remaining;
+
+ my ($tag_name, $pattern) = split($SPACE, $hint_like, 2);
+
+ if (!length $tag_name) {
+
+ my %error;
+ $error{message} = "Cannot parse line: $line";
+ $error{pointer} = $pointer;
+ push(@{$self->override_errors}, \%error);
+
+ next;
+ }
+
+ $pattern //= $EMPTY;
+
+ # There are no new comments, no "empty line" in between and
+ # this tag is the same as the last, so we "carry over" the
+ # comment from the previous override (if any).
+ $justification = $previous->justification
+ if !length $justification
+ && $tag_name eq $previous->tag_name;
+
+ my $current = Lintian::Override->new;
+
+ $current->tag_name($tag_name);
+ $current->architectures(\@architectures);
+ $current->pattern($pattern);
+ $current->position($position);
+
+ # combine successive white space
+ $justification =~ s{ \s+ }{ }gx;
+
+ $current->justification($justification);
+ $justification = $EMPTY;
+
+ push(@declared_overrides, $current);
+
+ $previous = $current;
+
+ } continue {
+ $position++;
+ }
+
+ return \@declared_overrides;
+}
+
+1;
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Felix Lechner <felix.lechner@lease-up.com> for
+Lintian.
+
+=head1 SEE ALSO
+
+lintian(1)
+
+=cut
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/lib/Lintian/Processable/Source.pm b/lib/Lintian/Processable/Source.pm
new file mode 100644
index 0000000..e4dc001
--- /dev/null
+++ b/lib/Lintian/Processable/Source.pm
@@ -0,0 +1,142 @@
+# Copyright (C) 2019-2020 Felix Lechner
+#
+# 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, you can find it on the World Wide
+# Web at https://www.gnu.org/copyleft/gpl.html, or write to the Free
+# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+
+package Lintian::Processable::Source;
+
+use v5.20;
+use warnings;
+use utf8;
+
+use Carp qw(croak);
+use File::Spec;
+use Unicode::UTF8 qw(encode_utf8);
+
+use Lintian::Deb822;
+
+use Moo;
+use namespace::clean;
+
+with
+ 'Lintian::Processable',
+ 'Lintian::Processable::Diffstat',
+ 'Lintian::Processable::Changelog::Version',
+ 'Lintian::Processable::Debian::Control',
+ 'Lintian::Processable::Fields::Files',
+ 'Lintian::Processable::IsNonFree',
+ 'Lintian::Processable::Source::Changelog',
+ 'Lintian::Processable::Source::Components',
+ 'Lintian::Processable::Source::Format',
+ 'Lintian::Processable::Source::Orig',
+ 'Lintian::Processable::Source::Overrides',
+ 'Lintian::Processable::Source::Patched',
+ 'Lintian::Processable::Source::Relation',
+ 'Lintian::Processable::Source::Repacked';
+
+=for Pod::Coverage BUILDARGS
+
+=head1 NAME
+
+Lintian::Processable::Source -- A dsc source package Lintian can process
+
+=head1 SYNOPSIS
+
+ use Lintian::Processable::Source;
+
+ my $processable = Lintian::Processable::Source->new;
+ $processable->init_from_file('path');
+
+=head1 DESCRIPTION
+
+This class represents a 'dsc' file that Lintian can process. Objects
+of this kind are often part of a L<Lintian::Group>, which
+represents all the files in a changes or buildinfo file.
+
+=head1 INSTANCE METHODS
+
+=over 4
+
+=item init_from_file (PATH)
+
+Initializes a new object from PATH.
+
+=cut
+
+sub init_from_file {
+ my ($self, $file) = @_;
+
+ croak encode_utf8("File $file does not exist")
+ unless -e $file;
+
+ $self->path($file);
+ $self->type('source');
+
+ my $primary = Lintian::Deb822->new;
+ my @sections = $primary->read_file($self->path)
+ or croak encode_utf8($self->path . ' is not valid dsc file');
+
+ $self->fields($sections[0]);
+
+ my $name = $self->fields->value('Source');
+ my $version = $self->fields->value('Version');
+ my $architecture = 'source';
+
+ # it is its own source package
+ my $source_name = $name;
+ my $source_version = $version;
+
+ croak encode_utf8($self->path . ' is missing Source field')
+ unless length $name;
+
+ $self->name($name);
+ $self->version($version);
+ $self->architecture($architecture);
+ $self->source_name($source_name);
+ $self->source_version($source_version);
+
+ # make sure none of these fields can cause traversal
+ $self->tainted(1)
+ if $self->name ne $name
+ || $self->version ne $version
+ || $self->architecture ne $architecture
+ || $self->source_name ne $source_name
+ || $self->source_version ne $source_version;
+
+ return;
+}
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Felix Lechner <felix.lechner@lease-up.com> for Lintian.
+
+=head1 SEE ALSO
+
+lintian(1)
+
+L<Lintian::Processable>
+
+=cut
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/lib/Lintian/Processable/Source/Changelog.pm b/lib/Lintian/Processable/Source/Changelog.pm
new file mode 100644
index 0000000..a781057
--- /dev/null
+++ b/lib/Lintian/Processable/Source/Changelog.pm
@@ -0,0 +1,109 @@
+# -*- perl -*- Lintian::Processable::Source::Changelog -- access to collected changelog data
+#
+# Copyright (C) 1998 Richard Braakman
+# Copyright (C) 2019-2021 Felix Lechner
+#
+# 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::Processable::Source::Changelog;
+
+use v5.20;
+use warnings;
+use utf8;
+
+use Moo::Role;
+use namespace::clean;
+
+=head1 NAME
+
+Lintian::Processable::Source::Changelog - access to collected changelog data
+
+=head1 SYNOPSIS
+
+ use Lintian::Processable;
+
+=head1 DESCRIPTION
+
+Lintian::Processable::Source::Changelog provides an interface to changelog data.
+
+=head1 INSTANCE METHODS
+
+=over 4
+
+=item changelog_item
+
+=cut
+
+has changelog_item => (
+ is => 'rw',
+ lazy => 1,
+ default => sub {
+ my ($self) = @_;
+
+ my $item = $self->patched->resolve_path('debian/changelog');
+
+ return $item;
+ }
+);
+
+=item changelog
+
+Returns the changelog of the source package as a Parse::DebianChangelog
+object, or an empty object if the changelog cannot be resolved safely.
+
+=cut
+
+has changelog => (
+ is => 'rw',
+ lazy => 1,
+ default => sub {
+ my ($self) = @_;
+
+ my $changelog = Lintian::Changelog->new;
+
+ my $item = $self->changelog_item;
+
+ # return empty changelog
+ return $changelog
+ unless defined $item && $item->is_open_ok;
+
+ return $changelog
+ unless $item->is_valid_utf8;
+
+ $changelog->parse($item->decoded_utf8);
+
+ return $changelog;
+ }
+);
+
+1;
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Felix Lechner <felix.lechner@lease-up.com> for
+Lintian.
+
+=head1 SEE ALSO
+
+lintian(1)
+
+=cut
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/lib/Lintian/Processable/Source/Components.pm b/lib/Lintian/Processable/Source/Components.pm
new file mode 100644
index 0000000..1541abe
--- /dev/null
+++ b/lib/Lintian/Processable/Source/Components.pm
@@ -0,0 +1,126 @@
+# -*- perl -*-
+# Lintian::Processable::Source::Components -- interface to orig tag components
+#
+# Copyright (C) 2020 Felix Lechner
+#
+# 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::Processable::Source::Components;
+
+use v5.20;
+use warnings;
+use utf8;
+
+use Const::Fast;
+use Unicode::UTF8 qw(encode_utf8);
+
+use Moo::Role;
+use namespace::clean;
+
+const my $EMPTY => q{};
+
+=head1 NAME
+
+Lintian::Processable::Source::Components - interface to orig tar components
+
+=head1 SYNOPSIS
+
+ use Moo;
+
+ with 'Lintian::Processable::Source::Components';
+
+=head1 DESCRIPTION
+
+Lintian::Processable::Source::Components provides an interface to data for
+upstream source components. Most sources only use one tarball.
+
+=head1 INSTANCE METHODS
+
+=over 4
+
+=item components
+
+Returns a reference to a hash containing information about source components
+listed in the .dsc file. The key is the filename, and the value is the name
+of the component.
+
+=cut
+
+has components => (
+ is => 'rw',
+ lazy => 1,
+ default => sub {
+ my ($self) = @_;
+
+ # determine source and version; handle missing fields
+ my $name = $self->fields->value('Source');
+ my $version = $self->fields->value('Version');
+ my $architecture = 'source';
+
+ # it is its own source package
+ my $source = $name;
+ my $source_version = $version;
+
+ # version handling based on Dpkg::Version::parseversion.
+ my $noepoch = $source_version;
+ if ($noepoch =~ /:/) {
+ $noepoch =~ s/^(?:\d+):(.+)/$1/
+ or die encode_utf8("Bad version number '$noepoch'");
+ }
+
+ my $baserev = $source . '_' . $noepoch;
+
+ # strip debian revision
+ $noepoch =~ s/(.+)-(?:.*)$/$1/;
+ my $base = $source . '_' . $noepoch;
+
+ my $files = $self->files;
+
+ my %components;
+ for my $name (keys %{$files}) {
+
+ # Look for $pkg_$version.orig(-$comp)?.tar.$ext (non-native)
+ # or $pkg_$version.tar.$ext (native)
+ # - This deliberately does not look for the debian packaging
+ # even when this would be a tarball.
+ if ($name
+ =~ /^(?:\Q$base\E\.orig(?:-(.*))?|\Q$baserev\E)\.tar\.(?:gz|bz2|lzma|xz)$/
+ ) {
+ $components{$name} = $1 // $EMPTY;
+ }
+ }
+
+ return \%components;
+ }
+);
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Adam D. Barratt <adsb@debian.org> for Lintian.
+
+=head1 SEE ALSO
+
+lintian(1), L<Lintian::Processable>
+
+=cut
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/lib/Lintian/Processable/Source/Format.pm b/lib/Lintian/Processable/Source/Format.pm
new file mode 100644
index 0000000..551f93e
--- /dev/null
+++ b/lib/Lintian/Processable/Source/Format.pm
@@ -0,0 +1,136 @@
+# -*- perl -*-
+# Lintian::Processable::Source::Format -- interface to source package data collection
+
+# Copyright (C) 2008 Russ Allbery
+# Copyright (C) 2009 Raphael Geissert
+# Copyright (C) 2020 Felix Lechner
+#
+# 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::Processable::Source::Format;
+
+use v5.20;
+use warnings;
+use utf8;
+
+use Const::Fast;
+use Path::Tiny;
+
+use Moo::Role;
+use namespace::clean;
+
+const my $UNDERSCORE => q{_};
+
+=head1 NAME
+
+Lintian::Processable::Source::Format - Lintian interface to source format
+
+=head1 SYNOPSIS
+
+ my $collect = Lintian::Processable::Source::Format->new;
+
+=head1 DESCRIPTION
+
+Lintian::Processable::Source::Format provides an interface to source format
+information.
+
+=head1 INSTANCE METHODS
+
+=over 4
+
+=item source_format
+
+=cut
+
+has source_format => (
+ is => 'rw',
+ lazy => 1,
+ default => sub {
+ my ($self) = @_;
+
+ my $format = $self->fields->value('Format') || '1.0';
+
+ return $format;
+ }
+);
+
+=item native
+
+Returns true if the source package is native and false otherwise.
+This is generally determined from the source format, though in the 1.0
+case the nativeness is determined by looking for the diff.gz (using
+the name of the source package and its version).
+
+If the source format is 1.0 and the version number is absent, this
+will return false (as native packages are a lot rarer than non-native
+ones).
+
+Note if the source format is missing, it is assumed to be a 1.0
+package.
+
+=cut
+
+has native => (
+ is => 'rw',
+ lazy => 1,
+ default => sub {
+ my ($self) = @_;
+
+ my $format = $self->source_format;
+
+ return 0
+ if $format =~ /^\s*2\.0\s*$/;
+
+ return 0
+ if $format =~ /^\s*3\.0\s+\(quilt|git\)\s*$/;
+
+ return 1
+ if $format =~ /^\s*3\.0\s+\(native\)\s*$/;
+
+ my $version = $self->fields->value('Version');
+ return 0
+ unless length $version;
+
+ # strip epoch
+ $version =~ s/^\d+://;
+
+ my $diffname = $self->name . $UNDERSCORE . "$version.diff.gz";
+
+ return 0
+ if exists $self->files->{$diffname};
+
+ return 1;
+ }
+);
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Russ Allbery <rra@debian.org> for Lintian.
+Amended by Felix Lechner <felix.lechner@lease-up.com> for Lintian.
+
+=head1 SEE ALSO
+
+lintian(1), Lintian::Relation(3)
+
+=cut
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/lib/Lintian/Processable/Source/Orig.pm b/lib/Lintian/Processable/Source/Orig.pm
new file mode 100644
index 0000000..dd263f5
--- /dev/null
+++ b/lib/Lintian/Processable/Source/Orig.pm
@@ -0,0 +1,200 @@
+# -*- perl -*- Lintian::Processable::Source::Orig
+#
+# Copyright (C) 2020 Felix Lechner
+#
+# 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::Processable::Source::Orig;
+
+use v5.20;
+use warnings;
+use utf8;
+
+use Const::Fast;
+use List::SomeUtils qw(uniq);
+use List::UtilsBy qw(sort_by);
+use Path::Tiny;
+use Unicode::UTF8 qw(encode_utf8);
+
+use Lintian::Index;
+
+const my $EMPTY => q{};
+const my $SPACE => q{ };
+const my $SLASH => q{/};
+
+use Moo::Role;
+use namespace::clean;
+
+=head1 NAME
+
+Lintian::Processable::Source::Orig - access to collected data about the upstream (orig) sources
+
+=head1 SYNOPSIS
+
+ use Lintian::Processable;
+
+=head1 DESCRIPTION
+
+Lintian::Processable::Source::Orig provides an interface to collected data about the upstream (orig) sources.
+
+=head1 INSTANCE METHODS
+
+=over 4
+
+=item orig
+
+Returns the index for orig.tar.gz.
+
+=cut
+
+my %DECOMPRESS_COMMAND = (
+ 'gz' => 'gzip --decompress --stdout',
+ 'bz2' => 'bzip2 --decompress --stdout',
+ 'xz' => 'xz --decompress --stdout',
+);
+
+has orig => (
+ is => 'rw',
+ lazy => 1,
+ default => sub {
+ my ($self) = @_;
+
+ my $index = Lintian::Index->new;
+ my $archive = $self->basename;
+ $index->identifier("$archive (orig)");
+ $index->basedir($self->basedir . $SLASH . 'orig');
+
+ return $index
+ if $self->native;
+
+ # source packages can be unpacked anywhere; no anchored roots
+ $index->anchored(0);
+
+ my %components = %{$self->components};
+
+ # keep sort order; root is missing below otherwise
+ my @tarballs = sort_by { $components{$_} } keys %components;
+
+ for my $tarball (@tarballs) {
+
+ my $component = $components{$tarball};
+
+ # so far, all archives with components had an extra level
+ my $component_dir = $index->basedir;
+ $component_dir .= $SLASH . $component
+ if length $component;
+
+ my $subindex = Lintian::Index->new;
+ $subindex->basedir($component_dir);
+
+ # source packages can be unpacked anywhere; no anchored roots
+ $index->anchored(0);
+
+ my ($extension) = ($tarball =~ /\.([^.]+)$/);
+ die encode_utf8("Source component $tarball has no file exension\n")
+ unless length $extension;
+
+ my $decompress = $DECOMPRESS_COMMAND{lc $extension};
+ die encode_utf8("Don't know how to decompress $tarball")
+ unless $decompress;
+
+ my @command
+ = (split($SPACE, $decompress),
+ $self->basedir . $SLASH . $tarball);
+
+ my $errors = $subindex->create_from_piped_tar(\@command);
+
+ push(@{$index->unpack_messages}, "$tarball . $_")
+ for grep { !/^tar: Ignoring / } uniq split(/\n/, $errors);
+
+ # treat hard links like regular files
+ my @hardlinks = grep { $_->is_hardlink } @{$subindex->sorted_list};
+ for my $item (@hardlinks) {
+
+ my $target = $subindex->lookup($item->link);
+
+ $item->unpacked_path($target->unpacked_path);
+ $item->size($target->size);
+ $item->link($EMPTY);
+
+ # turn into a regular file
+ my $perm = $item->perm;
+ $perm =~ s/^-/h/;
+ $item->perm($perm);
+
+ $item->path_info(
+ ($item->path_info & ~Lintian::Index::Item::TYPE_HARDLINK)
+ | Lintian::Index::Item::TYPE_FILE);
+ }
+
+ my @prefixes = @{$subindex->sorted_list};
+
+ # keep top level prefixes; no trailing slashes
+ s{^([^/]+).*$}{$1}s for @prefixes;
+
+ # squash identical values; ignore root entry ('')
+ my @unique = grep { length } uniq @prefixes;
+
+ # check for single common value
+ if (@unique == 1) {
+
+ # no trailing slash for directories
+ my $common = $unique[0];
+
+ # proceed if no file with that name (lacks slash)
+ my $conflict = $subindex->lookup($common);
+ unless (defined $conflict) {
+
+ if ($common ne $component || length $component) {
+
+ # shortens paths; keeps same base directory
+ my $sub_errors = $subindex->drop_common_prefix;
+
+ push(@{$index->unpack_errors}, "$tarball . $_")
+ for uniq split(/\n/, $sub_errors);
+ }
+ }
+ }
+
+ # lowers base directory to match index being merged into
+ $subindex->capture_common_prefix
+ if length $component;
+
+ $index->merge_in($subindex);
+ }
+
+ return $index;
+ }
+);
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Felix Lechner <felix.lechner@lease-up.com> for
+Lintian.
+
+=head1 SEE ALSO
+
+lintian(1)
+
+=cut
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/lib/Lintian/Processable/Source/Overrides.pm b/lib/Lintian/Processable/Source/Overrides.pm
new file mode 100644
index 0000000..d4c446f
--- /dev/null
+++ b/lib/Lintian/Processable/Source/Overrides.pm
@@ -0,0 +1,109 @@
+# -*- perl -*- Lintian::Processable::Source::Overrides
+#
+# Copyright (C) 2019-2021 Felix Lechner
+#
+# 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::Processable::Source::Overrides;
+
+use v5.20;
+use warnings;
+use utf8;
+
+use List::SomeUtils qw(first_value);
+
+use Moo::Role;
+use namespace::clean;
+
+with 'Lintian::Processable::Overrides';
+
+=head1 NAME
+
+Lintian::Processable::Source::Overrides - access to override data
+
+=head1 SYNOPSIS
+
+ use Lintian::Processable;
+
+=head1 DESCRIPTION
+
+Lintian::Processable::Source::Overrides provides an interface to overrides.
+
+=head1 INSTANCE METHODS
+
+=over 4
+
+=item override_file
+
+=cut
+
+has override_file => (
+ is => 'rw',
+ lazy => 1,
+ default => sub {
+ my ($self) = @_;
+
+ # prefer source/lintian-overrides to source.lintian-overrides
+ my @candidates = (
+ 'debian/source/lintian-overrides',
+ 'debian/source.lintian-overrides'
+ );
+
+ # pick the first
+ my $override_item= first_value { defined }
+ map { $self->patched->lookup($_) } @candidates;
+
+ return $override_item;
+ }
+);
+
+=item overrides
+
+=cut
+
+has overrides => (
+ is => 'rw',
+ lazy => 1,
+ default => sub {
+ my ($self) = @_;
+
+ return []
+ unless defined $self->override_file;
+
+ my $contents = $self->override_file->decoded_utf8;
+
+ return $self->parse_overrides($contents);
+ }
+);
+
+1;
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Felix Lechner <felix.lechner@lease-up.com> for
+Lintian.
+
+=head1 SEE ALSO
+
+lintian(1)
+
+=cut
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/lib/Lintian/Processable/Source/Patched.pm b/lib/Lintian/Processable/Source/Patched.pm
new file mode 100644
index 0000000..229311f
--- /dev/null
+++ b/lib/Lintian/Processable/Source/Patched.pm
@@ -0,0 +1,161 @@
+# -*- perl -*- Lintian::Processable::Source::Patched
+#
+# Copyright (C) 2008 Russ Allbery
+# Copyright (C) 2009 Raphael Geissert
+# Copyright (C) 2020 Felix Lechner
+#
+# 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::Processable::Source::Patched;
+
+use v5.20;
+use warnings;
+use utf8;
+
+use Const::Fast;
+use Cwd;
+use List::SomeUtils qw(uniq);
+use IPC::Run3;
+use Path::Tiny;
+use Unicode::UTF8 qw(encode_utf8 decode_utf8);
+
+use Lintian::Index;
+use Lintian::Index::Item;
+
+const my $COLON => q{:};
+const my $SLASH => q{/};
+const my $NEWLINE => qq{\n};
+
+const my $NO_UMASK => 0000;
+const my $WAIT_STATUS_SHIFT => 8;
+
+use Moo::Role;
+use namespace::clean;
+
+=head1 NAME
+
+Lintian::Processable::Source::Patched - access to sources with Debian patches applied
+
+=head1 SYNOPSIS
+
+ use Lintian::Processable;
+
+=head1 DESCRIPTION
+
+Lintian::Processable::Source::Patched provides an interface to collected data about patched sources.
+
+=head1 INSTANCE METHODS
+
+=over 4
+
+=item patched
+
+Returns a index object representing a patched source tree.
+
+=cut
+
+has patched => (
+ is => 'rw',
+ lazy => 1,
+ default => sub {
+ my ($self) = @_;
+
+ my $index = Lintian::Index->new;
+ my $archive = $self->basename;
+ $index->identifier("$archive (patched)");
+ $index->basedir($self->basedir . $SLASH . 'unpacked');
+
+ # source packages can be unpacked anywhere; no anchored roots
+ $index->anchored(0);
+
+ path($index->basedir)->remove_tree
+ if -d $index->basedir;
+
+ print encode_utf8("N: Using dpkg-source to unpack\n")
+ if $ENV{'LINTIAN_DEBUG'};
+
+ my $saved_umask = umask;
+ umask $NO_UMASK;
+
+ my @unpack_command= (
+ qw(dpkg-source -q --no-check --extract),
+ $self->path, $index->basedir
+ );
+
+ # ignore STDOUT; older versions are not completely quiet with -q
+ my $unpack_errors;
+
+ run3(\@unpack_command, \undef, \undef, \$unpack_errors);
+ my $status = ($? >> $WAIT_STATUS_SHIFT);
+
+ $unpack_errors = decode_utf8($unpack_errors)
+ if length $unpack_errors;
+
+ if ($status) {
+ my $message = "Non-zero status $status from @unpack_command";
+ $message .= $COLON . $NEWLINE . $unpack_errors
+ if length $unpack_errors;
+
+ die encode_utf8($message);
+ }
+
+ umask $saved_umask;
+
+ my $index_errors = $index->create_from_basedir;
+
+ my $savedir = getcwd;
+ chdir($index->basedir)
+ or die encode_utf8('Cannot change to directory ' . $index->basedir);
+
+ # fix permissions
+ my @permissions_command
+ = ('chmod', '-R', 'u+rwX,o+rX,o-w', $index->basedir);
+ my $permissions_errors;
+
+ run3(\@permissions_command, \undef, \undef, \$permissions_errors);
+
+ $permissions_errors = decode_utf8($permissions_errors)
+ if length $permissions_errors;
+
+ chdir($savedir)
+ or die encode_utf8("Cannot change to directory $savedir");
+
+ my @messages = grep { !/^tar: Ignoring / }
+ split(/\n/, $unpack_errors . $index_errors . $permissions_errors);
+ push(@{$index->unpack_messages}, @messages);
+
+ return $index;
+ }
+);
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Felix Lechner <felix.lechner@lease-up.com> for
+Lintian.
+
+=head1 SEE ALSO
+
+lintian(1)
+
+=cut
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/lib/Lintian/Processable/Source/Relation.pm b/lib/Lintian/Processable/Source/Relation.pm
new file mode 100644
index 0000000..e66297c
--- /dev/null
+++ b/lib/Lintian/Processable/Source/Relation.pm
@@ -0,0 +1,267 @@
+# -*- perl -*-
+# Lintian::Processable::Source::Relation -- interface to source package data collection
+
+# Copyright (C) 2008 Russ Allbery
+# Copyright (C) 2009 Raphael Geissert
+# Copyright (C) 2020 Felix Lechner
+#
+# 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::Processable::Source::Relation;
+
+use v5.20;
+use warnings;
+use utf8;
+
+use Carp qw(croak);
+use Unicode::UTF8 qw(encode_utf8);
+
+use Lintian::Relation;
+
+use Moo::Role;
+use namespace::clean;
+
+=head1 NAME
+
+Lintian::Processable::Source::Relation - Lintian interface to source package data collection
+
+=head1 SYNOPSIS
+
+ my ($name, $type, $dir) = ('foobar', 'source', '/path/to/lab-entry');
+ my $collect = Lintian::Processable::Source::Relation->new($name);
+ if ($collect->native) {
+ print encode_utf8("Package is native\n");
+ }
+
+=head1 DESCRIPTION
+
+Lintian::Processable::Source::Relation provides an interface to package data for source
+packages. It implements data collection methods specific to source
+packages.
+
+This module is in its infancy. Most of Lintian still reads all data from
+files in the laboratory whenever that data is needed and generates that
+data via collect scripts. The goal is to eventually access all data about
+source packages via this module so that the module can cache data where
+appropriate and possibly retire collect scripts in favor of caching that
+data in memory.
+
+=head1 INSTANCE METHODS
+
+=over 4
+
+=item binary_relation (PACKAGE, FIELD)
+
+Returns a L<Lintian::Relation> object for the specified FIELD in the
+binary package PACKAGE in the F<debian/control> file. FIELD should be
+one of the possible relationship fields of a Debian package or one of
+the following special values:
+
+=over 4
+
+=item All
+
+The concatenation of Pre-Depends, Depends, Recommends, and Suggests.
+
+=item Strong
+
+The concatenation of Pre-Depends and Depends.
+
+=item Weak
+
+The concatenation of Recommends and Suggests.
+
+=back
+
+If FIELD isn't present in the package, the returned Lintian::Relation
+object will be empty (present but satisfies nothing).
+
+Any substvars in F<debian/control> will be represented in the returned
+relation as packages named after the substvar.
+
+=item saved_binary_relations
+
+=cut
+
+has saved_binary_relations => (
+ is => 'rw',
+ coerce => sub { my ($hashref) = @_; return ($hashref // {}); },
+ default => sub { {} }
+);
+
+my %alias = (
+ all => [qw(Pre-Depends Depends Recommends Suggests)],
+ strong => [qw(Pre-Depends Depends)],
+ weak => [qw(Recommends Suggests)]
+);
+
+my %known = map { $_ => 1 }
+ qw(pre-depends depends recommends suggests enhances breaks
+ conflicts provides replaces);
+
+sub binary_relation {
+ my ($self, $package, $name) = @_;
+
+ return undef
+ unless length $name;
+
+ my $lowercase = lc $name;
+
+ return undef
+ unless length $package;
+
+ my $relation = $self->saved_binary_relations->{$package}{$lowercase};
+ unless (defined $relation) {
+
+ if (length $alias{$lowercase}) {
+ $relation
+ = Lintian::Relation->new->logical_and(
+ map { $self->binary_relation($package, $_) }
+ @{ $alias{$lowercase} });
+
+ } else {
+ croak encode_utf8("unknown relation field $name")
+ unless $known{$lowercase};
+
+ my $value
+ = $self->debian_control->installable_fields($package)
+ ->value($name);
+ $relation = Lintian::Relation->new->load($value);
+ }
+
+ $self->saved_binary_relations->{$package}{$lowercase} = $relation;
+ }
+
+ return $relation;
+}
+
+=item relation (FIELD)
+
+Returns a L<Lintian::Relation> object for the given build relationship
+field FIELD. In addition to the normal build relationship fields, the
+following special field names are supported:
+
+=over 4
+
+=item Build-Depends-All
+
+The concatenation of Build-Depends, Build-Depends-Arch and
+Build-Depends-Indep.
+
+=item Build-Conflicts-All
+
+The concatenation of Build-Conflicts, Build-Conflicts-Arch and
+Build-Conflicts-Indep.
+
+=back
+
+If FIELD isn't present in the package, the returned Lintian::Relation
+object will be empty (present but satisfies nothing).
+
+=item saved_relation
+
+=cut
+
+has saved_relations => (
+ is => 'rw',
+ coerce => sub { my ($hashref) = @_; return ($hashref // {}); },
+ default => sub { {} }
+);
+
+sub relation {
+ my ($self, $name) = @_;
+
+ return undef
+ unless length $name;
+
+ my $lowercase = lc $name;
+
+ my $relation = $self->saved_relations->{$lowercase};
+ unless (defined $relation) {
+
+ if ($name =~ /^Build-(Depends|Conflicts)-All$/i) {
+ my $type = $1;
+ my @fields
+ = ("Build-$type", "Build-$type-Indep", "Build-$type-Arch");
+ $relation
+ = Lintian::Relation->new->logical_and(map { $self->relation($_) }
+ @fields);
+
+ } elsif ($name =~ /^Build-(Depends|Conflicts)(?:-(?:Arch|Indep))?$/i){
+ my $value = $self->fields->value($name);
+ $relation = Lintian::Relation->new->load($value);
+
+ } else {
+ croak encode_utf8("unknown relation field $name");
+ }
+
+ $self->saved_relations->{$lowercase} = $relation;
+ }
+
+ return $relation;
+}
+
+=item relation_norestriction (FIELD)
+
+The same as L</relation (FIELD)>, but ignores architecture
+restrictions and build profile restrictions in the FIELD field.
+
+=item saved_relations_norestriction
+
+=cut
+
+has saved_relations_norestriction => (
+ is => 'rw',
+ coerce => sub { my ($hashref) = @_; return ($hashref // {}); },
+ default => sub { {} }
+);
+
+sub relation_norestriction {
+ my ($self, $name) = @_;
+
+ return undef
+ unless length $name;
+
+ my $lowercase = lc $name;
+
+ my $relation = $self->saved_relations_norestriction->{$lowercase};
+ unless (defined $relation) {
+
+ $relation = $self->relation($name)->restriction_less;
+ $self->saved_relations_norestriction->{$lowercase} = $relation;
+ }
+
+ return $relation;
+}
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Russ Allbery <rra@debian.org> for Lintian.
+Amended by Felix Lechner <felix.lechner@lease-up.com> for Lintian.
+
+=head1 SEE ALSO
+
+lintian(1)
+
+=cut
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/lib/Lintian/Processable/Source/Repacked.pm b/lib/Lintian/Processable/Source/Repacked.pm
new file mode 100644
index 0000000..4cf057b
--- /dev/null
+++ b/lib/Lintian/Processable/Source/Repacked.pm
@@ -0,0 +1,99 @@
+# -*- perl -*-
+# Lintian::Processable::Source::Repacked -- interface to source package data collection
+
+# Copyright (C) 2008 Russ Allbery
+# Copyright (C) 2009 Raphael Geissert
+# Copyright (C) 2020 Felix Lechner
+#
+# 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::Processable::Source::Repacked;
+
+use v5.20;
+use warnings;
+use utf8;
+
+use Lintian::Util qw($PKGREPACK_REGEX);
+
+use Moo::Role;
+use namespace::clean;
+
+=head1 NAME
+
+Lintian::Processable::Source::Repacked - Lintian interface to source package data collection
+
+=head1 SYNOPSIS
+
+ my ($name, $type, $dir) = ('foobar', 'source', '/path/to/lab-entry');
+ my $collect = Lintian::Processable::Source::Repacked->new($name);
+ if ($collect->native) {
+ print encode_utf8("Package is native\n");
+ }
+
+=head1 DESCRIPTION
+
+Lintian::Processable::Source::Repacked provides an interface to package data for source
+packages. It implements data collection methods specific to source
+packages.
+
+This module is in its infancy. Most of Lintian still reads all data from
+files in the laboratory whenever that data is needed and generates that
+data via collect scripts. The goal is to eventually access all data about
+source packages via this module so that the module can cache data where
+appropriate and possibly retire collect scripts in favor of caching that
+data in memory.
+
+=head1 INSTANCE METHODS
+
+=over 4
+
+=item repacked
+
+Returns true if the source package has been "repacked" and false otherwise.
+This is determined from the version name containing "dfsg" or similar.
+
+=cut
+
+has repacked => (
+ is => 'rw',
+ lazy => 1,
+ default => sub {
+ my ($self) = @_;
+
+ my $upstream = $self->changelog_version->upstream;
+
+ return $upstream =~ $PKGREPACK_REGEX;
+ }
+);
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Russ Allbery <rra@debian.org> for Lintian.
+Amended by Felix Lechner <felix.lechner@lease-up.com> for Lintian.
+
+=head1 SEE ALSO
+
+lintian(1)
+
+=cut
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et