File Coverage

blib/lib/FU/Util.pm
Criterion Covered Total %
statement 82 86 95.3
branch 22 28 78.5
condition 8 10 80.0
subroutine 15 16 93.7
pod 6 9 66.6
total 133 149 89.2


line stmt bran cond sub pod time code
1             package FU::Util 1.4;
2              
3 10     10   773604 use v5.36;
  10         34  
4 10     10   5833 use FU::XS;
  10         29  
  10         449  
5 10     10   101 use Carp 'confess';
  10         14  
  10         579  
6 10     10   50 use Exporter 'import';
  10         15  
  10         399  
7 10     10   5969 use Encode ();
  10         199606  
  10         440  
8 10     10   5019 use POSIX ();
  10         73084  
  10         444  
9 10     10   3058 use experimental 'builtin';
  10         28715  
  10         68  
10              
11             our @EXPORT_OK = qw/
12             to_bool
13             json_format json_parse
14             has_control check_control utf8_decode
15             uri_escape uri_unescape
16             query_decode query_encode
17             httpdate_format httpdate_parse
18             gzip_lib gzip_compress brotli_compress
19             fdpass_send fdpass_recv
20             /;
21              
22              
23             # Internal utility function
24 136 50   136 0 190 sub has_control :prototype($) ($s) { defined $s && $s =~ /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/ }
  136         219  
  136         203  
  136         898  
25 0 0   0 0 0 sub check_control :prototype($) ($s) { confess 'Invalid control character' if has_control $s; }
  0         0  
  0         0  
  0         0  
26              
27             # Deprecated, call Encode::decode() directly.
28             sub utf8_decode :prototype($) {
29 30 50   30 0 83 return if !defined $_[0];
30 30 100       47 eval {
31 30         152 $_[0] = Encode::decode('UTF-8', $_[0], Encode::FB_CROAK);
32 29         1594 1
33             } || confess($@ =~ s/ at .+\n$//r);
34 29         106 $_[0]
35             }
36              
37 21     21 1 33 sub uri_escape :prototype($) ($s) {
  21         30  
  21         30  
38 21         52 utf8::encode($s);
39 21         117 $s =~ s/([^A-Za-z0-9._~-])/sprintf '%%%02x', ord $1/eg;
  9         48  
40 21         84 $s;
41             }
42              
43 20     20 1 27 sub uri_unescape :prototype($) ($s) {
  20         34  
  20         26  
44 20 50       46 return if !defined $s;
45 20         50 utf8::encode($s);
46 20         39 $s =~ tr/+/ /;
47 20         48 $s =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
  14         50  
48 20         42 utf8_decode $s;
49             }
50              
51 4     4 1 130698 sub query_decode :prototype($) ($s) {
  4         10  
  4         7  
52 4         8 my %o;
53 4   50     27 for (split /&/, $s//'') {
54 14 100       51 next if !length;
55 11         42 my($k,$v) = map uri_unescape($_), split /=/, $_, 2;
56 10   100     35 $v //= builtin::true;
57 10 100       35 if (ref $o{$k}) { push $o{$k}->@*, $v }
  1 100       5  
58 2         9 elsif (exists $o{$k}) { $o{$k} = [ $o{$k}, $v ] }
59 7         25 else { $o{$k} = $v }
60             }
61 3         30 \%o
62             }
63              
64 4     4 1 11 sub query_encode :prototype($) ($o) {
  4         8  
  4         6  
65             return join '&', map {
66 4         46 my($k, $v) = ($_, $o->{$_});
  11         30  
67 11         27 $k = uri_escape $k;
68             map {
69 11 100       36 my $x = $_;
  17         25  
70 17 100 66     55 $x = $x->TO_QUERY() if builtin::blessed($x) && $x->can('TO_QUERY');
71 17         103 my $bool = to_bool($x);
72 17 100 100     108 !defined $x || !($bool//1) ? ()
    100          
73             : $bool ? $k
74             : $k.'='.uri_escape($x)
75             } ref $v eq 'ARRAY' ? @$v : ($v);
76             } sort keys %$o;
77             }
78              
79              
80             my @httpmonths = qw/Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec/;
81             my %httpmonths = map +($httpmonths[$_], $_), 0..11;
82             my @httpdays = qw/Sun Mon Tue Wed Thu Fri Sat/;
83             my $httpdays = '(?:'.join('|', @httpdays).')';
84              
85 2     2 1 67032 sub httpdate_format :prototype($) ($time) {
  2         4  
  2         4  
86 2         13 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = gmtime $time;
87 2         24 sprintf '%s, %02d %s %d %02d:%02d:%02d GMT',
88             $httpdays[$wday], $mday, $httpmonths[$mon], $year+1900, $hour, $min, $sec;
89             }
90              
91 4     4 1 10 sub httpdate_parse :prototype($) ($str) {
  4         11  
  4         5  
92 4 100       161 return if $str !~ /^\s*$httpdays, ([0-9]{2}) ([A-Z][a-z]{2}) ([0-9]{4}) ([0-9]{2}):([0-9]{2}):([0-9]{2}) GMT\s*$/;
93 2         31 my ($mday, $mon, $year, $hour, $min, $sec) = ($1, $httpmonths{$2}, $3, $4, $5, $6);
94 2 50       8 return if !defined $mon;
95             # mktime() interprets the broken down time as our local timezone,
96             # which is utter garbage. But we can work around that by subtracting the
97             # time offset between localtime and gmtime around the given date. Might be
98             # off for a few hours around DST changes, but ugh.
99 2         128 my $mktime = POSIX::mktime($sec, $min, $hour, $mday, $mon, $year-1900);
100 2         102 $mktime + (POSIX::mktime(localtime $mktime) - POSIX::mktime(gmtime $mktime));
101             }
102              
103              
104             1;
105             __END__