| 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__ |