Bug#433411: lintian: New checks for FreeDesktop's desktop files
Package: lintian
Version: 1.23.32
Priority: wishlist
Tags: patch
Following the recent discussion at debian-devel related to .desktop files
I've noticed that we lack some lintian checks for common issues in desktop
files (mispelled categories, wrong key value pairs, etc...)
Even though we don't have a policy for Desktop files, since most Desktop
environments (KDE, GNOME) are using them it might be sane to have a check for
desktop files just like we have for menu files.
I've written a first attempt at such check, heavily based on the current
menu-format method and reusing some of its code. It is capable of detecting
wrong Categories and invalid Type values and should be useful to detect
.desktop files which need to be fixed.
Notice that it could be improved to do much more tests, I'm not sure I'll
handle them myself but maybe this will help others scratch their itch..
Please consider its inclusion into the lintian lab.
Thanks,
Javier
# desktop format -- lintian check script -*- perl -*-
# Copyright (C) 2007 by Javier Fernandez-Sanguino
# based on the menu lintian check written by
# Copyright (C) 1998 by Joey Hess
#
# 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.
#
#
# This lintian check aims at implement some standard check for Desktop files
# which are currently not covered by Debian Policy (only menu entries are)
#
# This is a barebones module, it should be extended to check for some more things
# from FreeDesktop's standard:
#
# - Look for Deprecated Items
# - Check field codes in the Exec key
# - Check fields with locales
# - Check Group headers
# - ....
#
package Lintian::desktop_format;
use strict;
use Tags;
use Util;
use File::Basename;
# Authorative source of desktop keys:
# http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.0.html
#
# This is a list of all keys that should be in every desktop entry.
my @req_keys=qw(Type Name);
# This is a list of all known keys.
my @known_keys=qw(
Type
Name
Version
GenericName
NoDisplay
Comment
Icon
Hidden
OnlyShowIn
NotShowIn
TryExec
Exec
Path
Terminal
MimeType
Categories
StartupNotify
StartupWMClass
URL
);
my @deprecated_keys=qw(
Encoding
MiniIcon
TerminalOptions
Protocols
Extensions
BinaryPattern
MapNotify
SwallowTitle
SwallowExec
SortOrder
FilePattern
);
# Authorative source of desktop categories
# http://standards.freedesktop.org/menu-spec/menu-spec-1.0.html#category-registry
# This is a list of all valid section on the root menu.
my @root_categories=qw(AudioVideo Audio Video Development Education Game Graphics Network Office Settings System Utility);
# This is a list of all additional categories
my @categories=qw(
Building
Debugger
IDE
GUIDesigner
Profiling
RevisionControl
Translation
Calendar
ContactManagement
Database
Dictionary
Chart
Email
Finance
FlowChart
PDA
ProjectManagement
Presentation
Spreadsheet
WordProcessor
2DGraphics
VectorGraphics
RasterGraphics
3DGraphics
Scanning
OCR
Photography
Publishing
Viewer
TextTools
DesktopSettings
HardwareSettings
Printing
PackageManager
Dialup
InstantMessaging
Chat
IRCClient
FileTransfer
HamRadio
News
P2P
RemoteAccess
Telephony
TelephonyTools
VideoConference
WebBrowser
WebDevelopment
Midi
Mixer
Sequencer
Tuner
TV
AudioVideoEditing
Player
Recorder
DiscBurning
ActionGame
AdventureGame
ArcadeGame
BoardGame
BlocksGame
CardGame
KidsGame
LogicGame
RolePlaying
Simulation
SportsGame
StrategyGame
Art
Construction
Music
Languages
Science
ArtificialIntelligence
Astronomy
Biology
Chemistry
ComputerScience
DataVisualization
Economy
Electricity
Geography
Geology
Geoscience
History
ImageProcessing
Literature
Math
NumericalAnalysis
MedicalSoftware
Physics
Robotics
Sports
ParallelComputing
Amusement
Archiving
Compression
Electronics
Emulator
Engineering
FileTools
FileManager
TerminalEmulator
Filesystem
Monitor
Security
Accessibility
Calculator
Clock
TextEditor
Documentation
Core
KDE
GNOME
GTK
Qt
Motif
Java
ConsoleOnly
);
# Path in which to search for binaries referenced in menu entries.
my @path = qw(/usr/local/bin/ /usr/bin/ /bin/ /usr/X11R6/bin/ /usr/games/);
my %known_keys_hash;
my %deprecated_keys_hash;
my %root_categories_hash;
my %categories_hash;
my %all_files = ();
my %all_links = ();
# -----------------------------------
sub run {
my $pkg = shift;
my $type = shift;
# Things worth hashing.
foreach my $tag (@known_keys) {
$known_keys_hash{$tag}=1;
}
foreach my $tag (@deprecated_keys) {
$deprecated_keys_hash{$tag}=1;
}
foreach my $section (@root_categories) {
$root_categories_hash{$section}=1;
}
foreach my $section (@categories) {
$categories_hash{$section}=1;
}
# read package contents
open(IN,"index") or fail("cannot open index file index: $!");
while (<IN>) {
chomp;
my ($perm,$owner,$size,$date,$time,$file) = split(' ', $_, 6);
$file =~ s,^(\./),,;
add_file_link_info ($file);
$file =~ s/ link to .*//;
$file =~ s/ -> .*//;
my $operm = perm2oct($perm);
if ($perm =~ m,^-,o) { # file checks
# desktop file?
if ($file =~ m/\.desktop$/o) { # correct permissions?
if ($perm =~ m,x,o) {
tag "executable-desktop-file", sprintf("$file %04o",$operm);
}
# TODO: This should provide the
VerifyFile($file, $file);
}
}
}
close IN;
}
# -----------------------------------
# Pass this a line of a menu file, it sanitizes it and
# verifies that it is correct.
sub VerifyFile {
my ( $desktopfile, $fullname ) = @_;
my %vals;
open (DESKTOP, "unpacked/$fullname") or fail("cannot open desktop file $fullname: $!");
my $linecount = 0;
while ( my $line = <DESKTOP> ) {
chomp $line;
$linecount++;
next if ( $line =~ m/^\s*\#/ or $line = ~ m/^\s*$/);
# Tag = Value
# TODO: We do not check for properly formatted localised values for keys
# but might be worth checking if they are properly formatted (not their value)
if ( $line =~ /^(.*?)\s*=\s*(.*?)$/ ) {
my $tag = $1;
my $value = $2;
if (exists $vals{$tag}) {
tag "duplicated-key-in-desktop-entry", "$fullname $1:$linecount";
}
$vals{$tag} = $value;
}
}
close DESKTOP;
# Now validate the data in the desktop file.
# Test for important keys.
foreach my $tag (@req_keys) {
unless ( exists($vals{$tag}) && defined($vals{$tag}) ) {
tag "desktop-entry-missing-required-key", "$tag $fullname:$linecount";
}
}
# Make sure all keys are known and no deprecated keys are being used
foreach my $tag (keys %vals) {
if ( $deprecated_keys_hash{$tag} ) {
tag "desktop-entry-contains-deprecated-key", "$tag $fullname:$linecount";
} elsif (! $known_keys_hash{$tag} && $tag !~ /^X-/ ) {
# TODO: Will produce false positives for keys reserved by KDE
# which do not use X-KDE (see Standard)
tag "desktop-entry-contains-unknown-key", "$tag $fullname:$linecount";
}
}
# TODO: Check for deprecated keys: Encoding, MiniIcon, TerminalOptions, Protocols,
# Extensions, BinaryPattern, MapNotify, SwallowTitle, SwallowExec, SortOrder
# FilePattern,
if (exists($vals{'Exec'})) {
VerifyExec( $fullname, $linecount, $vals{'Exec'});
}
if (exists($vals{'Icon'})) {
VerifyIcon( $fullname, $linecount, $vals{'Icon'}, 32);
}
# Check the category tag
if (exists($vals{'Categories'})) {
# Is the main category there?
my $ccats = 0;
foreach my $cat (keys %root_categories_hash) {
$ccats++ if $vals{'Categories'} =~ /$cat;?/;
}
if ( $ccats == 0 ) {
tag "desktop-entry-lacks-main-category", "$fullname:$linecount";
} elsif ( $ccats > 1 ) {
tag "desktop-entry-multiple-main-category", "$fullname:$linecount";
}
foreach my $cat (split(';',$vals{'Categories'})) {
if (not $categories_hash{$cat}) {
tag "desktop-entry-invalid-category", "$cat $fullname:$linecount";
}
}
}
# TODO:
# - Check if the values correspond to the allowed type for a given key (boolean,
# integer...)
}
sub VerifyCmd {
# Verify if commands are installed with the package.
# TODO: Should check quoting and the check special field codes in Exec
my ( $fullname, $linecount, $exec) = @_;
# Read the file index:
my %file_index;
open(FILE_INDEX,"index") or fail("cannot open index file index: $!");
while(<FILE_INDEX>) {
$file_index{(split /\s+/, $_)[5]} = 1;
}
close FILE_INDEX;
# This routine handles su wrappers. The option parsing here is ugly and dead-simple,
# but it's hopefully good enough for what will show up in desktop files.
# su-to-root and sux require -c options, kdesu optionally allows one, and
# gksu has the command at the end of its arguments.
my @com = split(' ',$exec);
my $cmd;
if ($com[0] eq "/usr/sbin/su-to-root") {
tag "su-to-root-with-usr-sbin", "$fullname:$linecount";
} elsif ($com[0] =~ m,^(?:/usr/bin/)?(su-to-root|gksu|kdesu|sux)$,) {
my $wrapper = $1;
shift @com;
while (@com) {
unless ($com[0]) {
shift @com;
next;
}
if ($com[0] eq '-c') {
$cmd = $com[1];
last;
} elsif ($com[0] =~ /^-[Dfmupi]|^--(user|description|message)/) {
shift @com;
shift @com;
} elsif ($com[0] =~ /^-/) {
shift @com;
} else {
last;
}
}
if (!$cmd && $wrapper =~ /^(gk|kde)su$/) {
$cmd = $com[0];
} elsif (!$cmd) {
tag "su-wrapper-without--c", "$fullname:$linecount $wrapper";
}
} else {
$cmd = $com[0];
}
tag "desktop-command-not-in-package", "$fullname:$linecount $cmd"
if ($cmd
&& !($file_index{".$cmd"} || grep {$file_index{".".$_.$cmd}} @path));
}
# TODO: Shouldn't this allow PNG files too?
sub VerifyIcon {
my ( $fullname, $linecount, $icon, $size) = @_;
local *IN;
if ($icon eq 'none') {
tag "desktop-entry-uses-icon-none", "$fullname:$linecount";
return;
}
if (not ($icon =~ m/\.xpm$/i)) {
tag "desktop-icon-not-in-xpm-format", "$icon";
return;
}
# Try the explicit location, and if that fails, try the standard path.
my $iconfile = "unpacked/$icon";
if (! -f $iconfile) {
$iconfile = "unpacked/usr/share/pixmaps/$icon";
}
if (! open IN, $iconfile) {
tag "desktop-icon-missing", "$icon";
return;
}
my $parse = "XPM header";
my $line;
do { defined ($line = <IN>) or goto parse_error; }
until ($line =~ /\/\*\s*XPM\s*\*\//);
$parse = "size line";
do { defined ($line = <IN>) or goto parse_error; }
until ($line =~ /"\s*([0-9]+)\s*([0-9]+)\s*([0-9]+)\s*([0-9]+)\s*"/);
my $width = $1 + 0;
my $height = $2 + 0;
my $numcolours = $3 + 0;
my $cpp = $4 + 0;
if ($width > $size || $height > $size) {
tag "desktop-icon-too-big", "$icon: ${width}x${height} > ${size}x${size}";
}
close IN or die;
return;
parse_error:
close IN or die;
tag "desktop-icon-cannot-be-parsed", "$icon: looking for $parse";
return;
}
1;
# -----------------------------------
# Add file and link to %all_files and %all_links. Note that both files and
# links have to include a leading /.
sub add_file_link_info {
my $file = shift;
my $link = undef;
$file = "/" . $file if (not $file =~ m%^/%); # make file absolute
$file =~ s%/+%/%g; # remove duplicated `/'
($file, $link) = split(/ -> /, $file);
$all_files{$file} = 1;
if (defined $link) {
if (not $link =~ m,^/,) { # not absolute link
$link = "/" . $link; # make sure link starts with '/'
$link =~ s,/+\./+,/,g; # remove all /./ parts
my $dcount = 1;
while ($link =~ s,^/+\.\./+,/,) { #\ count & remove
$dcount++; #/ any leading /../ parts
}
my $f = $file;
while ($dcount--) { #\ remove last $dcount
$f =~ s,/[^/]*$,,; #/ path components from $file
}
$link = $f . $link; # now we should have absolute link
}
$all_links{$file} = $link unless ($link eq $file);
}
}
1;
# vim: syntax=perl ts=8 sw=4
Check-Script: desktop-format
Author: Javier Fernandez-Sanguino Pen~a <jfs@debian.rog>
Abbrev: dkf
Standards-Version: 3.6.1
Type: binary
Unpack-Level: 1
Info: This script validates the format of desktop files.
Tag: unparsable-desktop-entry
Type: error
Info: An entry of the desktop file cannot be parsed as a series of key=value
pairs.
Tag: duplicated-key-in-desktop-entry
Type: warning
Info: The desktop entry contains two instances of the same key. This is just a
waste of space, as only one will be used.
Tag: desktop-entry-missing-required-key
Type: error
Info: The desktop entry has a line that is missing a required key.
See: http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.0.html#recognized-keys
Tag: desktop-entry-contains-unknown-key
Type: warning
Info: The desktop entry has a line that has a key in it that is not one
of the standard keys defined in the FreeDesktop specification and this
key does not conform to the preferred method for introducing changes
(via the 'X-' prefix). It's more likely the key's name is misspelled.
See: http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.0.html#recognized-keys
Tag: desktop-entry-contains-deprecated-key
Type: warning
Info: The desktop entry has a line that has a key in it that has been
deprecated in the FreeDesktop specification.
See: http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.0.html#deprecated-items
Tag: desktop-icon-not-in-xpm-format
Type: error
Info: Icons in the Debian desktop system should be in XPM format.
Ref: desktop manual 3.7
Tag: desktop-icon-missing
Type: warning
Info: This icon file couldn't be found. If the path to the icon in the
desktop file is an absolute path, make sure that icon exists at that path in
the package. If the path is relative or a simple filename, make sure the
icon is installed in <tt>/usr/share/pixmaps</tt>, the default location.
.
If the icon is in a package this package depends on, add a lintian
override for this warning. lintian cannot check icons in other packages.
Ref: desktop manual 3.7
Tag: desktop-icon-too-big
Type: error
Info: Icons in the Debian desktop system should be at most 32x32 pixels
(icon16x16 icons should of course be at most 16x16 pixels)
Ref: desktop manual 3.7
Tag: desktop-icon-cannot-be-parsed
Type: warning
Info: The icon file could not be parsed. Perhaps this means a bad XPM file,
or perhaps it means the lintian parsing needs to be improved. If the
window managers and standard tools accept the file then probably it's the
latter; please file a bug on lintian then.
Tag: desktop-command-not-in-package
Type: warning
Info: The desktop entry specifies a command which is not available in the package.
In most cases this is a typo or after you moved a binary around, but forgot
to update the desktop file.
Reply to: