File Coverage

blib/lib/MIME/Field/ParamVal.pm
Criterion Covered Total %
statement 111 122 90.9
branch 47 62 75.8
condition 7 12 58.3
subroutine 14 15 93.3
pod 7 9 77.7
total 186 220 84.5


line stmt bran cond sub pod time code
1             package MIME::Field::ParamVal;
2              
3 27     27   240577 use MIME::Words;
  27         63  
  27         2066  
4              
5             =head1 NAME
6              
7             MIME::Field::ParamVal - subclass of Mail::Field, for structured MIME fields
8              
9              
10             =head1 SYNOPSIS
11              
12             # Create an object for a content-type field:
13             $field = new Mail::Field 'Content-type';
14              
15             # Set some attributes:
16             $field->param('_' => 'text/html');
17             $field->param('charset' => 'us-ascii');
18             $field->param('boundary' => '---ABC---');
19              
20             # Same:
21             $field->set('_' => 'text/html',
22             'charset' => 'us-ascii',
23             'boundary' => '---ABC---');
24              
25             # Get an attribute, or undefined if not present:
26             print "no id!" if defined($field->param('id'));
27              
28             # Same, but use empty string for missing values:
29             print "no id!" if ($field->paramstr('id') eq '');
30              
31             # Output as string:
32             print $field->stringify, "\n";
33              
34              
35             =head1 DESCRIPTION
36              
37             This is an abstract superclass of most MIME fields. It handles
38             fields with a general syntax like this:
39              
40             Content-Type: Message/Partial;
41             number=2; total=3;
42             id="oc=jpbe0M2Yt4s@thumper.bellcore.com"
43              
44             Comments are supported I items, like this:
45              
46             Content-Type: Message/Partial; (a comment)
47             number=2 (another comment) ; (yet another comment) total=3;
48             id="oc=jpbe0M2Yt4s@thumper.bellcore.com"
49              
50              
51             =head1 PUBLIC INTERFACE
52              
53             =over 4
54              
55             =cut
56              
57             #------------------------------
58              
59             require 5.001;
60              
61             # Pragmas:
62 27     27   158 use strict;
  27         49  
  27         943  
63 27     27   137 use re 'taint';
  27         46  
  27         1437  
64 27     27   181 use vars qw($VERSION @ISA);
  27         59  
  27         1857  
65              
66              
67             # Other modules:
68 27     27   3194 use Mail::Field;
  27         10272  
  27         277  
69              
70             # Kit modules:
71 27     27   628333 use MIME::Tools qw(:config :msgs);
  27         87  
  27         56400  
72              
73             @ISA = qw(Mail::Field);
74              
75              
76             #------------------------------
77             #
78             # Public globals...
79             #
80             #------------------------------
81              
82             # The package version, both in 1.23 style *and* usable by MakeMaker:
83             $VERSION = "5.517";
84              
85              
86             #------------------------------
87             #
88             # Private globals...
89             #
90             #------------------------------
91              
92             # Pattern to match parameter names (like fieldnames, but = not allowed):
93             my $PARAMNAME = '[^\x00-\x1f\x80-\xff :=]+';
94              
95             # Pattern to match the first value on the line:
96             my $FIRST = '[^\s\;\x00-\x1f\x80-\xff]*';
97              
98             # Pattern to match an RFC 2045 token:
99             #
100             # token = 1*
101             #
102             my $TSPECIAL = '()<>@,;:\
103              
104             #" Fix emacs highlighting...
105              
106             my $TOKEN = '[^ \x00-\x1f\x80-\xff' . "\Q$TSPECIAL\E" . ']+';
107              
108             my $QUOTED_STRING = '"([^\\\\"]*(?:\\\\.(?:[^\\\\"]*))*)"';
109              
110             # Encoded token:
111             my $ENCTOKEN = "=\\?[^?]*\\?[A-Za-z]\\?[^?]+\\?=";
112              
113             # Pattern to match spaces or comments:
114             my $SPCZ = '(?:\s|\([^\)]*\))*';
115              
116             # Pattern to match non-semicolon as fallback for broken MIME
117             # produced by some viruses
118             my $BADTOKEN = '[^;]+';
119              
120             #------------------------------
121             #
122             # Class init...
123             #
124             #------------------------------
125              
126             #------------------------------
127              
128             =item set [\%PARAMHASH | KEY=>VAL,...,KEY=>VAL]
129              
130             I Set this field.
131             The paramhash should contain parameter names
132             in I, with the special C<"_"> parameter name
133             signifying the "default" (unnamed) parameter for the field:
134              
135             # Set up to be...
136             #
137             # Content-type: Message/Partial; number=2; total=3; id="ocj=pbe0M2"
138             #
139             $conttype->set('_' => 'Message/Partial',
140             'number' => 2,
141             'total' => 3,
142             'id' => "ocj=pbe0M2");
143              
144             Note that a single argument is taken to be a I to
145             a paramhash, while multiple args are taken to be the elements
146             of the paramhash themselves.
147              
148             Supplying undef for a hashref, or an empty set of values, effectively
149             clears the object.
150              
151             The self object is returned.
152              
153             =cut
154              
155             sub set {
156 2719     2719 1 235206 my $self = shift;
157 2719 50 50     8913 my $params = ((@_ == 1) ? (shift || {}) : {@_});
158 2719         10477 %$self = %$params; # set 'em
159 2719         10723 $self;
160             }
161              
162             #------------------------------
163              
164             =item parse_params STRING
165              
166             I
167             Extract parameter info from a structured field, and return
168             it as a hash reference. For example, here is a field with parameters:
169              
170             Content-Type: Message/Partial;
171             number=2; total=3;
172             id="oc=jpbe0M2Yt4s@thumper.bellcore.com"
173              
174             Here is how you'd extract them:
175              
176             $params = $class->parse_params('content-type');
177             if ($$params{'_'} eq 'message/partial') {
178             $number = $$params{'number'};
179             $total = $$params{'total'};
180             $id = $$params{'id'};
181             }
182              
183             Like field names, parameter names are coerced to lowercase.
184             The special '_' parameter means the default parameter for the
185             field.
186              
187             B This has been provided as a public method to support backwards
188             compatibility, but you probably shouldn't use it.
189              
190             =cut
191              
192             sub rfc2231decode {
193 18     18 0 40 my($val) = @_;
194 18         36 my($enc, $lang, $rest);
195              
196 18         194 local($1,$2,$3);
197 18 50       122 if ($val =~ m/^([^']*)'([^']*)'(.*)\z/s) {
    0          
198 18         43 $enc = $1;
199 18         36 $lang = $2;
200 18         41 $rest = $3;
201             } elsif ($val =~ m/^([^']*)'([^']*)\z/s) {
202 0         0 $enc = $1;
203 0         0 $rest = $2;
204             } else {
205 0         0 $rest = $val;
206             # $enc remains undefined when charset/language info is missing
207             }
208 18         167 return ($enc, $lang, $rest);
209             }
210              
211             sub rfc2231percent {
212             # Do percent-substitution
213 18     18 0 42 my($str) = @_;
214 18         48 local $1;
215 18         96 $str =~ s/%([0-9a-fA-F]{2})/pack("C", hex($1))/ge;
  187         699  
216 18         102 return $str;
217             }
218              
219             sub parse_params {
220 2651     2651 1 5085 my ($self, $raw) = @_;
221 2651         17356 my %params;
222             my %dup_params;
223 2651         0 my %empty_params;
224 2651         0 my %rfc2231params;
225 2651         0 my %rfc2231encoding_is_used;
226 2651         0 my $param;
227 2651         0 my $val;
228 2651         0 my $part;
229              
230             # Get raw field, and unfold it:
231 2651 100       6155 defined($raw) or $raw = '';
232 2651         11089 $raw =~ s/\n//g;
233 2651         7056 $raw =~ s/\s+\z//; # Strip trailing whitespace
234              
235 2651         16306 local($1,$2,$3,$4,$5);
236             # Extract special first parameter:
237 2651 50       17796 $raw =~ m/\A$SPCZ($FIRST)$SPCZ/og or return {}; # nada!
238 2651         8680 $params{'_'} = $1;
239              
240             # Extract subsequent parameters.
241             # No, we can't just "split" on semicolons: they're legal in quoted strings!
242 2651         3915 while (1) { # keep chopping away until done...
243 3948 100       15625 $raw =~ m/\G[^;]*(\;$SPCZ)+/og or last; # skip leading separator
244 1336 100       6358 $raw =~ m/\G($PARAMNAME)\s*=\s*/og or last; # give up if not a param
245 1297         3402 $param = lc($1);
246 1297 50       16304 $raw =~ m/\G(?:$QUOTED_STRING|($ENCTOKEN)|($TOKEN)|($BADTOKEN)|())/g or last;
247 1297         6515 my ($qstr, $enctoken, $token, $badtoken, $empty_value) = ($1, $2, $3, $4, $5);
248 1297 100       3121 if (defined($qstr)) {
249             # unescape
250 774         1609 $qstr =~ s/\\(.)/$1/g;
251             }
252 1297 50       2635 if (defined($badtoken)) {
253             # Strip leading/trailing whitespace from badtoken
254 0         0 $badtoken =~ s/^\s+//;
255 0         0 $badtoken =~ s/\s+\z//;
256              
257             # Only keep token parameters in badtoken;
258             # cut it off at the first non-token char. CPAN RT #105455
259 0         0 $badtoken =~ /^($TOKEN)*/;
260 0         0 $badtoken = $1;
261 0 0       0 if (defined($badtoken)) {
262             # Cut it off at first whitespace too
263 0         0 $badtoken =~ s/\s.*//;
264             }
265             }
266 1297 100       3507 $val = defined($qstr) ? $qstr :
    50          
    100          
    100          
267             (defined($enctoken) ? $enctoken :
268             (defined($badtoken) ? $badtoken :
269             (defined($token) ? $token : $empty_value)));
270              
271             # Do RFC 2231 processing
272             # Pick out the parts of the parameter
273 1297 100 66     3984 if ($param =~ /\*/ &&
274             $param =~ /^ ([^*]+) (?: \* ([^*]+) )? (\*)? \z/xs) {
275             # We have param*number* or param*number or param*
276 26   100     136 my($name, $num) = ($1, $2||0);
277 26 100       68 if (defined($3)) {
278             # We have param*number* or param*
279             # RFC 2231: Asterisks ("*") are reused to provide the
280             # indicator that language and character set information
281             # is present and encoding is being used
282 18         58 $val = rfc2231percent($val);
283 18         58 $rfc2231encoding_is_used{$name} = 1;
284             }
285 26         130 $rfc2231params{$name}{$num} .= $val;
286             } else {
287             # Assign non-rfc2231 value directly. If we
288             # did get a mix of rfc2231 and non-rfc2231 values,
289             # the non-rfc2231 will be blown away in the
290             # "extract reconstructed parameters" loop.
291 1271 100       3132 if (defined($params{$param})) {
292 23         49 $dup_params{$param} = 1;
293             }
294 1271         2959 $params{$param} = $val;
295             }
296 1297 100 50     4767 if (($val // '') eq '') {
297 25         63 $empty_params{$param} = 1;
298             }
299             }
300              
301             # Extract reconstructed parameters
302 2651         6567 foreach $param (keys %rfc2231params) {
303             # If we got any rfc-2231 parameters, then
304             # blow away any potential non-rfc-2231 parameter.
305 22 100       62 if (defined($params{$param})) {
306 3         5 $dup_params{$param} = 1;
307             }
308 22         83 $params{$param} = '';
309 22         33 foreach $part (sort { $a <=> $b } keys %{$rfc2231params{$param}}) {
  4         13  
  22         98  
310 26         84 $params{$param} .= $rfc2231params{$param}{$part};
311             }
312 22 100       96 if ($rfc2231encoding_is_used{$param}) {
313 18         60 my($enc, $lang, $val) = rfc2231decode($params{$param});
314 18 50       60 if (defined $enc) {
315             # re-encode as QP, preserving charset and language info
316 18         167 $val =~ s{([=?_\x00-\x1F\x7F-\xFF])}
317 69         255 {sprintf("=%02X", ord($1))}eg;
318 18         48 $val =~ tr/ /_/;
319             # RFC 2231 section 5: Language specification in Encoded Words
320 18 50 33     108 $enc .= '*' . $lang if defined $lang && $lang ne '';
321 18         69 $params{$param} = '=?' . $enc . '?Q?' . $val . '?=';
322             }
323             }
324 22 50       71 if ($params{$param} eq '') {
325 0         0 $empty_params{$param} = 1;
326             }
327 22         137 debug " field param <$param> = <$params{$param}>";
328             }
329              
330             # If there are any duplicate parameters, store them in the
331             # special key '@duplicate_parameters' which should never be the
332             # name of a real parameter
333 2651 100       5566 if (%dup_params) {
334 19         100 $params{'@duplicate_parameters'} = [ sort(keys(%dup_params)) ];
335             }
336              
337             # If there are any empty parameters, store them in the
338             # special key '@empty_parameters' which should never be the
339             # name of a real parameter
340 2651 100       4774 if (%empty_params) {
341 25         93 $params{'@empty_parameters'} = [ sort(keys(%empty_params)) ];
342             }
343             # Done:
344 2651         17443 \%params;
345             }
346              
347             #------------------------------
348              
349             =item parse STRING
350              
351             I
352             Parse the string into the instance. Any previous information is wiped.
353             The self object is returned.
354              
355             May also be used as a constructor.
356              
357             =cut
358              
359             sub parse {
360 2645     2645 1 346626 my ($self, $string) = @_;
361              
362             # Allow use as constructor, for MIME::Head:
363 2645 100       9093 ref($self) or $self = bless({}, $self);
364              
365             # Get params, and stuff them into the self object:
366 2645         10867 $self->set($self->parse_params($string));
367             }
368              
369             #------------------------------
370              
371             =item param PARAMNAME,[VALUE]
372              
373             I
374             Return the given parameter, or undef if it isn't there.
375             With argument, set the parameter to that VALUE.
376             The PARAMNAME is case-insensitive. A "_" refers to the "default" parameter.
377              
378             =cut
379              
380             sub param {
381 2763     2763 1 9542 my ($self, $paramname, $value) = @_;
382 2763         4745 $paramname = lc($paramname);
383 2763 100       7176 $self->{$paramname} = $value if (@_ > 2);
384 2763         14936 $self->{$paramname}
385             }
386              
387             #------------------------------
388              
389             =item paramstr PARAMNAME,[VALUE]
390              
391             I
392             Like param(): return the given parameter, or I if it isn't there.
393             With argument, set the parameter to that VALUE.
394             The PARAMNAME is case-insensitive. A "_" refers to the "default" parameter.
395              
396             =cut
397              
398             sub paramstr {
399 106     106 1 211 my $val = shift->param(@_);
400 106 50       277 (defined($val) ? $val : '');
401             }
402              
403             #------------------------------
404              
405             =item stringify
406              
407             I
408             Convert the field to a string, and return it.
409              
410             =cut
411              
412             sub stringify {
413 82     82 1 120 my $self = shift;
414 82         239 my ($key, $val);
415              
416 82         127 my $str = $self->{'_'}; # default subfield
417 82         261 foreach $key (sort keys %$self) {
418 135 100       391 next if ($key !~ /^[a-z][a-z-_0-9]*$/); # only lowercase ones!
419 53 50       133 defined($val = $self->{$key}) or next;
420 53         141 $val =~ s/(["\\])/\\$1/g;
421 53         126 $str .= qq{; $key="$val"};
422             }
423 82         283 $str;
424             }
425              
426             #------------------------------
427              
428             =item tag
429              
430             I
431             Return the tag for this field.
432              
433             =cut
434              
435 0     0 1   sub tag { '' }
436              
437             =back
438              
439             =head1 SEE ALSO
440              
441             L
442              
443             =cut
444              
445             #------------------------------
446             1;