summaryrefslogtreecommitdiffstats
path: root/lib/Lintian/Check/AppstreamMetadata.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Lintian/Check/AppstreamMetadata.pm')
-rw-r--r--lib/Lintian/Check/AppstreamMetadata.pm269
1 files changed, 269 insertions, 0 deletions
diff --git a/lib/Lintian/Check/AppstreamMetadata.pm b/lib/Lintian/Check/AppstreamMetadata.pm
new file mode 100644
index 0000000..97a57d4
--- /dev/null
+++ b/lib/Lintian/Check/AppstreamMetadata.pm
@@ -0,0 +1,269 @@
+# appstream-metadata -- lintian check script -*- perl -*-
+
+# Copyright (C) 2016 Petter Reinholdtsen
+# Copyright (C) 2017-2018 Chris Lamb <lamby@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, 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::Check::AppstreamMetadata;
+
+# For .desktop files, the lintian check would be really easy: Check if
+# .desktop file is there, check if matching file exists in
+# /usr/share/metainfo, if not throw a warning. Maybe while we're at it
+# also check for legacy locations (stuff in /usr/share/appdata) and
+# legacy data (metainfo files starting with `<application>`).
+#
+# For modaliases, maybe udev rules could give some hints.
+# Check modalias values to ensure hex numbers are using capital A-F.
+
+use v5.20;
+use warnings;
+use utf8;
+use autodie qw(open);
+
+use File::Basename qw(basename);
+use Syntax::Keyword::Try;
+use XML::LibXML;
+
+use Moo;
+use namespace::clean;
+
+with 'Lintian::Check';
+
+sub installable {
+ my ($self) = @_;
+
+ my $pkg = $self->processable->name;
+ my $type = $self->processable->type;
+ my $processable = $self->processable;
+ my $group = $self->group;
+
+ my (%desktopfiles, %metainfo, @udevrules);
+ my $found_modalias = 0;
+ my $modaliases = [];
+ if (
+ defined(
+ my $dir
+ = $processable->installed->resolve_path(
+ 'usr/share/applications/')
+ )
+ ) {
+ for my $item ($dir->descendants) {
+ $desktopfiles{$item} = 1 if ($item->is_file);
+ }
+ }
+ if (
+ defined(
+ my $dir
+ = $processable->installed->resolve_path('usr/share/metainfo/')
+ )
+ ) {
+ for my $item ($dir->children) {
+ if ($item->is_file) {
+ $metainfo{$item} = 1;
+ $found_modalias|= $self->check_modalias($item, $modaliases);
+ }
+ }
+ }
+ if (
+ defined(
+ my $dir
+ = $processable->installed->resolve_path('usr/share/appdata/')
+ )
+ ) {
+ for my $item ($dir->descendants) {
+ if ($item->is_file) {
+
+ $self->pointed_hint('appstream-metadata-in-legacy-location',
+ $item->pointer);
+ $found_modalias|= $self->check_modalias($item, $modaliases);
+ }
+ }
+ }
+ foreach my $lib_dir (qw(usr/lib lib)) {
+ if (
+ defined(
+ my $dir = $processable->installed->resolve_path(
+ "$lib_dir/udev/rules.d/")
+ )
+ ) {
+ for my $item ($dir->descendants) {
+ push(@udevrules, $item) if ($item->is_file);
+ }
+ }
+ }
+
+ for my $udevrule (@udevrules) {
+ if ($self->check_udev_rules($udevrule, $modaliases)
+ && !$found_modalias) {
+
+ $self->hint('appstream-metadata-missing-modalias-provide',
+ $udevrule);
+ }
+ }
+ return;
+}
+
+sub check_modalias {
+ my ($self, $item, $modaliases) = @_;
+
+ if (!$item->is_open_ok) {
+ # FIXME report this as an error
+ return 0;
+ }
+
+ my $parser = XML::LibXML->new;
+ $parser->set_option('no_network', 1);
+
+ my $doc;
+ try {
+ $doc = $parser->parse_file($item->unpacked_path);
+
+ } catch {
+
+ $self->pointed_hint('appstream-metadata-invalid',$item->pointer);
+
+ return 0;
+ }
+
+ return 0
+ unless $doc;
+
+ if ($doc->findnodes('/application')) {
+
+ $self->pointed_hint('appstream-metadata-legacy-format',$item->pointer);
+ return 0;
+ }
+
+ my @provides = $doc->findnodes('/component/provides');
+ return 0
+ unless @provides;
+
+ # take first one
+ my $first = $provides[0];
+ return 0
+ unless $first;
+
+ my @nodes = $first->getChildrenByTagName('modalias');
+ return 0
+ unless @nodes;
+
+ for my $node (@nodes) {
+
+ my $alias = $node->firstChild->data;
+ next
+ unless $alias;
+
+ push(@{$modaliases}, $alias);
+
+ $self->pointed_hint('appstream-metadata-malformed-modalias-provide',
+ $item->pointer,
+ "include non-valid hex digit in USB matching rule '$alias'")
+ if $alias =~ /^usb:v[0-9a-f]{4}p[0-9a-f]{4}d/i
+ && $alias !~ /^usb:v[0-9A-F]{4}p[0-9A-F]{4}d/;
+ }
+
+ return 1;
+}
+
+sub provides_user_device {
+ my ($self, $item, $position, $rule, $data) = @_;
+
+ my $retval = 0;
+
+ if ( $rule =~ /plugdev/
+ || $rule =~ /uaccess/
+ || $rule =~ /MODE=\"0666\"/) {
+
+ $retval = 1;
+ }
+
+ if ($rule =~ m/SUBSYSTEM=="usb"/) {
+ my ($vmatch, $pmatch);
+ if ($rule =~ m/ATTR\{idVendor\}=="([0-9a-fA-F]{4})"/) {
+ $vmatch = 'v' . uc($1);
+ }
+
+ if ($rule =~ m/ATTR\{idProduct\}=="([0-9a-fA-F]{4})"/) {
+ $pmatch = 'p' . uc($1);
+ }
+
+ if (defined $vmatch && defined $pmatch) {
+ my $match = "usb:${vmatch}${pmatch}d";
+ my $foundmatch;
+ for my $aliasmatch (@{$data}) {
+ if (0 == index($aliasmatch, $match)) {
+ $foundmatch = 1;
+ }
+ }
+
+ $self->pointed_hint(
+ 'appstream-metadata-missing-modalias-provide',
+ $item->pointer($position),
+ "match rule $match*"
+ ) unless $foundmatch;
+ }
+ }
+
+ return $retval;
+}
+
+sub check_udev_rules {
+ my ($self, $item, $data) = @_;
+
+ open(my $fd, '<', $item->unpacked_path);
+
+ my $cont;
+ my $retval = 0;
+
+ my $position = 0;
+ while (my $line = <$fd>) {
+
+ chomp $line;
+
+ if (defined $cont) {
+ $line = $cont . $line;
+ $cont = undef;
+ }
+
+ if ($line =~ /^(.*)\\$/) {
+ $cont = $1;
+ next;
+ }
+
+ # skip comments
+ next
+ if $line =~ /^#.*/;
+
+ $retval |= $self->provides_user_device($item, $position, $line, $data);
+
+ } continue {
+ ++$position;
+ }
+
+ close $fd;
+
+ return $retval;
+}
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et