File Coverage

blib/lib/Mojo/Util.pm
Criterion Covered Total %
statement 337 356 94.6
branch 127 136 93.3
condition 46 58 79.3
subroutine 75 77 97.4
pod 36 36 100.0
total 621 663 93.6


line stmt bran cond sub pod time code
1             package Mojo::Util;
2 98     98   109522 use Mojo::Base -strict;
  98         212  
  98         810  
3              
4 98     98   655 use Carp qw(carp croak);
  98         243  
  98         7194  
5 98     98   68801 use Data::Dumper ();
  98         1057285  
  98         4716  
6 98     98   848 use Digest::MD5 qw(md5 md5_hex);
  98         196  
  98         9744  
7 98     98   60147 use Digest::SHA qw(hmac_sha1_hex sha1 sha1_hex);
  98         377909  
  98         12706  
8 98     98   61623 use Encode qw(find_encoding);
  98         1907536  
  98         12414  
9 98     98   992 use Exporter qw(import);
  98         224  
  98         4158  
10 98     98   594 use File::Basename qw(dirname);
  98         227  
  98         10200  
11 98     98   85898 use Getopt::Long qw(GetOptionsFromArray);
  98         1454771  
  98         640  
12 98     98   91951 use IO::Compress::Gzip;
  98         4329925  
  98         8313  
13 98     98   57496 use IO::Poll qw(POLLIN POLLPRI);
  98         106557  
  98         12708  
14 98     98   68981 use IO::Uncompress::Gunzip;
  98         1849608  
  98         8303  
15 98     98   1033 use List::Util qw(min);
  98         197  
  98         9509  
16 98     98   59448 use MIME::Base64 qw(decode_base64 encode_base64);
  98         106637  
  98         9338  
17 98     98   860 use Mojo::BaseUtil qw(class_to_path monkey_patch);
  98         288  
  98         7422  
18 98     98   62784 use Pod::Usage qw(pod2usage);
  98         5680472  
  98         11462  
19 98     98   44062 use Socket qw(inet_pton AF_INET6 AF_INET);
  98         314702  
  98         20107  
20 98     98   1068 use Symbol qw(delete_package);
  98         212  
  98         4966  
21 98     98   1179 use Time::HiRes ();
  98         1275  
  98         2085  
22 98     98   61915 use Unicode::Normalize ();
  98         316996  
  98         16161  
23              
24             # Encryption support requires CryptX 0.080+
25 98 50       748 use constant CRYPTX => $ENV{MOJO_NO_CRYPTX} ? 0 : !!(eval {
26 98         28586 require CryptX;
27 0         0 require Crypt::AuthEnc::ChaCha20Poly1305;
28 0         0 require Crypt::KeyDerivation;
29 0         0 require Crypt::Misc;
30 0         0 require Crypt::PRNG;
31 0         0 CryptX->VERSION('0.080');
32 0         0 1;
33 98     98   984 });
  98         244  
34              
35             # Check for monotonic clock support
36 98     98   605 use constant MONOTONIC => !!eval { Time::HiRes::clock_gettime(Time::HiRes::CLOCK_MONOTONIC()) };
  98         198  
  98         212  
  98         939  
37              
38             # Punycode bootstring parameters
39             use constant {
40 98         415405 PC_BASE => 36,
41             PC_TMIN => 1,
42             PC_TMAX => 26,
43             PC_SKEW => 38,
44             PC_DAMP => 700,
45             PC_INITIAL_BIAS => 72,
46             PC_INITIAL_N => 128
47 98     98   13205 };
  98         237  
48              
49             # To generate a new HTML entity table run this command
50             # perl examples/entities.pl > lib/Mojo/resources/html_entities.txt
51             my %ENTITIES;
52             {
53             # Don't use Mojo::File here due to circular dependencies
54             my $path = File::Spec->catfile(dirname(__FILE__), 'resources', 'html_entities.txt');
55              
56             open my $file, '<', $path or croak "Unable to open html entities file ($path): $!";
57             my $lines = do { local $/; <$file> };
58              
59             for my $line (split /\n/, $lines) {
60             next unless $line =~ /^(\S+)\s+U\+(\S+)(?:\s+U\+(\S+))?/;
61             $ENTITIES{$1} = defined $3 ? (chr(hex $2) . chr(hex $3)) : chr(hex $2);
62             }
63             }
64              
65             # Characters that should be escaped in XML
66             my %XML = ('&' => '&', '<' => '<', '>' => '>', '"' => '"', '\'' => ''');
67              
68             # "Sun, 06 Nov 1994 08:49:37 GMT" and "Sunday, 06-Nov-94 08:49:37 GMT"
69             my $EXPIRES_RE = qr/(\w+\W+\d+\W+\w+\W+\d+\W+\d+:\d+:\d+\W*\w+)/;
70              
71             # Header key/value pairs
72             my $QUOTED_VALUE_RE = qr/\G=\s*("(?:\\\\|\\"|[^"])*")/;
73             my $UNQUOTED_VALUE_RE = qr/\G=\s*([^;, ]*)/;
74              
75             # HTML entities
76             my $ENTITY_RE = qr/&(?:\#((?:[0-9]{1,7}|x[0-9a-fA-F]{1,6}));|(\w+[;=]?))/;
77              
78             # Encoding, encryption and pattern caches
79             my (%ENCODING, %ENCRYPTION, %PATTERN);
80              
81             our @EXPORT_OK = (
82             qw(b64_decode b64_encode camelize class_to_file class_to_path decamelize decode decrypt_cookie deprecated dumper),
83             qw(encode encrypt_cookie extract_usage generate_secret getopt gunzip gzip header_params hmac_sha1_sum),
84             qw(html_attr_unescape html_unescape humanize_bytes md5_bytes md5_sum monkey_patch network_contains punycode_decode),
85             qw(punycode_encode quote scope_guard secure_compare sha1_bytes sha1_sum slugify split_cookie_header split_header),
86             qw(steady_time tablify term_escape trim unindent unquote url_escape url_unescape xml_escape xor_encode)
87             );
88              
89             # Aliases
90             monkey_patch(__PACKAGE__, 'b64_decode', \&decode_base64);
91             monkey_patch(__PACKAGE__, 'b64_encode', \&encode_base64);
92             monkey_patch(__PACKAGE__, 'hmac_sha1_sum', \&hmac_sha1_hex);
93             monkey_patch(__PACKAGE__, 'md5_bytes', \&md5);
94             monkey_patch(__PACKAGE__, 'md5_sum', \&md5_hex);
95             monkey_patch(__PACKAGE__, 'sha1_bytes', \&sha1);
96             monkey_patch(__PACKAGE__, 'sha1_sum', \&sha1_hex);
97              
98             # Use a monotonic clock if possible
99             monkey_patch(__PACKAGE__, 'steady_time',
100 100597     100597   250813 MONOTONIC ? sub () { Time::HiRes::clock_gettime(Time::HiRes::CLOCK_MONOTONIC()) } : \&Time::HiRes::time);
101              
102             sub camelize {
103 42     42 1 351490 my $str = shift;
104 42 100       233 return $str if $str =~ /^[A-Z]/;
105              
106             # CamelCase words
107             return join '::', map {
108 39         180 join('', map { ucfirst lc } split /_/)
  53         146  
  81         493  
109             } split /-/, $str;
110             }
111              
112             sub class_to_file {
113 10     10 1 5590 my $class = shift;
114 10         74 $class =~ s/::|'//g;
115 10         68 $class =~ s/([A-Z])([A-Z]*)/$1 . lc $2/ge;
  18         98  
116 10         31 return decamelize($class);
117             }
118              
119             sub decamelize {
120 28     28 1 7810 my $str = shift;
121 28 100       193 return $str if $str !~ /^[A-Z]/;
122              
123             # snake_case words
124             return join '-', map {
125 23         106 join('_', map {lc} grep {length} split /([A-Z]{1}[^A-Z]*)/)
  26         182  
  49         3625  
  98         193  
126             } split /::/, $str;
127             }
128              
129             sub decrypt_cookie {
130 0     0 1 0 my ($value, $key, $salt) = @_;
131 0         0 croak 'CryptX 0.080+ required for encrypted cookie support' unless CRYPTX;
132              
133 0 0       0 return undef unless $value =~ /^([^-]+)-([^-]+)-([^-]+)$/;
134 0         0 my ($ct, $iv, $tag) = ($1, $2, $3);
135 0         0 ($ct, $iv, $tag) = (Crypt::Misc::decode_b64($ct), Crypt::Misc::decode_b64($iv), Crypt::Misc::decode_b64($tag));
136              
137 0   0     0 my $dk = $ENCRYPTION{$key}{$salt} ||= Crypt::KeyDerivation::pbkdf2($key, $salt);
138 0         0 return Crypt::AuthEnc::ChaCha20Poly1305::chacha20poly1305_decrypt_verify($dk, $iv, '', $ct, $tag);
139             }
140              
141             sub decode {
142 7822     7822 1 36694 my ($encoding, $bytes) = @_;
143 7822 100       14007 return undef unless eval { $bytes = _encoding($encoding)->decode("$bytes", 1); 1 };
  7822         22231  
  7735         73418  
144 7735         30991 return $bytes;
145             }
146              
147             sub deprecated {
148 2     2 1 7805 local $Carp::CarpLevel = 1;
149 2 100       348 $ENV{MOJO_FATAL_DEPRECATIONS} ? croak @_ : carp @_;
150             }
151              
152 303     303 1 7441 sub dumper { Data::Dumper->new([@_])->Indent(1)->Sortkeys(1)->Terse(1)->Useqq(1)->Dump }
153              
154 11021     11021 1 79520 sub encode { _encoding($_[0])->encode("$_[1]", 0) }
155              
156             sub encrypt_cookie {
157 0     0 1 0 my ($value, $key, $salt) = @_;
158 0         0 croak 'CryptX 0.080+ required for encrypted cookie support' unless CRYPTX;
159              
160 0   0     0 my $dk = $ENCRYPTION{$key}{$salt} ||= Crypt::KeyDerivation::pbkdf2($key, $salt);
161 0         0 my $iv = Crypt::PRNG::random_bytes(12);
162 0         0 my ($ct, $tag) = Crypt::AuthEnc::ChaCha20Poly1305::chacha20poly1305_encrypt_authenticate($dk, $iv, '', $value);
163              
164 0         0 return join '-', Crypt::Misc::encode_b64($ct), Crypt::Misc::encode_b64($iv), Crypt::Misc::encode_b64($tag);
165             }
166              
167             sub extract_usage {
168 26 100   26 1 5552 my $file = @_ ? "$_[0]" : (caller)[1];
169              
170 26         529 open my $handle, '>', \my $output;
171 26         262 pod2usage -exitval => 'noexit', -input => $file, -output => $handle;
172 26         707034 $output =~ s/^.*\n|\n$//;
173 26         237 $output =~ s/\n$//;
174              
175 26         168 return unindent($output);
176             }
177              
178             sub generate_secret {
179 2     2 1 9761 return Crypt::Misc::encode_b64u(Crypt::PRNG::random_bytes(128)) if CRYPTX;
180 2         87 srand;
181 2         13 return sha1_sum($$ . steady_time() . rand);
182             }
183              
184             sub getopt {
185 120 100   120 1 22015 my ($array, $opts) = map { ref $_[0] eq 'ARRAY' ? shift : $_ } \@ARGV, [];
  240         1196  
186              
187 120         1175 my $save = Getopt::Long::Configure(qw(default no_auto_abbrev no_ignore_case), @$opts);
188 120         14802 my $result = GetOptionsFromArray $array, @_;
189 120         88032 Getopt::Long::Configure($save);
190              
191 120         15549 return $result;
192             }
193              
194             sub gunzip {
195 2     2 1 1604 my $compressed = shift;
196 2 50       16 IO::Uncompress::Gunzip::gunzip \$compressed, \my $uncompressed
197             or croak "Couldn't gunzip: $IO::Uncompress::Gunzip::GzipError";
198 2         5514 return $uncompressed;
199             }
200              
201             sub gzip {
202 59     59 1 20337 my $uncompressed = shift;
203 59 50       425 IO::Compress::Gzip::gzip \$uncompressed, \my $compressed or croak "Couldn't gzip: $IO::Compress::Gzip::GzipError";
204 59         215004 return $compressed;
205             }
206              
207             sub header_params {
208 16     16 1 7507 my $value = shift;
209              
210 16         34 my $params = {};
211 16         102 while ($value =~ /\G[;\s]*([^=;, ]+)\s*/gc) {
212 20         50 my $name = $1;
213              
214             # Quoted value
215 20 100 66     142 if ($value =~ /$QUOTED_VALUE_RE/gco) { $params->{$name} //= unquote($1) }
  4 100       31  
216              
217             # Unquoted value
218 15   66     113 elsif ($value =~ /$UNQUOTED_VALUE_RE/gco) { $params->{$name} //= $1 }
219             }
220              
221 16   100     141 return ($params, substr($value, pos($value) // 0));
222             }
223              
224 34130     34130 1 71260 sub html_attr_unescape { _html(shift, 1) }
225 3000     3000 1 40462 sub html_unescape { _html(shift, 0) }
226              
227             sub humanize_bytes {
228 19     19 1 4608 my $size = shift;
229              
230 19 100       68 my $prefix = $size < 0 ? '-' : '';
231              
232 19 100       114 return "$prefix${size}B" if ($size = abs $size) < 1024;
233 16 100       72 return $prefix . _round($size) . 'KiB' if ($size /= 1024) < 1024;
234 11 100       48 return $prefix . _round($size) . 'MiB' if ($size /= 1024) < 1024;
235 8 100       40 return $prefix . _round($size) . 'GiB' if ($size /= 1024) < 1024;
236 2         9 return $prefix . _round($size /= 1024) . 'TiB';
237             }
238              
239             sub network_contains {
240 99     99 1 13288 my ($cidr, $addr) = @_;
241 99 100 100     529 return undef unless length $cidr && length $addr;
242              
243             # Parse inputs
244 93         344 my ($net, $mask) = split m!/!, $cidr, 2;
245 93         322 my $v6 = $net =~ /:/;
246 93 100 100     481 return undef if $v6 xor $addr =~ /:/;
247              
248             # Convert addresses to binary
249 91 100       525 return undef unless $net = inet_pton($v6 ? AF_INET6 : AF_INET, $net);
    100          
250 89 100       371 return undef unless $addr = inet_pton($v6 ? AF_INET6 : AF_INET, $addr);
    100          
251 87 100       236 my $length = $v6 ? 128 : 32;
252              
253             # Apply mask if given
254 87 100       547 $addr &= pack "B$length", '1' x $mask if defined $mask;
255              
256             # Compare
257 87         848 return 0 == unpack "B$length", ($net ^ $addr);
258             }
259              
260             # Direct translation of RFC 3492
261             sub punycode_decode {
262 23     23 1 4764 my $input = shift;
263 98     98   1021 use integer;
  98         225  
  98         1060  
264              
265 23         68 my ($n, $i, $bias, @output) = (PC_INITIAL_N, 0, PC_INITIAL_BIAS);
266              
267             # Consume all code points before the last delimiter
268 23 100       267 push @output, split(//, $1) if $input =~ s/(.*)\x2d//s;
269              
270 23         119 while (length $input) {
271 219         440 my ($oldi, $w) = ($i, 1);
272              
273             # Base to infinity in steps of base
274 219         668 for (my $k = PC_BASE; 1; $k += PC_BASE) {
275 458         1253 my $digit = ord substr $input, 0, 1, '';
276 458 100       985 $digit = $digit < 0x40 ? $digit + (26 - 0x30) : ($digit & 0x1f) - 1;
277 458         705 $i += $digit * $w;
278 458         731 my $t = $k - $bias;
279 458 100       1024 $t = $t < PC_TMIN ? PC_TMIN : $t > PC_TMAX ? PC_TMAX : $t;
    100          
280 458 100       1063 last if $digit < $t;
281 239         441 $w *= PC_BASE - $t;
282             }
283              
284 219         546 $bias = _adapt($i - $oldi, @output + 1, $oldi == 0);
285 219         463 $n += $i / (@output + 1);
286 219         371 $i = $i % (@output + 1);
287 219         742 splice @output, $i++, 0, chr $n;
288             }
289              
290 23         242 return join '', @output;
291             }
292              
293             # Direct translation of RFC 3492
294             sub punycode_encode {
295 64     64 1 35543 my $output = shift;
296 98     98   46843 use integer;
  98         267  
  98         503  
297              
298 64         200 my ($n, $delta, $bias) = (PC_INITIAL_N, 0, PC_INITIAL_BIAS);
299              
300             # Extract basic code points
301 64         458 my @input = map {ord} split //, $output;
  553         1053  
302 64         481 $output =~ s/[^\x00-\x7f]+//gs;
303 64         203 my $h = my $basic = length $output;
304 64 100       241 $output .= "\x2d" if $basic > 0;
305              
306 64         143 for my $m (sort grep { $_ >= PC_INITIAL_N } @input) {
  553         1312  
307 260 100       686 next if $m < $n;
308 218         412 $delta += ($m - $n) * ($h + 1);
309 218         383 $n = $m;
310              
311 218         435 for my $c (@input) {
312              
313 3630 100       7638 if ($c < $n) { $delta++ }
  2033 100       3906  
314             elsif ($c == $n) {
315 260         435 my $q = $delta;
316              
317             # Base to infinity in steps of base
318 260         447 for (my $k = PC_BASE; 1; $k += PC_BASE) {
319 581         918 my $t = $k - $bias;
320 581 100       1258 $t = $t < PC_TMIN ? PC_TMIN : $t > PC_TMAX ? PC_TMAX : $t;
    100          
321 581 100       1274 last if $q < $t;
322 321         575 my $o = $t + (($q - $t) % (PC_BASE - $t));
323 321 100       826 $output .= chr $o + ($o < 26 ? 0x61 : 0x30 - 26);
324 321         611 $q = ($q - $t) / (PC_BASE - $t);
325             }
326              
327 260 50       662 $output .= chr $q + ($q < 26 ? 0x61 : 0x30 - 26);
328 260         658 $bias = _adapt($delta, $h + 1, $h == $basic);
329 260         480 $delta = 0;
330 260         492 $h++;
331             }
332             }
333              
334 218         336 $delta++;
335 218         415 $n++;
336             }
337              
338 64         440 return $output;
339             }
340              
341             sub quote {
342 21     21 1 7608 my $str = shift;
343 21         181 $str =~ s/(["\\])/\\$1/g;
344 21         107 return qq{"$str"};
345             }
346              
347 18     18 1 5139 sub scope_guard { Mojo::Util::_Guard->new(cb => shift) }
348              
349             sub secure_compare {
350 97     97 1 25224 my ($one, $two) = @_;
351 97         239 my $r = length $one != length $two;
352 97 100       372 $two = $one if $r;
353 97         2430 $r |= ord(substr $one, $_) ^ ord(substr $two, $_) for 0 .. length($one) - 1;
354 97         560 return $r == 0;
355             }
356              
357             sub slugify {
358 14     14 1 4690 my ($value, $allow_unicode) = @_;
359              
360 14 100       50 if ($allow_unicode) {
361              
362             # Force unicode semantics by upgrading string
363 6         125 utf8::upgrade($value = Unicode::Normalize::NFKC($value));
364 6         44 $value =~ s/[^\w\s-]+//g;
365             }
366             else {
367 8         122 $value = Unicode::Normalize::NFKD($value);
368 8         84 $value =~ s/[^a-zA-Z0-9_\p{PosixSpace}-]+//g;
369             }
370 14         47 (my $new = lc trim($value)) =~ s/[-\s]+/-/g;
371              
372 14         87 return $new;
373             }
374              
375 1072     1072 1 11017 sub split_cookie_header { _header(shift, 1) }
376 225     225 1 11257 sub split_header { _header(shift, 0) }
377              
378             sub tablify {
379 18     18 1 8528 my $rows = shift;
380              
381 18         42 my @spec;
382 18         55 for my $row (@$rows) {
383 87         203 for my $i (0 .. $#$row) {
384 176   100     513 ($row->[$i] //= '') =~ y/\r\n//d;
385 176         313 my $len = length $row->[$i];
386 176 100 100     1811 $spec[$i] = $len if $len >= ($spec[$i] // 0);
387             }
388             }
389              
390 18         84 my @fm = (map({"\%-${_}s"} @spec[0 .. $#spec - 1]), '%s');
  23         117  
391 18         53 return join '', map { sprintf join(' ', @fm[0 .. $#$_]) . "\n", @$_ } @$rows;
  87         562  
392             }
393              
394             sub term_escape {
395 4     4 1 4688 my $str = shift;
396 4         34 $str =~ s/([\x00-\x09\x0b-\x1f\x7f\x80-\x9f])/sprintf '\\x%02x', ord $1/ge;
  16         93  
397 4         23 return $str;
398             }
399              
400             sub trim {
401 1372     1372 1 8015 my $str = shift;
402 1372         5950 $str =~ s/^\s+//;
403 1372         4410 $str =~ s/\s+$//;
404 1372         4822 return $str;
405             }
406              
407             sub unindent {
408 37     37 1 5526 my $str = shift;
409 37 100       842 my $min = min map { m/^([ \t]*)/; length $1 || () } split /\n/, $str;
  426         1113  
  426         1722  
410 37 100       1228 $str =~ s/^[ \t]{0,$min}//gm if $min;
411 37         1001 return $str;
412             }
413              
414             sub unquote {
415 48     48 1 4806 my $str = shift;
416 48 50       342 return $str unless $str =~ s/^"(.*)"$/$1/g;
417 48         129 $str =~ s/\\\\/\\/g;
418 48         146 $str =~ s/\\"/"/g;
419 48         124 return $str;
420             }
421              
422             sub url_escape {
423 6302     6302 1 28405 my ($str, $pattern) = @_;
424              
425 6302 100       17635 if ($pattern) {
426 6287 100       17362 unless (exists $PATTERN{$pattern}) {
427 142         2532 (my $quoted = $pattern) =~ s!([/\$\[])!\\$1!g;
428 142 50       44851 $PATTERN{$pattern} = eval "sub { \$_[0] =~ s/([$quoted])/sprintf '%%%02X', ord \$1/ge }" or croak $@;
429             }
430 6287         190259 $PATTERN{$pattern}->($str);
431             }
432 15         102 else { $str =~ s/([^A-Za-z0-9\-._~])/sprintf '%%%02X', ord $1/ge }
  22         125  
433              
434 6302         27065 return $str;
435             }
436              
437             sub url_unescape {
438 8145     8145 1 24759 my $str = shift;
439 8145         21157 $str =~ s/%([0-9a-fA-F]{2})/chr hex $1/ge;
  793         2940  
440 8145         19954 return $str;
441             }
442              
443             sub xml_escape {
444 486 100 66 486 1 14591 return $_[0] if ref $_[0] && ref $_[0] eq 'Mojo::ByteStream';
  8715 100 100 8715   97600  
445 485   50     997 my $str = shift // '';
  6705   100     13101  
446 485         1041 $str =~ s/([&<>"'])/$XML{$1}/ge;
  42         267  
  6705         15464  
  7644         23097  
447 485         1838 return $str;
  6705         17938  
448             }
449              
450             sub xor_encode {
451 295     295 1 6175 my ($input, $key) = @_;
452              
453             # Encode with variable key length
454 295         535 my $len = length $key;
455 295         638 my $buffer = my $output = '';
456 295         1887 $output .= $buffer ^ $key while length($buffer = substr($input, 0, $len, '')) == $len;
457 295         2004 return $output .= $buffer ^ substr($key, 0, length $buffer, '');
458             }
459              
460             sub _adapt {
461 479     479   1017 my ($delta, $numpoints, $firsttime) = @_;
462 98     98   308983 use integer;
  98         232  
  98         647  
463              
464 479 100       1017 $delta = $firsttime ? $delta / PC_DAMP : $delta / 2;
465 479         818 $delta += $delta / $numpoints;
466 479         770 my $k = 0;
467 479         1147 while ($delta > ((PC_BASE - PC_TMIN) * PC_TMAX) / 2) {
468 110         177 $delta /= PC_BASE - PC_TMIN;
469 110         260 $k += PC_BASE;
470             }
471              
472 479         1056 return $k + (((PC_BASE - PC_TMIN + 1) * $delta) / ($delta + PC_SKEW));
473             }
474              
475 18843   66 18843   174910 sub _encoding { $ENCODING{$_[0]} //= find_encoding($_[0]) // croak "Unknown encoding '$_[0]'" }
      66        
476              
477             sub _entity {
478 1003     1003   1803 my ($point, $name, $attr) = @_;
479              
480             # Code point
481 1003 100       1743 return chr($point !~ /^x/ ? $point : hex $point) unless defined $name;
    100          
482              
483             # Named character reference
484 962         1072 my $rest = my $last = '';
485 962         1300 while (length $name) {
486             return $ENTITIES{$name} . reverse $rest
487 1000 100 100     4204 if exists $ENTITIES{$name} && (!$attr || $name =~ /;$/ || $last !~ /[A-Za-z0-9=]/);
      100        
488 48         129 $rest .= $last = chop $name;
489             }
490 10         62 return '&' . reverse $rest;
491             }
492              
493             sub _header {
494 1297     1297   3800 my ($str, $cookie) = @_;
495              
496 1297         2824 my (@tree, @part);
497 1297         5695 while ($str =~ /\G[,;\s]*([^=;, ]+)\s*/gc) {
498 833         2185 push @part, $1, undef;
499 833   100     3226 my $expires = $cookie && @part > 2 && lc $1 eq 'expires';
500              
501             # Special "expires" value
502 833 100 100     6237 if ($expires && $str =~ /\G=\s*$EXPIRES_RE/gco) { $part[-1] = $1 }
  125 100       290  
    100          
503              
504             # Quoted value
505 42         112 elsif ($str =~ /$QUOTED_VALUE_RE/gco) { $part[-1] = unquote $1 }
506              
507             # Unquoted value
508 563         1159 elsif ($str =~ /$UNQUOTED_VALUE_RE/gco) { $part[-1] = $1 }
509              
510             # Separator
511 833 100       3084 next unless $str =~ /\G[;\s]*,\s*/gc;
512 107         349 push @tree, [@part];
513 107         441 @part = ();
514             }
515              
516             # Take care of final part
517 1297 100       7129 return [@part ? (@tree, \@part) : @tree];
518             }
519              
520             sub _html {
521 37130     37130   71704 my ($str, $attr) = @_;
522 37130         59283 $str =~ s/$ENTITY_RE/_entity($1, $2, $attr)/geo;
  1003         1420  
523 37130         210333 return $str;
524             }
525              
526             sub _options {
527              
528             # Hash or name (one)
529 1817 100   1817   6772 return ref $_[0] eq 'HASH' ? (undef, %{shift()}) : @_ if @_ == 1;
  1012 100       4173  
530              
531             # Name and values (odd)
532 349 100       1333 return shift, @_ if @_ % 2;
533              
534             # Name and hash or just values (even)
535 269 100       1493 return ref $_[1] eq 'HASH' ? (shift, %{shift()}) : (undef, @_);
  18         95  
536             }
537              
538             # This may break in the future, but is worth it for performance
539 796     796   14382 sub _readable { !!(IO::Poll::_poll(@_[0, 1], my $m = POLLIN | POLLPRI) > 0) }
540              
541 16 100   16   231 sub _round { $_[0] < 10 ? int($_[0] * 10 + 0.5) / 10 : int($_[0] + 0.5) }
542              
543             sub _stash {
544 22413     22413   47785 my ($name, $object) = (shift, shift);
545              
546             # Hash
547 22413 100 100     142187 return $object->{$name} //= {} unless @_;
548              
549             # Get
550 1405 100 100     6872 return $object->{$name}{$_[0]} unless @_ > 1 || ref $_[0];
551              
552             # Set
553 1165 100       6024 my $values = ref $_[0] ? $_[0] : {@_};
554 1165         4460 @{$object->{$name}}{keys %$values} = values %$values;
  1165         4184  
555              
556 1165         6188 return $object;
557             }
558              
559             sub _teardown {
560 826 50   826   39402 return unless my $class = shift;
561              
562             # @ISA has to be cleared first because of circular references
563 98     98   175362 no strict 'refs';
  98         263  
  98         16852  
564 826         1199 @{"${class}::ISA"} = ();
  826         9828  
565 826         2303 delete_package $class;
566             }
567              
568             package Mojo::Util::_Guard;
569 98     98   781 use Mojo::Base -base;
  98         207  
  98         1090  
570              
571 18     18   1268 sub DESTROY { shift->{cb}() }
572              
573             1;
574              
575             =encoding utf8
576              
577             =head1 NAME
578              
579             Mojo::Util - Portable utility functions
580              
581             =head1 SYNOPSIS
582              
583             use Mojo::Util qw(b64_encode url_escape url_unescape);
584              
585             my $str = 'test=23';
586             my $escaped = url_escape $str;
587             say url_unescape $escaped;
588             say b64_encode $escaped, '';
589              
590             =head1 DESCRIPTION
591              
592             L provides portable utility functions for L.
593              
594             =head1 FUNCTIONS
595              
596             L implements the following functions, which can be imported individually.
597              
598             =head2 b64_decode
599              
600             my $bytes = b64_decode $b64;
601              
602             Base64 decode bytes with L.
603              
604             =head2 b64_encode
605              
606             my $b64 = b64_encode $bytes;
607             my $b64 = b64_encode $bytes, "\n";
608              
609             Base64 encode bytes with L, the line ending defaults to a newline.
610              
611             =head2 camelize
612              
613             my $camelcase = camelize $snakecase;
614              
615             Convert C string to C and replace C<-> with C<::>.
616              
617             # "FooBar"
618             camelize 'foo_bar';
619              
620             # "FooBar::Baz"
621             camelize 'foo_bar-baz';
622              
623             # "FooBar::Baz"
624             camelize 'FooBar::Baz';
625              
626             =head2 class_to_file
627              
628             my $file = class_to_file 'Foo::Bar';
629              
630             Convert a class name to a file.
631              
632             # "foo_bar"
633             class_to_file 'Foo::Bar';
634              
635             # "foobar"
636             class_to_file 'FOO::Bar';
637              
638             # "foo_bar"
639             class_to_file 'FooBar';
640              
641             # "foobar"
642             class_to_file 'FOOBar';
643              
644             =head2 class_to_path
645              
646             my $path = class_to_path 'Foo::Bar';
647              
648             Convert class name to path, as used by C<%INC>.
649              
650             # "Foo/Bar.pm"
651             class_to_path 'Foo::Bar';
652              
653             # "FooBar.pm"
654             class_to_path 'FooBar';
655              
656             =head2 decamelize
657              
658             my $snakecase = decamelize $camelcase;
659              
660             Convert C string to C and replace C<::> with C<->.
661              
662             # "foo_bar"
663             decamelize 'FooBar';
664              
665             # "foo_bar-baz"
666             decamelize 'FooBar::Baz';
667              
668             # "foo_bar-baz"
669             decamelize 'foo_bar-baz';
670              
671             =head2 decode
672              
673             my $chars = decode 'UTF-8', $bytes;
674              
675             Decode bytes to characters with L, or return C if decoding failed.
676              
677             =head2 decrypt_cookie
678              
679             my $value = decrypt_cookie $encrypted, 'passw0rd', 'salt';
680              
681             Decrypt cookie value encrypted with L, returns the decrypted value or C.
682              
683             =head2 deprecated
684              
685             deprecated 'foo is DEPRECATED in favor of bar';
686              
687             Warn about deprecated feature from perspective of caller. You can also set the C environment
688             variable to make them die instead with L.
689              
690             =head2 dumper
691              
692             my $perl = dumper {some => 'data'};
693              
694             Dump a Perl data structure with L.
695              
696             =head2 encode
697              
698             my $bytes = encode 'UTF-8', $chars;
699              
700             Encode characters to bytes with L.
701              
702             =head2 encrypt_cookie
703              
704             my $encrypted = encrypt_cookie $value, 'passw0rd', 'salt';
705              
706             Encrypt cookie value.
707              
708             =head2 extract_usage
709              
710             my $usage = extract_usage;
711             my $usage = extract_usage '/home/sri/foo.pod';
712              
713             Extract usage message from the SYNOPSIS section of a file containing POD documentation, defaults to using the file this
714             function was called from.
715              
716             # "Usage: APPLICATION test [OPTIONS]\n"
717             extract_usage;
718              
719             =head1 SYNOPSIS
720              
721             Usage: APPLICATION test [OPTIONS]
722              
723             =cut
724              
725             =head2 generate_secret
726              
727             my $secret = generate_secret;
728              
729             Generate a random secret with a cryptographically secure random number generator if available, and a less secure
730             fallback if not.
731              
732             =head2 getopt
733              
734             getopt
735             'H|headers=s' => \my @headers,
736             't|timeout=i' => \my $timeout,
737             'v|verbose' => \my $verbose;
738             getopt $array,
739             'H|headers=s' => \my @headers,
740             't|timeout=i' => \my $timeout,
741             'v|verbose' => \my $verbose;
742             getopt $array, ['pass_through'],
743             'H|headers=s' => \my @headers,
744             't|timeout=i' => \my $timeout,
745             'v|verbose' => \my $verbose;
746              
747             Extract options from an array reference with L, but without changing its global configuration, defaults
748             to using C<@ARGV>. The configuration options C and C are enabled by default.
749              
750             # Extract "charset" option
751             getopt ['--charset', 'UTF-8'], 'charset=s' => \my $charset;
752             say $charset;
753              
754             =head2 gunzip
755              
756             my $uncompressed = gunzip $compressed;
757              
758             Uncompress bytes with L.
759              
760             =head2 gzip
761              
762             my $compressed = gzip $uncompressed;
763              
764             Compress bytes with L.
765              
766             =head2 header_params
767              
768             my ($params, $remainder) = header_params 'one=foo; two="bar", three=baz';
769              
770             Extract HTTP header field parameters until the first comma according to L.
771              
772             =head2 hmac_sha1_sum
773              
774             my $checksum = hmac_sha1_sum $bytes, 'passw0rd';
775              
776             Generate HMAC-SHA1 checksum for bytes with L.
777              
778             # "11cedfd5ec11adc0ec234466d8a0f2a83736aa68"
779             hmac_sha1_sum 'foo', 'passw0rd';
780              
781             =head2 html_attr_unescape
782              
783             my $str = html_attr_unescape $escaped;
784              
785             Same as L, but handles special rules from the L
786             for HTML attributes.
787              
788             # "foo=bar<est=baz"
789             html_attr_unescape 'foo=bar<est=baz';
790              
791             # "foo=bar
792             html_attr_unescape 'foo=bar<est=baz';
793              
794             =head2 html_unescape
795              
796             my $str = html_unescape $escaped;
797              
798             Unescape all HTML entities in string.
799              
800             # "
"
801             html_unescape '<div>';
802              
803             =head2 humanize_bytes
804              
805             my $str = humanize_bytes 1234;
806              
807             Turn number of bytes into a simplified human readable format.
808              
809             # "1B"
810             humanize_bytes 1;
811              
812             # "7.5GiB"
813             humanize_bytes 8007188480;
814              
815             # "13GiB"
816             humanize_bytes 13443399680;
817              
818             # "-685MiB"
819             humanize_bytes -717946880;
820              
821             =head2 md5_bytes
822              
823             my $checksum = md5_bytes $bytes;
824              
825             Generate binary MD5 checksum for bytes with L.
826              
827             =head2 md5_sum
828              
829             my $checksum = md5_sum $bytes;
830              
831             Generate MD5 checksum for bytes with L.
832              
833             # "acbd18db4cc2f85cedef654fccc4a4d8"
834             md5_sum 'foo';
835              
836             =head2 monkey_patch
837              
838             monkey_patch $package, foo => sub {...};
839             monkey_patch $package, foo => sub {...}, bar => sub {...};
840              
841             Monkey patch functions into package.
842              
843             monkey_patch 'MyApp',
844             one => sub { say 'One!' },
845             two => sub { say 'Two!' },
846             three => sub { say 'Three!' };
847              
848             =head2 network_contains
849              
850             my $bool = network_contains $network, $address;
851              
852             Check that a given address is contained within a network in CIDR form. If the network is a single address, the
853             addresses must be equivalent.
854              
855             # True
856             network_contains('10.0.0.0/8', '10.10.10.10');
857             network_contains('10.10.10.10', '10.10.10.10');
858             network_contains('fc00::/7', 'fc::c0:ff:ee');
859              
860             # False
861             network_contains('10.0.0.0/29', '10.10.10.10');
862             network_contains('10.10.10.12', '10.10.10.10');
863             network_contains('fc00::/7', '::1');
864            
865             =head2 punycode_decode
866              
867             my $str = punycode_decode $punycode;
868              
869             Punycode decode string as described in L.
870              
871             # "bücher"
872             punycode_decode 'bcher-kva';
873              
874             =head2 punycode_encode
875              
876             my $punycode = punycode_encode $str;
877              
878             Punycode encode string as described in L.
879              
880             # "bcher-kva"
881             punycode_encode 'bücher';
882              
883             =head2 quote
884              
885             my $quoted = quote $str;
886              
887             Quote string.
888              
889             =head2 scope_guard
890              
891             my $guard = scope_guard sub {...};
892              
893             Create anonymous scope guard object that will execute the passed callback when the object is destroyed.
894              
895             # Execute closure at end of scope
896             {
897             my $guard = scope_guard sub { say "Mojo!" };
898             say "Hello";
899             }
900              
901             =head2 secure_compare
902              
903             my $bool = secure_compare $str1, $str2;
904              
905             Constant time comparison algorithm to prevent timing attacks. The secret string should be the second argument, to avoid
906             leaking information about the length of the string.
907              
908             =head2 sha1_bytes
909              
910             my $checksum = sha1_bytes $bytes;
911              
912             Generate binary SHA1 checksum for bytes with L.
913              
914             =head2 sha1_sum
915              
916             my $checksum = sha1_sum $bytes;
917              
918             Generate SHA1 checksum for bytes with L.
919              
920             # "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"
921             sha1_sum 'foo';
922              
923             =head2 slugify
924              
925             my $slug = slugify $string;
926             my $slug = slugify $string, $bool;
927              
928             Returns a URL slug generated from the input string. Non-word characters are removed, the string is trimmed and
929             lowercased, and whitespace characters are replaced by a dash. By default, non-ASCII characters are normalized to ASCII
930             word characters or removed, but if a true value is passed as the second parameter, all word characters will be allowed
931             in the result according to unicode semantics.
932              
933             # "joel-is-a-slug"
934             slugify 'Joel is a slug';
935              
936             # "this-is-my-resume"
937             slugify 'This is: my - résumé! ☃ ';
938              
939             # "this-is-my-résumé"
940             slugify 'This is: my - résumé! ☃ ', 1;
941              
942             =head2 split_cookie_header
943              
944             my $tree = split_cookie_header 'a=b; expires=Thu, 07 Aug 2008 07:07:59 GMT';
945              
946             Same as L, but handles C values from L.
947              
948             =head2 split_header
949              
950             my $tree = split_header 'foo="bar baz"; test=123, yada';
951              
952             Split HTTP header value into key/value pairs, each comma separated part gets its own array reference, and keys without
953             a value get C assigned.
954              
955             # "one"
956             split_header('one; two="three four", five=six')->[0][0];
957              
958             # "two"
959             split_header('one; two="three four", five=six')->[0][2];
960              
961             # "three four"
962             split_header('one; two="three four", five=six')->[0][3];
963              
964             # "five"
965             split_header('one; two="three four", five=six')->[1][0];
966              
967             # "six"
968             split_header('one; two="three four", five=six')->[1][1];
969              
970             =head2 steady_time
971              
972             my $time = steady_time;
973              
974             High resolution time elapsed from an arbitrary fixed point in the past, resilient to time jumps if a monotonic clock is
975             available through L.
976              
977             =head2 tablify
978              
979             my $table = tablify [['foo', 'bar'], ['baz', 'yada']];
980              
981             Row-oriented generator for text tables.
982              
983             # "foo bar\nyada yada\nbaz yada\n"
984             tablify [['foo', 'bar'], ['yada', 'yada'], ['baz', 'yada']];
985              
986             =head2 term_escape
987              
988             my $escaped = term_escape $str;
989              
990             Escape all POSIX control characters except for C<\n>.
991              
992             # "foo\\x09bar\\x0d\n"
993             term_escape "foo\tbar\r\n";
994              
995             =head2 trim
996              
997             my $trimmed = trim $str;
998              
999             Trim whitespace characters from both ends of string.
1000              
1001             # "foo bar"
1002             trim ' foo bar ';
1003              
1004             =head2 unindent
1005              
1006             my $unindented = unindent $str;
1007              
1008             Unindent multi-line string.
1009              
1010             # "foo\nbar\nbaz\n"
1011             unindent " foo\n bar\n baz\n";
1012              
1013             =head2 unquote
1014              
1015             my $str = unquote $quoted;
1016              
1017             Unquote string.
1018              
1019             =head2 url_escape
1020              
1021             my $escaped = url_escape $str;
1022             my $escaped = url_escape $str, '^A-Za-z0-9\-._~';
1023              
1024             Percent encode unsafe characters in string as described in L, the pattern
1025             used defaults to C<^A-Za-z0-9\-._~>.
1026              
1027             # "foo%3Bbar"
1028             url_escape 'foo;bar';
1029              
1030             =head2 url_unescape
1031              
1032             my $str = url_unescape $escaped;
1033              
1034             Decode percent encoded characters in string as described in L.
1035              
1036             # "foo;bar"
1037             url_unescape 'foo%3Bbar';
1038              
1039             =head2 xml_escape
1040              
1041             my $escaped = xml_escape $str;
1042              
1043             Escape unsafe characters C<&>, C>, C>, C<"> and C<'> in string, but do not escape L
1044             objects.
1045              
1046             # "<div>"
1047             xml_escape '
';
1048              
1049             # "
"
1050             use Mojo::ByteStream qw(b);
1051             xml_escape b('
');
1052              
1053             =head2 xor_encode
1054              
1055             my $encoded = xor_encode $str, $key;
1056              
1057             XOR encode string with variable length key.
1058              
1059             =head1 SEE ALSO
1060              
1061             L, L, L.
1062              
1063             =cut