File Coverage

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


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