File Coverage

blib/lib/HTTP/Negotiate.pm
Criterion Covered Total %
statement 145 150 96.6
branch 100 136 73.5
condition 27 38 71.0
subroutine 2 3 66.6
pod 1 2 50.0
total 275 329 83.5


line stmt bran cond sub pod time code
1             package HTTP::Negotiate;
2              
3             $VERSION = "6.01";
4 0     0 1 0 sub Version { $VERSION; }
5              
6             require Exporter;
7             @ISA = qw(Exporter);
8             @EXPORT = qw(choose);
9              
10             require HTTP::Headers;
11              
12             $DEBUG = 0;
13              
14             sub choose ($;$)
15             {
16 5     5 0 16880 my($variants, $request) = @_;
17 5         7 my(%accept);
18              
19 5 100       17 unless (defined $request) {
20             # Create a request object from the CGI environment variables
21 2         11 $request = HTTP::Headers->new;
22 2 50       28 $request->header('Accept', $ENV{HTTP_ACCEPT})
23             if $ENV{HTTP_ACCEPT};
24 2 50       8 $request->header('Accept-Charset', $ENV{HTTP_ACCEPT_CHARSET})
25             if $ENV{HTTP_ACCEPT_CHARSET};
26 2 50       7 $request->header('Accept-Encoding', $ENV{HTTP_ACCEPT_ENCODING})
27             if $ENV{HTTP_ACCEPT_ENCODING};
28 2 50       13 $request->header('Accept-Language', $ENV{HTTP_ACCEPT_LANGUAGE})
29             if $ENV{HTTP_ACCEPT_LANGUAGE};
30             }
31              
32             # Get all Accept values from the request. Build a hash initialized
33             # like this:
34             #
35             # %accept = ( type => { 'audio/*' => { q => 0.2, mbx => 20000 },
36             # 'audio/basic' => { q => 1 },
37             # },
38             # language => { 'no' => { q => 1 },
39             # }
40             # );
41              
42             $request->scan(sub {
43 7     7   92 my($key, $val) = @_;
44              
45 7         8 my $type;
46 7 100       36 if ($key =~ s/^Accept-//) {
    50          
47 5         9 $type = lc($key);
48             }
49             elsif ($key eq "Accept") {
50 2         5 $type = "type";
51             }
52             else {
53 0         0 return;
54             }
55              
56 7         24 $val =~ s/\s+//g;
57 7         9 my $default_q = 1;
58 7         18 for my $name (split(/,/, $val)) {
59 12         13 my(%param, $param);
60 12 100       44 if ($name =~ s/;(.*)//) {
61 5         14 for $param (split(/;/, $1)) {
62 5         12 my ($pk, $pv) = split(/=/, $param, 2);
63 5         17 $param{lc $pk} = $pv;
64             }
65             }
66 12         21 $name = lc $name;
67 12 100       23 if (defined $param{'q'}) {
68 4 50       14 $param{'q'} = 1 if $param{'q'} > 1;
69 4 50       11 $param{'q'} = 0 if $param{'q'} < 0;
70             }
71             else {
72 8         14 $param{'q'} = $default_q;
73              
74             # This makes sure that the first ones are slightly better off
75             # and therefore more likely to be chosen.
76 8         13 $default_q -= 0.0001;
77             }
78 12         76 $accept{$type}{$name} = \%param;
79             }
80 5         150 });
81              
82             # Check if any of the variants specify a language. We do this
83             # because it influences how we treat those without (they default to
84             # 0.5 instead of 1).
85 5         143 my $any_lang = 0;
86 5         15 for $var (@$variants) {
87 5 50       16 if ($var->[5]) {
88 5         7 $any_lang = 1;
89 5         8 last;
90             }
91             }
92              
93 5 100       15 if ($DEBUG) {
94 2         285 print "Negotiation parameters in the request\n";
95 2         22 for $type (keys %accept) {
96 2         246 print " $type:\n";
97 2         5 for $name (keys %{$accept{$type}}) {
  2         9  
98 5         520 print " $name\n";
99 5         10 for $pv (keys %{$accept{$type}{$name}}) {
  5         22  
100 5         546 print " $pv = $accept{$type}{$name}{$pv}\n";
101             }
102             }
103             }
104             }
105              
106 5         10 my @Q = (); # This is where we collect the results of the
107             # quality calculations
108              
109             # Calculate quality for all the variants that are available.
110 5         12 for (@$variants) {
111 16         44 my($id, $qs, $ct, $enc, $cs, $lang, $bs) = @$_;
112 16 100       77 $qs = 1 unless defined $qs;
113 16 100       27 $ct = '' unless defined $ct;
114 16 100       31 $bs = 0 unless defined $bs;
115 16 100       38 $lang = lc($lang) if $lang; # lg tags are always case-insensitive
116 16 100       41 if ($DEBUG) {
117 7         1162 print "\nEvaluating $id (ct='$ct')\n";
118 7         793 printf " qs = %.3f\n", $qs;
119 7 50 33     30 print " enc = $enc\n" if $enc && !ref($enc);
120 7 50 33     16 print " enc = @$enc\n" if $enc && ref($enc);
121 7 50       19 print " cs = $cs\n" if $cs;
122 7 100       702 print " lang = $lang\n" if $lang;
123 7 50       21 print " bs = $bs\n" if $bs;
124             }
125              
126             # Calculate encoding quality
127 16         23 my $qe = 1;
128             # If the variant has no assigned Content-Encoding, or if no
129             # Accept-Encoding field is present, then the value assigned
130             # is "qe=1". If *all* of the variant's content encodings
131             # are listed in the Accept-Encoding field, then the value
132             # assigned is "qw=1". If *any* of the variant's content
133             # encodings are not listed in the provided Accept-Encoding
134             # field, then the value assigned is "qe=0"
135 16 100 100     50 if (exists $accept{'encoding'} && $enc) {
136 2 100       8 my @enc = ref($enc) ? @$enc : ($enc);
137 2         60 for (@enc) {
138 2 50       6 print "Is encoding $_ accepted? " if $DEBUG;
139 2 100       8 unless(exists $accept{'encoding'}{$_}) {
140 1 50       24 print "no\n" if $DEBUG;
141 1         3 $qe = 0;
142 1         2 last;
143             }
144             else {
145 1 50       6 print "yes\n" if $DEBUG;
146             }
147             }
148             }
149              
150             # Calculate charset quality
151 16         20 my $qc = 1;
152             # If the variant's media-type has no charset parameter,
153             # or the variant's charset is US-ASCII, or if no Accept-Charset
154             # field is present, then the value assigned is "qc=1". If the
155             # variant's charset is listed in the Accept-Charset field,
156             # then the value assigned is "qc=1. Otherwise, if the variant's
157             # charset is not listed in the provided Accept-Encoding field,
158             # then the value assigned is "qc=0".
159 16 100 100     49 if (exists $accept{'charset'} && $cs && $cs ne 'us-ascii' ) {
      66        
160 2 100       8 $qc = 0 unless $accept{'charset'}{$cs};
161             }
162              
163             # Calculate language quality
164 16         23 my $ql = 1;
165 16 100 100     67 if ($lang && exists $accept{'language'}) {
166 8 50       26 my @lang = ref($lang) ? @$lang : ($lang);
167             # If any of the variant's content languages are listed
168             # in the Accept-Language field, the the value assigned is
169             # the largest of the "q" parameter values for those language
170             # tags.
171 8         11 my $q = undef;
172 8         20 for (@lang) {
173 8 100       25 next unless exists $accept{'language'}{$_};
174 4         11 my $this_q = $accept{'language'}{$_}{'q'};
175 4 50       17 $q = $this_q unless defined $q;
176 4 50       22 $q = $this_q if $this_q > $q;
177             }
178 8 100       19 if(defined $q) {
179 4 100       420 $DEBUG and print " -- Exact language match at q=$q\n";
180             }
181             else {
182             # If there was no exact match and at least one of
183             # the Accept-Language field values is a complete
184             # subtag prefix of the content language tag(s), then
185             # the "q" parameter value of the largest matching
186             # prefix is used.
187 4 100       312 $DEBUG and print " -- No exact language match\n";
188 4         7 my $selected = undef;
189 4         6 for $al (keys %{ $accept{'language'} }) {
  4         14  
190 8 100       23 if (index($al, "$lang-") == 0) {
191             # $lang starting with $al isn't enough, or else
192             # Accept-Language: hu (Hungarian) would seem
193             # to accept a document in hup (Hupa)
194 1 50       120 $DEBUG and print " -- $al ISA $lang\n";
195 1 50       7 $selected = $al unless defined $selected;
196 1 50       5 $selected = $al if length($al) > length($selected);
197             }
198             else {
199 7 100       465 $DEBUG and print " -- $lang isn't a $al\n";
200             }
201             }
202 4 100       14 $q = $accept{'language'}{$selected}{'q'} if $selected;
203              
204             # If none of the variant's content language tags or
205             # tag prefixes are listed in the provided
206             # Accept-Language field, then the value assigned
207             # is "ql=0.001"
208 4 100       10 $q = 0.001 unless defined $q;
209             }
210 8         21 $ql = $q;
211             }
212             else {
213 8 100 66     39 $ql = 0.5 if $any_lang && exists $accept{'language'};
214             }
215              
216 16         20 my $q = 1;
217 16         18 my $mbx = undef;
218             # If no Accept field is given, then the value assigned is "q=1".
219             # If at least one listed media range matches the variant's media
220             # type, then the "q" parameter value assigned to the most specific
221             # of those matched is used (e.g. "text/html;version=3.0" is more
222             # specific than "text/html", which is more specific than "text/*",
223             # which in turn is more specific than "*/*"). If not media range
224             # in the provided Accept field matches the variant's media type,
225             # then the value assigned is "q=0".
226 16 100 66     51 if (exists $accept{'type'} && $ct) {
227             # First we clean up our content-type
228 3         7 $ct =~ s/\s+//g;
229 3         4 my $params = "";
230 3 100       13 $params = $1 if $ct =~ s/;(.*)//;
231 3         9 my($type, $subtype) = split("/", $ct, 2);
232 3         5 my %param = ();
233 3         9 for $param (split(/;/, $params)) {
234 1         4 my($pk,$pv) = split(/=/, $param, 2);
235 1         4 $param{$pk} = $pv;
236             }
237              
238 3         6 my $sel_q = undef;
239 3         10 my $sel_mbx = undef;
240 3         4 my $sel_specificness = 0;
241              
242 3         10 ACCEPT_TYPE:
243 3         4 for $at (keys %{ $accept{'type'} }) {
244 9 50       21 print "Consider $at...\n" if $DEBUG;
245 9         24 my($at_type, $at_subtype) = split("/", $at, 2);
246             # Is it a match on the type
247 9 100 66     132 next if $at_type ne '*' && $at_type ne $type;
248 5 100 100     21 next if $at_subtype ne '*' && $at_subtype ne $subtype;
249 4         6 my $specificness = 0;
250 4 50       7 $specificness++ if $at_type ne '*';
251 4 100       9 $specificness++ if $at_subtype ne '*';
252             # Let's see if content-type parameters also match
253 4         16 while (($pk, $pv) = each %param) {
254 1 50       3 print "Check if $pk = $pv is true\n" if $DEBUG;
255 1 50       7 next unless exists $accept{'type'}{$at}{$pk};
256             next ACCEPT_TYPE
257 0 0       0 unless $accept{'type'}{$at}{$pk} eq $pv;
258 0 0       0 print "yes it is!!\n" if $DEBUG;
259 0         0 $specificness++;
260             }
261 4 50       7 print "Hurray, type match with specificness = $specificness\n"
262             if $DEBUG;
263              
264 4 50 66     13 if (!defined($sel_q) || $sel_specificness < $specificness) {
265 4         10 $sel_q = $accept{'type'}{$at}{'q'};
266 4         8 $sel_mbx = $accept{'type'}{$at}{'mbx'};
267 4         7 $sel_specificness = $specificness;
268             }
269             }
270 3   50     8 $q = $sel_q || 0;
271 3         9 $mbx = $sel_mbx;
272             }
273              
274 16         19 my $Q;
275 16 100 66     46 if (!defined($mbx) || $mbx >= $bs) {
276 15         38 $Q = $qs * $qe * $qc * $ql * $q;
277             }
278             else {
279 1         1 $Q = 0;
280 1 50       4 print "Variant's size is too large ==> Q=0\n" if $DEBUG;
281             }
282              
283 16 100       161 if ($DEBUG) {
284 7 50       18 $mbx = "undef" unless defined $mbx;
285 7         584 printf "Q=%.4f", $Q;
286 7         729 print " (q=$q, mbx=$mbx, qe=$qe, qc=$qc, ql=$ql, qs=$qs)\n";
287             }
288              
289 16         83 push(@Q, [$id, $Q, $bs]);
290             }
291              
292              
293 5 50       23 @Q = sort { $b->[1] <=> $a->[1] || $a->[2] <=> $b->[2] } @Q;
  16         47  
294              
295 5 100       37 return @Q if wantarray;
296 3 50       8 return undef unless @Q;
297 3 50       10 return undef if $Q[0][1] == 0;
298 3         22 $Q[0][0];
299             }
300              
301             1;
302              
303             __END__