# files -- lintian check script -*- perl -*-

# Copyright (C) 1998 Christian Schwarz and Richard Braakman
#
# 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::files;
use strict;
use Tags;
use Util;
use Lintian::Data;

our $FONT_PACKAGES;

# A list of known packaged Javascript libraries
# and the packages providing them
our @jslibraries = (
    [ qr,(?i)mochikit\.js(\.gz)?$, => qr'libjs-mochikit' ],
    [ qr,(?i)mootools((\.v|-)[\d\.]+)?(-((core(-server)?)|more)(-(yc|jm|nc))?)?\.js(\.gz)?$, => qr'libjs-mootools' ],
    [ qr,(?i)jquery(\.(min|lite|pack))?\.js(\.gz)?$, => qr'libjs-jquery' ],
    [ qr,(?i)prototype(-[\d\.]+)?\.js(\.gz)?$, => qr'libjs-prototype' ],
    [ qr,(?i)scriptaculous\.js(\.gz)?$, => qr'libjs-scriptaculous' ],
    [ qr,(?i)fckeditor\.js(\.gz)?$, => qr'fckeditor' ],
    [ qr,(?i)cropper(\.uncompressed)?\.js(\.gz)?$, => qr'libjs-cropper' ],
    [ qr,(?i)(yahoo|yui)-(dom-event|min)\.js(\.gz)?$, => qr'libjs-yui' ],
# Disabled due to false positives.  Needs a content check adding to verify
# that the file being checked is /the/ yahoo.js
#    [ qr,(?i)yahoo\.js(\.gz)?$, => qr'libjs-yui' ],
    [ qr,(?i)jsjac(\.packed)?\.js(\.gz)?$, => qr'libjs-jac' ],
    [ qr,(?i)jsMath(-fallback-\w+)?\.js(\.gz)?$, => qr'jsmath' ],
    [ qr,(?i)tiny_mce(_(popup|src))?\.js(\.gz)?$, => qr'tinymce2?' ],
# not yet available in unstable:
#    [ qr,(?i)(htmlarea|Xinha(Loader|Core))\.js$, => qr'xinha' ],
);

# A list of known packaged PEAR modules
# and the packages providing them
our @pearmodules = (
    [ qr,(?<!Auth/)HTTP\.php$, => 'php-http' ],
    [ qr,Auth\.php$, => 'php-auth' ],
    [ qr,Auth/HTTP\.php$, => 'php-auth-http' ],
    [ qr,Benchmark/(Timer|Profiler|Iterate)\.php$, => 'php-benchmark' ],
    [ qr,Cache\.php$, => 'php-cache' ],
    [ qr,Cache/Lite\.php$, => 'php-cache-lite' ],
    [ qr,Compat\.php$, => 'php-compat' ],
    [ qr,Config\.php$, => 'php-config' ],
    [ qr,CBC\.php$, => 'php-crypt-cbc' ],
    [ qr,Date\.php$, => 'php-date' ],
    [ qr,(?<!Container)/DB\.php$, => 'php-db' ],
    [ qr,(?<!Container)/File\.php$, => 'php-file' ],
    [ qr,Log\.php$, => 'php-log' ],
    [ qr,Log/(file|error_log|null|syslog|sql\w*)\.php$, => 'php-log' ],
    [ qr,Mail\.php$, => 'php-mail' ],
    [ qr,(?i)mime(Part)?\.php$, => 'php-mail-mime' ],
    [ qr,mimeDecode\.php$, => 'php-mail-mimedecode' ],
    [ qr,FTP\.php$, => 'php-net-ftp' ],
    [ qr,(?<!Container/)IMAP\.php$, => 'php-net-imap' ],
    [ qr,SMTP\.php$, => 'php-net-smtp' ],
    [ qr,(?<!FTP/)Socket\.php$, => 'php-net-socket' ],
    [ qr,IPv4\.php$, => 'php-net-ipv4' ],
    [ qr,(?<!Container/)LDAP\.php$, => 'php-net-ldap' ],
);

# A list of known packaged php (!PEAR) libraries
# and the packages providing them
our @phplibraries = (
    [ qr,(?i)adodb\.inc\.php$, => 'libphp-adodb' ],
    [ qr,(?i)Smarty(_Compiler)?\.class\.php$, => 'smarty' ],
    [ qr,(?i)class\.phpmailer(\.(php|inc))+$, => 'libphp-phpmailer' ],
    [ qr,(?i)phpsysinfo\.dtd$, => 'phpsysinfo' ],
    [ qr,(?i)class\.(Linux|(Open|Net|Free|)BSD)\.inc\.php$, => 'phpsysinfo' ],
    [ qr,Auth/(OpenID|Yadis/Yadis)\.php$, => 'php-openid' ],
    [ qr,(?i)Snoopy\.class\.(php|inc)$, => 'libphp-snoopy' ],
    [ qr,(?i)markdown\.php$, => 'libmarkdown-php' ],
    [ qr,(?i)geshi\.php$, => 'php-geshi' ],
    [ qr,(?i)(class[.-])?pclzip\.(inc|lib)?\.php$, => 'libphp-pclzip' ],
    [ qr,(?i).*layersmenu.*/(lib/)?PHPLIB\.php$, => 'libphp-phplayersmenu' ],
    [ qr,(?i)phpSniff\.(class|core)\.php$, => 'libphp-phpsniff' ],
    [ qr,(?i)(class\.)?jabber\.php$, => 'libphp-jabber' ],
    [ qr,(?i)simplepie(\.(php|inc))+$, => 'libphp-simplepie' ],
    [ qr,(?i)jpgraph\.php$, => 'libphp-jpgraph' ],
    [ qr,(?i)fpdf\.php$, => 'php-fpdf' ],
    [ qr,(?i)getid3\.(lib\.)?(\.(php|inc))+$, => 'php-getid3' ],
    [ qr,(?i)streams\.php$, => 'php-gettext' ],
    [ qr,(?i)rss_parse\.(php|inc)$, => 'libphp-magpierss' ],
    [ qr,(?i)unit_tester\.php$, => 'php-simpletest' ],
    [ qr,(?i)Sparkline\.php$, => 'libsparkline-php' ],
# not yet available in unstable:,
#    [ qr,(?i)IXR_Library(\.inc|\.php)+$, => 'libphp-ixr' ],
#    [ qr,(?i)(class\.)?kses\.php$, => 'libphp-kses' ],
);

# A list of known non-free flash executables
our @flash_nonfree = (
    qr<(?i)dewplayer(?:-\w+)?\.swf$>,
    qr<(?i)(?:mp3|flv)player\.swf$>,
# Situation needs to be clarified:
#    qr,(?i)multipleUpload\.swf$,
#    qr,(?i)xspf_jukebox\.swf$,
);

sub run {

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

my $file;
my $source_pkg = "";
my $pkg_section = "";
my $is_python;
my $is_perl;
my $has_binary_perl_file;
my @nonbinary_perl_files_in_lib;

my %linked_against_libvga;

# read data from objdump-info file
foreach my $file (sort keys %{$info->objdump_info}) {
    my $objdump = $info->objdump_info->{$file};
    $file = './' . $file;

    if (defined $objdump->{NEEDED}) {
	my $lib = $objdump->{NEEDED};
	$linked_against_libvga{$file} = 1
	    if $lib =~ m/libvga/;
    }
}

# Get source package name, if possible.
if (defined $info->field('source')) {
    $source_pkg = $info->field('source') || "";
}

# Get section.
if (defined $info->field('section')) {
   $pkg_section = $info->field('section');
}

# find out which files are scripts
my %script = map {$_ => 1} (sort keys %{$info->scripts});

# We only want to warn about these once.
my $warned_debug_name = 0;

my @devhelp;
my @devhelp_links;

# X11 bitmapped font directories under /usr/share/fonts/X11 in which we've
# seen files.
my %x11_font_dirs;

# Read package contents...
foreach my $file (sort keys %{$info->index}) {
    next if $file eq "";
    my $index_info = $info->index->{$file};
    my $owner = $index_info->{owner} . '/' . $index_info->{group};
    my $operm = $index_info->{operm};
    my $link = $index_info->{link};
    if ($index_info->{type} eq 'h') {
	my $link_target_dir = $link;
	$link_target_dir =~ s,[^/]*$,,;

	# It may look weird to sort the file and link target here, but since
	# it's a hard link, both files are equal and either could be
	# legitimately reported first.	tar will generate different tar files
	# depending on the hashing of the directory, and this sort produces
	# stable lintian output despite that.
	#
	# TODO: actually, policy says 'conffile', not '/etc' -> extend!
	tag "package-contains-hardlink", join (' -> ', sort ($file, $link))
	    if $file =~ m,^etc/,
		or $link =~ m,^etc/,
		or $file !~ m,^\Q$link_target_dir\E[^/]*$,;
    }

    my ($year) = ($index_info->{date} =~ /^(\d{4})/);
    if ( $year <= 1984 ) { # value from dak CVS: Dinstall::PastCutOffYear
	tag "package-contains-ancient-file", "$file " . $index_info->{date};
    }

    if (!($index_info->{uid} < 100 || $index_info->{uid} == 65534
	  || ($index_info->{uid} >= 60000 && $index_info->{uid} < 65000))
	|| !($index_info->{gid} < 100 || $index_info->{gid} == 65534
	     || ($index_info->{gid} >= 60000 && $index_info->{gid} < 65000))) {
	tag "wrong-file-owner-uid-or-gid", $file, $index_info->{uid} . '/' . $index_info->{gid};
    }

    # *.devhelp and *.devhelp2 files must be accessible from a directory in
    # the devhelp search path: /usr/share/devhelp/books and
    # /usr/share/gtk-doc/html.  We therefore look for any links in one of
    # those directories to another directory.  The presence of such a link
    # blesses any file below that other directory.
    if (defined $link and $file =~ m,usr/share/(?:devhelp/books|gtk-doc/html)/,) {
	my $blessed = $link;
	if ($blessed !~ m,^/,) {
	    my $base = $file;
	    $base =~ s,/+[^/]+$,,;
	    while ($blessed =~ s,^\.\./,,) {
		$base =~ s,/+[^/]+$,,;
	    }
	    $blessed = "$base/$blessed";
	}
	push (@devhelp_links, $blessed);
    }

    # ---------------- /etc
    if ($file =~ m,^etc/,) {
	if ($file =~ m,^etc/nntpserver, ) {
	    tag "package-uses-obsolete-file", "$file";
	}
	# ---------------- /etc/cron.daily, etc.
	elsif ($file =~ m,^etc/cron\.(?:daily|hourly|monthly|weekly)/[^\.].*\., ) {
	    tag "run-parts-cron-filename-contains-full-stop", "$file";
	}
	# ---------------- /etc/cron.d
	elsif ($file =~ m,^etc/cron\.d/\S, and $operm != 0644) {
	    tag "bad-permissions-for-etc-cron.d-script", sprintf("%s %04o != 0644",$file,$operm);
	}
	# ---------------- /etc/emacs.*
	elsif ($file =~ m,^etc/emacs.*/\S, and $index_info->{type} =~ m,^[-h],
	       and $operm != 0644) {
	    tag "bad-permissions-for-etc-emacs-script", sprintf("%s %04o != 0644",$file,$operm);
	}
	# ---------------- /etc/gconf/schemas
	elsif ($file =~ m,^etc/gconf/schemas/\S,) {
	    tag "package-installs-into-etc-gconf-schemas", "$file";
	}
	# ---------------- /etc/init.d
	elsif ($file =~ m,^etc/init\.d/\S,
	       and $file !~ m,^etc/init\.d/(?:README|skeleton)$,
	       and $operm != 0755
	       and $index_info->{type} =~ m,^[-h],) {
	    tag "non-standard-file-permissions-for-etc-init.d-script",
		sprintf("%s %04o != 0755",$file,$operm);
	}
	#----------------- /etc/ld.so.conf.d
	elsif ($file =~ m,^etc/ld\.so\.conf\.d/(.+)$, and $pkg !~ /^libc/) {
	    tag 'package-modifies-ld.so-search-path', "$file";
	}
	#----------------- /etc/modprobe.d
	elsif ($file =~ m,^etc/modprobe\.d/(.+)$, and $1 !~ m,\.conf$, and $index_info->{type} !~ m/^d/) {
	    tag "non-conf-file-in-modprobe.d", $file;
	}
	#----------------- /etc/pam.conf
	elsif ($file =~ m,^etc/pam.conf, and $pkg ne "libpam-runtime" ) {
	    tag "config-file-reserved", "$file by libpam-runtime";
	}
	# ---------------- /etc/rc.d
	elsif ($type ne 'udeb' and $file =~ m,^etc/rc\.d/\S, and $pkg !~ /^(?:sysvinit|file-rc)$/) {
	    tag "package-installs-into-etc-rc.d", "$file";
	}
	# ---------------- /etc/rc?.d
	elsif ($type ne 'udeb' and $file =~ m,^etc/rc(?:\d|S)\.d/\S, and $pkg !~ /^(?:sysvinit|file-rc)$/) {
	    tag "package-installs-into-etc-rc.d", "$file";
	}
	# ---------------- /etc/rc.boot
	elsif ($file =~ m,^etc/rc\.boot/\S,) {
	    tag "package-installs-into-etc-rc.boot", "$file";
	}
    }
    # ---------------- /usr
    elsif ($file =~ m,^usr/,) {
	# ---------------- /usr/share/doc
	if ($file =~ m,^usr/share/doc/\S,) {
	    if ($type eq 'udeb') {
		tag "udeb-contains-documentation-file", "$file";
	    } else {
		# file not owned by root?
		if ($owner ne 'root/root') {
		    tag "bad-owner-for-doc-file", "$file $owner != root/root";
		}

		# file directly in /usr/share/doc ?
		if ($index_info->{type} =~ m/^[-h]/ and $file =~ m,^usr/share/doc/[^/]+$,) {
		    tag "file-directly-in-usr-share-doc", "$file";
		}

		# executable in /usr/share/doc ?
		if ($index_info->{type} =~ m/^[-h]/ and
		    $file !~ m,^usr/share/doc/(?:[^/]+/)?examples/, and
		    ($operm & 01 or $operm & 010 or $operm & 0100)) {
		    if ($script{$file}) {
			tag "script-in-usr-share-doc", "$file";
		    } else {
			tag "executable-in-usr-share-doc", $file, (sprintf "%04o", $operm);
		    }
		}

		# zero byte file in /usr/share/doc/
		if ($index_info->{size} == 0 and $index_info->{type} =~ m,^-,) {
		    # Exceptions: examples may contain empty files for various
		    # reasons, Doxygen generates empty *.map files, and Python
		    # uses __init__.py to mark module directories.
		    unless ($file =~ m,^usr/share/doc/(?:[^/]+/)?examples/,
			    or $file =~ m,^usr/share/doc/(?:.+/)?html/.*\.map$,
			    or $file =~ m,^usr/share/doc/(?:.+/)?__init__\.py$,) {
			tag "zero-byte-file-in-doc-directory", "$file";
		    }
		}
		# gzipped zero byte files:
		# 276 is 255 bytes (maximal length for a filename) + gzip overhead
		if ($file =~ m,.gz$, and $index_info->{size} <= 276
		    and $index_info->{type} =~ m,^[-h],
		    and $info->file_info->{$file} =~ m/gzip compressed/) {
		    unless (`gzip -dc unpacked/$file`) {
			tag "zero-byte-file-in-doc-directory", "$file";
		    }
		}

		# contains an INSTALL file?
		my $tmp = quotemeta($pkg);
		if ($file =~ m,^usr/share/doc/$tmp/INSTALL(?:\..+)*$,) {
		    tag "package-contains-upstream-install-documentation", "$file";
		}

		# contains a README for another distribution/platform?
		if ($file =~ m,^usr/share/doc/$tmp/readme\.(?:apple|aix|atari|be|beos|bsd|bsdi|
		                cygwin|darwin|irix|gentoo|freebsd|mac|macos|macosx|netbsd|
				openbsd|osf|redhat|sco|sgi|solaris|suse|sun|vms|win32|win9x|
				windows)(?:\.txt)?(?:\.gz)?$,xi){
		    tag "package-contains-readme-for-other-platform-or-distro", "$file";
		}
	    }
	}
	# ---------------- /usr/doc
	elsif ($file =~ m,^usr/doc/\S,) {
	    if ($file =~ m,^usr/doc/examples/\S+, and $index_info->{type} eq 'd') {
		tag "old-style-example-dir", "$file";
	    }
	}
	# ---------------- /usr/X11R6/lib/X11/app-defaults
	elsif ($file =~ m,usr/X11R6/lib/X11/app-defaults,) {
	    tag "old-app-defaults-directory", "$file";
	}

	#----------------- /usr/X11R6/
	elsif ($file =~ m,^usr/X11R6/bin, && $pkg ne 'x11-common') {
	    tag "package-installs-file-to-usr-x11r6-bin", "$file";
	}
	elsif ($file =~ m,^usr/X11R6/lib/X11/fonts,) {
	    tag "package-installs-font-to-usr-x11r6", "$file";
	}
	elsif ($file =~ m,^usr/X11R6/, and
	       $index_info->{type} !~ m,^l,) { #links to FHS locations are allowed
	    tag "package-installs-file-to-usr-x11r6", "$file";
	}

	# ---------------- /usr/lib/debug
	elsif ($file =~ m,^usr/lib/debug/\S,) {
	    unless ($warned_debug_name) {
		tag "debug-package-should-be-named-dbg", "$file"
		    unless ($pkg =~ /-dbg$/);
		$warned_debug_name = 1;
	    }
	}

	# ---------------- /usr/lib/sgml
	elsif ($file =~ m,^usr/lib/sgml/\S,) {
	    tag "file-in-usr-lib-sgml", $file;
	}
	# ---------------- perllocal.pod
	elsif ($file =~ m,^usr/lib/perl.*/perllocal.pod$,) {
	    tag "package-installs-perllocal-pod", "$file";
	}
	# ---------------- .packlist files
	elsif ($file =~ m,^usr/lib/perl.*/.packlist$,) {
	    tag "package-installs-packlist", "$file";
	}
	elsif ($file =~ m,^usr/lib/perl5/.*\.(?:pl|pm)$,) {
	    push @nonbinary_perl_files_in_lib, $file;
	}
	elsif ($file =~ m,^usr/lib/perl5/.*\.(?:bs|so)$,) {
	    $has_binary_perl_file = 1;
	}
	# ---------------- /usr/lib -- needs to go after the other usr/lib/*
	elsif ($file =~ m,^usr/lib/,) {
	    if ($type ne 'udeb' and $file =~ m,\.(?:gif|jpeg|jpg|png|tiff|xpm|xbm)$, and not defined $link) {
		tag "image-file-in-usr-lib", "$file"
	    }
	}
	# ---------------- /usr/local
	elsif ($file =~ m,^usr/local/\S+,) {
	    if ($index_info->{type} =~ m/^d/) {
		tag "dir-in-usr-local", "$file";
	    } else {
		tag "file-in-usr-local", "$file";
	    }
	}
	# ---------------- /usr/share/man and /usr/X11R6/man
	elsif ($file =~ m,^usr/X11R6/man/\S+, or $file =~ m,^usr/share/man/\S+,) {
	    if ($type eq 'udeb') {
		tag "documentation-file", "$file";
	    }
	    if ($index_info->{type} =~ m/^d/) {
		tag "stray-directory-in-manpage-directory", "$file"
		    if ($file !~ m,^usr/(?:X11R6|share)/man/(?:[^/]+/)?(?:man\d/)?$,);
	    } elsif ($index_info->{type} =~ m,^[-h], and
		($operm & 01 or $operm & 010 or $operm & 0100)) {
		tag "executable-manpage", "$file";
	    }
	}
	# ---------------- /usr/share/fonts/X11
	elsif ($file =~ m,^usr/share/fonts/X11/([^/]+)/\S+,) {
	    my ($dir, $filename) = ($1, $2);
	    if ($dir =~ /^(?:PEX|CID|Speedo|cyrillic)$/) {
		tag 'file-in-discouraged-x11-font-directory', $file;
	    } elsif ($dir !~ /^(?:100dpi|75dpi|misc|Type1|encodings|util)$/) {
		tag 'file-in-unknown-x11-font-directory', $file;
	    }
	    if ($dir =~ /^(?:100dpi|75dpi|misc)$/) {
		$x11_font_dirs{$dir}++;
	    }
	}
	# ---------------- /usr/share/info
	elsif ($file =~ m,^usr/share/info\S+,) {
	    if ($type eq 'udeb') {
		tag "documentation-file", "$file";
	    }
	    if ($file =~ m,^usr/share/info/dir(?:\.old)?(?:\.gz)?$,) {
		tag 'package-contains-info-dir-file', $file;
	    }
	}
	# ---------------- /usr/share/linda/overrides
	elsif ($file =~ m,^usr/share/linda/overrides/\S+,) {
	    tag "package-contains-linda-override", $file;
	}
	# ---------------- /usr/share
	elsif ($file =~ m,^usr/share/[^/]+$,) {
	    if ($index_info->{type} =~ m/^[-h]/) {
		tag "file-directly-in-usr-share", "$file";
	    }
	}
        # ---------------- /usr/bin
	elsif ($file =~ m,^usr/bin/,) {
	    if ($index_info->{type} =~ m/^d/ and $file =~ m,^usr/bin/., and $file !~ m,^usr/bin/(?:X11|mh)/,) {
		tag "subdir-in-usr-bin", "$file";
	    }
	}
	# ---------------- /usr subdirs
	elsif ($type ne 'udeb' and $file =~ m,^usr/[^/]+/$,) { # FSSTND dirs
	    if ( $file =~ m,^usr/(?:dict|doc|etc|info|man|adm|preserve)/,) {
		tag "FSSTND-dir-in-usr", "$file";
	    }
	    # FHS dirs
	    elsif ($file !~ m,^usr/(?:X11R6|X386|
				    bin|games|include|
				    lib|lib32|lib64|
				    local|sbin|share|
				    src|spool|tmp)/,x) {
		tag "non-standard-dir-in-usr", "$file";
	    } elsif ($file =~ m,^usr/share/doc,) {
		tag "uses-FHS-doc-dir", "$file";
	    }

	    # unless $file =~ m,^usr/[^/]+-linuxlibc1/,; was tied into print
	    # above...
	    # Make an exception for the altdev dirs, which will go away
	    # at some point and are not worth moving.
	}
	# ---------------- .desktop files
	# People have placed them everywhere, but nowadays the consensus seems
	# to be to stick to the fd.org standard drafts, which says that
	# .desktop files intended for menus should be placed in
	# $XDG_DATA_DIRS/applications.  The default for $XDG_DATA_DIRS is
	# /usr/local/share/:/usr/share/, according to the basedir-spec on
	# fd.org. As distributor, we should only allow /usr/share.
	#
	# KDE hasn't moved its files from /usr/share/applnk, so don't warn
	# about this yet until KDE adopts the new location.
	elsif ($file =~ m,^usr/share/gnome/apps/.*\.desktop$,) {
	    tag "desktop-file-in-wrong-dir", $file;
	}

	# ---------------- png files under /usr/share/apps/*/icons/*
	elsif ($file =~ m,^usr/share/apps/[^/]+/icons/[^/]+/(\d+x\d+)/.*\.png$,) {
	    my ($dsize, $fsize) = ($1);
	    $info->file_info->{$file} =~ m/,\s*(\d+)\s*x\s*(\d+)\s*,/;
	    $fsize = $1.'x'.$2;
	    tag 'icon-size-and-directory-name-mismatch', $file, $fsize
		unless ($dsize eq $fsize);
	}
	# ---------------- non-games-specific data in games subdirectory
	elsif ($file =~ m,^usr/share/games/(?:applications|mime|icons|pixmaps)/,
	       and $index_info->{type} !~ m/^d/) {
	    tag "global-data-in-games-directory", $file;
	}
    }
    # ---------------- /var subdirs
    elsif ($type ne 'udeb' and $file =~ m,^var/[^/]+/$,) { # FSSTND dirs
	if ( $file =~ m,^var/(?:adm|catman|named|nis|preserve)/, ) {
	    tag "FSSTND-dir-in-var", "$file";
	}
	# base-files is special
	elsif ($pkg eq 'base-files' && $file =~ m,^var/(?:backups|local)/,) {
	    # ignore
	}
	# FHS dirs with exception in Debian policy
	elsif ( $file !~ m,^var/(?:account|lib|cache|crash|games|lock|log|opt|run|spool|state|tmp|www|yp)/,) {
	    tag "non-standard-dir-in-var", "$file";
	}
    }
    elsif ($type ne 'udeb' and $file =~ m,^var/lib/games/.,) {
	tag "non-standard-dir-in-var", "$file";
    }
    # ---------------- /var/lock, /var/run
    elsif ($type ne 'udeb' and $file =~ m,^var/lock/.,) {
	tag "dir-or-file-in-var-lock", "$file";
    }
    elsif ($type ne 'udeb' and $file =~ m,^var/run/.,) {
	tag "dir-or-file-in-var-run", "$file";
    }
    # ---------------- /var/www
    # Packages are allowed to create /var/www since it's historically been the
    # default document root, but they shouldn't be installing stuff under that
    # directory.
    elsif ($file =~ m,^var/www/\S+,) {
	tag "dir-or-file-in-var-www", $file;
    }
    # ---------------- /opt
    elsif ($file =~ m,^opt/.,) {
	tag "dir-or-file-in-opt", "$file";
    }
    elsif ($file =~ m,^hurd/.,) {
	next;
    } elsif ($file =~ m,^server/.,) {
	next;
    }
    # ---------------- /tmp, /var/tmp, /usr/tmp
    elsif ($file =~ m,^tmp/., or $file =~ m,^(?:var|usr)/tmp/.,) {
	tag "dir-or-file-in-tmp", "$file";
    }
    # ---------------- /mnt
    elsif ($file =~ m,^mnt/.,) {
	tag "dir-or-file-in-mnt", "$file";
    }
    # ---------------- /bin
    elsif ($file =~ m,^bin/,) {
	if ($index_info->{type} =~ m/^d/ and $file =~ m,^bin/.,) {
	    tag "subdir-in-bin", "$file";
	}
    }
    # ---------------- /srv
    elsif ($file =~ m,^srv/.,) {
	tag "dir-or-file-in-srv", "$file";
    }
    # ---------------- FHS directory?
    elsif ($file =~ m,^[^/]+/$, and $file ne './' and
	   $file !~ m,^(?:bin|boot|dev|etc|home|lib(?:64|32)?|mnt|opt|root|sbin|srv|tmp|usr|var)/,) {
	# Make an exception for the base-files package here and other similar
	# packages because they install a slew of top-level directories for
	# setting up the base system.  (Specifically, /cdrom, /floppy,
	# /initrd, and /proc are not mentioned in the FHS).
	#
	# Also make an exception for /emul, which is used for multiarch
	# support in Debian at the moment.
	tag "non-standard-toplevel-dir", "$file"
	    unless $pkg eq 'base-files'
		or $pkg eq 'hurd'
		or $pkg =~ /^rootskel(?:-bootfloppy)?/
		or $file =~ m,^emul/,;
    }

    # ---------------- compatibility symlinks should not be used
    if ($file =~ m,^usr/(?:spool|tmp)/, or
	$file =~ m,^usr/(?:doc|bin)/X11/, or
	$file =~ m,^var/adm/,) {
	tag "use-of-compat-symlink", "$file";
    }

    # ---------------- .ali files (Ada Library Information)
    if ($file =~ m,^usr/lib/.*\.ali$, && $operm != 0444) {
	tag "bad-permissions-for-ali-file", "$file";
    }

    # ---------------- any files
    if ($index_info->{type} !~ m/^d/) {
	unless ($type eq 'udeb'
		or $file =~ m,^usr/(?:bin|dict|doc|games|
				    include|info|lib(?:32|64)?|
				    man|sbin|share|src|X11R6)/,x
		or $file =~ m,^lib(?:32|64)?/(?:modules/|libc5-compat/)?,
		or $file =~ m,^var/(?:games|lib|www|named)/,
		or $file =~ m,^(?:bin|boot|dev|etc|sbin)/,
		# non-FHS, but still usual
		or $file =~ m,^usr/[^/]+-linux[^/]*/,
		or $file =~ m,^usr/iraf/,
		or $file =~ m,^emul/ia32-linux/(?:lib|usr/lib)/,
		# not allowed, but tested indivudually
		or $file =~ m,^(?:mnt|opt|srv|(?:(?:usr|var)/)?tmp)|var/www/,) {
	    tag "file-in-unusual-dir", "$file";
	}
    }

    # ---------------- .pyc (compiled python files
    if ($file =~ m,^usr/lib/python\d\.\d/.*.pyc$,) {
	tag "package-installs-python-pyc", "$file"
    }

    # ---------------- /usr/lib/site-python
    if ($file =~ m,^usr/lib/site-python/\S,) {
	tag "file-in-usr-lib-site-python", "$file";
    }

    # ---------------- pythonX.Y extensions
    if ($file =~ m,^usr/lib/python\d\.\d/\S,
	and not $file =~ m,^usr/lib/python\d\.\d/(?:site|dist)-packages/,) {
        # check if it's one of the Python proper packages
	unless (defined $is_python) {
	    $is_python = 0;
	    if (defined $info->field('source')) {
		local $_ = $info->field('source');
		$is_python = 1 if /^python(?:\d\.\d)?(?:$|\s)/;
	    }
	}
	tag "third-party-package-in-python-dir", "$file"
	    unless $is_python;
    }
    # ---------------- perl modules
    if ($file =~ m,^usr/(?:share|lib)/perl/\S,) {
       # check if it's the "perl" package itself
       unless (defined $is_perl) {
           $is_perl = 0;
	   if (defined $info->field('source')) {
               local $_ = $info->field('source');;
               $is_perl = 1 if /^perl(?:$|\s)/;
           }
       }
       tag "perl-module-in-core-directory", "$file"
           unless $is_perl;
    }

    # ---------------- license files
    if ($file =~ m,(?:copying|licen[cs]e)(?:\.[^/]+)?$,i
	# Ignore some common extensions for source or compiled extension files.
	# There was at least one file named "license.el".  These are probably
	# license-displaying code, not license files.  Also ignore binaries in
	# /usr/bin and friends.
	#
	# Another exception is made for .html and .php because preserving
	# working links is more important than saving some bytes, and
	# because a package had a HTML form for licenses called like that.
	# Another exception is made for various picture formats since
	# those are likely to just be simply pictures.
	#
	# DTD files are excluded at the request of the Mozilla suite
	# maintainers.	Zope products include license files for runtime
	# display.  underXXXlicense.docbook files are from KDE.
	#
	# Ignore extra license files in examples, since various package
	# building software includes example packages with licenses.
	and not $file =~ m/\.(?:el|c|h|py|cc|pl|pm|hi|p_hi|html|php|rb|xpm|png|jpe?g|gif|svg|dtd)$/
	and not $file =~ m,^usr/share/zope/Products/.*\.(?:dtml|pt|cpt)$,
	and not $file =~ m,/under\S+License\.docbook$,
	and not $file =~ m,^(?:usr/)?s?bin/,
	and not $file =~ m,^usr/share/doc/[^/]+/examples/,
	and not defined $link) {
	tag "extra-license-file", "$file";
    }

    # ---------------- .devhelp2? files
    if ($file =~ m,\.devhelp2?(?:\.gz)?$,
	# If the file is located in a directory not searched by devhelp, we
	# check later to see if it's in a symlinked directory.
	and not $file =~ m,^usr/share/(?:devhelp/books|gtk-doc/html)/,
	and not $file =~ m,^usr/share/doc/[^/]+/examples/,) {
	push (@devhelp, $file);
    }

    # ---------------- weird file names
    if ($file =~ m,\s+\z,) {
	tag "file-name-ends-in-whitespace", "$file";
    }

    # ---------------- misplaced lintian overrides
    my $tmp = quotemeta($pkg);
    if ($file =~ m,^usr/share/doc/$tmp/override\.[lL]intian(?:\.gz)?$, or
	$file =~ m,^usr/share/lintian/overrides/$tmp/.+,) {
	tag "override-file-in-wrong-location", "$file";
    }

    # ---------------- plain files
    if ($index_info->{type} =~ m/^[-h]/) {
	my $wanted_operm;
	# ---------------- backup files and autosave files
	if ($file =~ /~$/ or $file =~ m,\#[^/]+\#$, or $file =~ m,/\.[^/]+\.swp$,) {
	    tag "backup-file-in-package", "$file";
	}
	if ($file =~ m,/\.nfs[^/]+$,) {
	    tag "nfs-temporary-file-in-package", "$file";
	}

	# ---------------- vcs control files
	if ($file =~ m/\.(?:(?:cvs|git|hg)ignore|arch-inventory|hgtags|hg_archival\.txt)$/) {
	    tag "package-contains-vcs-control-file", "$file";
	}

	# ---------------- subversion and svk commit message backups
	if ($file =~ m/svn-commit.*\.tmp$/) {
	    tag "svn-commit-file-in-package", "$file";
	}
	if ($file =~ m/svk-commit.+\.tmp$/) {
	    tag "svk-commit-file-in-package", "$file";
	}

	# ---------------- executables with language extensions
	if ($file =~ m,^(?:usr/)?(?:s?bin|games)/[^/]+\.(?:pl|sh|py|php|rb|tcl|bsh|csh|tcl)$,) {
	    tag "script-with-language-extension", "$file";
	}

	# ---------------- Devel files for Windows
	if ($file =~ m,/.+\.(?:vcproj|sln|dsp|dsw)(?:\.gz)?$,
	    and $file !~ m,^usr/share/doc/,) {
	    tag "windows-devel-file-in-package", "$file";
	}

	# ---------------- Autogenerated databases from other OSes
	if ($file =~ m,/Thumbs\.db(?:\.gz)?$,i) {
	    tag "windows-thumbnail-database-in-package", "$file";
	}
	if ($file =~ m,/\.DS_Store(?:\.gz)?$,) {
	    tag "macos-ds-store-file-in-package", "$file";
	}
	if ($file =~ m,/\._[^_/][^/]*$, and $file !~ m/\.swp$/) {
	    tag "macos-resource-fork-file-in-package", "$file";
	}

	# ---------------- embedded Javascript libraries
	foreach my $jslibrary (@jslibraries) {
	    if ($file =~ m,/$jslibrary->[0], and $pkg !~ m,^$jslibrary->[1]$,) {
		tag "embedded-javascript-library", "$file";
	    }
	}

	# ---------------- embedded Feedparser library
	if ($file =~ m,/feedparser\.py$, and $pkg ne "python-feedparser") {
	    open(FEEDPARSER, '<', "unpacked/$file") or fail("cannot open feedparser.py file: $!");
	    while (<FEEDPARSER>) {
		if (m,Universal feed parser,) {
		    tag "embedded-feedparser-library", "$file";
		    last;
		}
	    }
	    close(FEEDPARSER);
	}

	# ---------------- embedded PEAR modules
	foreach my $pearmodule (@pearmodules) {
	    if ($file =~ m,/$pearmodule->[0], and $pkg ne $pearmodule->[1]) {
		open (PEAR, '<', "unpacked/$file") or fail("cannot open PHP file: $!");
		while (<PEAR>) {
		    if (m,/pear[/.],i) {
			tag "embedded-pear-module", "$file";
			last;
		    }
		}
		close(PEAR);
	    }
	}

	# ---------------- embedded php libraries
	foreach my $phplibrary (@phplibraries) {
	    if ($file =~ m,/$phplibrary->[0], and $pkg ne $phplibrary->[1]) {
		tag "embedded-php-library", "$file";
	    }
	}

	# ---------------- fonts
	if ($file =~ m,/([\w-]+\.(?:[to]tf|pfb))$,i) {
	    my $font = lc $1;
	    $FONT_PACKAGES = Lintian::Data->new('files/fonts', '\s+')
		unless defined($FONT_PACKAGES);
	    if ($FONT_PACKAGES->known($font)) {
		tag 'duplicate-font-file', "$file also in", $FONT_PACKAGES->value($font)
		    if ($pkg ne $FONT_PACKAGES->value($font) and $type ne 'udeb');
	    } elsif ($pkg !~ m/^(?:[ot]tf|t1|xfonts)-/) {
		tag 'font-in-non-font-package', $file;
	    }
	}

	# ---------------- non-free .swf files
	foreach my $flash (@flash_nonfree) {
	    last if ($pkg_section =~ m,^non-free/,);
	    if ($file =~ m,/$flash,) {
		tag "non-free-flash", $file;
	    }
	}

	# ---------------- .gz files
	if ($file =~ m/\.gz$/) {
	    my $info = $info->file_info->{$file} || '';
	    if ($info !~ m/gzip compressed/) {
		tag "gz-file-not-gzip", "$file";
	    }
	}

	# ---------------- general: setuid/setgid files!
	if ($operm & 04000 or $operm & 02000) {
	    my ($setuid, $setgid) = ("","");
	    # get more info:
	    $setuid = $index_info->{owner} if ($operm & 04000);
	    $setgid = $index_info->{group} if ($operm & 02000);

	    # 1st special case: program is using svgalib:
	    if (exists $linked_against_libvga{$file}) {
		# setuid root is ok, so remove it
		if ($setuid eq 'root') {
		    undef $setuid;
		}
	    }

	    # 2nd special case: program is a setgid game
	    if ($file =~ m,usr/lib/games/\S+, or $file =~ m,usr/games/\S+,) {
		# setgid games is ok, so remove it
		if ($setgid eq 'games') {
		    undef $setgid;
		}
	    }

	    # 3rd special case: allow anything with suid in the name
	    if ($pkg =~ m,-suid,) {
		undef $setuid;
	    }

	    # Check for setuid and setgid that isn't expected.
	    if ($setuid and $setgid) {
		tag "setuid-gid-binary", $file, sprintf("%04o %s",$operm,$owner);
	    } elsif ($setuid) {
		tag "setuid-binary", $file, sprintf("%04o %s",$operm,$owner);
	    } elsif ($setgid) {
		tag "setgid-binary", $file, sprintf("%04o %s",$operm,$owner);
	    }

	    # Check for permission problems other than the setuid status.
	    if (($operm & 0444) != 0444) {
		tag "executable-is-not-world-readable", $file,
		    sprintf("%04o",$operm);
	    } elsif ($operm != 04755 && $operm != 02755 && $operm != 06755 && $operm != 04754) {
		tag "non-standard-setuid-executable-perm", $file,
		    sprintf("%04o",$operm);
	    }
	}
	# ---------------- general: executable files
	elsif ($operm & 01 or $operm & 010 or $operm & 0100) {
	    # executable
	    if ($owner =~ m,root/games,) {
		if ($operm != 2755) {
		    tag "non-standard-game-executable-perm", $file,
			sprintf("%04o != 2755",$operm);
	    	}
	    } else {
		if (($operm & 0444) != 0444) {
		    tag "executable-is-not-world-readable", $file,
			sprintf("%04o != 0755",$operm);
		} elsif ($operm != 0755) {
		    tag "non-standard-executable-perm", $file,
			sprintf("%04o != 0755",$operm);
		}
	    }
	}
	# ---------------- general: normal (non-executable) files
	else {
	    # not executable
	    # special case first: game data
	    if ($operm == 0664 and $owner =~ m,root/games, and
		$file =~ m,var/(lib/)?games/\S+,) {
		# everything is ok
	    } elsif ($operm == 0444 and $file =~ m,usr/lib/.*\.ali$,) {
		# Ada library information files should be read-only
		# since GNAT behaviour depends on that
		# everything is ok
	    } elsif ($operm == 0600 and $file =~ m,etc/backup.d/,) {
		# backupninja expects configurations files to be 0600
	    } elsif ($operm != 0644) {
		tag "non-standard-file-perm", $file,
		    sprintf("%04o != 0644",$operm);
	    }
	}
    }
    # ---------------- directories
    elsif ($index_info->{type} =~ m/^d/) {
	# special cases first:
        # game directory with setgid bit
	if ($file =~ m,var/(?:lib/)?games/\S+, and $operm == 02775
            and $owner eq 'root/games') {
            # do nothing, this is allowed, but not mandatory
        }
	elsif (($file eq 'tmp/' or $file eq 'var/tmp/'
		or $file eq 'var/lock/')
	       and $operm == 01777 and $owner eq 'root/root') {
	    # actually shipping files here is warned about elsewhere
	}
	elsif ($file eq 'usr/src/' and $operm == 02775
	       and $owner eq 'root/src') {
	    # /usr/src as created by base-files is a special exception
	}
	elsif ($file eq 'var/local/' and $operm == 02775
	       and $owner eq 'root/staff') {
	    # actually shipping files here is warned about elsewhere
	}
	# otherwise, complain if it's not 0755.
	elsif ($operm != 0755) {
	    tag "non-standard-dir-perm", $file,
		sprintf("%04o != 0755", $operm);
	}
	if ($file =~ m,/CVS/?$,) {
	    tag "package-contains-vcs-control-dir", "$file";
	}
	if ($file =~ m,/\.(?:svn|bzr|git|hg)/?$,) {
	    tag "package-contains-vcs-control-dir", "$file";
	}
	if (($file =~ m,/\.arch-ids/?$,)
	    || ($file =~ m,/\{arch\}/?$,)) {
	    tag "package-contains-vcs-control-dir", "$file";
	}
	if ($file =~ m,/\.(?:be|ditrack)/?$,) {
	    tag "package-contains-bts-control-dir", "$file";
	}
	if ($file =~ m,/.xvpics/?$,) {
	    tag "package-contains-xvpics-dir", "$file";
	}
	if ($file =~ m,usr/share/doc/[^/]+/examples/examples/?$,) {
	    tag "nested-examples-directory", "$file";
	}
    }
    # ---------------- symbolic links
    elsif ($index_info->{type} =~ m/^l/) {
	# link

	my $mylink = $link;
	if ($mylink =~ s,//+,/,g) {
	    tag "symlink-has-double-slash", "$file $link";
	}
	if ($mylink =~ s,(.)/$,$1,) {
	    tag "symlink-ends-with-slash", "$file $link";
	}

	# determine top-level directory of file
	$file =~ m,^/?([^/]*),;
	my $filetop = $1;

	if ($mylink =~ m,^/([^/]*),) {
	    # absolute link, including link to /

	    # determine top-level directory of link
	    $mylink =~ m,^/([^/]*),;
	    my $linktop = $1;

	    if ($type ne 'udeb' and $filetop eq $linktop) {
		# absolute links within one toplevel directory are _not_ ok!
		tag "symlink-should-be-relative", "$file $link";
	    }

	    # Any other case is already definitely non-recursive
	    tag "symlink-is-self-recursive", "$file $link"
	    	if $mylink eq '/';

	} else {
	    # relative link, we can assume from here that the link starts nor
	    # ends with /

	    my @filecomponents = split('/', $file);
	    # chop off the name of the symlink
	    pop @filecomponents;

	    my @linkcomponents = split('/', $mylink);

	    # handle `../' at beginning of $link
	    my $lastpop = undef;
	    my $linkcomponent = undef;
	    while ($linkcomponent = shift @linkcomponents) {
		if ($linkcomponent eq '.') {
		    tag "symlink-contains-spurious-segments", "$file $link"
		    	unless $mylink eq '.';
		    next;
		}
		last if $linkcomponent ne '..';
		if (@filecomponents) {
		    $lastpop = pop @filecomponents;
		} else {
		    tag "symlink-has-too-many-up-segments", "$file $link";
		    goto NEXT_LINK;
		}
	    }

	    if (!defined $linkcomponent) {
		# After stripping all starting .. components, nothing left
		tag "symlink-is-self-recursive", "$file $link";
	    }

	    # does the link go up and then down into the same directory?
	    # (lastpop indicates there was a backref at all, no linkcomponent
	    # means the symlink doesn't get up anymore)
	    if (defined $lastpop && defined $linkcomponent &&
		$linkcomponent eq $lastpop) {
		tag "lengthy-symlink", "$file $link";
	    }

	    if ($#filecomponents == -1) {
		# we've reached the root directory
		if (($type ne 'udeb') 
		    && (!defined $linkcomponent)
		    || ($filetop ne $linkcomponent)) {
		    # relative link into other toplevel directory.
		    # this hits a relative symbolic link in the root too.
		    tag "symlink-should-be-absolute", "$file $link";
		}
	    }

	    # check additional segments for mistakes like `foo/../bar/'
	    foreach (@linkcomponents) {
		if ($_ eq '..' || $_ eq '.') {
		    tag "symlink-contains-spurious-segments", "$file $link";
		    last;
		}
	    }
	}
    NEXT_LINK:

	if ($link =~ m,\.(gz|z|Z|bz|bz2|tgz|zip)\s*$,) {
	    # symlink is pointing to a compressed file

	    # symlink has correct extension?
	    unless ($file =~ m,\.$1\s*$,) {
		tag "compressed-symlink-with-wrong-ext", "$file $link";
	    }
	}
    }
    # ---------------- special files
    else {
	# special file
	tag "special-file", $file, sprintf("%04o",$operm);
    }
}

# Check for section games but nothing in /usr/games.  Check for any binary to
# save ourselves from game-data false positives:
my $games = dir_counts($info, "usr/games/");
my $other = dir_counts($info, "bin/") + dir_counts($info, "usr/bin/");
if ($pkg_section =~ m,games$, and $games == 0 and $other > 0) {
    tag "package-section-games-but-contains-no-game";
}
if ($pkg_section =~ m,games$, and $games > 0 and $other > 0) {
    tag "package-section-games-but-has-usr-bin";
}
if ($pkg_section !~ m,games$, and $games > 0 and $other == 0) {
    tag 'games-package-should-be-section-games';
}

# Warn about empty directories, but ignore empty directories in /var (packages
# create directories to hold dynamically created data) or /etc (configuration
# files generated by maintainer scripts).  Also skip base-files, which is a
# very special case.
#
# Empty Perl directories are an ExtUtils::MakeMaker artifact that will be
# fixed in Perl 5.10, and people can cause more problems by trying to fix it,
# so just ignore them.
#
# python-support needs a directory for each package even it might be empty
foreach my $dir (sort keys %{$info->index}) {
    next if $dir eq "" or $info->index->{$dir}->{type} ne 'd';
    next if ($dir =~ m{^var/} or $dir =~ m{^etc/});
    next if $pkg eq 'base-files';
    if (dir_counts($info, $dir) == 0) {
	if ($dir ne 'usr/lib/perl5/'
	    and $dir ne 'usr/share/perl5/'
	    and $dir !~ m;^usr/share/python-support/;) {
	    tag "package-contains-empty-directory", $dir;
	}
    }
}

if (!$has_binary_perl_file && @nonbinary_perl_files_in_lib) {
    foreach my $file (@nonbinary_perl_files_in_lib) {
	tag "package-installs-nonbinary-perl-in-usr-lib-perl5", "$file";
    }
}

# Check for .devhelp2? files that aren't symlinked into paths searched by
# devhelp.
for my $file (@devhelp) {
    my $found = 0;
    for my $link (@devhelp_links) {
	if ($file =~ m,^\Q$link,) {
	    $found = 1;
	    last;
	}
    }
    tag 'package-contains-devhelp-file-without-symlink', $file unless $found;
}

# Check for including multiple different DPIs of fonts in the same X11 bitmap
# font package.
if ($x11_font_dirs{'100dpi'} and $x11_font_dirs{'75dpi'}) {
    tag 'package-contains-multiple-dpi-fonts';
}
if ($x11_font_dirs{misc} and keys (%x11_font_dirs) > 1) {
    tag 'package-mixes-misc-and-dpi-fonts';
}

}

sub dir_counts {
    my ($info, $dir) = @_;

    if (defined $info->index->{$dir}) {
	return $info->index->{$dir}->{count} || 0;
    } else {
	return 0;
    }
}

1;

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