6 |
|
|
7 |
=cut |
=cut |
8 |
|
|
9 |
|
require 5.6.0; |
10 |
package Message::Field::UA; |
package Message::Field::UA; |
11 |
use strict; |
use strict; |
12 |
use vars qw(@ISA %REG $VERSION); |
use re 'eval'; |
13 |
|
use vars qw(%DEFAULT @ISA %REG $VERSION); |
14 |
$VERSION=do{my @r=(q$Revision$=~/\d+/g);sprintf "%d."."%02d" x $#r,@r}; |
$VERSION=do{my @r=(q$Revision$=~/\d+/g);sprintf "%d."."%02d" x $#r,@r}; |
|
require Message::Util; |
|
15 |
require Message::Field::Structured; |
require Message::Field::Structured; |
16 |
push @ISA, qw(Message::Field::Structured); |
push @ISA, qw(Message::Field::Structured); |
17 |
use overload '""' => sub { $_[0]->stringify }, |
use overload '.=' => sub { |
|
'@{}' => sub { $_[0]->product }, |
|
|
'.=' => sub { |
|
18 |
if (ref $_[1] eq 'HASH') { |
if (ref $_[1] eq 'HASH') { |
19 |
$_[0]->add (%{$_[1]}); |
$_[0]->add (%{$_[1]}); |
20 |
} elsif (ref $_[1] eq 'ARRAY') { |
} elsif (ref $_[1] eq 'ARRAY') { |
26 |
}, |
}, |
27 |
fallback => 1; |
fallback => 1; |
28 |
|
|
29 |
%REG = %Message::Util::REG; |
*REG = \%Message::Util::REG; |
30 |
$REG{product} = qr#(?:$REG{http_token}|$REG{quoted_string})(?:$REG{FWS}/$REG{FWS}(?:$REG{http_token}|$REG{quoted_string}))?#; |
|
31 |
$REG{M_product} = qr#($REG{http_token}|$REG{quoted_string})(?:$REG{FWS}/$REG{FWS}($REG{http_token}|$REG{quoted_string}))?#; |
## Initialize of this class -- called by constructors |
32 |
|
%DEFAULT = ( |
33 |
|
-_HASH_NAME => 'product', |
34 |
|
-_METHODS => [qw|add count delete item|], |
35 |
|
-_MEMBERS => [qw|product|], |
36 |
|
-by => 'product-name', ## Default key for item, delete,... |
37 |
|
#encoding_after_encode |
38 |
|
#encoding_before_decode |
39 |
|
#field_param_name |
40 |
|
#field_name |
41 |
|
#format |
42 |
|
#hook_encode_string |
43 |
|
#hook_decode_string |
44 |
|
-prepend => 1, ## For add, replace |
45 |
|
-use_Config => 1, |
46 |
|
-use_comment => 1, |
47 |
|
#-use_quoted_string => 1, |
48 |
|
-use_Win32 => 1, |
49 |
|
); |
50 |
|
|
51 |
=head1 CONSTRUCTORS |
=head1 CONSTRUCTORS |
52 |
|
|
60 |
sub _init ($;%) { |
sub _init ($;%) { |
61 |
my $self = shift; |
my $self = shift; |
62 |
my %options = @_; |
my %options = @_; |
|
my %DEFAULT = ( |
|
|
#encoding_after_encode ## Inherited |
|
|
#encoding_before_decode ## Inherited |
|
|
-field_name => 'user-agent', |
|
|
#format ## Inherited |
|
|
#hook_encode_string ## Inherited |
|
|
#hook_decode_string ## Inherited |
|
|
-prepend => 1, |
|
|
-use_Config => 1, |
|
|
-use_Win32 => 1, |
|
|
); |
|
63 |
$self->SUPER::_init (%DEFAULT, %options); |
$self->SUPER::_init (%DEFAULT, %options); |
64 |
|
|
65 |
|
unless (defined $self->{option}->{use_quoted_string}) { |
66 |
|
if ($self->{option}->{format} =~ /http/) { |
67 |
|
$self->{option}->{use_quoted_string} = 0; |
68 |
|
} else { |
69 |
|
$self->{option}->{use_quoted_string} = 1; |
70 |
|
} |
71 |
|
} |
72 |
|
|
73 |
my @a = (); |
my @a = (); |
74 |
for (grep {/^[^-]/} keys %options) { |
for (grep {/^[^-]/} keys %options) { |
75 |
push @a, $_ => $options{$_}; |
push @a, $_ => $options{$_}; |
106 |
}goex; |
}goex; |
107 |
''; |
''; |
108 |
}goex; |
}goex; |
109 |
$field_body =~ s{$REG{M_product}((?:$REG{FWS}$REG{comment})*)}{ |
$field_body =~ s{ |
110 |
my ($product, $product_version, $comments) = ($1, $2, $3); |
($REG{quoted_string}|[^\x09\x20\x22\x28\x2F]+) ## product-name |
111 |
|
(?: |
112 |
|
((?:$REG{FWS}$REG{comment})*)$REG{FWS} |
113 |
|
/ |
114 |
|
((?:$REG{FWS}$REG{comment})*)$REG{FWS} |
115 |
|
($REG{quoted_string}|[^\x09\x20\x22\x28]+) ## product-version |
116 |
|
)? |
117 |
|
((?:$REG{FWS}$REG{comment})*) ## comment |
118 |
|
}{ |
119 |
|
my ($product, $product_version, $comments) = ($1, $4, $2.$3.$5); |
120 |
for ($product, $product_version) { |
for ($product, $product_version) { |
121 |
my ($s,$q) = (Message::Util::unquote_if_quoted_string ($_), 0); |
my ($s,$q) = (Message::Util::unquote_if_quoted_string ($_), 0); |
122 |
my %s = &{$self->{option}->{hook_decode_string}} ($self, $s, |
my %s = &{$self->{option}->{hook_decode_string}} ($self, $s, |
141 |
|
|
142 |
=over 4 |
=over 4 |
143 |
|
|
|
=item $self->stringify () |
|
|
|
|
|
Returns C<field-body> as a string. |
|
|
|
|
|
=cut |
|
|
|
|
|
sub stringify ($;%) { |
|
|
my $self = shift; |
|
|
my %option = @_; |
|
|
$option{format} ||= $self->{option}->{format}; |
|
|
my @r = (); |
|
|
for my $p (@{$self->{product}}) { |
|
|
if ($p->{name}) { |
|
|
if ($option{format} eq 'http' |
|
|
&& ( $p->{name} =~ /$REG{NON_http_token}/ |
|
|
|| $p->{version} =~ /$REG{NON_http_token}/)) { |
|
|
my $f = $p->{name}; |
|
|
$f .= '/'.$p->{version} if $p->{version}; |
|
|
push @r, '('. $self->encode_ccontent ($f) .')'; |
|
|
} else { |
|
|
my %e = &{$self->{option}->{hook_encode_string}} ($self, |
|
|
$p->{name}, type => 'token'); |
|
|
my %f = &{$self->{option}->{hook_encode_string}} ($self, |
|
|
$p->{version}, type => 'token'); |
|
|
push @r, |
|
|
Message::Util::quote_unsafe_string ($e{value}, unsafe => 'NON_http_token') |
|
|
.($f{value} ? '/' |
|
|
.Message::Util::quote_unsafe_string ($f{value}, unsafe => 'NON_http_token') |
|
|
:''); |
|
|
} |
|
|
} elsif ($p->{version}) { ## Error! |
|
|
push @r, '('. $self->Message::Util::encode_ccontent ($p->{version}) .')'; |
|
|
} |
|
|
for (@{$p->{comment}}) { |
|
|
push @r, '('. $self->Message::Util::encode_ccontent ($_) .')' if $_; |
|
|
} |
|
|
} |
|
|
join ' ', @r; |
|
|
} |
|
|
*as_string = \&stringify; |
|
|
|
|
|
=item $array = $self->product |
|
|
|
|
|
Returns array reference of C<product>s. Each of array elements |
|
|
are hash reference, and it has three key: C<name>, C<version>, |
|
|
and C<comment>. C<comment> is array reference. |
|
|
|
|
|
Example: |
|
|
|
|
|
my $p = $ua->product->[0]; |
|
|
printf "%s\t%s\t%s\n", $p->{name}, $p->{version}, join ('; ', @{$p->{comment}}); |
|
|
|
|
|
=cut |
|
|
|
|
|
sub product ($;%) { |
|
|
my $self = shift; |
|
|
$self->_delete_empty; |
|
|
$self->{product}; |
|
|
} |
|
|
|
|
|
=item $name = $ua->product_name ($index) |
|
|
|
|
|
=item $version = $ua->product_version ($index) |
|
|
|
|
|
Returns product-name/-version of C<$index>'th C<product>. |
|
|
|
|
|
=cut |
|
|
|
|
|
sub product_name ($;$%) { |
|
|
my $self = shift; |
|
|
my $index = shift; |
|
|
$self->{product}->[$index]->{product} if ref $self->{product}->[$index]; |
|
|
} |
|
|
|
|
|
sub product_version ($;$%) { |
|
|
my $self = shift; |
|
|
my $index = shift; |
|
|
$self->{product}->[$index]->{product_version} if ref $self->{product}->[$index]; |
|
|
} |
|
|
|
|
|
=item $comment_ref = $ua->product_comment ($index) |
|
|
|
|
|
Returns array reference of C<comment> of C<$index>'th C<product>. |
|
|
(You can edit this array.) |
|
|
|
|
144 |
=cut |
=cut |
145 |
|
|
|
sub product_comment ($;$%) { |
|
|
my $self = shift; |
|
|
my $index = shift; |
|
|
$self->{product}->[$index]->{comment} if ref $self->{product}->[$index]; |
|
|
} |
|
146 |
|
|
147 |
=item $hdr->add ($name, $version, [$name, $version, ...]) |
=item $hdr->add ($name, $version, [$name, $version, ...]) |
148 |
|
|
164 |
|
|
165 |
=cut |
=cut |
166 |
|
|
167 |
sub add ($%) { |
sub _add_hash_check ($$$\%) { |
168 |
my $self = shift; |
my $self = shift; |
169 |
my %products = @_; |
my ($name, $version, $option) = @_; |
170 |
my %option = %{$self->{option}}; |
my @comment; |
171 |
for (grep {/^-/} keys %products) {$option{substr ($_, 1)} = $products{$_}} |
if (ref $version eq 'ARRAY') { |
172 |
for (grep {/^[^-]/} keys %products) { |
($version, @comment) = @$version; |
|
my $name = $_; |
|
|
my ($ver, $comment); |
|
|
if (ref $products{$_} eq 'ARRAY') { |
|
|
$ver = shift @{$products{$_}}; |
|
|
$comment = $products{$_}; |
|
|
} else { |
|
|
$ver = $products{$_}; |
|
|
$comment = []; |
|
|
} |
|
|
## BUG: binary unsafe:-) (ISO-2022-KR, UCS-2,... also can't treat) |
|
|
if ($ver =~ /[\x00-\x08\x0B\x0E-\x1A\x1C-\x1F\x7F]/) { |
|
|
$ver = sprintf '%vd', $ver; |
|
|
} |
|
|
if ($option{prepend}) { |
|
|
unshift @{$self->{product}}, {name => $name, version => $ver, |
|
|
comment => $comment}; |
|
|
} else { |
|
|
push @{$self->{product}}, {name => $name, version => $ver, |
|
|
comment => $comment}; |
|
|
} |
|
173 |
} |
} |
174 |
|
|
175 |
|
## Convert vX.Y.Z value to string (But there is no way to be sure that |
176 |
|
## the value is a version value.) |
177 |
|
#$^V gt v5.6.0 && ## <- This check itself doesn't work before v5.6.0:) |
178 |
|
if ($version =~ /[\x00-\x1F]/) { |
179 |
|
$version = sprintf '%vd', $version; |
180 |
|
} |
181 |
|
|
182 |
|
(1, $name => { |
183 |
|
name => $name, |
184 |
|
version => $version, |
185 |
|
comment => \@comment, |
186 |
|
}); |
187 |
|
} |
188 |
|
|
189 |
|
*_add_return_value = \&_replace_return_value; |
190 |
|
|
191 |
|
## (1/0, $name => $value) = $self->_replace_hash_check ($name => $value, \%option) |
192 |
|
## -- Checks given value and prepares saving value (hash version) |
193 |
|
*_replace_hash_check = \&_add_hash_check; |
194 |
|
|
195 |
|
|
196 |
|
## $value = $self->_replace_hash_shift (\%values, $name, $option) |
197 |
|
## -- Returns a value (from %values) and deletes it from %values |
198 |
|
## (like CORE::shift for array). |
199 |
|
sub _replace_hash_shift ($\%$\%) { |
200 |
|
shift; my $r = shift; my $n = $_[0]->{name}; |
201 |
|
if ($$r{$n}) { |
202 |
|
my $d = $$r{$n}; |
203 |
|
$$r{$n} = undef; |
204 |
|
return $d; |
205 |
|
} |
206 |
|
undef; |
207 |
} |
} |
208 |
|
|
209 |
=item $hdr->replace ($field-name, $field-body, [$name, $body, ...]) |
## $value = $self->_replace_return_value (\$item, \%option) |
210 |
|
## -- Returns returning value of replace method |
211 |
Adds some field name/body pairs. If there are already |
sub _replace_return_value ($\$\%) { |
212 |
one or more field with name of C<$field-name>, it is replaced |
my $self = shift; |
213 |
by new one. |
my ($item, $value) = @_; |
214 |
|
$$item; |
215 |
|
} |
216 |
|
|
217 |
Instead of C<$version>, you can pass array reference. |
## 1/0 = $self->_delete_match ($by, \$item, \%delete_list, \%option) |
218 |
[0] is used for C<version>, the others are saved as elements |
## -- Checks and returns whether given item is matched with |
219 |
of C<comment>. |
## deleting item list |
220 |
|
sub _delete_match ($$\$\%\%) { |
221 |
|
my $self = shift; |
222 |
|
my ($by, $item, $list, $option) = @_; |
223 |
|
return 0 unless ref $$item; ## Already removed |
224 |
|
if ($by eq 'name') { |
225 |
|
return 1 if $$list{ $$item->{name} }; |
226 |
|
} elsif ($by eq 'version') { |
227 |
|
return 1 if $$list{ $$item->{version} }; |
228 |
|
} |
229 |
|
0; |
230 |
|
} |
231 |
|
|
232 |
C<-prepend> options is available. C<1> is default. |
## Delete empty items |
233 |
|
sub _delete_empty ($) { |
234 |
|
my $self = shift; |
235 |
|
my $array = $self->{option}->{_HASH_NAME}; |
236 |
|
$self->{ $array } = [grep { ref $_ } @{$self->{ $array }}] if $array; |
237 |
|
} |
238 |
|
|
239 |
=cut |
*_item_match = \&_delete_match; |
240 |
|
*_item_return_value = \&_replace_return_value; |
241 |
|
|
242 |
sub replace ($%) { |
## $item = $self->_item_new_value ($name, \%option) |
243 |
my $self = shift; |
## -- Returns new item with key of $name (called when |
244 |
my %params = @_; |
## no returned value is found and -new_value_unless_exist |
245 |
my %option = %{$self->{option}}; |
## option is true) |
246 |
for (grep {/^-/} keys %params) {$option{substr ($_, 1)} = $params{$_}} |
sub _item_new_value ($$\%) { |
247 |
my (%new_product); |
my $self = shift; |
248 |
for (grep {/^[^-]/} keys %params) { |
my ($key, $option) = @_; |
249 |
my $name = $_; |
if ($option->{by} eq 'name') { |
250 |
my ($ver, $comment); |
return {name => $key, version => '', comment => []}; |
251 |
if (ref $params{$_} eq 'ARRAY') { |
} elsif ($option->{by} eq 'version') { |
252 |
$ver = shift @{$params{$_}}; |
return {name => '', version => $key, comment => []}; |
|
$comment = $params{$_}; |
|
|
} else { |
|
|
$ver = $params{$_}; |
|
|
$comment = []; |
|
|
} |
|
|
## BUG: binary unsafe:-) (ISO-2022-KR, UCS-2,... also can't treat) |
|
|
if ($ver =~ /[\x00-\x08\x0B\x0E-\x1A\x1C-\x1F\x7F]/) { |
|
|
$ver = sprintf '%vd', $ver; |
|
|
} |
|
|
$new_product{$name} = {name => $name, version => $ver, comment => $comment}; |
|
|
} |
|
|
for my $product (@{$self->{product}}) { |
|
|
if ($product->{name} && defined $new_product{$product->{name}}) { |
|
|
$product = $new_product {$product->{name}}; |
|
|
$new_product{$product->{name}} = undef; |
|
|
} |
|
|
} |
|
|
for (keys %new_product) { |
|
|
push @{$self->{product}}, $new_product{$_}; |
|
253 |
} |
} |
254 |
|
undef; |
255 |
} |
} |
256 |
|
|
257 |
=item $ua->delete ($name, [$name, $name,...]); |
## TODO: Implement count,item_exist method |
258 |
|
|
259 |
Deletes C<product>s whose name is C<$name>. |
=item $self->stringify () |
260 |
|
|
261 |
|
Returns C<field-body> as a string. |
262 |
|
|
263 |
=cut |
=cut |
264 |
|
|
265 |
sub delete ($@) { |
sub stringify ($;%) { |
266 |
my $self = shift; |
my $self = shift; |
267 |
my %delete; for (@_) {$delete{$_} = 1} |
my %o = @_; my %option = %{$self->{option}}; |
268 |
for my $product (@{$self->{product}}) { |
for (grep {/^-/} keys %o) {$option{substr ($_, 1)} = $o{$_}} |
269 |
undef $product if $delete{$product->{name}}; |
my @r = (); |
270 |
|
for my $p (@{$self->{product}}) { |
271 |
|
if (length $p->{name}) { |
272 |
|
my %name = &{$self->{option}->{hook_encode_string}} ($self, |
273 |
|
$p->{name}, type => 'token'); |
274 |
|
my %version = &{$self->{option}->{hook_encode_string}} ($self, |
275 |
|
$p->{version}, type => 'token'); |
276 |
|
if (!$option{use_quoted_string} |
277 |
|
&& ( $name{value} =~ /$REG{NON_http_token}/ |
278 |
|
|| $version{value} =~ /$REG{NON_http_token}/)) { |
279 |
|
if ($name{value} =~ /$REG{NON_http_token}/) { |
280 |
|
## Both of name & version are unsafe |
281 |
|
push @r, '(' . Message::Util::quote_ccontent ( |
282 |
|
$name{value} . |
283 |
|
(length $version{value}? '/' . $version{value} : '') |
284 |
|
) . ')'; |
285 |
|
} else { |
286 |
|
## Only version is unsafe |
287 |
|
push @r, $name{value} |
288 |
|
.' (' . Message::Util::quote_ccontent ($version{value}) . ')'; |
289 |
|
} |
290 |
|
} else { |
291 |
|
push @r, |
292 |
|
Message::Util::quote_unsafe_string |
293 |
|
($name{value}, unsafe => 'NON_http_token') |
294 |
|
.(length $version{value} ? |
295 |
|
'/' . Message::Util::quote_unsafe_string |
296 |
|
($version{value}, unsafe => 'NON_http_token') : ''); |
297 |
|
} |
298 |
|
} elsif ($p->{version}) { |
299 |
|
## There is no product-name but the product-version. It's error! |
300 |
|
push @r, '('. $self->Message::Util::encode_ccontent ($p->{version}) .')'; |
301 |
|
} |
302 |
|
## If there are some additional information, |
303 |
|
for (@{$p->{comment}}) { |
304 |
|
push @r, '('. $self->Message::Util::encode_ccontent ($_) .')' if $_; |
305 |
|
} |
306 |
} |
} |
307 |
|
join ' ', @r; |
308 |
} |
} |
309 |
|
*as_string = \&stringify; |
|
sub _delete_empty ($) { |
|
|
my $self = shift; |
|
|
my @nid; |
|
|
for my $id (@{$self->{product}}) {push @nid, $id if ref $id} |
|
|
$self->{product} = \@nid; |
|
|
} |
|
310 |
|
|
311 |
=item $option-value = $ua->option ($option-name) |
=item $option-value = $ua->option ($option-name) |
312 |
|
|
327 |
|
|
328 |
=cut |
=cut |
329 |
|
|
330 |
sub clone ($) { |
## Inherited |
|
my $self = shift; |
|
|
$self->_delete_empty; |
|
|
my $clone = $self->SUPER::clone; |
|
|
my @p; |
|
|
for (@{$self->{product}}) { |
|
|
my $name = ref $_->{name}? $_->{name}->clone: $_->{name}; |
|
|
my $ver = ref $_->{version}? $_->{version}->clone: $_->{version}; |
|
|
my @comment; |
|
|
for (@{$_->{comment}}) { |
|
|
push @comment, ref $_? $_->clone: $_; |
|
|
} |
|
|
push @p, {name => $name, version => $ver, comment => \@comment}; |
|
|
} |
|
|
$clone->{product} = \@p; |
|
|
$clone; |
|
|
} |
|
331 |
|
|
332 |
sub add_our_name ($;%) { |
sub add_our_name ($;%) { |
333 |
my $ua = shift; |
my $ua = shift; |