File Coverage

blib/lib/Parse/MIME.pm
Criterion Covered Total %
statement 58 58 100.0
branch 22 22 100.0
condition 20 21 95.2
subroutine 15 15 100.0
pod 7 7 100.0
total 122 123 99.1


line stmt bran cond sub pod time code
1 1     1   68469 use 5.006; use strict; use warnings;
  1     1   4  
  1     1   4  
  1         2  
  1         16  
  1         4  
  1         1  
  1         71  
2              
3             package Parse::MIME;
4              
5             our $VERSION = '1.005';
6              
7 1     1   5 use Exporter ();
  1         2  
  1         63  
8             our @ISA = 'Exporter';
9             our @EXPORT_OK = qw(
10             &parse_mime_type &parse_media_range &parse_media_range_list
11             &fitness_and_quality_parsed &quality_parsed &quality
12             &best_match
13             );
14             our %EXPORT_TAGS = ( all => \@EXPORT_OK );
15              
16 1     1   6 sub _numify($) { no warnings 'numeric'; 0 + shift }
  1     102   1  
  1         710  
  102         305  
17              
18             # takes any number of args and returns copies stripped of surrounding whitespace
19 237     237   653 sub _strip { s/\A +//, s/ +\z// for my @s = @_; @s[ 0 .. $#s ] }
  237         580  
20              
21             # check whether first two args are equal or one of them is a wildcard
22 119 100   119   267 sub _match { $_[0] eq $_[1] or grep { $_ eq '*' } @_[0,1] }
  124         285  
23              
24             sub parse_mime_type {
25 89     89 1 18773 my ( $mime_type ) = @_;
26              
27 89         156 my @part = split /;/, $mime_type;
28 89         127 my $full_type = _strip shift @part;
29 89         126 my %param = map { _strip split /=/, $_, 2 } @part;
  59         95  
30              
31             # Java URLConnection class sends an Accept header that includes a single "*"
32             # Turn it into a legal wildcard.
33 89 100       159 $full_type = '*/*' if $full_type eq '*';
34              
35 89         143 my ( $type, $subtype ) = _strip split m!/!, $full_type;
36              
37 89         217 return ( $type, $subtype, \%param );
38             }
39              
40             sub parse_media_range {
41 87     87 1 6092 my ( $range ) = @_;
42              
43 87         725 my ( $type, $subtype, $param ) = parse_mime_type $range;
44              
45             $param->{'q'} = 1
46             unless defined $param->{'q'}
47             and length $param->{'q'}
48             and _numify $param->{'q'} <= 1
49 87 100 100     264 and _numify $param->{'q'} >= 0;
      100        
      66        
50              
51 87         199 return ( $type, $subtype, $param );
52             }
53              
54             sub parse_media_range_list {
55 18     18 1 24 my ( $media_range_list ) = @_;
56 18         55 return map { parse_media_range $_ } split /,/, $media_range_list;
  51         70  
57             }
58              
59             sub fitness_and_quality_parsed {
60 29     29 1 61 my ( $mime_type, @parsed_ranges ) = @_;
61              
62 29         37 my ( $best_fitness, $best_fit_q ) = ( -1, 0 );
63              
64 29         35 my ( $target_type, $target_subtype, $target_param )
65             = parse_media_range $mime_type;
66              
67 29         63 while ( my ( $type, $subtype, $param ) = splice @parsed_ranges, 0, 3 ) {
68              
69 67 100 100     92 if ( _match( $type, $target_type ) and _match( $subtype, $target_subtype ) ) {
70              
71 42 100       76 my $fitness
    100          
72             = ( $type eq $target_type ? 100 : 0 )
73             + ( $subtype eq $target_subtype ? 10 : 0 )
74             ;
75              
76 42         100 while ( my ( $k, $v ) = each %$param ) {
77             ++$fitness
78             if $k ne 'q'
79             and exists $target_param->{ $k }
80 50 100 100     143 and $target_param->{ $k } eq $v;
      100        
81             }
82              
83 42 100       130 ( $best_fitness, $best_fit_q ) = ( $fitness, $param->{'q'} )
84             if $fitness > $best_fitness;
85             }
86             }
87              
88 29         41 return ( $best_fitness, _numify $best_fit_q );
89             }
90              
91             sub quality_parsed {
92 7     7 1 16 return +( fitness_and_quality_parsed @_ )[1];
93             }
94              
95             sub quality {
96 7     7 1 7931 my ( $mime_type, $ranges ) = @_;
97 7         17 my @parsed_range = parse_media_range_list $ranges;
98 7         26 return quality_parsed $mime_type, @parsed_range;
99             }
100              
101             sub best_match {
102 11     11 1 4385 my ( $supported, $header ) = @_;
103 11         23 my @parsed_header = parse_media_range_list $header;
104              
105             # fitness_and_quality_parsed will return fitness -1 on failure,
106             # so we want to start with an invalid value greater than that
107 11         18 my ( $best_fitness, $best_fit_q, $match ) = ( -.5, 0 );
108              
109 11         18 for my $type ( @$supported ) {
110 22         31 my ( $fitness, $fit_q ) = fitness_and_quality_parsed $type, @parsed_header;
111 22 100       48 next if $fitness < $best_fitness;
112 14 100 100     31 next if $fitness == $best_fitness and $fit_q < $best_fit_q;
113 13         24 ( $best_fitness, $best_fit_q, $match ) = ( $fitness, $fit_q, $type );
114             }
115              
116 11 100       20 return if not defined $match;
117 10         39 return $match;
118             }
119              
120             __END__