# control-file -- lintian check script -*- perl -*-
#
# Copyright (C) 2004 Marc Brockschmidt
#
# 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 http://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::control_file;
use strict;

use lib "$ENV{'LINTIAN_ROOT'}/checks/";
use common_data;

use Lintian::Relation ();
use Tags;
use Util;

# The list of libc packages, used for checking for a hard-coded dependency
# rather than using ${shlibs:Depends}.
our @LIBCS = qw(libc6 libc6.1 libc0.1 libc0.3);

sub run {

my $pkg = shift;
my $type = shift;

if (-l "debfiles/control") {
    tag "debian-control-file-is-a-symlink", "";
}

# check that control is UTF-8 encoded
my $line = file_is_encoded_in_non_utf8("debfiles/control", $type, $pkg);
if ($line) {
    tag "debian-control-file-uses-obsolete-national-encoding", "at line $line"
}

# Check that each field is only used once:
my $seen_fields = {};
open (CONTROL, '<', "debfiles/control")
    or fail "Couldn't read debfiles/control: $!";
while (<CONTROL>) {
	s/\s*\n$//;
	next if /^\#/;

	#Reset seen_fields if we enter a new section:
	$seen_fields = {} if /^$/;

	#line with field:
	if (/^(\S+):/) {
		my $field = lc ($1);
		if ($seen_fields->{$field}) {
			tag "debian-control-with-duplicate-fields", "$field: $$seen_fields{$field}, $.";
		}
		$seen_fields->{$field} = $.;
		if ($field =~ /^xs-vcs-/) {
			my $base = $field;
			$base =~ s/^xs-//;
			tag "xs-vcs-header-in-debian-control", "$field"
			    if $known_source_fields{$base};
		}
		unless (/^\S+: \S/ || /^\S+:$/) {
			tag 'debian-control-has-unusual-field-spacing', "line $.";
		}
	}
}
close CONTROL;

my ($header, @binary_controls) = read_dpkg_control("debfiles/control");

for my $binary_control (@binary_controls) {
	tag "build-info-in-binary-control-file-section", "Package ".$binary_control->{"package"}
	    if ($binary_control->{"build-depends"} || $binary_control->{"build-depends-indep"} ||
	        $binary_control->{"build-conflicts"} || $binary_control->{"build-conflicts-indep"});
	for my $field (keys %$binary_control) {
		tag 'binary-control-field-duplicates-source', "field \"$field\" in package ".$binary_control->{'package'},
		    if ($header->{$field} && $binary_control->{$field} eq $header->{$field});
	}
}

# Check that fields which should be comma-separated or pipe-separated have
# separators.  Places where this tends to cause problems are with wrapped
# lines such as:
#
#     Depends: foo, bar
#      baz
#
# or with substvars.  If two substvars aren't separated by a comma, but at
# least one of them expands to an empty string, there will be a lurking bug.
# The result will be syntactically correct, but as soon as both expand into
# something non-empty, there will be a syntax error.
#
# The architecture list can contain things that look like packages separated
# by spaces, so we have to remove any architecture restrictions first.  This
# unfortunately distorts our report a little, but hopefully not too much.
for my $control ($header, @binary_controls) {
	for my $field (qw(pre-depends depends recommends suggests breaks
			  conflicts provides replaces enhances
			  build-depends build-depends-indep
			  build-conflics build-conflicts-indep)) {
		next unless $control->{$field};
		my $value = $control->{$field};
		$value =~ s/\n(\s)/$1/g;
		$value =~ s/\[[^\]]*\]//g;
		if ($value =~ /(?:^|\s)
			       (
				(?:\w[^\s,|\(]+|\$\{\S+\})\s*
				(?:\([^\)]*\)\s*)?
			       )
			       \s+
			       (
				(?:\w[^\s,|\(]+|\$\{\S+\})\s*
				(?:\([^\)]*\)\s*)?
			       )/x) {
			my ($prev, $next) = ($1, $2);
			for ($prev, $next) {
				s/\s+$//;
			}
			tag 'missing-separator-between-items', 'in',
			    ($control->{source} ? 'source' : $control->{package}),
			    "$field field between '$prev' and '$next'";
		}
	}
}

# Make sure that a stronger dependency field doesn't imply any of the elements
# of a weaker dependency field.  dpkg-gencontrol will fix this up for us, but
# we want to check the source package since dpkg-gencontrol may silently "fix"
# something that's a more subtle bug.
#
# Also check if a package declares a simple dependency on itself, since
# similarly dpkg-gencontrol will clean this up for us but it may be a sign of
# another problem, and check that the package doesn't hard-code a dependency
# on libc.  We have to do the latter check here rather than in checks/fields
# to distinguish from dependencies created by ${shlibs:Depends}.
#
# Use this traversal to build a list of package names built from this source
# package, which we'll use later to check for dependencies in -dev packages.
my @dep_fields = qw(pre-depends depends recommends suggests);
my $libcs = Lintian::Relation->new(join(' | ', @LIBCS));
my @package_names;
for my $control (@binary_controls) {
	push (@package_names, $control->{package});
	for my $strong (0 .. $#dep_fields) {
		next unless $control->{$dep_fields[$strong]};
		my $relation = Lintian::Relation->new($control->{$dep_fields[$strong]});
		tag "package-depends-on-itself", $control->{package}, $dep_fields[$strong]
		    if $relation->implies($control->{package});
		tag 'package-depends-on-hardcoded-libc', $control->{package}, $dep_fields[$strong]
		    if ($relation->implies($libcs) and $pkg ne "glibc");
		for my $weak (($strong + 1) .. $#dep_fields) {
			next unless $control->{$dep_fields[$weak]};
			for my $dependency (split /\s*,\s*/, $control->{$dep_fields[$weak]}) {
				next unless $dependency;
				tag "stronger-dependency-implies-weaker", $control->{package}, "$dep_fields[$strong] -> $dep_fields[$weak]", $dependency
				    if $relation->implies($dependency);
			}
		}
	}
}

# Check that every package is in the same archive area, except that
# sources in main can deliver both main and contrib packages.  The source
# package may or may not have a section specified; if it doesn't, derive the
# expected archive area from the first binary package by leaving $area
# undefined until parsing the first binary section.  Missing sections will be
# caught by other checks.
#
# Check any package that looks like a library -dev package for a dependency on
# a shared library package built from the same source.  If found, such a
# dependency should have a tight version dependency on that package.
#
# Also accumulate short and long descriptions for each package so that we can
# check for duplication, but skip udeb packages.  Ideally, we should check the
# udeb package descriptions separately for duplication, but udeb packages
# should be able to duplicate the descriptions of non-udeb packages and the
# package description for udebs is much less important or significant to the
# user.
my $area;
if ($header->{'section'}) {
	if ($header->{'section'} =~ m%^([^/]+)/%) {
		$area = $1;
	} else {
		$area = '';
	}
} else {
	tag "no-section-field-for-source", "";
}
my @descriptions;
for my $binary_control (@binary_controls) {
	my $package = $binary_control->{'package'};

	# Accumulate the description.
	my $desc = $binary_control->{'description'};
	if ($desc and (not $binary_control->{'xc-package-type'}
		       or $binary_control->{'xc-package-type'} ne 'udeb')) {
		push(@descriptions, [ $package, split("\n", $desc, 2) ]);
	}

	# If this looks like a -dev package, check its dependencies.
	if ($package =~ /-dev$/) {
		for my $depend (split /\s*,\s*/, $binary_control->{'depends'}) {
			my ($target, $version) = ($depend =~ /^([\w.+-]+)(?:\s*\(([^\)]+)\))?/);
			next unless $target;
			if ($target =~ /^lib[\w.+-]+\d/ and $target !~ /-dev$/ and grep { $target eq $_ } @package_names) {
				tag 'weak-library-dev-dependency', "$package on $depend"
				    unless ($version and $version =~ /^\s*=\s*\$\{(?:binary:Version|Source-Version)\}/);
			}
		}
	}

	# Check mismatches in archive area.
	next unless $binary_control->{'section'};
	if (!defined ($area)) {
		if ($binary_control->{'section'} =~ m%^([^/]+)/%) {
			$area = ($1 eq 'contrib') ? '' : $1;
		} else {
			$area = '';
		}
		next;
	}
	tag "section-area-mismatch", "Package " . $package
		if ($area && $binary_control->{'section'} !~ m%^$area/%);
	tag "section-area-mismatch", "Package " . $package
		if (!$area && $binary_control->{'section'} =~ m%^([^/]+)/% && $1 ne 'contrib');
}

# Check for duplicate descriptions.
my (%seen_short, %seen_long);
for my $i (0 .. $#descriptions) {
	my (@short, @long);
	for my $j (($i + 1) .. $#descriptions) {
		if ($descriptions[$i][1] eq $descriptions[$j][1]) {
			my $package = $descriptions[$j][0];
			push(@short, $package) unless $seen_short{$package};
		}
		next unless ($descriptions[$i][2] and $descriptions[$j][2]);
		if ($descriptions[$i][2] eq $descriptions[$j][2]) {
			my $package = $descriptions[$j][0];
			push(@long, $package) unless $seen_long{$package};
		}
	}
	if (@short) {
		tag 'duplicate-short-description', $descriptions[$i][0], @short;
		for (@short) { $seen_short{$_} = 1 }
	}
	if (@long) {
		tag 'duplicate-long-description', $descriptions[$i][0], @long;
		for (@long) { $seen_long{$_} = 1 }
	}
}

}

1;

# Local Variables:
# indent-tabs-mode: t
# cperl-indent-level: 8
# End:
# vim: syntax=perl sw=4 ts=4 noet shiftround
