diff options
author | Guillem Jover <guillem@debian.org> | 2013-07-18 18:24:12 +0200 |
---|---|---|
committer | Guillem Jover <guillem@debian.org> | 2013-07-26 23:53:33 +0200 |
commit | a2d801487228e2616c19461fb101190b0ccb7bd7 (patch) | |
tree | 2bdf2e1c361ba3bae4a9b83f8dc1df03a2a053c6 /scripts/Dpkg/Control | |
parent | a3f98a7e4789d00a71817e33b3571c290b1abb5a (diff) | |
download | dpkg-a2d801487228e2616c19461fb101190b0ccb7bd7.tar.gz |
Dpkg::Control::Fields: Detangle Dpkg::Vendor
Move the bulk of the code into two new vendor-neutral Core sub-modules
Dpkg::Control::FieldsCore and Dpkg::Control::HashCore, that get
imported by Dpkg::Control::Fields and Dpkg::Control::Hash respectively;
so that modules like Dpkg::Control::HashCore can make use of
Dpkg::Control::FieldsCore w/o getting into a circular import due
to Dpkg::Vendor previously using Dpkg::Control::Hash.
The old dependency graph would have been:
Control::Hash → Control::Fields
↑ ↓
Vendor
The new one would be:
Control::Hash → Control::Fields
↓ ↓ ↓
↓ Vendor ↓
↓ ↓ ↓
Control::HashCore → Control::FieldsCore
Diffstat (limited to 'scripts/Dpkg/Control')
-rw-r--r-- | scripts/Dpkg/Control/Fields.pm | 564 | ||||
-rw-r--r-- | scripts/Dpkg/Control/FieldsCore.pm | 599 | ||||
-rw-r--r-- | scripts/Dpkg/Control/Hash.pm | 471 | ||||
-rw-r--r-- | scripts/Dpkg/Control/HashCore.pm | 510 |
4 files changed, 1119 insertions, 1025 deletions
diff --git a/scripts/Dpkg/Control/Fields.pm b/scripts/Dpkg/Control/Fields.pm index e7bd103b9..a3d9b74fe 100644 --- a/scripts/Dpkg/Control/Fields.pm +++ b/scripts/Dpkg/Control/Fields.pm @@ -21,319 +21,10 @@ use warnings; our $VERSION = '1.00'; use Exporter qw(import); -use Dpkg::Gettext; -use Dpkg::ErrorHandling; -use Dpkg::Control::Types; -use Dpkg::Checksums; +use Dpkg::Control::FieldsCore; use Dpkg::Vendor qw(run_vendor_hook); -our @EXPORT = qw(field_capitalize field_is_official field_is_allowed_in - field_transfer_single field_transfer_all - field_list_src_dep field_list_pkg_dep field_get_dep_type - field_ordered_list field_register - field_insert_after field_insert_before); - -use constant { - ALL_PKG => CTRL_INFO_PKG | CTRL_INDEX_PKG | CTRL_PKG_DEB | CTRL_FILE_STATUS, - ALL_SRC => CTRL_INFO_SRC | CTRL_INDEX_SRC | CTRL_PKG_SRC, - ALL_CHANGES => CTRL_FILE_CHANGES | CTRL_CHANGELOG, -}; - -# The canonical list of fields - -# Note that fields used only in dpkg's available file are not listed -# Deprecated fields of dpkg's status file are also not listed -our %FIELDS = ( - 'Architecture' => { - allowed => (ALL_PKG | ALL_SRC | CTRL_FILE_CHANGES) & (~CTRL_INFO_SRC), - }, - 'Binary' => { - allowed => CTRL_PKG_SRC | CTRL_FILE_CHANGES, - }, - 'Binary-Only' => { - allowed => ALL_CHANGES, - }, - 'Breaks' => { - allowed => ALL_PKG, - dependency => 'union', - dep_order => 7, - }, - 'Bugs' => { - allowed => (ALL_PKG | CTRL_INFO_SRC | CTRL_FILE_VENDOR) & (~CTRL_INFO_PKG), - }, - 'Build-Conflicts' => { - allowed => ALL_SRC, - dependency => 'union', - dep_order => 4, - }, - 'Build-Conflicts-Arch' => { - allowed => ALL_SRC, - dependency => 'union', - dep_order => 5, - }, - 'Build-Conflicts-Indep' => { - allowed => ALL_SRC, - dependency => 'union', - dep_order => 6, - }, - 'Build-Depends' => { - allowed => ALL_SRC, - dependency => 'normal', - dep_order => 1, - }, - 'Build-Depends-Arch' => { - allowed => ALL_SRC, - dependency => 'normal', - dep_order => 2, - }, - 'Build-Depends-Indep' => { - allowed => ALL_SRC, - dependency => 'normal', - dep_order => 3, - }, - 'Built-Using' => { - allowed => ALL_PKG, - dependency => 'union', - dep_order => 10, - }, - 'Changed-By' => { - allowed => CTRL_FILE_CHANGES, - }, - 'Changes' => { - allowed => ALL_CHANGES, - }, - 'Closes' => { - allowed => ALL_CHANGES, - }, - 'Conffiles' => { - allowed => CTRL_FILE_STATUS, - }, - 'Config-Version' => { - allowed => CTRL_FILE_STATUS, - }, - 'Conflicts' => { - allowed => ALL_PKG, - dependency => 'union', - dep_order => 6, - }, - 'Date' => { - allowed => ALL_CHANGES, - }, - 'Depends' => { - allowed => ALL_PKG, - dependency => 'normal', - dep_order => 2, - }, - 'Description' => { - allowed => ALL_PKG | CTRL_FILE_CHANGES, - }, - 'Directory' => { - allowed => CTRL_INDEX_SRC, - }, - 'Distribution' => { - allowed => ALL_CHANGES, - }, - 'Enhances' => { - allowed => ALL_PKG, - dependency => 'union', - dep_order => 5, - }, - 'Essential' => { - allowed => ALL_PKG, - }, - 'Filename' => { - allowed => CTRL_INDEX_PKG, - }, - 'Files' => { - allowed => CTRL_PKG_SRC | CTRL_FILE_CHANGES, - }, - 'Format' => { - allowed => CTRL_PKG_SRC | CTRL_FILE_CHANGES, - }, - 'Homepage' => { - allowed => ALL_SRC | ALL_PKG, - }, - 'Installed-Size' => { - allowed => ALL_PKG & ~CTRL_INFO_PKG, - }, - 'Installer-Menu-Item' => { - allowed => ALL_PKG, - }, - 'Kernel-Version' => { - allowed => ALL_PKG, - }, - 'Origin' => { - allowed => (ALL_PKG | ALL_SRC) & (~CTRL_INFO_PKG), - }, - 'Maintainer' => { - allowed => CTRL_PKG_DEB | ALL_SRC | ALL_CHANGES, - }, - 'Multi-Arch' => { - allowed => ALL_PKG, - }, - 'Package' => { - allowed => ALL_PKG, - }, - 'Package-List' => { - allowed => ALL_SRC & ~CTRL_INFO_SRC, - }, - 'Package-Type' => { - allowed => ALL_PKG, - }, - 'Parent' => { - allowed => CTRL_FILE_VENDOR, - }, - 'Pre-Depends' => { - allowed => ALL_PKG, - dependency => 'normal', - dep_order => 1, - }, - 'Priority' => { - allowed => CTRL_INFO_SRC | CTRL_INDEX_SRC | ALL_PKG, - }, - 'Provides' => { - allowed => ALL_PKG, - dependency => 'union', - dep_order => 9, - }, - 'Recommends' => { - allowed => ALL_PKG, - dependency => 'normal', - dep_order => 3, - }, - 'Replaces' => { - allowed => ALL_PKG, - dependency => 'union', - dep_order => 8, - }, - 'Section' => { - allowed => CTRL_INFO_SRC | CTRL_INDEX_SRC | ALL_PKG, - }, - 'Size' => { - allowed => CTRL_INDEX_PKG, - }, - 'Source' => { - allowed => (ALL_PKG | ALL_SRC | ALL_CHANGES) & - (~(CTRL_INDEX_SRC | CTRL_INFO_PKG)), - }, - 'Standards-Version' => { - allowed => ALL_SRC, - }, - 'Status' => { - allowed => CTRL_FILE_STATUS, - }, - 'Subarchitecture' => { - allowed => ALL_PKG, - }, - 'Suggests' => { - allowed => ALL_PKG, - dependency => 'normal', - dep_order => 4, - }, - 'Tag' => { - allowed => ALL_PKG, - }, - 'Task' => { - allowed => ALL_PKG, - }, - 'Triggers-Awaited' => { - allowed => CTRL_FILE_STATUS, - }, - 'Triggers-Pending' => { - allowed => CTRL_FILE_STATUS, - }, - 'Uploaders' => { - allowed => ALL_SRC, - }, - 'Urgency' => { - allowed => ALL_CHANGES, - }, - 'Vcs-Browser' => { - allowed => ALL_SRC, - }, - 'Vcs-Arch' => { - allowed => ALL_SRC, - }, - 'Vcs-Bzr' => { - allowed => ALL_SRC, - }, - 'Vcs-Cvs' => { - allowed => ALL_SRC, - }, - 'Vcs-Darcs' => { - allowed => ALL_SRC, - }, - 'Vcs-Git' => { - allowed => ALL_SRC, - }, - 'Vcs-Hg' => { - allowed => ALL_SRC, - }, - 'Vcs-Mtn' => { - allowed => ALL_SRC, - }, - 'Vcs-Svn' => { - allowed => ALL_SRC, - }, - 'Vendor' => { - allowed => CTRL_FILE_VENDOR, - }, - 'Vendor-Url' => { - allowed => CTRL_FILE_VENDOR, - }, - 'Version' => { - allowed => (ALL_PKG | ALL_SRC | ALL_CHANGES) & - (~(CTRL_INFO_SRC | CTRL_INFO_PKG)), - }, -); - -my @checksum_fields = map { &field_capitalize("Checksums-$_") } checksums_get_list(); -my @sum_fields = map { $_ eq 'md5' ? 'MD5sum' : &field_capitalize($_) } - checksums_get_list(); -&field_register($_, CTRL_PKG_SRC | CTRL_FILE_CHANGES) foreach @checksum_fields; -&field_register($_, CTRL_INDEX_PKG) foreach @sum_fields; - -our %FIELD_ORDER = ( - CTRL_PKG_DEB() => [ - qw(Package Package-Type Source Version Built-Using Kernel-Version - Architecture Subarchitecture Installer-Menu-Item Essential Origin Bugs - Maintainer Installed-Size), &field_list_pkg_dep(), - qw(Section Priority Multi-Arch Homepage Description Tag Task) - ], - CTRL_PKG_SRC() => [ - qw(Format Source Binary Architecture Version Origin Maintainer - Uploaders Homepage Standards-Version Vcs-Browser - Vcs-Arch Vcs-Bzr Vcs-Cvs Vcs-Darcs Vcs-Git Vcs-Hg Vcs-Mtn - Vcs-Svn), &field_list_src_dep(), qw(Package-List), - @checksum_fields, qw(Files) - ], - CTRL_FILE_CHANGES() => [ - qw(Format Date Source Binary Binary-Only Architecture Version - Distribution Urgency Maintainer Changed-By Description - Closes Changes), - @checksum_fields, qw(Files) - ], - CTRL_CHANGELOG() => [ - qw(Source Binary-Only Version Distribution Urgency Maintainer - Date Closes Changes) - ], - CTRL_FILE_STATUS() => [ # Same as fieldinfos in lib/dpkg/parse.c - qw(Package Essential Status Priority Section Installed-Size Origin - Maintainer Bugs Architecture Multi-Arch Source Version Config-Version - Replaces Provides Depends Pre-Depends Recommends Suggests Breaks - Conflicts Enhances Conffiles Description Triggers-Pending - Triggers-Awaited) - ], -); -# Order for CTRL_INDEX_PKG is derived from CTRL_PKG_DEB -$FIELD_ORDER{CTRL_INDEX_PKG()} = [ @{$FIELD_ORDER{CTRL_PKG_DEB()}} ]; -&field_insert_before(CTRL_INDEX_PKG, 'Section', 'Filename', 'Size', @sum_fields); -# Order for CTRL_INDEX_SRC is derived from CTRL_PKG_SRC -$FIELD_ORDER{CTRL_INDEX_SRC()} = [ @{$FIELD_ORDER{CTRL_PKG_SRC()}} ]; -@{$FIELD_ORDER{CTRL_INDEX_SRC()}} = map { $_ eq 'Source' ? 'Package' : $_ } - @{$FIELD_ORDER{CTRL_PKG_SRC()}}; -&field_insert_after(CTRL_INDEX_SRC, 'Version', 'Priority', 'Section'); -&field_insert_before(CTRL_INDEX_SRC, 'Checksums-Md5', 'Directory'); +our @EXPORT = @Dpkg::Control::FieldsCore::EXPORT; # Register vendor specifics fields foreach my $op (run_vendor_hook('register-custom-fields')) { @@ -358,253 +49,10 @@ Dpkg::Control::Fields - manage (list of official) control fields =head1 DESCRIPTION -The modules contains a list of fieldnames with associated meta-data explaining -in which type of control information they are allowed. The types are the -CTRL_* constants exported by Dpkg::Control. - -=head1 FUNCTIONS - -=over 4 - -=item my $f = field_capitalize($field_name) - -Returns the field name properly capitalized. All characters are lowercase, -except the first of each word (words are separated by a dash in field names). - -=cut - -sub field_capitalize($) { - my $field = lc(shift); - # Some special cases due to history - return 'MD5sum' if $field eq 'md5sum'; - return uc($field) if checksums_is_supported($field); - # Generic case - return join '-', map { ucfirst } split /-/, $field; -} - -=item field_is_official($fname) - -Returns true if the field is official and known. - -=cut - -sub field_is_official($) { - return exists $FIELDS{field_capitalize($_[0])}; -} - -=item field_is_allowed_in($fname, @types) - -Returns true (1) if the field $fname is allowed in all the types listed in -the list. Note that you can use type sets instead of individual types (ex: -CTRL_FILE_CHANGES | CTRL_CHANGELOG). - -field_allowed_in(A|B, C) returns true only if the field is allowed in C -and either A or B. - -Undef is returned for non-official fields. - -=cut - -sub field_is_allowed_in($@) { - my ($field, @types) = @_; - $field = field_capitalize($field); - return unless field_is_official($field); - - return 0 if not scalar(@types); - foreach my $type (@types) { - next if $type == CTRL_UNKNOWN; # Always allowed - return 0 unless $FIELDS{$field}{allowed} & $type; - } - return 1; -} - -=item field_transfer_single($from, $to, $field) - -If appropriate, copy the value of the field named $field taken from the -$from Dpkg::Control object to the $to Dpkg::Control object. - -Official fields are copied only if the field is allowed in both types of -objects. Custom fields are treated in a specific manner. When the target -is not among CTRL_PKG_SRC, CTRL_PKG_DEB or CTRL_FILE_CHANGES, then they -are alway copied as is (the X- prefix is kept). Otherwise they are not -copied except if the target object matches the target destination encoded -in the field name. The initial X denoting custom fields can be followed by -one or more letters among "S" (Source: corresponds to CTRL_PKG_SRC), "B" -(Binary: corresponds to CTRL_PKG_DEB) or "C" (Changes: corresponds to -CTRL_FILE_CHANGES). - -Returns undef if nothing has been copied or the name of the new field -added to $to otherwise. - -=cut - -sub field_transfer_single($$;$) { - my ($from, $to, $field) = @_; - $field //= $_; - my ($from_type, $to_type) = ($from->get_type(), $to->get_type()); - $field = field_capitalize($field); - - if (field_is_allowed_in($field, $from_type, $to_type)) { - $to->{$field} = $from->{$field}; - return $field; - } elsif ($field =~ /^X([SBC]*)-/i) { - my $dest = $1; - if (($dest =~ /B/i and $to_type == CTRL_PKG_DEB) or - ($dest =~ /S/i and $to_type == CTRL_PKG_SRC) or - ($dest =~ /C/i and $to_type == CTRL_FILE_CHANGES)) - { - my $new = $field; - $new =~ s/^X([SBC]*)-//i; - $to->{$new} = $from->{$field}; - return $new; - } elsif ($to_type != CTRL_PKG_DEB and - $to_type != CTRL_PKG_SRC and - $to_type != CTRL_FILE_CHANGES) - { - $to->{$field} = $from->{$field}; - return $field; - } - } elsif (not field_is_allowed_in($field, $from_type)) { - warning(_g("unknown information field '%s' in input data in %s"), - $field, $from->get_option('name') || _g('control information')); - } - return; -} - -=item field_transfer_all($from, $to) - -Transfer all appropriate fields from $from to $to. Calls -field_transfer_single() on all fields available in $from. - -Returns the list of fields that have been added to $to. - -=cut - -sub field_transfer_all($$) { - my ($from, $to) = @_; - my (@res, $res); - foreach my $k (keys %$from) { - $res = field_transfer_single($from, $to, $k); - push @res, $res if $res and defined wantarray; - } - return @res; -} - -=item field_ordered_list($type) - -Returns an ordered list of fields for a given type of control information. -This list can be used to output the fields in a predictable order. -The list might be empty for types where the order does not matter much. - -=cut - -sub field_ordered_list($) { - my ($type) = @_; - return @{$FIELD_ORDER{$type}} if exists $FIELD_ORDER{$type}; - return (); -} - -=item field_list_src_dep() - -List of fields that contains dependencies-like information in a source -Debian package. - -=cut - -sub field_list_src_dep() { - my @list = sort { - $FIELDS{$a}{dep_order} <=> $FIELDS{$b}{dep_order} - } grep { - field_is_allowed_in($_, CTRL_PKG_SRC) and - exists $FIELDS{$_}{dependency} - } keys %FIELDS; - return @list; -} - -=item field_list_pkg_dep() - -List of fields that contains dependencies-like information in a binary -Debian package. The fields that express real dependencies are sorted from -the stronger to the weaker. - -=cut - -sub field_list_pkg_dep() { - my @keys = keys %FIELDS; - my @list = sort { - $FIELDS{$a}{dep_order} <=> $FIELDS{$b}{dep_order} - } grep { - field_is_allowed_in($_, CTRL_PKG_DEB) and - exists $FIELDS{$_}{dependency} - } @keys; - return @list; -} - -=item field_get_dep_type($field) - -Return the type of the dependency expressed by the given field. Can -either be "normal" for a real dependency field (Pre-Depends, Depends, ...) -or "union" for other relation fields sharing the same syntax (Conflicts, -Breaks, ...). Returns undef for fields which are not dependencies. - -=cut - -sub field_get_dep_type($) { - my $field = field_capitalize($_[0]); - return unless field_is_official($field); - return $FIELDS{$field}{dependency} if exists $FIELDS{$field}{dependency}; - return; -} - -=item field_register($field, $allowed_types, %opts) - -Register a new field as being allowed in control information of specified -types. %opts is optional - -=cut - -sub field_register($$;@) { - my ($field, $types, %opts) = @_; - $field = field_capitalize($field); - $FIELDS{$field} = { - allowed => $types, - %opts - }; -} - -=item field_insert_after($type, $ref, @fields) - -Place field after another one ($ref) in output of control information of -type $type. - -=cut -sub field_insert_after($$@) { - my ($type, $field, @fields) = @_; - return 0 if not exists $FIELD_ORDER{$type}; - ($field, @fields) = map { field_capitalize($_) } ($field, @fields); - @{$FIELD_ORDER{$type}} = map { - ($_ eq $field) ? ($_, @fields) : $_ - } @{$FIELD_ORDER{$type}}; - return 1; -} - -=item field_insert_before($type, $ref, @fields) - -Place field before another one ($ref) in output of control information of -type $type. - -=cut -sub field_insert_before($$@) { - my ($type, $field, @fields) = @_; - return 0 if not exists $FIELD_ORDER{$type}; - ($field, @fields) = map { field_capitalize($_) } ($field, @fields); - @{$FIELD_ORDER{$type}} = map { - ($_ eq $field) ? (@fields, $_) : $_ - } @{$FIELD_ORDER{$type}}; - return 1; -} - -=back +The module contains a list of vendor-neutral and vendor-specific fieldnames +with associated meta-data explaining in which type of control information +they are allowed. The vendor-neutral fieldnames and all functions are +inherited from Dpkg::Control::FieldsCore. =head1 AUTHOR diff --git a/scripts/Dpkg/Control/FieldsCore.pm b/scripts/Dpkg/Control/FieldsCore.pm new file mode 100644 index 000000000..1f34eaeed --- /dev/null +++ b/scripts/Dpkg/Control/FieldsCore.pm @@ -0,0 +1,599 @@ +# Copyright © 2007-2009 Raphaël Hertzog <hertzog@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, see <http://www.gnu.org/licenses/>. + +package Dpkg::Control::FieldsCore; + +use strict; +use warnings; + +our $VERSION = '1.00'; + +use Exporter qw(import); +use Dpkg::Gettext; +use Dpkg::ErrorHandling; +use Dpkg::Control::Types; +use Dpkg::Checksums; + +our @EXPORT = qw(field_capitalize field_is_official field_is_allowed_in + field_transfer_single field_transfer_all + field_list_src_dep field_list_pkg_dep field_get_dep_type + field_ordered_list field_register + field_insert_after field_insert_before); + +use constant { + ALL_PKG => CTRL_INFO_PKG | CTRL_INDEX_PKG | CTRL_PKG_DEB | CTRL_FILE_STATUS, + ALL_SRC => CTRL_INFO_SRC | CTRL_INDEX_SRC | CTRL_PKG_SRC, + ALL_CHANGES => CTRL_FILE_CHANGES | CTRL_CHANGELOG, +}; + +# The canonical list of fields + +# Note that fields used only in dpkg's available file are not listed +# Deprecated fields of dpkg's status file are also not listed +our %FIELDS = ( + 'Architecture' => { + allowed => (ALL_PKG | ALL_SRC | CTRL_FILE_CHANGES) & (~CTRL_INFO_SRC), + }, + 'Binary' => { + allowed => CTRL_PKG_SRC | CTRL_FILE_CHANGES, + }, + 'Binary-Only' => { + allowed => ALL_CHANGES, + }, + 'Breaks' => { + allowed => ALL_PKG, + dependency => 'union', + dep_order => 7, + }, + 'Bugs' => { + allowed => (ALL_PKG | CTRL_INFO_SRC | CTRL_FILE_VENDOR) & (~CTRL_INFO_PKG), + }, + 'Build-Conflicts' => { + allowed => ALL_SRC, + dependency => 'union', + dep_order => 4, + }, + 'Build-Conflicts-Arch' => { + allowed => ALL_SRC, + dependency => 'union', + dep_order => 5, + }, + 'Build-Conflicts-Indep' => { + allowed => ALL_SRC, + dependency => 'union', + dep_order => 6, + }, + 'Build-Depends' => { + allowed => ALL_SRC, + dependency => 'normal', + dep_order => 1, + }, + 'Build-Depends-Arch' => { + allowed => ALL_SRC, + dependency => 'normal', + dep_order => 2, + }, + 'Build-Depends-Indep' => { + allowed => ALL_SRC, + dependency => 'normal', + dep_order => 3, + }, + 'Built-Using' => { + allowed => ALL_PKG, + dependency => 'union', + dep_order => 10, + }, + 'Changed-By' => { + allowed => CTRL_FILE_CHANGES, + }, + 'Changes' => { + allowed => ALL_CHANGES, + }, + 'Closes' => { + allowed => ALL_CHANGES, + }, + 'Conffiles' => { + allowed => CTRL_FILE_STATUS, + }, + 'Config-Version' => { + allowed => CTRL_FILE_STATUS, + }, + 'Conflicts' => { + allowed => ALL_PKG, + dependency => 'union', + dep_order => 6, + }, + 'Date' => { + allowed => ALL_CHANGES, + }, + 'Depends' => { + allowed => ALL_PKG, + dependency => 'normal', + dep_order => 2, + }, + 'Description' => { + allowed => ALL_PKG | CTRL_FILE_CHANGES, + }, + 'Directory' => { + allowed => CTRL_INDEX_SRC, + }, + 'Distribution' => { + allowed => ALL_CHANGES, + }, + 'Enhances' => { + allowed => ALL_PKG, + dependency => 'union', + dep_order => 5, + }, + 'Essential' => { + allowed => ALL_PKG, + }, + 'Filename' => { + allowed => CTRL_INDEX_PKG, + }, + 'Files' => { + allowed => CTRL_PKG_SRC | CTRL_FILE_CHANGES, + }, + 'Format' => { + allowed => CTRL_PKG_SRC | CTRL_FILE_CHANGES, + }, + 'Homepage' => { + allowed => ALL_SRC | ALL_PKG, + }, + 'Installed-Size' => { + allowed => ALL_PKG & ~CTRL_INFO_PKG, + }, + 'Installer-Menu-Item' => { + allowed => ALL_PKG, + }, + 'Kernel-Version' => { + allowed => ALL_PKG, + }, + 'Origin' => { + allowed => (ALL_PKG | ALL_SRC) & (~CTRL_INFO_PKG), + }, + 'Maintainer' => { + allowed => CTRL_PKG_DEB | ALL_SRC | ALL_CHANGES, + }, + 'Multi-Arch' => { + allowed => ALL_PKG, + }, + 'Package' => { + allowed => ALL_PKG, + }, + 'Package-List' => { + allowed => ALL_SRC & ~CTRL_INFO_SRC, + }, + 'Package-Type' => { + allowed => ALL_PKG, + }, + 'Parent' => { + allowed => CTRL_FILE_VENDOR, + }, + 'Pre-Depends' => { + allowed => ALL_PKG, + dependency => 'normal', + dep_order => 1, + }, + 'Priority' => { + allowed => CTRL_INFO_SRC | CTRL_INDEX_SRC | ALL_PKG, + }, + 'Provides' => { + allowed => ALL_PKG, + dependency => 'union', + dep_order => 9, + }, + 'Recommends' => { + allowed => ALL_PKG, + dependency => 'normal', + dep_order => 3, + }, + 'Replaces' => { + allowed => ALL_PKG, + dependency => 'union', + dep_order => 8, + }, + 'Section' => { + allowed => CTRL_INFO_SRC | CTRL_INDEX_SRC | ALL_PKG, + }, + 'Size' => { + allowed => CTRL_INDEX_PKG, + }, + 'Source' => { + allowed => (ALL_PKG | ALL_SRC | ALL_CHANGES) & + (~(CTRL_INDEX_SRC | CTRL_INFO_PKG)), + }, + 'Standards-Version' => { + allowed => ALL_SRC, + }, + 'Status' => { + allowed => CTRL_FILE_STATUS, + }, + 'Subarchitecture' => { + allowed => ALL_PKG, + }, + 'Suggests' => { + allowed => ALL_PKG, + dependency => 'normal', + dep_order => 4, + }, + 'Tag' => { + allowed => ALL_PKG, + }, + 'Task' => { + allowed => ALL_PKG, + }, + 'Triggers-Awaited' => { + allowed => CTRL_FILE_STATUS, + }, + 'Triggers-Pending' => { + allowed => CTRL_FILE_STATUS, + }, + 'Uploaders' => { + allowed => ALL_SRC, + }, + 'Urgency' => { + allowed => ALL_CHANGES, + }, + 'Vcs-Browser' => { + allowed => ALL_SRC, + }, + 'Vcs-Arch' => { + allowed => ALL_SRC, + }, + 'Vcs-Bzr' => { + allowed => ALL_SRC, + }, + 'Vcs-Cvs' => { + allowed => ALL_SRC, + }, + 'Vcs-Darcs' => { + allowed => ALL_SRC, + }, + 'Vcs-Git' => { + allowed => ALL_SRC, + }, + 'Vcs-Hg' => { + allowed => ALL_SRC, + }, + 'Vcs-Mtn' => { + allowed => ALL_SRC, + }, + 'Vcs-Svn' => { + allowed => ALL_SRC, + }, + 'Vendor' => { + allowed => CTRL_FILE_VENDOR, + }, + 'Vendor-Url' => { + allowed => CTRL_FILE_VENDOR, + }, + 'Version' => { + allowed => (ALL_PKG | ALL_SRC | ALL_CHANGES) & + (~(CTRL_INFO_SRC | CTRL_INFO_PKG)), + }, +); + +my @checksum_fields = map { &field_capitalize("Checksums-$_") } checksums_get_list(); +my @sum_fields = map { $_ eq 'md5' ? 'MD5sum' : &field_capitalize($_) } + checksums_get_list(); +&field_register($_, CTRL_PKG_SRC | CTRL_FILE_CHANGES) foreach @checksum_fields; +&field_register($_, CTRL_INDEX_PKG) foreach @sum_fields; + +our %FIELD_ORDER = ( + CTRL_PKG_DEB() => [ + qw(Package Package-Type Source Version Built-Using Kernel-Version + Architecture Subarchitecture Installer-Menu-Item Essential Origin Bugs + Maintainer Installed-Size), &field_list_pkg_dep(), + qw(Section Priority Multi-Arch Homepage Description Tag Task) + ], + CTRL_PKG_SRC() => [ + qw(Format Source Binary Architecture Version Origin Maintainer + Uploaders Homepage Standards-Version Vcs-Browser + Vcs-Arch Vcs-Bzr Vcs-Cvs Vcs-Darcs Vcs-Git Vcs-Hg Vcs-Mtn + Vcs-Svn), &field_list_src_dep(), qw(Package-List), + @checksum_fields, qw(Files) + ], + CTRL_FILE_CHANGES() => [ + qw(Format Date Source Binary Binary-Only Architecture Version + Distribution Urgency Maintainer Changed-By Description + Closes Changes), + @checksum_fields, qw(Files) + ], + CTRL_CHANGELOG() => [ + qw(Source Binary-Only Version Distribution Urgency Maintainer + Date Closes Changes) + ], + CTRL_FILE_STATUS() => [ # Same as fieldinfos in lib/dpkg/parse.c + qw(Package Essential Status Priority Section Installed-Size Origin + Maintainer Bugs Architecture Multi-Arch Source Version Config-Version + Replaces Provides Depends Pre-Depends Recommends Suggests Breaks + Conflicts Enhances Conffiles Description Triggers-Pending + Triggers-Awaited) + ], +); +# Order for CTRL_INDEX_PKG is derived from CTRL_PKG_DEB +$FIELD_ORDER{CTRL_INDEX_PKG()} = [ @{$FIELD_ORDER{CTRL_PKG_DEB()}} ]; +&field_insert_before(CTRL_INDEX_PKG, 'Section', 'Filename', 'Size', @sum_fields); +# Order for CTRL_INDEX_SRC is derived from CTRL_PKG_SRC +$FIELD_ORDER{CTRL_INDEX_SRC()} = [ @{$FIELD_ORDER{CTRL_PKG_SRC()}} ]; +@{$FIELD_ORDER{CTRL_INDEX_SRC()}} = map { $_ eq 'Source' ? 'Package' : $_ } + @{$FIELD_ORDER{CTRL_PKG_SRC()}}; +&field_insert_after(CTRL_INDEX_SRC, 'Version', 'Priority', 'Section'); +&field_insert_before(CTRL_INDEX_SRC, 'Checksums-Md5', 'Directory'); + +=encoding utf8 + +=head1 NAME + +Dpkg::Control::FieldsCore - manage (list of official) control fields + +=head1 DESCRIPTION + +The modules contains a list of fieldnames with associated meta-data explaining +in which type of control information they are allowed. The types are the +CTRL_* constants exported by Dpkg::Control. + +=head1 FUNCTIONS + +=over 4 + +=item my $f = field_capitalize($field_name) + +Returns the field name properly capitalized. All characters are lowercase, +except the first of each word (words are separated by a dash in field names). + +=cut + +sub field_capitalize($) { + my $field = lc(shift); + # Some special cases due to history + return 'MD5sum' if $field eq 'md5sum'; + return uc($field) if checksums_is_supported($field); + # Generic case + return join '-', map { ucfirst } split /-/, $field; +} + +=item field_is_official($fname) + +Returns true if the field is official and known. + +=cut + +sub field_is_official($) { + return exists $FIELDS{field_capitalize($_[0])}; +} + +=item field_is_allowed_in($fname, @types) + +Returns true (1) if the field $fname is allowed in all the types listed in +the list. Note that you can use type sets instead of individual types (ex: +CTRL_FILE_CHANGES | CTRL_CHANGELOG). + +field_allowed_in(A|B, C) returns true only if the field is allowed in C +and either A or B. + +Undef is returned for non-official fields. + +=cut + +sub field_is_allowed_in($@) { + my ($field, @types) = @_; + $field = field_capitalize($field); + return unless field_is_official($field); + + return 0 if not scalar(@types); + foreach my $type (@types) { + next if $type == CTRL_UNKNOWN; # Always allowed + return 0 unless $FIELDS{$field}{allowed} & $type; + } + return 1; +} + +=item field_transfer_single($from, $to, $field) + +If appropriate, copy the value of the field named $field taken from the +$from Dpkg::Control object to the $to Dpkg::Control object. + +Official fields are copied only if the field is allowed in both types of +objects. Custom fields are treated in a specific manner. When the target +is not among CTRL_PKG_SRC, CTRL_PKG_DEB or CTRL_FILE_CHANGES, then they +are alway copied as is (the X- prefix is kept). Otherwise they are not +copied except if the target object matches the target destination encoded +in the field name. The initial X denoting custom fields can be followed by +one or more letters among "S" (Source: corresponds to CTRL_PKG_SRC), "B" +(Binary: corresponds to CTRL_PKG_DEB) or "C" (Changes: corresponds to +CTRL_FILE_CHANGES). + +Returns undef if nothing has been copied or the name of the new field +added to $to otherwise. + +=cut + +sub field_transfer_single($$;$) { + my ($from, $to, $field) = @_; + $field //= $_; + my ($from_type, $to_type) = ($from->get_type(), $to->get_type()); + $field = field_capitalize($field); + + if (field_is_allowed_in($field, $from_type, $to_type)) { + $to->{$field} = $from->{$field}; + return $field; + } elsif ($field =~ /^X([SBC]*)-/i) { + my $dest = $1; + if (($dest =~ /B/i and $to_type == CTRL_PKG_DEB) or + ($dest =~ /S/i and $to_type == CTRL_PKG_SRC) or + ($dest =~ /C/i and $to_type == CTRL_FILE_CHANGES)) + { + my $new = $field; + $new =~ s/^X([SBC]*)-//i; + $to->{$new} = $from->{$field}; + return $new; + } elsif ($to_type != CTRL_PKG_DEB and + $to_type != CTRL_PKG_SRC and + $to_type != CTRL_FILE_CHANGES) + { + $to->{$field} = $from->{$field}; + return $field; + } + } elsif (not field_is_allowed_in($field, $from_type)) { + warning(_g("unknown information field '%s' in input data in %s"), + $field, $from->get_option('name') || _g('control information')); + } + return; +} + +=item field_transfer_all($from, $to) + +Transfer all appropriate fields from $from to $to. Calls +field_transfer_single() on all fields available in $from. + +Returns the list of fields that have been added to $to. + +=cut + +sub field_transfer_all($$) { + my ($from, $to) = @_; + my (@res, $res); + foreach my $k (keys %$from) { + $res = field_transfer_single($from, $to, $k); + push @res, $res if $res and defined wantarray; + } + return @res; +} + +=item field_ordered_list($type) + +Returns an ordered list of fields for a given type of control information. +This list can be used to output the fields in a predictable order. +The list might be empty for types where the order does not matter much. + +=cut + +sub field_ordered_list($) { + my ($type) = @_; + return @{$FIELD_ORDER{$type}} if exists $FIELD_ORDER{$type}; + return (); +} + +=item field_list_src_dep() + +List of fields that contains dependencies-like information in a source +Debian package. + +=cut + +sub field_list_src_dep() { + my @list = sort { + $FIELDS{$a}{dep_order} <=> $FIELDS{$b}{dep_order} + } grep { + field_is_allowed_in($_, CTRL_PKG_SRC) and + exists $FIELDS{$_}{dependency} + } keys %FIELDS; + return @list; +} + +=item field_list_pkg_dep() + +List of fields that contains dependencies-like information in a binary +Debian package. The fields that express real dependencies are sorted from +the stronger to the weaker. + +=cut + +sub field_list_pkg_dep() { + my @keys = keys %FIELDS; + my @list = sort { + $FIELDS{$a}{dep_order} <=> $FIELDS{$b}{dep_order} + } grep { + field_is_allowed_in($_, CTRL_PKG_DEB) and + exists $FIELDS{$_}{dependency} + } @keys; + return @list; +} + +=item field_get_dep_type($field) + +Return the type of the dependency expressed by the given field. Can +either be "normal" for a real dependency field (Pre-Depends, Depends, ...) +or "union" for other relation fields sharing the same syntax (Conflicts, +Breaks, ...). Returns undef for fields which are not dependencies. + +=cut + +sub field_get_dep_type($) { + my $field = field_capitalize($_[0]); + return unless field_is_official($field); + return $FIELDS{$field}{dependency} if exists $FIELDS{$field}{dependency}; + return; +} + +=item field_register($field, $allowed_types, %opts) + +Register a new field as being allowed in control information of specified +types. %opts is optional + +=cut + +sub field_register($$;@) { + my ($field, $types, %opts) = @_; + $field = field_capitalize($field); + $FIELDS{$field} = { + allowed => $types, + %opts + }; +} + +=item field_insert_after($type, $ref, @fields) + +Place field after another one ($ref) in output of control information of +type $type. + +=cut +sub field_insert_after($$@) { + my ($type, $field, @fields) = @_; + return 0 if not exists $FIELD_ORDER{$type}; + ($field, @fields) = map { field_capitalize($_) } ($field, @fields); + @{$FIELD_ORDER{$type}} = map { + ($_ eq $field) ? ($_, @fields) : $_ + } @{$FIELD_ORDER{$type}}; + return 1; +} + +=item field_insert_before($type, $ref, @fields) + +Place field before another one ($ref) in output of control information of +type $type. + +=cut +sub field_insert_before($$@) { + my ($type, $field, @fields) = @_; + return 0 if not exists $FIELD_ORDER{$type}; + ($field, @fields) = map { field_capitalize($_) } ($field, @fields); + @{$FIELD_ORDER{$type}} = map { + ($_ eq $field) ? (@fields, $_) : $_ + } @{$FIELD_ORDER{$type}}; + return 1; +} + +=back + +=head1 AUTHOR + +Raphaël Hertzog <hertzog@debian.org>. + +=cut + +1; diff --git a/scripts/Dpkg/Control/Hash.pm b/scripts/Dpkg/Control/Hash.pm index 59217ec56..d1a4f1889 100644 --- a/scripts/Dpkg/Control/Hash.pm +++ b/scripts/Dpkg/Control/Hash.pm @@ -22,17 +22,9 @@ our $VERSION = '1.00'; use Dpkg::Gettext; use Dpkg::ErrorHandling; +use Dpkg::Control::Fields; # Force execution of vendor hook. -# This module must absolutely not use Dpkg::Control::Fields -# it's used by other modules that are required to compile -# Dpkg::Control::Fields itself (Dpkg::Vendor) -# That's why field_capitalize is duplicated - -use parent qw(Dpkg::Interface::Storable); - -use overload - '%{}' => sub { ${$_[0]}->{fields} }, - 'eq' => sub { "$_[0]" eq "$_[1]" }; +use parent qw(Dpkg::Control::HashCore); =encoding utf8 @@ -42,463 +34,8 @@ Dpkg::Control::Hash - parse and manipulate a block of RFC822-like fields =head1 DESCRIPTION -The Dpkg::Control::Hash object is a hash-like representation of a set of -RFC822-like fields. The fields names are case insensitive and are always -capitalized the same when output (see field_capitalize function in -Dpkg::Control::Fields). -The order in which fields have been set is remembered and is used -to be able to dump back the same content. The output order can also be -overridden if needed. - -You can store arbitrary values in the hash, they will always be properly -escaped in the output to conform to the syntax of control files. This is -relevant mainly for multilines values: while the first line is always output -unchanged directly after the field name, supplementary lines are -modified. Empty lines and lines containing only dots are prefixed with -" ." (space + dot) while other lines are prefixed with a single space. - -During parsing, trailing spaces are stripped on all lines while leading -spaces are stripped only on the first line of each field. - -=head1 FUNCTIONS - -=over 4 - -=item my $c = Dpkg::Control::Hash->new(%opts) - -Creates a new object with the indicated options. Supported options -are: - -=over 8 - -=item allow_pgp - -Configures the parser to accept PGP signatures around the control -information. Value can be 0 (default) or 1. - -=item allow_duplicate - -Configures the parser to allow duplicate fields in the control -information. Value can be 0 (default) or 1. - -=item drop_empty - -Defines if empty fields are dropped during the output. Value can be 0 -(default) or 1. - -=item name - -The user friendly name of the information stored in the object. It might -be used in some error messages or warnings. A default name might be set -depending on the type. - -=back - -=cut - -sub new { - my ($this, %opts) = @_; - my $class = ref($this) || $this; - - # Object is a scalar reference and not a hash ref to avoid - # infinite recursion due to overloading hash-derefencing - my $self = \{ - in_order => [], - out_order => [], - allow_pgp => 0, - allow_duplicate => 0, - drop_empty => 0, - }; - bless $self, $class; - - $$self->{fields} = Dpkg::Control::Hash::Tie->new($self); - - # Options set by the user override default values - $$self->{$_} = $opts{$_} foreach keys %opts; - - return $self; -} - -# There is naturally a circular reference between the tied hash and its -# containing object. Happily, the extra layer of scalar reference can -# be used to detect the destruction of the object and break the loop so -# that everything gets garbage-collected. - -sub DESTROY { - my ($self) = @_; - delete $$self->{fields}; -} - -=item $c->set_options($option, %opts) - -Changes the value of one or more options. - -=cut - -sub set_options { - my ($self, %opts) = @_; - $$self->{$_} = $opts{$_} foreach keys %opts; -} - -=item my $value = $c->get_option($option) - -Returns the value of the corresponding option. - -=cut - -sub get_option { - my ($self, $k) = @_; - return $$self->{$k}; -} - -=item $c->load($file) - -Parse the content of $file. Exits in case of errors. Returns true if some -fields have been parsed. - -=item $c->parse($fh, $description) - -Parse a control file from the given filehandle. Exits in case of errors. -$description is used to describe the filehandle, ideally it's a filename -or a description of where the data comes from. It's used in error -messages. Returns true if some fields have been parsed. - -=cut - -sub parse { - my ($self, $fh, $desc) = @_; - - my $paraborder = 1; - my $parabody = 0; - my $cf; # Current field - my $expect_pgp_sig = 0; - my $pgp_signed = 0; - - while (<$fh>) { - s/\s*\n$//; - next if (m/^$/ and $paraborder); - next if (m/^#/); - $paraborder = 0; - if (m/^(\S+?)\s*:\s*(.*)$/) { - $parabody = 1; - if (exists $self->{$1}) { - unless ($$self->{allow_duplicate}) { - syntaxerr($desc, sprintf(_g('duplicate field %s found'), $1)); - } - } - $self->{$1} = $2; - $cf = $1; - } elsif (m/^\s(\s*\S.*)$/) { - my $line = $1; - unless (defined($cf)) { - syntaxerr($desc, _g('continued value line not in field')); - } - if ($line =~ /^\.+$/) { - $line = substr $line, 1; - } - $self->{$cf} .= "\n$line"; - } elsif (m/^-----BEGIN PGP SIGNED MESSAGE-----$/) { - $expect_pgp_sig = 1; - if ($$self->{allow_pgp} and not $parabody) { - # Skip PGP headers - while (<$fh>) { - last if m/^\s*$/; - } - } else { - syntaxerr($desc, _g('PGP signature not allowed here')); - } - } elsif (m/^$/ || ($expect_pgp_sig && m/^-----BEGIN PGP SIGNATURE-----$/)) { - if ($expect_pgp_sig) { - # Skip empty lines - $_ = <$fh> while defined($_) && $_ =~ /^\s*$/; - length($_) || - syntaxerr($desc, _g('expected PGP signature, found EOF ' . - 'after blank line')); - s/\s*\n$//; - unless (m/^-----BEGIN PGP SIGNATURE-----$/) { - syntaxerr($desc, sprintf(_g('expected PGP signature, ' . - "found something else \`%s'"), $_)); - } - # Skip PGP signature - while (<$fh>) { - s/\s*\n$//; - last if m/^-----END PGP SIGNATURE-----$/; - } - unless (defined($_)) { - syntaxerr($desc, _g('unfinished PGP signature')); - } - # This does not mean the signature is correct, that needs to - # be verified by gnupg. - $pgp_signed = 1; - } - last; # Finished parsing one block - } else { - syntaxerr($desc, - _g('line with unknown format (not field-colon-value)')); - } - } - - if ($expect_pgp_sig and not $pgp_signed) { - syntaxerr($desc, _g('unfinished PGP signature')); - } - - return defined($cf); -} - -=item $c->find_custom_field($name) - -Scan the fields and look for a user specific field whose name matches the -following regex: /X[SBC]*-$name/i. Return the name of the field found or -undef if nothing has been found. - -=cut - -sub find_custom_field { - my ($self, $name) = @_; - foreach my $key (keys %$self) { - return $key if $key =~ /^X[SBC]*-\Q$name\E$/i; - } - return; -} - -=item $c->get_custom_field($name) - -Identify a user field and retrieve its value. - -=cut - -sub get_custom_field { - my ($self, $name) = @_; - my $key = $self->find_custom_field($name); - return $self->{$key} if defined $key; - return; -} - -=item $c->save($filename) - -Write the string representation of the control information to a -file. - -=item my $str = $c->output() - -=item "$c" - -Get a string representation of the control information. The fields -are sorted in the order in which they have been read or set except -if the order has been overridden with set_output_order(). - -=item $c->output($fh) - -Print the string representation of the control information to a -filehandle. - -=cut - -sub output { - my ($self, $fh) = @_; - my $str = ''; - my @keys; - if (@{$$self->{out_order}}) { - my $i = 1; - my $imp = {}; - $imp->{$_} = $i++ foreach @{$$self->{out_order}}; - @keys = sort { - if (defined $imp->{$a} && defined $imp->{$b}) { - $imp->{$a} <=> $imp->{$b}; - } elsif (defined($imp->{$a})) { - -1; - } elsif (defined($imp->{$b})) { - 1; - } else { - $a cmp $b; - } - } keys %$self; - } else { - @keys = @{$$self->{in_order}}; - } - - foreach my $key (@keys) { - if (exists $self->{$key}) { - my $value = $self->{$key}; - # Skip whitespace-only fields - next if $$self->{drop_empty} and $value !~ m/\S/; - # Escape data to follow control file syntax - my @lines = split(/\n/, $value); - $value = (scalar @lines) ? shift @lines : ''; - foreach (@lines) { - s/\s+$//; - if (/^$/ or /^\.+$/) { - $value .= "\n .$_"; - } else { - $value .= "\n $_"; - } - } - # Print it out - if ($fh) { - print $fh "$key: $value\n" || - syserr(_g('write error on control data')); - } - $str .= "$key: $value\n" if defined wantarray; - } - } - return $str; -} - -=item $c->set_output_order(@fields) - -Define the order in which fields will be displayed in the output() method. - -=cut - -sub set_output_order { - my ($self, @fields) = @_; - - $$self->{out_order} = [@fields]; -} - -=item $c->apply_substvars($substvars) - -Update all fields by replacing the variables references with -the corresponding value stored in the Dpkg::Substvars object. - -=cut - -sub apply_substvars { - my ($self, $substvars, %opts) = @_; - - # Add substvars to refer to other fields - foreach my $f (keys %$self) { - $substvars->set_as_used("F:$f", $self->{$f}); - } - - foreach my $f (keys %$self) { - my $v = $substvars->substvars($self->{$f}, %opts); - if ($v ne $self->{$f}) { - # If we replaced stuff, ensure we're not breaking - # a dependency field by introducing empty lines, or multiple - # commas - $v =~ s/\n[ \t]*(\n|$)/$1/; # Drop empty/whitespace-only lines - # TODO: do this only for dependency fields - $v =~ s/,[\s,]*,/,/g; - $v =~ s/^\s*,\s*//; - $v =~ s/\s*,\s*$//; - } - $v =~ s/\$\{\}/\$/g; # XXX: what for? - - $self->{$f} = $v; - } -} - -package Dpkg::Control::Hash::Tie; - -# This object is used to tie a hash. It implements hash-like functions by -# normalizing the name of fields received in keys (using -# Dpkg::Control::Fields::field_capitalize). It also stores the order in -# which fields have been added in order to be able to dump them in the -# same order. But the order information is stored in a parent object of -# type Dpkg::Control. - -use Dpkg::Checksums; - -use Tie::Hash; -use parent -norequire, qw(Tie::ExtraHash); - -sub field_capitalize($) { - my $field = lc(shift); - # Some special cases due to history - return 'MD5sum' if $field eq 'md5sum'; - return uc($field) if checksums_is_supported($field); - # Generic case - return join '-', map { ucfirst } split /-/, $field; -} - -# $self->[0] is the real hash -# $self->[1] is a reference to the hash contained by the parent object. -# This reference bypasses the top-level scalar reference of a -# Dpkg::Control::Hash, hence ensuring that that reference gets DESTROYed -# properly. - -# Dpkg::Control::Hash->new($parent) -# -# Return a reference to a tied hash implementing storage of simple -# "field: value" mapping as used in many Debian-specific files. - -sub new { - my $class = shift; - my $hash = {}; - tie %{$hash}, $class, @_; - return $hash; -} - -sub TIEHASH { - my ($class, $parent) = @_; - die 'Parent object must be Dpkg::Control::Hash' - if not $parent->isa('Dpkg::Control::Hash'); - return bless [ {}, $$parent ], $class; -} - -sub FETCH { - my ($self, $key) = @_; - $key = lc($key); - return $self->[0]->{$key} if exists $self->[0]->{$key}; - return; -} - -sub STORE { - my ($self, $key, $value) = @_; - my $parent = $self->[1]; - $key = lc($key); - if (not exists $self->[0]->{$key}) { - push @{$parent->{in_order}}, field_capitalize($key); - } - $self->[0]->{$key} = $value; -} - -sub EXISTS { - my ($self, $key) = @_; - $key = lc($key); - return exists $self->[0]->{$key}; -} - -sub DELETE { - my ($self, $key) = @_; - my $parent = $self->[1]; - my $in_order = $parent->{in_order}; - $key = lc($key); - if (exists $self->[0]->{$key}) { - delete $self->[0]->{$key}; - @$in_order = grep { lc($_) ne $key } @$in_order; - return 1; - } else { - return 0; - } -} - -sub FIRSTKEY { - my $self = shift; - my $parent = $self->[1]; - foreach (@{$parent->{in_order}}) { - return $_ if exists $self->[0]->{lc($_)}; - } -} - -sub NEXTKEY { - my ($self, $last) = @_; - my $parent = $self->[1]; - my $found = 0; - foreach (@{$parent->{in_order}}) { - if ($found) { - return $_ if exists $self->[0]->{lc($_)}; - } else { - $found = 1 if $_ eq $last; - } - } - return; -} - -1; - -=back +This module is just like Dpkg::Control::HashCore, with vendor-specific +field knowledge. =head1 AUTHOR diff --git a/scripts/Dpkg/Control/HashCore.pm b/scripts/Dpkg/Control/HashCore.pm new file mode 100644 index 000000000..53d826f29 --- /dev/null +++ b/scripts/Dpkg/Control/HashCore.pm @@ -0,0 +1,510 @@ +# Copyright © 2007-2009 Raphaël Hertzog <hertzog@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, see <http://www.gnu.org/licenses/>. + +package Dpkg::Control::HashCore; + +use strict; +use warnings; + +our $VERSION = '1.00'; + +use Dpkg::Gettext; +use Dpkg::ErrorHandling; + +# This module must absolutely not use Dpkg::Control::Fields +# it's used by other modules that are required to compile +# Dpkg::Control::Fields itself (Dpkg::Vendor) +# That's why field_capitalize is duplicated + +use parent qw(Dpkg::Interface::Storable); + +use overload + '%{}' => sub { ${$_[0]}->{fields} }, + 'eq' => sub { "$_[0]" eq "$_[1]" }; + +=encoding utf8 + +=head1 NAME + +Dpkg::Control::HashCore - parse and manipulate a block of RFC822-like fields + +=head1 DESCRIPTION + +The Dpkg::Control::Hash object is a hash-like representation of a set of +RFC822-like fields. The fields names are case insensitive and are always +capitalized the same when output (see field_capitalize function in +Dpkg::Control::Fields). +The order in which fields have been set is remembered and is used +to be able to dump back the same content. The output order can also be +overridden if needed. + +You can store arbitrary values in the hash, they will always be properly +escaped in the output to conform to the syntax of control files. This is +relevant mainly for multilines values: while the first line is always output +unchanged directly after the field name, supplementary lines are +modified. Empty lines and lines containing only dots are prefixed with +" ." (space + dot) while other lines are prefixed with a single space. + +During parsing, trailing spaces are stripped on all lines while leading +spaces are stripped only on the first line of each field. + +=head1 FUNCTIONS + +=over 4 + +=item my $c = Dpkg::Control::Hash->new(%opts) + +Creates a new object with the indicated options. Supported options +are: + +=over 8 + +=item allow_pgp + +Configures the parser to accept PGP signatures around the control +information. Value can be 0 (default) or 1. + +=item allow_duplicate + +Configures the parser to allow duplicate fields in the control +information. Value can be 0 (default) or 1. + +=item drop_empty + +Defines if empty fields are dropped during the output. Value can be 0 +(default) or 1. + +=item name + +The user friendly name of the information stored in the object. It might +be used in some error messages or warnings. A default name might be set +depending on the type. + +=back + +=cut + +sub new { + my ($this, %opts) = @_; + my $class = ref($this) || $this; + + # Object is a scalar reference and not a hash ref to avoid + # infinite recursion due to overloading hash-derefencing + my $self = \{ + in_order => [], + out_order => [], + allow_pgp => 0, + allow_duplicate => 0, + drop_empty => 0, + }; + bless $self, $class; + + $$self->{fields} = Dpkg::Control::HashCore::Tie->new($self); + + # Options set by the user override default values + $$self->{$_} = $opts{$_} foreach keys %opts; + + return $self; +} + +# There is naturally a circular reference between the tied hash and its +# containing object. Happily, the extra layer of scalar reference can +# be used to detect the destruction of the object and break the loop so +# that everything gets garbage-collected. + +sub DESTROY { + my ($self) = @_; + delete $$self->{fields}; +} + +=item $c->set_options($option, %opts) + +Changes the value of one or more options. + +=cut + +sub set_options { + my ($self, %opts) = @_; + $$self->{$_} = $opts{$_} foreach keys %opts; +} + +=item my $value = $c->get_option($option) + +Returns the value of the corresponding option. + +=cut + +sub get_option { + my ($self, $k) = @_; + return $$self->{$k}; +} + +=item $c->load($file) + +Parse the content of $file. Exits in case of errors. Returns true if some +fields have been parsed. + +=item $c->parse($fh, $description) + +Parse a control file from the given filehandle. Exits in case of errors. +$description is used to describe the filehandle, ideally it's a filename +or a description of where the data comes from. It's used in error +messages. Returns true if some fields have been parsed. + +=cut + +sub parse { + my ($self, $fh, $desc) = @_; + + my $paraborder = 1; + my $parabody = 0; + my $cf; # Current field + my $expect_pgp_sig = 0; + my $pgp_signed = 0; + + while (<$fh>) { + s/\s*\n$//; + next if (m/^$/ and $paraborder); + next if (m/^#/); + $paraborder = 0; + if (m/^(\S+?)\s*:\s*(.*)$/) { + $parabody = 1; + if (exists $self->{$1}) { + unless ($$self->{allow_duplicate}) { + syntaxerr($desc, sprintf(_g('duplicate field %s found'), $1)); + } + } + $self->{$1} = $2; + $cf = $1; + } elsif (m/^\s(\s*\S.*)$/) { + my $line = $1; + unless (defined($cf)) { + syntaxerr($desc, _g('continued value line not in field')); + } + if ($line =~ /^\.+$/) { + $line = substr $line, 1; + } + $self->{$cf} .= "\n$line"; + } elsif (m/^-----BEGIN PGP SIGNED MESSAGE-----$/) { + $expect_pgp_sig = 1; + if ($$self->{allow_pgp} and not $parabody) { + # Skip PGP headers + while (<$fh>) { + last if m/^\s*$/; + } + } else { + syntaxerr($desc, _g('PGP signature not allowed here')); + } + } elsif (m/^$/ || ($expect_pgp_sig && m/^-----BEGIN PGP SIGNATURE-----$/)) { + if ($expect_pgp_sig) { + # Skip empty lines + $_ = <$fh> while defined($_) && $_ =~ /^\s*$/; + length($_) || + syntaxerr($desc, _g('expected PGP signature, found EOF ' . + 'after blank line')); + s/\s*\n$//; + unless (m/^-----BEGIN PGP SIGNATURE-----$/) { + syntaxerr($desc, sprintf(_g('expected PGP signature, ' . + "found something else \`%s'"), $_)); + } + # Skip PGP signature + while (<$fh>) { + s/\s*\n$//; + last if m/^-----END PGP SIGNATURE-----$/; + } + unless (defined($_)) { + syntaxerr($desc, _g('unfinished PGP signature')); + } + # This does not mean the signature is correct, that needs to + # be verified by gnupg. + $pgp_signed = 1; + } + last; # Finished parsing one block + } else { + syntaxerr($desc, + _g('line with unknown format (not field-colon-value)')); + } + } + + if ($expect_pgp_sig and not $pgp_signed) { + syntaxerr($desc, _g('unfinished PGP signature')); + } + + return defined($cf); +} + +=item $c->find_custom_field($name) + +Scan the fields and look for a user specific field whose name matches the +following regex: /X[SBC]*-$name/i. Return the name of the field found or +undef if nothing has been found. + +=cut + +sub find_custom_field { + my ($self, $name) = @_; + foreach my $key (keys %$self) { + return $key if $key =~ /^X[SBC]*-\Q$name\E$/i; + } + return; +} + +=item $c->get_custom_field($name) + +Identify a user field and retrieve its value. + +=cut + +sub get_custom_field { + my ($self, $name) = @_; + my $key = $self->find_custom_field($name); + return $self->{$key} if defined $key; + return; +} + +=item $c->save($filename) + +Write the string representation of the control information to a +file. + +=item my $str = $c->output() + +=item "$c" + +Get a string representation of the control information. The fields +are sorted in the order in which they have been read or set except +if the order has been overridden with set_output_order(). + +=item $c->output($fh) + +Print the string representation of the control information to a +filehandle. + +=cut + +sub output { + my ($self, $fh) = @_; + my $str = ''; + my @keys; + if (@{$$self->{out_order}}) { + my $i = 1; + my $imp = {}; + $imp->{$_} = $i++ foreach @{$$self->{out_order}}; + @keys = sort { + if (defined $imp->{$a} && defined $imp->{$b}) { + $imp->{$a} <=> $imp->{$b}; + } elsif (defined($imp->{$a})) { + -1; + } elsif (defined($imp->{$b})) { + 1; + } else { + $a cmp $b; + } + } keys %$self; + } else { + @keys = @{$$self->{in_order}}; + } + + foreach my $key (@keys) { + if (exists $self->{$key}) { + my $value = $self->{$key}; + # Skip whitespace-only fields + next if $$self->{drop_empty} and $value !~ m/\S/; + # Escape data to follow control file syntax + my @lines = split(/\n/, $value); + $value = (scalar @lines) ? shift @lines : ''; + foreach (@lines) { + s/\s+$//; + if (/^$/ or /^\.+$/) { + $value .= "\n .$_"; + } else { + $value .= "\n $_"; + } + } + # Print it out + if ($fh) { + print $fh "$key: $value\n" || + syserr(_g('write error on control data')); + } + $str .= "$key: $value\n" if defined wantarray; + } + } + return $str; +} + +=item $c->set_output_order(@fields) + +Define the order in which fields will be displayed in the output() method. + +=cut + +sub set_output_order { + my ($self, @fields) = @_; + + $$self->{out_order} = [@fields]; +} + +=item $c->apply_substvars($substvars) + +Update all fields by replacing the variables references with +the corresponding value stored in the Dpkg::Substvars object. + +=cut + +sub apply_substvars { + my ($self, $substvars, %opts) = @_; + + # Add substvars to refer to other fields + foreach my $f (keys %$self) { + $substvars->set_as_used("F:$f", $self->{$f}); + } + + foreach my $f (keys %$self) { + my $v = $substvars->substvars($self->{$f}, %opts); + if ($v ne $self->{$f}) { + # If we replaced stuff, ensure we're not breaking + # a dependency field by introducing empty lines, or multiple + # commas + $v =~ s/\n[ \t]*(\n|$)/$1/; # Drop empty/whitespace-only lines + # TODO: do this only for dependency fields + $v =~ s/,[\s,]*,/,/g; + $v =~ s/^\s*,\s*//; + $v =~ s/\s*,\s*$//; + } + $v =~ s/\$\{\}/\$/g; # XXX: what for? + + $self->{$f} = $v; + } +} + +package Dpkg::Control::HashCore::Tie; + +# This object is used to tie a hash. It implements hash-like functions by +# normalizing the name of fields received in keys (using +# Dpkg::Control::Fields::field_capitalize). It also stores the order in +# which fields have been added in order to be able to dump them in the +# same order. But the order information is stored in a parent object of +# type Dpkg::Control. + +use Dpkg::Checksums; + +use Tie::Hash; +use parent -norequire, qw(Tie::ExtraHash); + +sub field_capitalize($) { + my $field = lc(shift); + # Some special cases due to history + return 'MD5sum' if $field eq 'md5sum'; + return uc($field) if checksums_is_supported($field); + # Generic case + return join '-', map { ucfirst } split /-/, $field; +} + +# $self->[0] is the real hash +# $self->[1] is a reference to the hash contained by the parent object. +# This reference bypasses the top-level scalar reference of a +# Dpkg::Control::Hash, hence ensuring that that reference gets DESTROYed +# properly. + +# Dpkg::Control::Hash->new($parent) +# +# Return a reference to a tied hash implementing storage of simple +# "field: value" mapping as used in many Debian-specific files. + +sub new { + my $class = shift; + my $hash = {}; + tie %{$hash}, $class, @_; + return $hash; +} + +sub TIEHASH { + my ($class, $parent) = @_; + die 'Parent object must be Dpkg::Control::Hash' + if not $parent->isa('Dpkg::Control::HashCore') and + not $parent->isa('Dpkg::Control::Hash'); + return bless [ {}, $$parent ], $class; +} + +sub FETCH { + my ($self, $key) = @_; + $key = lc($key); + return $self->[0]->{$key} if exists $self->[0]->{$key}; + return; +} + +sub STORE { + my ($self, $key, $value) = @_; + my $parent = $self->[1]; + $key = lc($key); + if (not exists $self->[0]->{$key}) { + push @{$parent->{in_order}}, field_capitalize($key); + } + $self->[0]->{$key} = $value; +} + +sub EXISTS { + my ($self, $key) = @_; + $key = lc($key); + return exists $self->[0]->{$key}; +} + +sub DELETE { + my ($self, $key) = @_; + my $parent = $self->[1]; + my $in_order = $parent->{in_order}; + $key = lc($key); + if (exists $self->[0]->{$key}) { + delete $self->[0]->{$key}; + @$in_order = grep { lc($_) ne $key } @$in_order; + return 1; + } else { + return 0; + } +} + +sub FIRSTKEY { + my $self = shift; + my $parent = $self->[1]; + foreach (@{$parent->{in_order}}) { + return $_ if exists $self->[0]->{lc($_)}; + } +} + +sub NEXTKEY { + my ($self, $last) = @_; + my $parent = $self->[1]; + my $found = 0; + foreach (@{$parent->{in_order}}) { + if ($found) { + return $_ if exists $self->[0]->{lc($_)}; + } else { + $found = 1 if $_ eq $last; + } + } + return; +} + +1; + +=back + +=head1 AUTHOR + +Raphaël Hertzog <hertzog@debian.org>. + +=cut + +1; |