File Coverage

blib/lib/YATT/Lite/WebMVC0/Connection.pm
Criterion Covered Total %
statement 231 336 68.7
branch 91 196 46.4
condition 47 107 43.9
subroutine 36 51 70.5
pod 0 33 0.0
total 405 723 56.0


line stmt bran cond sub pod time code
1             package YATT::Lite::WebMVC0::Connection; sub PROP () {__PACKAGE__}
2 13     13   82 use strict;
  13         31  
  13         375  
3 13     13   62 use warnings qw(FATAL all NONFATAL misc);
  13         26  
  13         389  
4 13     13   63 use Carp;
  13         26  
  13         677  
5              
6 13     13   82 use base qw(YATT::Lite::Connection);
  13         37  
  13         4947  
7             use YATT::Lite::MFields
8 13         96 (qw/cf_cgi
9             cf_is_psgi cf_hmv
10             cf_parameters
11              
12             cf_site_prefix
13              
14             cf_no_nested_query
15              
16             cf_no_unicode_params
17              
18             current_user
19 13     13   155 /);
  13         44  
20 13     13   117 use YATT::Lite::Util qw(globref url_encode nonempty empty rootname lexpand);
  13         35  
  13         841  
21 13     13   80 use YATT::Lite::PSGIEnv;
  13         32  
  13         85  
22              
23 13     13   6305 use YATT::Lite::Util::CGICompat;
  13         46  
  13         175  
24              
25             #----------------------------------------
26              
27             BEGIN {
28             # print STDERR join("\n", sort(keys our %FIELDS)), "\n";
29              
30 13     13   64 foreach my $name (qw(raw_body uploads upload)) {
31 39         125 *{globref(PROP, $name)} = sub {
32 4     4   2848 my PROP $prop = (my $glob = shift)->prop;
33 4 50       23 unless ($prop->{cf_is_psgi}) {
34 0         0 croak "Connection method $name is PSGI mode only!"
35             }
36 4         36 $prop->{cf_cgi}->$name(@_);
37 39         184 };
38             }
39              
40 13         46 foreach my $name (qw(url_param)) {
41 13         52 *{globref(PROP, $name)} = sub {
42 0     0   0 my PROP $prop = (my $glob = shift)->prop;
43 0         0 $prop->{cf_cgi}->$name(@_);
44 13         52 };
45             }
46              
47 13         190 foreach my $item ([referer => 'HTTP_REFERER']
48             , map([lc($_) => uc($_)]
49             , qw/REMOTE_ADDR
50             REQUEST_METHOD
51             SCRIPT_NAME
52             PATH_INFO
53             QUERY_STRING
54             SERVER_NAME
55             SERVER_PORT
56             SERVER_PROTOCOL
57             CONTENT_LENGTH
58             CONTENT_TYPE
59             /)
60             ) {
61 143         316 my ($method, $env) = @$item;
62 143         377 *{globref(PROP, $method)} = sub {
63 3     3   41 my PROP $prop = (my $glob = shift)->prop;
64 3         10 my ($default) = @_;
65 3 50 0     15 if ($prop->{cf_env}) {
    0          
66 3   33     20 $prop->{cf_env}->{$env} // $default;
67             } elsif ($prop->{cf_cgi} and my $sub = $prop->{cf_cgi}->can($method)) {
68 0   0     0 $sub->($prop->{cf_cgi}) // $default;
69             } else {
70 0         0 $default;
71             }
72 143         471 };
73             }
74              
75 13         58 foreach my $name (qw(file subpath)) {
76 26         67 my $cf = "cf_$name";
77 26         83 *{globref(PROP, $name)} = sub {
78 0     0   0 my PROP $prop = (my $glob = shift)->prop;
79 0         0 $prop->{$cf};
80 26         79 };
81             }
82             }
83              
84             #========================================
85              
86             sub param {
87 299     299 0 8406 my PROP $prop = (my $glob = shift)->prop;
88 299 50       937 if (my $ixh = $prop->{cf_parameters}) {
    0          
    0          
89 299 100       1710 return keys %$ixh unless @_;
90 14 50       56 defined (my $key = shift)
91             or croak "undefined key!";
92 14 50       45 if (@_) {
93 0 0       0 if (@_ >= 2) {
94 0         0 $ixh->{$key} = [@_]
95             } else {
96 0         0 $ixh->{$key} = shift;
97             }
98             } else {
99             # If cf_parameters is enabled, value is returned AS-IS.
100 14         94 $ixh->{$key};
101             }
102             } elsif (my $hmv = $prop->{cf_hmv}) {
103 0 0       0 return $hmv->keys unless @_;
104 0 0       0 if (@_ == 1) {
105 0 0       0 return wantarray ? $hmv->get_all($_[0]) : $hmv->get($_[0]);
106             } else {
107 0         0 $hmv->add(@_);
108 0         0 return $glob;
109             }
110             } elsif (my $cgi = $prop->{cf_cgi}) {
111 0         0 return $cgi->param(@_);
112             } else {
113 0         0 croak "Neither Hash::MultiValue nor CGI is found in connection!";
114             }
115             }
116              
117             # Annoying multi_param support.
118             sub multi_param {
119 1     1 0 4 my PROP $prop = (my $glob = shift)->prop;
120 1 50 0     5 if (my $ixh = $prop->{cf_parameters}) {
    0          
    0          
121 1 50       4 return keys %$ixh unless @_;
122 1 50       5 defined (my $key = shift)
123             or croak "undefined key!";
124             # If cf_parameters is enabled, value is returned AS-IS.
125 1         4 $ixh->{$key};
126              
127             } elsif (my $hmv = ($prop->{cf_hmv} // do {
128             $prop->{cf_is_psgi} && $prop->{cf_cgi}->parameters
129 0 0       0 })) {
130 0 0       0 return $hmv->keys unless @_;
131 0 0       0 return wantarray ? $hmv->get_all($_[0]) : $hmv->get($_[0]);
132             } elsif (my $cgi = $prop->{cf_cgi}) {
133 0         0 return $cgi->multi_param(@_);
134             } else {
135 0         0 croak "Neither Hash::MultiValue nor CGI is found in connection!";
136             }
137             }
138              
139             sub queryobj {
140 3     3 0 10 my PROP $prop = (my $glob = shift)->prop;
141 3 50 33     18 $prop->{cf_parameters} || $prop->{cf_hmv} || $prop->{cf_cgi};
142             }
143              
144             *remove_param = *delete_param; *remove_param = *delete_param;
145             sub delete_param {
146 0     0 0 0 my PROP $prop = (my $glob = shift)->prop;
147 0         0 my ($key) = @_;
148 0 0       0 unless (defined $key) {
149 0         0 croak "Undefined key!";
150             }
151 0 0       0 if (my $dict = $prop->{cf_parameters}) {
    0          
    0          
    0          
152             # For nested_query (array_param)
153 0         0 delete $dict->{$key};
154             } elsif ($dict = $prop->{cf_hmv}) {
155             # For direct Hash::MultiValue.
156 0         0 $dict->remove($key);
157             } elsif (($dict = $prop->{cf_cgi})->can("parameters")) {
158             # For Plack::Request
159 0         0 $dict->parameters->remove($key);
160             } elsif ($dict->can("delete")) {
161             # For CGI family.
162 0         0 $dict->delete($key);
163             } else {
164 0         0 croak "No queryobj found!";
165             }
166             }
167              
168             #========================================
169              
170             sub configure_cgi {
171 178     178 0 23353 my PROP $prop = (my $glob = shift)->prop;
172 178         497 $prop->{cf_cgi} = my $cgi = shift;
173 178 50       858 return unless $glob->is_form_content_type($cgi->content_type);
174 178 50       614 return if $prop->{cf_parameters};
175 178 50       587 unless ($prop->{cf_no_nested_query}) {
176 178 100       529 if ($prop->{cf_is_psgi}) {
177 162         580 $glob->convert_array_param_psgi($cgi);
178             } else {
179 16         56 $glob->convert_array_param_cgi($cgi);
180             }
181             }
182             }
183              
184             sub is_form_content_type {
185 178     178 0 1717 my ($self, $real_ct) = @_;
186 178 100 100     1368 return 1 if ($real_ct // '') eq '';
187 9         35 foreach my $check_ct ($self->form_content_types) {
188 16 100       127 return 1 if $real_ct =~ $check_ct;
189             }
190 0         0 return 0;
191             }
192              
193             sub form_content_types {
194 9     9 0 73 (qr(^multipart/form-data\s*(?:;|$))i
195             , qr(^application/x-www-form-urlencoded$)i);
196             }
197              
198             sub parse_nested_query {
199 187     187 0 8423 my PROP $prop = (my $glob = shift)->prop;
200 187         451 my ($obj_or_string) = @_;
201             YATT::Lite::Util::parse_nested_query
202             ($obj_or_string
203             , (!$prop->{cf_no_unicode_params} && $prop->{cf_encoding})
204 187   33     1348 );
205             }
206              
207             sub convert_array_param_psgi {
208 162     162 0 567 my PROP $prop = (my $glob = shift)->prop;
209 162         399 my ($req) = @_;
210 162         380 my Env $env = $prop->{cf_env};
211 162         328 $prop->{cf_parameters} = do {
212 162 100 66     661 if ($env->{CONTENT_TYPE} and defined $env->{CONTENT_LENGTH}) {
213 9         51 my $body = $glob->parse_nested_query([$req->body_parameters->flatten]);
214 9         40 my $qs = $glob->parse_nested_query($env->{QUERY_STRING});
215 9         62 foreach my $key (keys %$qs) {
216 5 50       97 if (exists $body->{$key}) {
217 0         0 die $glob->error("Attempt to overwrite post param '%s' by qs"
218             , $key);
219             }
220 5         47 $body->{$key} = $qs->{$key};
221             }
222 9         145 $body;
223             } else {
224 153         510 $glob->parse_nested_query($env->{QUERY_STRING});
225             }
226             };
227             }
228              
229             sub convert_array_param_cgi {
230 16     16 0 67 my PROP $prop = (my $glob = shift)->prop;
231 16         47 my ($cgi) = @_;
232 16 50 50     52 return if ($cgi->content_type // "") eq "application/json";
233             $prop->{cf_parameters}
234 16         191 = $glob->parse_nested_query($cgi->query_string);
235             }
236              
237             # Location(path part of url) of overall SiteApp.
238             sub site_location {
239 2     2 0 6 shift->site_prefix . '/';
240             }
241             *site_loc = *site_location; *site_loc = *site_location;
242             sub site_prefix {
243 2     2 0 8 my PROP $prop = (my $glob = shift)->prop;
244             # Note: This is safe because site_prefix 0 is meaningless(I hope).
245 2 50       14 $prop->{cf_site_prefix} || do {
246 0         0 my Env $env = $prop->{cf_env};
247 0   0     0 $env->{'yatt.script_name'} // ''
248             }
249             }
250              
251             # Location of DirApp
252             sub location {
253 0     0 0 0 my PROP $prop = (my $glob = shift)->prop;
254 0   0     0 (my $loc = ($prop->{cf_location} // '')) =~ s,/*$,/,;
255 0         0 $loc;
256             }
257             *dir_location = *location; *dir_location = *location;
258              
259             sub _invoke_or {
260 16     16   36 my ($default, $obj, $method, @args) = @_;
261 16 50 33     76 if (defined $obj and my $sub = $obj->can($method)) {
262 16         40 $sub->($obj, @args)
263             } else {
264 0         0 $default;
265             }
266             }
267              
268             # XXX: parameter の加減算も?
269             # XXX: 絶対 path/相対 path の選択?
270             # scheme
271             # authority
272             # path
273             # query
274             # fragment
275             sub mkurl {
276 17     17 0 63 my PROP $prop = (my $glob = shift)->prop;
277 17         46 my ($file, $param, %opts) = @_;
278              
279 17         27 my $req = do {
280 17 50       39 if ($opts{mapped_path}) {
281 0         0 $glob->mapped_path;
282             } else {
283 17         39 $glob->request_path;
284             }
285             };
286              
287 17         27 my $path = do {
288 17 50 66     67 if (defined $file and $file =~ m!^/!) {
289 0         0 $glob->site_prefix.$file;
290             } else {
291 17         36 my ($orig, $dir) = ('');
292 17 100       83 if (($dir = $req) =~ s{([^/]+)$}{}) {
293 13         32 $orig = $1;
294             }
295 17 100 66     67 if (not defined $file or $file eq '') {
    100          
296 11         24 $dir . $orig;
297             } elsif ($file eq '.') {
298 2         6 $dir
299             } else {
300 4         11 $dir . $file;
301             }
302             }
303             };
304              
305             # XXX: /../ truncation
306             # XXX: If sep is '&', scalar ref quoting is required.
307             # XXX: connection should have default separator.
308 17         28 my $url = '';
309 17 100       57 $url .= $glob->mkprefix unless $opts{local};
310 17         58 $url .= $path . $glob->mkquery($param, $opts{separator});
311 17         91 $url;
312             }
313              
314             sub mkprefix {
315 16     16 0 42 my PROP $prop = (my $glob = shift)->prop;
316 16         30 my Env $env = $prop->{cf_env};
317 16   66     58 my $scheme = $env->{'psgi.url_scheme'} || $prop->{cf_cgi}->protocol;
318 16         284 my $host = $glob->mkhost($scheme);
319 16         63 $scheme . '://' . $host . join("", @_);
320             }
321              
322             sub http_host_domain {
323 0     0 0 0 my PROP $prop = (my $glob = shift)->prop;
324 0         0 my Env $env = $prop->{cf_env};
325             my $host = $env->{HTTP_HOST}
326 0 0       0 or return undef;
327 0         0 $host =~ s/:\d+$//;
328 0         0 $host;
329             }
330              
331             sub server_name_or_localhost {
332 0     0 0 0 my PROP $prop = (my $glob = shift)->prop;
333 0         0 my ($default) = @_;
334 0         0 my Env $env = $prop->{cf_env};
335 0 0 0     0 $env->{SERVER_NAME} || ($default // 'localhost');
336             }
337              
338             sub mkhost {
339 18     18 0 54 my PROP $prop = (my $glob = shift)->prop;
340 18         41 my ($scheme) = @_;
341 18   100     48 $scheme ||= 'http';
342 18         35 my Env $env = $prop->{cf_env};
343              
344             # XXX? Is this secure?
345 18 100       64 return $env->{HTTP_HOST} if nonempty($env->{HTTP_HOST});
346              
347             my $base = $env->{SERVER_NAME}
348 8   33     29 // _invoke_or('localhost', $prop->{cf_cgi}, 'server_name');
349 8 50 33     52 if (my $port = $env->{SERVER_PORT}
350             || _invoke_or(80, $prop->{cf_cgi}, 'server_port')) {
351 8 0 33     63 $base .= ":$port" unless ($scheme eq 'http' and $port == 80
      0        
      33        
352             or $scheme eq 'https' and $port == 443);
353             }
354 8         16 $base;
355             }
356              
357             sub mkquery {
358 19     19 0 87 my ($self, $param, $sep) = @_;
359 19   50     92 $sep //= '&';
360              
361 19         29 my @enc_param;
362 19         33 my ($fkeys, $fgetall);
363 19 100 66     64 if (not defined $param or not ref $param) {
364 14 50       48 return wantarray ? () : '';
365             # nop
366             }
367              
368 5 100       18 if (UNIVERSAL::isa($param, ref $self)) {
369             # $CON->mkquery($CON) == $CON->mkquery($CON->queryobj)
370 3         10 $param = $param->queryobj;
371             }
372              
373 5 100 33     32 if (ref $param eq 'HASH') {
    50 33        
    0          
374             push @enc_param, $self->url_encode($_).'='.$self->url_encode($param->{$_})
375 2         14 for sort keys %$param;
376             } elsif ($fkeys = UNIVERSAL::can($param, 'keys')
377             and $fgetall = UNIVERSAL::can($param, 'get_all')
378             or ($fkeys = $fgetall = UNIVERSAL::can($param, 'param'))) {
379 3         7 foreach my $key (YATT::Lite::Util::unique($fkeys->($param))) {
380 5         16 my $enc = $self->url_encode($key);
381             push @enc_param, "$enc=".$self->url_encode($_)
382 5         12 for $fgetall->($param, $key);
383             }
384             } elsif (ref $param eq 'ARRAY') {
385 0 0       0 if (grep {not defined} @$param) {
  0         0  
386 0         0 croak "Undef found in mkquery()! " . YATT::Lite::Util::terse_dump($param);
387             }
388 0         0 my @list = @$param;
389 0         0 while (my ($key, $value) = splice @list, 0, 2) {
390 0         0 push @enc_param, $self->url_encode($key).'='.$self->url_encode($value);
391             }
392             }
393              
394 5 50       13 unless (@enc_param) {
395 0 0       0 wantarray ? () : '';
396             } else {
397 5 50       28 wantarray ? @enc_param : '?'.join($sep, @enc_param);
398             }
399             }
400              
401              
402             # script_name + path_info - subpage
403             # (script_name == location of this dir (DirApp))
404             #
405             sub file_location {
406 8     8 0 32 my PROP $prop = (my $glob = shift)->prop;
407 8         26 my Env $env = $prop->{cf_env};
408 8         17 my $loc = do {
409 8 100       31 if (my $sn = $env->{'yatt.script_name'}) {
410 4         15 "$sn/"
411             } else {
412 4   50     19 $prop->{cf_location} // "/";
413             }
414             };
415 8 100 66     53 if (not $prop->{cf_is_index}
416             and my $fn = $prop->{cf_file}) {
417 4         34 $fn =~ s/\..*//;
418 4         14 $loc .= $fn;
419             }
420 8         46 $loc;
421             }
422              
423             # XXX: not yet tested.
424             sub is_current_file {
425 0     0 0 0 my PROP $prop = (my $glob = shift)->prop;
426 0         0 my ($fn) = @_;
427 0         0 $glob->file_location eq $fn
428             }
429              
430             sub is_current_page {
431 576     576 0 1549 my PROP $prop = (my $glob = shift)->prop;
432 576         912 my ($file, $page) = do {
433 576 50       1976 @_ <= 1 ? (rootname($prop->{cf_file}), $_[0]) : @_;
434             };
435 576 50       1575 rootname($prop->{cf_file}) eq $file
436             or return 0;
437 576   100     1767 $page //= '';
438 576         1317 $page =~ s{^/}{}; # Treat /foo as foo.
439 576 100       1685 if (empty(my $subpath = $prop->{cf_subpath})) {
    100          
440 192         1071 $page eq '';
441             } elsif ($page eq '') {
442 128         882 0
443             } else {
444 256         3186 $subpath =~ m{^/$page};
445             }
446             }
447              
448             sub mapped_path {
449 8     8 0 409 my PROP $prop = (my $glob = shift)->prop;
450 8         13 my @path = do {
451 8   100     29 my $loc = $prop->{cf_location} // "/";
452             $loc .= $prop->{cf_file} if defined $prop->{cf_file}
453 8 100 100     38 and not $prop->{cf_is_index};
454 8         21 ($loc);
455             };
456 8 100       22 if (defined (my $sp = $prop->{cf_subpath})) {
457 6         27 $sp =~ s!^/*!/!;
458 6         14 push @path, $sp;
459             }
460 8 100       17 if (wantarray) {
461 4         24 @path;
462             } else {
463 4         10 my $res = join "", @path;
464 4         13 $res =~ s!^/+!/!;
465 4         23 $res;
466             }
467             }
468              
469             sub request_path {
470 18   100 18 0 43 (my $uri = shift->request_uri // '') =~ s/\?.*//;
471 18         43 $uri;
472             }
473              
474             sub request_uri {
475 18     18 0 43 my PROP $prop = (my $glob = shift)->prop;
476 18 100 33     54 if (my Env $env = $prop->{cf_env}) {
    50          
477 17         70 $env->{REQUEST_URI};
478             } elsif ($prop->{cf_cgi}
479             and my $sub = $prop->{cf_cgi}->can('request_uri')) {
480 0         0 $sub->($prop->{cf_cgi});
481             } else {
482 1         8 $ENV{REQUEST_URI};
483             }
484             }
485              
486             #========================================
487              
488             sub redirect {
489 3     3 0 19 my PROP $prop = (my $glob = shift)->prop;
490 3 50 33     19 croak "undefined url" unless @_ and defined $_[0];
491 3         8 my $url = do {
492 3 100 0     13 if (ref $_[0]) {
    50 33        
493             # To do external redirect, $url should pass as SCALAR REF.
494 2         3 my $arg = shift;
495             # die "redirect url is not a scalar ref: $arg";
496 2         5 $$arg;
497             } elsif ($_[0] =~ m{^(?:\w+:)?//([^/]+)}
498             and $1 ne ($glob->mkhost // '')) {
499 0         0 die $glob->error("External redirect is not allowed: %s", $_[0]);
500             } else {
501             # taint check
502 1         4 shift;
503             }
504             };
505 3 50       13 if ($prop->{header_was_sent}++) {
506 0         0 die "Can't redirect multiple times!";
507             }
508              
509             # Make sure session is flushed before redirection.
510 3         21 $glob->finalize_headers;
511              
512 3         6 ${$prop->{cf_buffer}} = '';
  3         9  
513              
514 3         24 die [302, [Location => $url, $glob->list_header], []];
515             }
516              
517             #========================================
518             # Session support is delegated to 'system'.
519             # 'system' must implement session_{start,resume,flush,destroy}
520              
521             # To avoid confusion against $system->session_$verb,
522             # connection side interface is named ${verb}_session.
523              
524             sub get_session {
525 0     0 0 0 my PROP $prop = (my $glob = shift)->prop;
526             # To avoid repeative false session tests.
527 0 0       0 if (exists $prop->{session}) {
528 0         0 $prop->{session};
529             } else {
530 0         0 $prop->{cf_system}->session_resume($glob);
531             }
532             }
533              
534             sub start_session {
535 0     0 0 0 my PROP $prop = (my $glob = shift)->prop;
536 0 0       0 if (defined (my $sess = $prop->{session})) {
537 0         0 die $glob->error("load_session is called twice! sid=%s", $sess->id);
538             }
539 0         0 $prop->{cf_system}->session_start($glob, @_);
540             }
541              
542             sub delete_session {
543 0     0 0 0 my PROP $prop = (my $glob = shift)->prop;
544 0         0 $prop->{cf_system}->session_delete($glob);
545             }
546              
547             sub flush_session {
548 0     0 0 0 my PROP $prop = (my $glob = shift)->prop;
549 0         0 $prop->{cf_system}->session_flush($glob);
550             }
551              
552             #========================================
553              
554             sub current_user {
555 0     0 0 0 my PROP $prop = (my $glob = shift)->prop;
556 0         0 my $cu = do {
557 0 0       0 if (exists $prop->{current_user}) {
    0          
558             $prop->{current_user}
559 0         0 } elsif (defined $prop->{cf_system}) {
560 0         0 $prop->{current_user} = $prop->{cf_system}->load_current_user($glob);
561             } else {
562 0         0 $prop->{current_user} = undef;
563             }
564             };
565              
566 0 0       0 return $cu unless @_;
567 0 0       0 die $glob->error("current_user is empty") unless defined $cu;
568 0         0 my $method = shift;
569              
570 0         0 $cu->$method(@_);
571             }
572              
573             #========================================
574              
575 13     13   1981 use YATT::Lite::RegexpNames; # For re_name, re_integer, ...
  13         36  
  13         8449  
576              
577             sub param_type {
578 2     2 0 20 my PROP $prop = (my $glob = shift)->prop;
579 2   33     8 my $name = shift // croak "Undefined name!";
580 2   33     8 my $type = shift // croak "Undefined type!";
581 2         5 my $diag = shift;
582 2         5 my $opts = shift;
583 2 50       7 my $pat = ref $type eq 'Regexp' ? $type : do {
584 2 50       17 my $pat_sub = $glob->can("re_$type")
585             or croak "Unknown type: $type";
586 2         12 $pat_sub->();
587             };
588              
589 2         13 my $value = $glob->param($name);
590              
591 2 50 66     94 if (defined $value && $value =~ $pat) {
    50          
    100          
592 0         0 return $&; # Also for taint check.
593             } elsif ($diag) {
594 0 0       0 die $glob->error_with_status
595             (400, (ref $diag eq 'CODE' ? $diag->($value) : $diag)
596             , $name, $value);
597             } elsif (not defined $value) {
598 1 50       4 return undef if $opts->{allow_undef};
599 1         6 die $glob->error_with_status
600             (400, "Parameter '%s' is missing!", $name);
601             } else {
602             # Just for default message. Production code should provide $diag.
603 1         8 die $glob->error_with_status
604             (400, "Parameter '%s' must match %s!: '%s'"
605             , $name, $type, $value);
606             }
607             }
608              
609             #========================================
610              
611             sub accept_language {
612 4     4 0 20 my PROP $prop = (my $glob = shift)->prop;
613 4         12 my (%opts) = @_;
614 4         7 my $filter = delete $opts{filter};
615 4         8 my $detail = delete $opts{detail};
616 4         8 my $long = delete $opts{long};
617 4 50       14 if (keys %opts) {
618 0         0 die $glob->error("Unknown option for accept_language: %s"
619             , join ", ", keys %opts);
620             }
621              
622 4         9 my Env $env = $prop->{cf_env};
623             my $langlist = $env->{HTTP_ACCEPT_LANGUAGE}
624 4 50       10 or return;
625             my @langlist = sort {
626 12         35 $$b[-1] <=> $$a[-1]
627             } map {
628 4         27 my ($lang, $qual) = split /\s*;\s*q=/;
  12         36  
629 12   100     55 [$lang, $qual // 1]
630             } split /\s*,\s*/, $langlist;
631              
632 4 50       12 if ($filter) {
633 0         0 my $filtsub = do {
634 0 0       0 if (ref $filter eq 'CODE') {
    0          
    0          
    0          
635 0         0 $filter
636             } elsif (ref $filter eq 'Regexp') {
637 0     0   0 sub { grep {$$_[0] =~ $filter} @_ }
  0         0  
638 0         0 } elsif (ref $filter eq 'HASH') {
639 0     0   0 sub { grep {$filter->{$$_[0]}} @_ }
  0         0  
640 0         0 } elsif (ref $filter eq 'ARRAY') {
641 0         0 my $hash = +{map {$_ => 1} lexpand($filter)};
  0         0  
642 0     0   0 sub { grep {$hash->{$$_[0]}} @_ }
  0         0  
643 0         0 } else {
644 0         0 die $glob->error("Unknown filter type for accept_language");
645             }
646             };
647 0         0 @langlist = $filtsub->(@langlist);
648             }
649              
650 4 100       10 if ($detail) {
651             @langlist
652 1         11 } else {
653 3 100       7 if ($long) {
654             # en-US => en_US
655 1         7 $$_[0] =~ s/-/_/g for @langlist;
656             } else {
657             # en-US => en
658 2         12 $$_[0] =~ s/-.*// for @langlist;
659             }
660 3         7 my %dup;
661 3 100       12 wantarray ? (map {$dup{$$_[0]}++ ? () : $$_[0]} @langlist)
  6 100       33  
662             : $langlist[0][0];
663             }
664             }
665              
666             1;