File Coverage

blib/lib/CPE.pm
Criterion Covered Total %
statement 55 66 83.3
branch 31 44 70.4
condition 2 3 66.6
subroutine 9 16 56.2
pod 8 8 100.0
total 105 137 76.6


line stmt bran cond sub pod time code
1             package CPE;
2 2     2   143922 use strict;
  2         13  
  2         61  
3 2     2   11 use warnings;
  2         5  
  2         61  
4 2     2   12 use Carp ();
  2         3  
  2         511  
5              
6             our $VERSION = '0.02';
7              
8             for my $accessor_name (qw(
9             cpe_version
10             part vendor product version update edition
11             language sw_edition target_sw target_hw other)
12             ) {
13             my $sub = sub {
14 72 50   72   34225 die "method '$accessor_name' takes 0 or 1 arguments, not " . scalar(@_ - 1) if @_ > 2;
15 72         135 my ($self, $new) = @_;
16 72         126 my $old = $self->{$accessor_name};
17              
18 72 50       132 if (@_ == 2) {
19 0 0       0 my $validator = $accessor_name eq 'cpe_version' ? qr/\A2\.3\z/
    0          
20             : $accessor_name eq 'part'? qr/\A[aoh]\z/
21             : qr/\A[a-z0-9\._\-~%]*\z/;
22 0 0       0 die "invalid value '$new' for '$accessor_name'"
23             unless $new =~ $validator;
24 0         0 $self->{$accessor_name} = $new;
25             }
26 72         235 return $old;
27             };
28 2     2   16 { no strict 'refs'; *$accessor_name = $sub; }
  2         3  
  2         764  
29             }
30              
31 0     0 1 0 sub is_equal { die 'TODO' }
32 0     0 1 0 sub is_subset { die 'TODO' }
33 0     0 1 0 sub is_superset { die 'TODO' }
34 0     0 1 0 sub is_disjoint { die 'TODO' }
35              
36 0     0 1 0 sub as_string { die 'TODO' }
37 0     0 1 0 sub as_wfn { die 'TODO' }
38 0     0 1 0 sub as_uri { die 'TODO' }
39              
40             sub new {
41 6     6 1 2755 my ($class, @args) = @_;
42 6 100       23 my $self = @args == 1 ? _from_string($args[0]) : _from_hash(@args);
43 6         34 return bless $self, $class;
44             }
45              
46             sub _from_string {
47 5     5   9 my ($str) = @_;
48              
49 5 50       56 if ($str =~ m{cpe:/
50             (?[aoh])?
51             (?: \: (? [^:]*) )?
52             (?: \: (? [^:]*) )?
53             (?: \: (? [^:]*) )?
54             (?: \: (? [^:]*) )?
55             (?: \: (? [^:]*) )?
56             (?: \: (? [^:]*) )?
57             (?: \: (? [^:]*) )?
58             (?: \: (? [^:]*) )?
59             (?: \: (? [^:]*) )?
60             (?: \: (? [^:]*) )?
61             }ix
62             ) {
63 2     2   1024 my %data = %+;
  2         1069  
  2         1521  
  5         100  
64 5         28 foreach my $k (keys %data) {
65 28 100       90 if ($data{$k} eq '') {
    100          
    100          
66 3         15 $data{$k} = 'ANY';
67             }
68             elsif ($data{$k} eq '-') {
69 1         5 $data{$k} = 'NA';
70             }
71             elsif ($data{$k} =~ /\%/) {
72             # URI CPEs may have percent-encoded special characters
73             # which must be decoded to proper values.
74 4         58 my %decoded = (
75             '21' => '!', '22' => '"', '23' => '#', '24' => '$',
76             '25' => '%', '26' => '&', '27' => q('), '28' => '(',
77             '29' => ')', '2a' => '*', '2b' => '+', '2c' => ',',
78             '2f' => '/', '3a' => ':', '3b' => ';', '3c' => '<',
79             '3d' => '=', '3e' => '>', '3f' => '?', '40' => '@',
80             '5b' => '[', '5c' => '\\', '5d' => ']', '5e' => '^',
81             '60' => '`', '7b' => '{', '7c' => '|', '7d' => '}',
82             '7e' => '~',
83             );
84 4 100       18 $data{$k} =~ s{\%01}{?}g if index($data{$k}, '%01') >= 0;
85 4 100       13 $data{$k} =~ s{\%02}{*}g if index($data{$k}, '%02') >= 0;
86 4         17 foreach my $special (keys %decoded) {
87 116 100       249 if (index($data{$k}, '%' . $special) >= 0) {
88 2         43 $data{$k} =~ s{\%$special}{\\$decoded{$special}}ig;
89             }
90             }
91             }
92             }
93             # this is a compatibility layer between CPE 2.2 and 2.3.
94             # URIs using 2.3 format will have the 'edition' field starting
95             # with a '~' and with '~' dividing all the new 2.3 fields within.
96             # In 2.2 this is not done, and those fields don't exist.
97 5 100 66     29 if (defined $data{edition} && substr($data{edition}, 0, 1) eq '~') {
98             (undef,
99             $data{edition},
100             $data{sw_edition},
101             $data{target_sw},
102             $data{target_hw},
103             $data{other},
104 21 50       54 ) = map { $_ eq '' ? 'ANY' : $_ eq '-' ? 'NA' : $_ }
    100          
105             # split() ignores empty values unless there is a defined
106             # value afterwards, so we add an extra '!' element to the list
107             # and ignore it:
108 3         14 split /\~/ => $data{edition} . '~!';
109             }
110 5         21 return _from_hash(cpe_version => 2.3, %data);
111             }
112 0         0 die 'sorry, only URI CPEs can be parsed at this point. Patches welcome!';
113             }
114              
115             sub _from_hash {
116 6     6   27 my (%args) = @_;
117 6         15 my $self = { cpe_version => '2.3', part => 'ANY' };
118 6         15 foreach my $key (qw(vendor product version update edition
119             language sw_edition target_sw target_hw other)
120             ) {
121 60 100       98 if (!exists $args{$key}) {
122 25         42 $self->{$key} = 'ANY';
123 25         37 next;
124             }
125             Carp::croak "invalid characters '$args{$key}' in '$key' field."
126 35 50       104 unless $args{$key} =~ m/\A(?:[
127             a-z 0-9 \. _ # regular characters
128             \- \~ # special meaning characters
129             \* \? # quantifiers
130             # or any of the following special characters:
131             ! " \# \$ \% \& ' \( \) \+ , \/ \:
132             ; \< \= \> \@ \[ \\ \] \^ \` \{ \| \}
133             ]*
134             | ANY | NA # 'ANY' and 'NA' are special values
135             )\z/x;
136 35         63 $self->{$key} = $args{$key};
137             }
138 6 100       13 if (exists $args{'part'}) {
139             Carp::croak "'part' field must be 'a', 'o' or 'h'."
140 5 50       14 unless $args{'part'} =~ m/\A[aoh]\z/;
141 5         8 $self->{'part'} = $args{'part'};
142             }
143 6 100       14 if (exists $args{'cpe_version'}) {
144             Carp::croak "only cpe_version 2.2 and 2.3 are accepted"
145 5 50       36 unless $args{'cpe_version'} =~ m/\A2\.[23]\z/;
146 5         11 $self->{'cpe_version'} = $args{'cpe_version'};
147             }
148 6         27 return $self;
149             }
150              
151             1;
152             __END__