| 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 |
||||||
| 593 | |||||||
| 594 | =head1 FUNCTIONS | ||||||
| 595 | |||||||
| 596 | L |
||||||
| 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 |
||||||
| 610 | |||||||
| 611 | =head2 camelize | ||||||
| 612 | |||||||
| 613 | my $camelcase = camelize $snakecase; | ||||||
| 614 | |||||||
| 615 | Convert 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 |
||||||
| 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 |
||||||
| 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 |
||||||
| 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 |
||||||
| 748 | to using C<@ARGV>. The configuration options C |
||||||
| 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"html_unescape">, 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"split_header">, but handles C |
||||||
| 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 |
||||||
| 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 |
||||||
| 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 |
||||||
| 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 |
||||||
| 1062 | |||||||
| 1063 | =cut |