File Coverage

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


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