/[suikacvs]/messaging/manakai/lib/Message/Field/Params.pm
Suika

Contents of /messaging/manakai/lib/Message/Field/Params.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.2 - (hide annotations) (download)
Sat Mar 23 11:41:36 2002 UTC (22 years, 8 months ago) by wakaba
Branch: MAIN
Changes since 1.1: +7 -4 lines
2002-03-23  wakaba <w@suika.fam.cx>

	* Params.pm, ContentType.pm, ContentDisposition.pm,
	ValueParams.pm: New files.

1 wakaba 1.1
2     =head1 NAME
3    
4     Message::Field::Params Perl module
5    
6     =head1 DESCRIPTION
7    
8     Perl module for parameters field body (such as C<Content-Type:>).
9    
10     =cut
11    
12     package Message::Field::Params;
13     use strict;
14     require 5.6.0;
15     use re 'eval';
16     use vars qw(%DEFAULT %REG $VERSION);
17     $VERSION=do{my @r=(q$Revision: 1.1 $=~/\d+/g);sprintf "%d."."%02d" x $#r,@r};
18    
19     use Carp;
20     use overload '@{}' => sub {shift->_delete_empty()->{param}},
21     '""' => sub {shift->stringify};
22    
23     $REG{WSP} = qr/[\x09\x20]/;
24     $REG{FWS} = qr/[\x09\x20]*/;
25    
26     $REG{comment} = qr/\x28(?:\x5C[\x00-\xFF]|[\x00-\x0C\x0E-\x27\x2A-\x5B\x5D-\xFF]+|(??{$REG{comment}}))*\x29/;
27     $REG{quoted_string} = qr/\x22(?:\x5C[\x00-\xFF]|[\x00-\x0C\x0E-\x21\x23-\x5B\x5D-\xFF])*\x22/;
28     $REG{domain_literal} = qr/\x5B(?:\x5C[\x00-\xFF]|[\x00-\x0C\x0E-\x5A\x5E-\xFF])*\x5D/;
29     $REG{atext} = qr/[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x41-\x5A\x5E-\x7E]+/;
30     $REG{atext_dot} = qr/[\x21\x23-\x27\x2A\x2B\x2D-\x39\x3D\x3F\x41-\x5A\x5E-\x7E]+/;
31     $REG{token} = qr/[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E]+/;
32     $REG{attribute_char} = qr/[\x21\x23-\x24\x26\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E]+/;
33    
34     $REG{param} = qr/(?:$REG{atext_dot}|$REG{quoted_string})(?:$REG{atext_dot}|$REG{quoted_string}|$REG{WSP}|,)*/;
35     ## more naive C<parameter>. (Comma is allowed for RFC 1049)
36     $REG{parameter} = qr/$REG{token}=(?:$REG{token}|$REG{quoted_string})?/;
37     ## as defined by RFC 2045, not RFC 2231.
38    
39     $REG{M_quoted_string} = qr/\x22((?:\x5C[\x00-\xFF]|[\x00-\x0C\x0E-\x21\x23-\x5B\x5D-\xFF])*)\x22/;
40     $REG{M_parameter} = qr/($REG{token})=($REG{token}|$REG{quoted_string})?/;
41     ## as defined by RFC 2045, not RFC 2231.
42     $REG{M_parameter_name} = qr/($REG{attribute_char}+)(?:\*([0-9]+)(\*)?|(\*))/;
43     ## as defined by RFC 2231.
44     $REG{M_parameter_extended_value} = qr/([^']*)'([^']*)'($REG{token}*)/;
45     ## as defined by RFC 2231, but more naive.
46    
47     $REG{NON_atext} = qr/[^\x21\x23-\x27\x2A\x2B\x2D\x2F\x30-\x39\x3D\x3F\x41-\x5A\x5E-\x7E]/;
48     $REG{NON_atext_dot} = qr/[^\x21\x23-\x27\x2A\x2B\x2D-\x39\x3D\x3F\x41-\x5A\x5E-\x7E]/;
49     $REG{NON_token} = qr/[^\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E]/;
50     $REG{NON_attribute_char} = qr/[^\x21\x23-\x24\x26\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E]/;
51    
52    
53     %DEFAULT = (
54 wakaba 1.2 delete_fws => 1,
55 wakaba 1.1 parameter_value_max => 78,
56     use_parameter_extension => -1,
57     );
58    
59     =head2 Message::Field::Params->new ([%option])
60    
61     Returns new Message::Field::Params. Some options can be given as hash.
62    
63     =cut
64    
65     sub new ($;%) {
66     my $class = shift;
67     my $self = bless {option => {@_}}, $class;
68     $self->_initialize_new ();
69     for (keys %DEFAULT) {$self->{option}->{$_} ||= $DEFAULT{$_}}
70     $self;
71     }
72    
73     ## Initialization for new () method.
74     sub _initialize_new ($;%) {
75     my $self = shift;
76     #for (keys %DEFAULT) {$self->{option}->{$_} ||= $DEFAULT{$_}}
77     }
78    
79     =head2 Message::Field::Params->parse ($nantara, [%option])
80    
81     Parse Message::Field::Params and new ContentType instance.
82     Some options can be given as hash.
83    
84     =cut
85    
86     sub parse ($$;%) {
87     my $class = shift;
88     my $body = shift;
89     my $self = bless {option => {@_}}, $class;
90     $self->_initialize_parse ();
91     for (keys %DEFAULT) {$self->{option}->{$_} ||= $DEFAULT{$_}}
92 wakaba 1.2 $body = $self->_delete_comment ($body);
93     $body = $self->_delete_fws ($body) if $self->{option}->{delete_fws}>0;
94 wakaba 1.1 my @b = ();
95     $body =~ s{$REG{FWS}($REG{param})$REG{FWS}(?:;$REG{FWS}|$)}{
96     my $param = $1;
97     push @b, $self->_parse_param ($param);
98     }goex;
99     @b = $self->_restore_param (@b);
100     $self->_save_param (@b);
101     $self;
102     }
103    
104     ## Initialization for parse () method.
105     sub _initialize_parse ($;%) {
106     my $self = shift;
107     #for (keys %DEFAULT) {$self->{option}->{$_} ||= $DEFAULT{$_}}
108     }
109    
110     sub _parse_param ($$) {
111     my $self = shift;
112     my $param = shift;
113     if ($param =~ /^$REG{M_parameter}$/) {
114     my ($name, $value) = (lc $1, $2);
115     my ($seq, $isencoded, $charset, $lang) = (-1, 0, '', '');
116     if ($name =~ /^$REG{M_parameter_name}$/) {
117     ($name, $seq, $isencoded) = ($1, $4?-1:$2, ($3||$4)?1:0);
118     }
119     if ($isencoded && $value =~ /^$REG{M_parameter_extended_value}$/) {
120     ($charset, $lang, $value) = ($1, $2, $3);
121     }
122     return [$name, {value => $value, seq => $seq, is_encoded => $isencoded,
123     charset => $charset, language => $lang, is_parameter => 1}];
124     } else {
125     return [$param, {is_parameter => 0}];
126     }
127     }
128    
129     sub _restore_param ($@) {
130     my $self = shift;
131     my @p = @_;
132     my @ret;
133     my %part;
134     for my $i (@p) {
135     if ($i->[1]->{is_parameter}) {
136     my $p = $i->[1];
137     if ($p->{seq}<0) {
138     my $s = $p->{value};
139     if ($p->{is_encoded}) {
140     $s =~ s/%([0-9A-Fa-f][0-9A-Fa-f])/chr(hex($1))/eg;
141     } else {
142     $s = $self->_unquote_if_quoted_string ($p->{value});
143     }
144     push @ret, [$i->[0], {value => $s, language => $p->{language},
145     charset => $p->{charset}, is_parameter => 1}];
146     } else {
147     $part{$i->[0]}->[$p->{seq}] = {
148     value => $self->_unquote_if_quoted_string ($p->{value}),
149     language => $p->{language}, charset => $p->{charset},
150     is_encoded => $p->{is_encoded}};
151     }
152     } else {push @ret, $i}
153     }
154     for my $name (keys %part) {
155     my $t = join '', map {
156     my $v = $_;
157     my $s = $v->{value};
158     $s =~ s/%([0-9A-Fa-f][0-9A-Fa-f])/chr(hex($1))/eg if $v->{is_encoded};
159     $s;
160     } @{$part{$name}};
161     push @ret, [$name, {value => $t, charset => $part{$name}->[0]->{charset},
162     language => $part{$name}->[0]->{language},
163     is_parameter => 1}];
164     }
165     @ret;
166     }
167    
168     sub _save_param ($@) {
169     my $self = shift;
170     my @p = @_;
171     $self->{param} = \@p;
172     $self;
173     }
174    
175     =head2 $self->add ($name, [$value]. [%option]
176    
177     Adds parameter name=value pair.
178    
179     Example:
180     $self->add (title => 'foo of bar'); ## title="foo of bar"
181     $self->add (subject => 'hogehoge, foo'); ## subject*=''hogehoge%2C%20foo
182     $self->add (foo => 'bar', language => 'en') ## foo*='en'bar
183     $self->add ('text/plain', '', value => 1) ## text/plain
184    
185     This method returns array reference of (name, {value => value, attribute...}).
186    
187     Available options: charset (charset name), language (language tag),
188     value (1/0, see example above).
189    
190     =cut
191    
192     sub add ($$;$%) {
193     my $self = shift;
194     my ($name, $value, %option) = (lc shift, shift, @_);
195     my $p = [$name, {value => $value, charset => $option{charset},
196     is_parameter => 1, language => $option{language}}];
197     $p->[1]->{is_parameter} = 0 if !$value && $option{value}>0;
198     croak "add: \$name contains of non-attribute-char: $name"
199     if $p->[1]->{is_parameter} && $name =~ /$REG{NON_attribute_char}/;
200     $p->[1]->{value} = $self->_param_value ($name => $p->[1]->{value});
201     if ($option{prepend}) {
202     unshift @{$self->{param}}, $p;
203     } else {
204     push @{$self->{param}}, $p;
205     }
206     $p;
207     }
208     sub replace ($$;$%) {
209     my $self = shift;
210     my ($name, $value, %option) = (lc shift, shift, @_);
211     for my $param (@{$self->{param}}) {
212     if ($param->[0] eq $name) {
213     $param->[1] = {value => $value, charset => $option{charset},
214     is_parameter => 1, language => $option{language}};
215     $param->[1]->{is_parameter} = 0 if !$value && $option{value}>0;
216     $param->[1]->{value} = $self->_param_value ($name => $param->[1]->{value});
217     return $param;
218     }
219     }
220     my $p = [$name, {value => $value, charset => $option{charset},
221     is_parameter => 1, language => $option{language}}];
222     $p->[1]->{is_parameter} = 0 if !$value && $option{value}>0;
223     croak "replace: \$name contains of non-attribute-char: $name"
224     if $p->[1]->{is_parameter} && $name =~ /$REG{NON_attribute_char}/;
225     $p->[1]->{value} = $self->_param_value ($name => $p->[1]->{value});
226     push @{$self->{param}}, $p;
227     $p;
228     }
229    
230     sub delete ($$;%) {
231     my $self = shift;
232     my ($name, $index) = (lc shift, shift);
233     my $i = 0;
234     for my $param (@{$self->{param}}) {
235     if ($param->[0] eq $name) {
236     $i++;
237     if ($index == 0 || $i == $index) {
238     undef $param;
239     return $self if $i == $index;
240     }
241     }
242     }
243     $self;
244     }
245    
246     sub count ($;$%) {
247     my $self = shift;
248     my ($name) = (lc shift);
249     unless ($name) {
250     $self->_delete_empty ();
251     return $#{$self->{param}}+1;
252     }
253     my $count = 0;
254     for my $param (@{$self->{param}}) {
255     if ($param->[0] eq $name) {
256     $count++;
257     }
258     }
259     $count;
260     }
261    
262    
263     sub parameter ($$;$) {
264     my $self = shift;
265     my $name = lc shift;
266     my $newvalue = shift;
267     return $self->replace ($name => $newvalue,@_)->[1]->{value} if defined $newvalue;
268     my @ret;
269     for my $param (@{$self->{param}}) {
270     if ($param->[0] eq $name) {
271     unless (wantarray) {
272     $self->{param}->[1]->{value}
273     = $self->_param_value ($name => $self->{param}->[1]->{value});
274     return $param->[1]->{value};
275     } else {
276     $self->{param}->[1]->{value}
277     = $self->_param_value ($name => $self->{param}->[1]->{value});
278     push @ret, $param->[1]->{value};
279     }
280     }
281     }
282     @ret;
283     }
284    
285     sub parameter_name ($$;$) {
286     my $self = shift;
287     my $i = shift;
288     my $newname = shift;
289     if ($newname) {
290     return 0 if $newname =~ /$REG{NON_attribute_char}/;
291     $self->{param}->[$i]->[0] = $newname;
292     }
293     $self->{param}->[$i]->[0];
294     }
295     sub parameter_value ($$;$) {
296     my $self = shift;
297     my $i = shift;
298     my $newvalue = shift;
299     if ($newvalue) {
300     $newvalue = $self->_param_value ($self->{param}->[$i]->[0] => $newvalue);
301     $self->{param}->[$i]->[1]->{value} = $newvalue;
302     }
303     $self->{param}->[$i]->[1]->{value}
304     = $self->_param_value
305     ($self->{param}->[$i]->[0] => $self->{param}->[$i]->[1]->{value});
306     $self->{param}->[$i]->[1]->{value};
307     }
308    
309     ## Hook called before returning C<value>.
310     ## $self->_param_value ($name, $value);
311     sub _param_value ($$$) {$_[2]}
312    
313     sub _delete_empty ($) {
314     my $self = shift;
315     my @ret;
316     for my $param (@{$self->{param}}) {
317     push @ret, $param if $param->[0];
318     }
319     $self->{param} = \@ret;
320     $self;
321     }
322    
323    
324     =head2 $self->stringify ([%option])
325    
326     Returns Message::Field::Params as a string.
327    
328     =head2 $self->as_string ([%option])
329    
330     An alias of C<stringify>.
331    
332     =cut
333    
334     sub stringify ($;%) {
335     my $self = shift;
336     my %option = @_;
337     my $use_xparam = $option{use_parameter_extension}
338     || $self->{option}->{use_parameter_extension};
339     $option{parameter_value_max}
340     ||= $self->{option}->{parameter_value_max};
341     $self->_delete_empty ();
342     join '; ',
343     map {
344     my $v = $_->[1];
345     my $new = '';
346     if ($v->{is_parameter}) {
347     my ($encoded, @value) = (0, '');
348 wakaba 1.2 if ($use_xparam>0 && ($v->{charset} || $v->{language}
349     || $v->{value} =~ /[\x00\x0D\x0A\x80-\xFF]/)) {
350 wakaba 1.1 my ($charset, $lang);
351     $encoded = 1;
352     ($charset, $lang) = ($v->{charset}, $v->{language});
353     ## Note: %-quoting for charset and for language is not allowed.
354     ## But charset name can be included non-sttribute-char such as "'".
355     ## How can we treat this?
356     $charset =~ s/($REG{NON_attribute_char})/sprintf('%%%02X', ord $1)/ge;
357     $lang =~ s/($REG{NON_attribute_char})/sprintf('%%%02X', ord $1)/ge;
358     if (length $v->{value} > $option{parameter_value_max}) {
359     for my $i (0..length ($v->{value})/$option{parameter_value_max}) {
360     $value[$i] = substr ($v->{value}, $i*$option{parameter_value_max},
361     $option{parameter_value_max});
362     }
363     } else {$value[0] = $v->{value}}
364     for my $i (0..$#value) {
365     $value[$i] =~ s/($REG{NON_attribute_char})/sprintf('%%%02X', ord $1)/ge;
366     }
367     $value[0] = "${charset}'${lang}'".$value[0];
368     } elsif (length $v->{value} == 0) {
369     $value[0] = '""';
370     } else {
371     if ($use_xparam>0 && length $v->{value} > $option{parameter_value_max}) {
372     for my $i (0..length ($v->{value})/$option{parameter_value_max}) {
373     $value[$i] = $self->_quote_unsafe_string
374     (substr ($v->{value}, $i*$option{parameter_value_max},
375     $option{parameter_value_max}), unsafe => 'NON_attribute_char');
376     }
377     } else {
378     $value[0] = $self->_quote_unsafe_string
379     ($v->{value}, unsafe => 'NON_attribute_char');
380     }
381     }
382     ## Note: quoted-string for parameter name is not allowed.
383     ## But it is better than output bare non-atext.
384     if ($#value == 0) {
385     $new =
386     $self->_quote_unsafe_string ($_->[0], unsafe => 'NON_attribute_char')
387     .($encoded?'*':'').'='.$value[0];
388     } else {
389     my @new;
390     my $name = $self->_quote_unsafe_string
391     ($_->[0], unsafe => 'NON_attribute_char');
392     for my $i (0..$#value) {
393     push @new, $name.'*'.$i.($encoded?'*':'').'='.$value[$i];
394     }
395     $new = join '; ', @new;
396     }
397     } else {
398     $new = $self->_quote_unsafe_string ($_->[0], unsafe => 'NON_token');
399     }
400     $new;
401     } @{$self->{param}}
402     ;
403     }
404     sub as_string ($;%) {shift->stringify (@_)}
405    
406     =head2 $self->option ($option_name)
407    
408     Returns/set (new) value of the option.
409    
410     =cut
411    
412     sub option ($$;$) {
413     my $self = shift;
414     my ($name, $newval) = @_;
415     if ($newval) {
416     $self->{option}->{$name} = $newval;
417     }
418     $self->{option}->{$name};
419     }
420    
421     sub _quote_unsafe_string ($$;%) {
422     my $self = shift;
423     my $string = shift;
424     my %option = @_;
425     $option{unsafe} ||= 'NON_atext_dot';
426     if ($string =~ /$REG{$option{unsafe}}/ || $string =~ /$REG{WSP}$REG{WSP}+/) {
427     $string =~ s/([\x22\x5C])/\x5C$1/g;
428     $string = '"'.$string.'"';
429     }
430     $string;
431     }
432    
433     =head2 $self->_unquote_quoted_string ($string)
434    
435     Unquote C<quoted-string>. Get rid of C<DQUOTE>s and
436     C<REVERSED SOLIDUS> included in C<quoted-pair>.
437     This method is intended for internal use.
438    
439     =cut
440    
441     sub _unquote_quoted_string ($$) {
442     my $self = shift;
443     my $quoted_string = shift;
444     $quoted_string =~ s{$REG{M_quoted_string}}{
445     my $qtext = $1;
446     $qtext =~ s/\x5C([\x00-\xFF])/$1/g;
447     $qtext;
448     }goex;
449     $quoted_string;
450     }
451    
452     sub _unquote_if_quoted_string ($$) {
453     my $self = shift;
454     my $quoted_string = shift;
455     $quoted_string =~ s{^$REG{M_quoted_string}$}{
456     my $qtext = $1;
457     $qtext =~ s/\x5C([\x00-\xFF])/$1/g;
458     $qtext;
459     }goex;
460     $quoted_string;
461     }
462    
463     =head2 $self->_delete_comment ($field_body)
464    
465     Remove all C<comment> in given strictured C<field-body>.
466     This method is intended for internal use.
467    
468     =cut
469    
470     sub _delete_comment ($$) {
471     my $self = shift;
472     my $body = shift;
473     $body =~ s{($REG{quoted_string}|$REG{domain_literal})|$REG{comment}}{
474     my $o = $1; $o? $o : ' ';
475     }gex;
476     $body;
477     }
478    
479     sub _delete_fws ($$) {
480     my $self = shift;
481     my $body = shift;
482 wakaba 1.2 $body =~ s{($REG{quoted_string}|$REG{domain_literal}|$REG{attribute_char}$REG{WSP}+$REG{attribute_char})|$REG{WSP}+}{
483 wakaba 1.1 my $o = $1; $o? $o : '';
484     }gex;
485     $body;
486     }
487    
488     =head1 LICENSE
489    
490     Copyright 2002 wakaba E<lt>w@suika.fam.cxE<gt>.
491    
492     This program is free software; you can redistribute it and/or modify
493     it under the terms of the GNU General Public License as published by
494     the Free Software Foundation; either version 2 of the License, or
495     (at your option) any later version.
496    
497     This program is distributed in the hope that it will be useful,
498     but WITHOUT ANY WARRANTY; without even the implied warranty of
499     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
500     GNU General Public License for more details.
501    
502     You should have received a copy of the GNU General Public License
503     along with this program; see the file COPYING. If not, write to
504     the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
505     Boston, MA 02111-1307, USA.
506    
507     =head1 CHANGE
508    
509     See F<ChangeLog>.
510 wakaba 1.2 $Date: 2002/03/23 10:41:33 $
511 wakaba 1.1
512     =cut
513    
514     1;

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24