line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package File::KDBX::Util; |
2
|
|
|
|
|
|
|
# ABSTRACT: Utility functions for working with KDBX files |
3
|
|
|
|
|
|
|
|
4
|
27
|
|
|
27
|
|
407
|
use 5.010; |
|
27
|
|
|
|
|
88
|
|
5
|
27
|
|
|
27
|
|
116
|
use warnings; |
|
27
|
|
|
|
|
41
|
|
|
27
|
|
|
|
|
635
|
|
6
|
27
|
|
|
27
|
|
134
|
use strict; |
|
27
|
|
|
|
|
62
|
|
|
27
|
|
|
|
|
731
|
|
7
|
|
|
|
|
|
|
|
8
|
27
|
|
|
27
|
|
9911
|
use Crypt::PRNG qw(random_bytes random_string); |
|
27
|
|
|
|
|
110953
|
|
|
27
|
|
|
|
|
1700
|
|
9
|
27
|
|
|
27
|
|
12681
|
use Encode qw(decode encode); |
|
27
|
|
|
|
|
216145
|
|
|
27
|
|
|
|
|
1757
|
|
10
|
27
|
|
|
27
|
|
172
|
use Exporter qw(import); |
|
27
|
|
|
|
|
45
|
|
|
27
|
|
|
|
|
566
|
|
11
|
27
|
|
|
27
|
|
10008
|
use File::KDBX::Error; |
|
27
|
|
|
|
|
70
|
|
|
27
|
|
|
|
|
1893
|
|
12
|
27
|
|
|
27
|
|
293
|
use List::Util 1.33 qw(any all); |
|
27
|
|
|
|
|
444
|
|
|
27
|
|
|
|
|
2335
|
|
13
|
27
|
|
|
27
|
|
10861
|
use Module::Load; |
|
27
|
|
|
|
|
25679
|
|
|
27
|
|
|
|
|
143
|
|
14
|
27
|
|
|
27
|
|
11026
|
use Ref::Util qw(is_arrayref is_coderef is_hashref is_ref is_refref is_scalarref); |
|
27
|
|
|
|
|
35288
|
|
|
27
|
|
|
|
|
2009
|
|
15
|
27
|
|
|
27
|
|
195
|
use Scalar::Util qw(blessed looks_like_number readonly); |
|
27
|
|
|
|
|
45
|
|
|
27
|
|
|
|
|
1195
|
|
16
|
27
|
|
|
27
|
|
11828
|
use Time::Piece 1.33; |
|
27
|
|
|
|
|
273763
|
|
|
27
|
|
|
|
|
148
|
|
17
|
27
|
|
|
27
|
|
11065
|
use boolean; |
|
27
|
|
|
|
|
25026
|
|
|
27
|
|
|
|
|
113
|
|
18
|
27
|
|
|
27
|
|
1848
|
use namespace::clean -except => 'import'; |
|
27
|
|
|
|
|
54
|
|
|
27
|
|
|
|
|
171
|
|
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
our $VERSION = '0.904'; # VERSION |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
our %EXPORT_TAGS = ( |
23
|
|
|
|
|
|
|
assert => [qw(DEBUG assert)], |
24
|
|
|
|
|
|
|
class => [qw(extends has list_attributes)], |
25
|
|
|
|
|
|
|
clone => [qw(clone clone_nomagic)], |
26
|
|
|
|
|
|
|
coercion => [qw(to_bool to_number to_string to_time to_tristate to_uuid)], |
27
|
|
|
|
|
|
|
crypt => [qw(pad_pkcs7)], |
28
|
|
|
|
|
|
|
debug => [qw(DEBUG dumper)], |
29
|
|
|
|
|
|
|
fork => [qw(can_fork)], |
30
|
|
|
|
|
|
|
function => [qw(memoize recurse_limit)], |
31
|
|
|
|
|
|
|
empty => [qw(empty nonempty)], |
32
|
|
|
|
|
|
|
erase => [qw(erase erase_scoped)], |
33
|
|
|
|
|
|
|
gzip => [qw(gzip gunzip)], |
34
|
|
|
|
|
|
|
int => [qw(int64 pack_ql pack_Ql unpack_ql unpack_Ql)], |
35
|
|
|
|
|
|
|
io => [qw(read_all)], |
36
|
|
|
|
|
|
|
load => [qw(load_optional load_xs try_load_optional)], |
37
|
|
|
|
|
|
|
search => [qw(query query_any search simple_expression_query)], |
38
|
|
|
|
|
|
|
text => [qw(snakify trim)], |
39
|
|
|
|
|
|
|
uuid => [qw(format_uuid generate_uuid is_uuid uuid UUID_NULL)], |
40
|
|
|
|
|
|
|
uri => [qw(split_url uri_escape_utf8 uri_unescape_utf8)], |
41
|
|
|
|
|
|
|
); |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
$EXPORT_TAGS{all} = [map { @$_ } values %EXPORT_TAGS]; |
44
|
|
|
|
|
|
|
our @EXPORT_OK = @{$EXPORT_TAGS{all}}; |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
BEGIN { |
47
|
27
|
|
|
27
|
|
17415
|
my $debug = $ENV{DEBUG}; |
48
|
27
|
50
|
|
|
|
206
|
$debug = looks_like_number($debug) ? (0 + $debug) : ($debug ? 1 : 0); |
|
|
50
|
|
|
|
|
|
49
|
|
|
|
|
|
|
*DEBUG = $debug == 1 ? sub() { 1 } : |
50
|
|
|
|
|
|
|
$debug == 2 ? sub() { 2 } : |
51
|
|
|
|
|
|
|
$debug == 3 ? sub() { 3 } : |
52
|
27
|
50
|
|
|
|
34439
|
$debug == 4 ? sub() { 4 } : sub() { 0 }; |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
53
|
|
|
|
|
|
|
} |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
my %OPS = ( |
56
|
|
|
|
|
|
|
'eq' => 2, # binary |
57
|
|
|
|
|
|
|
'ne' => 2, |
58
|
|
|
|
|
|
|
'lt' => 2, |
59
|
|
|
|
|
|
|
'gt' => 2, |
60
|
|
|
|
|
|
|
'le' => 2, |
61
|
|
|
|
|
|
|
'ge' => 2, |
62
|
|
|
|
|
|
|
'==' => 2, |
63
|
|
|
|
|
|
|
'!=' => 2, |
64
|
|
|
|
|
|
|
'<' => 2, |
65
|
|
|
|
|
|
|
'>' => 2, |
66
|
|
|
|
|
|
|
'<=' => 2, |
67
|
|
|
|
|
|
|
'>=' => 2, |
68
|
|
|
|
|
|
|
'=~' => 2, |
69
|
|
|
|
|
|
|
'!~' => 2, |
70
|
|
|
|
|
|
|
'!' => 1, # unary |
71
|
|
|
|
|
|
|
'!!' => 1, |
72
|
|
|
|
|
|
|
'-not' => 1, # special |
73
|
|
|
|
|
|
|
'-false' => 1, |
74
|
|
|
|
|
|
|
'-true' => 1, |
75
|
|
|
|
|
|
|
'-defined' => 1, |
76
|
|
|
|
|
|
|
'-undef' => 1, |
77
|
|
|
|
|
|
|
'-empty' => 1, |
78
|
|
|
|
|
|
|
'-nonempty' => 1, |
79
|
|
|
|
|
|
|
'-or' => -1, |
80
|
|
|
|
|
|
|
'-and' => -1, |
81
|
|
|
|
|
|
|
); |
82
|
|
|
|
|
|
|
my %OP_NEG = ( |
83
|
|
|
|
|
|
|
'eq' => 'ne', |
84
|
|
|
|
|
|
|
'ne' => 'eq', |
85
|
|
|
|
|
|
|
'lt' => 'ge', |
86
|
|
|
|
|
|
|
'gt' => 'le', |
87
|
|
|
|
|
|
|
'le' => 'gt', |
88
|
|
|
|
|
|
|
'ge' => 'lt', |
89
|
|
|
|
|
|
|
'==' => '!=', |
90
|
|
|
|
|
|
|
'!=' => '==', |
91
|
|
|
|
|
|
|
'<' => '>=', |
92
|
|
|
|
|
|
|
'>' => '<=', |
93
|
|
|
|
|
|
|
'<=' => '>', |
94
|
|
|
|
|
|
|
'>=' => '<', |
95
|
|
|
|
|
|
|
'=~' => '!~', |
96
|
|
|
|
|
|
|
'!~' => '=~', |
97
|
|
|
|
|
|
|
); |
98
|
|
|
|
|
|
|
my %ATTRIBUTES; |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
my $XS_LOADED; |
102
|
|
|
|
|
|
|
sub load_xs { |
103
|
38
|
|
|
38
|
1
|
77
|
my $version = shift; |
104
|
|
|
|
|
|
|
|
105
|
38
|
100
|
|
|
|
156
|
goto IS_LOADED if defined $XS_LOADED; |
106
|
|
|
|
|
|
|
|
107
|
27
|
100
|
66
|
|
|
312
|
if ($ENV{PERL_ONLY} || (exists $ENV{PERL_FILE_KDBX_XS} && !$ENV{PERL_FILE_KDBX_XS})) { |
|
|
|
66
|
|
|
|
|
108
|
1
|
|
|
|
|
5
|
return $XS_LOADED = !1; |
109
|
|
|
|
|
|
|
} |
110
|
|
|
|
|
|
|
|
111
|
26
|
|
|
|
|
52
|
$XS_LOADED = !!eval { require File::KDBX::XS; 1 }; |
|
26
|
|
|
|
|
10138
|
|
|
26
|
|
|
|
|
8493
|
|
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
IS_LOADED: |
114
|
|
|
|
|
|
|
{ |
115
|
37
|
|
|
|
|
73
|
local $@; |
|
37
|
|
|
|
|
61
|
|
116
|
37
|
50
|
|
|
|
186
|
return $XS_LOADED if !$version; |
117
|
0
|
|
|
|
|
0
|
return !!eval { File::KDBX::XS->VERSION($version); 1 }; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
118
|
|
|
|
|
|
|
} |
119
|
|
|
|
|
|
|
} |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
sub assert(&) { ## no critic (ProhibitSubroutinePrototypes) |
123
|
2320
|
|
|
2320
|
1
|
3108
|
return if !DEBUG; |
124
|
0
|
|
|
|
|
0
|
my $code = shift; |
125
|
0
|
0
|
|
|
|
0
|
return if $code->(); |
126
|
|
|
|
|
|
|
|
127
|
0
|
|
|
|
|
0
|
(undef, my $file, my $line) = caller; |
128
|
0
|
|
|
|
|
0
|
$file =~ s!([^/\\]+)$!$1!; |
129
|
0
|
|
|
|
|
0
|
my $assertion = ''; |
130
|
0
|
0
|
|
|
|
0
|
if (try_load_optional('B::Deparse')) { |
131
|
0
|
|
|
|
|
0
|
my $deparse = B::Deparse->new(qw{-P -x9}); |
132
|
0
|
|
|
|
|
0
|
$assertion = $deparse->coderef2text($code); |
133
|
0
|
|
|
|
|
0
|
$assertion =~ s/^\{(?:\s*(?:package[^;]+|use[^;]+);)*\s*(.*?);\s*\}$/$1/s; |
134
|
0
|
|
|
|
|
0
|
$assertion =~ s/\s+/ /gs; |
135
|
0
|
|
|
|
|
0
|
$assertion = ": $assertion"; |
136
|
|
|
|
|
|
|
} |
137
|
0
|
|
|
|
|
0
|
die "$0: $file:$line: Assertion failed$assertion\n"; |
138
|
|
|
|
|
|
|
} |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
sub can_fork { |
142
|
15
|
|
|
15
|
1
|
103
|
require Config; |
143
|
15
|
50
|
|
|
|
799
|
return 1 if $Config::Config{d_fork}; |
144
|
0
|
0
|
0
|
|
|
0
|
return 0 if $^O ne 'MSWin32' && $^O ne 'NetWare'; |
145
|
0
|
0
|
|
|
|
0
|
return 0 if !$Config::Config{useithreads}; |
146
|
0
|
0
|
|
|
|
0
|
return 0 if $Config::Config{ccflags} !~ /-DPERL_IMPLICIT_SYS/; |
147
|
0
|
0
|
|
|
|
0
|
return 0 if $] < 5.008001; |
148
|
0
|
0
|
0
|
|
|
0
|
if ($] == 5.010000 && $Config::Config{ccname} eq 'gcc' && $Config::Config{gccversion}) { |
|
|
|
0
|
|
|
|
|
149
|
0
|
0
|
|
|
|
0
|
return 0 if $Config::Config{gccversion} !~ m/^(\d+)\.(\d+)/; |
150
|
0
|
|
|
|
|
0
|
my @parts = split(/[\.\s]+/, $Config::Config{gccversion}); |
151
|
0
|
0
|
0
|
|
|
0
|
return 0 if $parts[0] > 4 || ($parts[0] == 4 && $parts[1] >= 8); |
|
|
|
0
|
|
|
|
|
152
|
|
|
|
|
|
|
} |
153
|
0
|
0
|
|
|
|
0
|
return 0 if $INC{'Devel/Cover.pm'}; |
154
|
0
|
|
|
|
|
0
|
return 1; |
155
|
|
|
|
|
|
|
} |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
sub clone { |
159
|
0
|
|
|
0
|
1
|
0
|
require Storable; |
160
|
0
|
|
|
|
|
0
|
goto &Storable::dclone; |
161
|
|
|
|
|
|
|
} |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
sub clone_nomagic { |
165
|
0
|
|
|
0
|
1
|
0
|
my $thing = shift; |
166
|
0
|
0
|
|
|
|
0
|
if (is_arrayref($thing)) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
167
|
0
|
|
|
|
|
0
|
my @arr = map { clone_nomagic($_) } @$thing; |
|
0
|
|
|
|
|
0
|
|
168
|
0
|
|
|
|
|
0
|
return \@arr; |
169
|
|
|
|
|
|
|
} |
170
|
|
|
|
|
|
|
elsif (is_hashref($thing)) { |
171
|
0
|
|
|
|
|
0
|
my %hash; |
172
|
0
|
|
|
|
|
0
|
$hash{$_} = clone_nomagic($thing->{$_}) for keys %$thing; |
173
|
0
|
|
|
|
|
0
|
return \%hash; |
174
|
|
|
|
|
|
|
} |
175
|
|
|
|
|
|
|
elsif (is_ref($thing)) { |
176
|
0
|
|
|
|
|
0
|
return clone($thing); |
177
|
|
|
|
|
|
|
} |
178
|
0
|
|
|
|
|
0
|
return $thing; |
179
|
|
|
|
|
|
|
} |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
sub dumper { |
183
|
0
|
|
|
0
|
1
|
0
|
require Data::Dumper; |
184
|
|
|
|
|
|
|
# avoid "once" warnings |
185
|
0
|
|
|
|
|
0
|
local $Data::Dumper::Deepcopy = $Data::Dumper::Deepcopy = 1; |
186
|
0
|
|
|
|
|
0
|
local $Data::Dumper::Deparse = $Data::Dumper::Deparse = 1; |
187
|
0
|
|
|
|
|
0
|
local $Data::Dumper::Indent = 1; |
188
|
0
|
|
|
|
|
0
|
local $Data::Dumper::Quotekeys = 0; |
189
|
0
|
|
|
|
|
0
|
local $Data::Dumper::Sortkeys = 1; |
190
|
0
|
|
|
|
|
0
|
local $Data::Dumper::Terse = 1; |
191
|
0
|
|
|
|
|
0
|
local $Data::Dumper::Trailingcomma = 1; |
192
|
0
|
|
|
|
|
0
|
local $Data::Dumper::Useqq = 1; |
193
|
|
|
|
|
|
|
|
194
|
0
|
|
|
|
|
0
|
my @dumps; |
195
|
0
|
|
|
|
|
0
|
for my $struct (@_) { |
196
|
0
|
|
|
|
|
0
|
my $str = Data::Dumper::Dumper($struct); |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
# boolean |
199
|
0
|
|
|
|
|
0
|
$str =~ s/bless\( do\{\\\(my \$o = ([01])\)\}, 'boolean' \)/boolean($1)/gs; |
200
|
|
|
|
|
|
|
# Time::Piece |
201
|
0
|
|
|
|
|
0
|
$str =~ s/bless\([^\)]+?(\d+)'?,\s+\d+,?\s+\], 'Time::Piece' \),/ |
202
|
0
|
|
|
|
|
0
|
"scalar gmtime($1), # " . scalar gmtime($1)->datetime/ges; |
203
|
|
|
|
|
|
|
|
204
|
0
|
0
|
|
|
|
0
|
print STDERR $str if !defined wantarray; |
205
|
0
|
|
|
|
|
0
|
push @dumps, $str; |
206
|
0
|
|
|
|
|
0
|
return $str; |
207
|
|
|
|
|
|
|
} |
208
|
0
|
|
|
|
|
0
|
return join("\n", @dumps); |
209
|
|
|
|
|
|
|
} |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
|
212
|
583
|
|
|
583
|
1
|
10313
|
sub empty { _empty(@_) } |
213
|
786
|
|
|
786
|
1
|
1361
|
sub nonempty { !_empty(@_) } |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
sub _empty { |
216
|
1397
|
100
|
|
1397
|
|
2552
|
return 1 if @_ == 0; |
217
|
1393
|
|
|
|
|
1989
|
local $_ = shift; |
218
|
1393
|
|
66
|
|
|
12548
|
return !defined $_ |
219
|
|
|
|
|
|
|
|| $_ eq '' |
220
|
|
|
|
|
|
|
|| (is_arrayref($_) && @$_ == 0) |
221
|
|
|
|
|
|
|
|| (is_hashref($_) && keys %$_ == 0) |
222
|
|
|
|
|
|
|
|| (is_scalarref($_) && (!defined $$_ || $$_ eq '')) |
223
|
|
|
|
|
|
|
|| (is_refref($_) && _empty($$_)); |
224
|
|
|
|
|
|
|
} |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
BEGIN { |
228
|
27
|
100
|
|
27
|
|
136
|
if (load_xs) { |
|
|
50
|
|
|
|
|
|
229
|
26
|
|
|
|
|
2419
|
*_CowREFCNT = \&File::KDBX::XS::CowREFCNT; |
230
|
|
|
|
|
|
|
} |
231
|
1
|
|
|
|
|
172
|
elsif (eval { require B::COW; 1 }) { |
|
0
|
|
|
|
|
0
|
|
232
|
0
|
|
|
|
|
0
|
*_CowREFCNT = \&B::COW::cowrefcnt; |
233
|
|
|
|
|
|
|
} |
234
|
|
|
|
|
|
|
else { |
235
|
1
|
|
|
0
|
|
111
|
*_CowREFCNT = sub { undef }; |
|
0
|
|
|
|
|
0
|
|
236
|
|
|
|
|
|
|
} |
237
|
|
|
|
|
|
|
} |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
sub erase { |
240
|
|
|
|
|
|
|
# Only bother zeroing out memory if we have the last SvPV COW reference, otherwise we'll end up just |
241
|
|
|
|
|
|
|
# creating a copy and erasing the copy. |
242
|
|
|
|
|
|
|
# TODO - Is this worth doing? Need some benchmarking. |
243
|
1966
|
|
|
1966
|
1
|
4692
|
for (@_) { |
244
|
2053
|
100
|
|
|
|
4540
|
if (!is_ref($_)) { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
245
|
348
|
100
|
66
|
|
|
1282
|
next if !defined $_ || readonly $_; |
246
|
235
|
|
|
|
|
472
|
my $cowrefcnt = _CowREFCNT($_); |
247
|
235
|
50
|
33
|
|
|
800
|
goto FREE_NONREF if defined $cowrefcnt && 1 < $cowrefcnt; |
248
|
|
|
|
|
|
|
# if (__PACKAGE__->can('erase_xs')) { |
249
|
|
|
|
|
|
|
# erase_xs($_); |
250
|
|
|
|
|
|
|
# } |
251
|
|
|
|
|
|
|
# else { |
252
|
235
|
|
|
|
|
671
|
substr($_, 0, length($_), "\0" x length($_)); |
253
|
|
|
|
|
|
|
# } |
254
|
|
|
|
|
|
|
FREE_NONREF: { |
255
|
27
|
|
|
27
|
|
178
|
no warnings 'uninitialized'; |
|
27
|
|
|
|
|
36
|
|
|
27
|
|
|
|
|
3210
|
|
|
235
|
|
|
|
|
312
|
|
256
|
235
|
|
|
|
|
456
|
undef $_; |
257
|
|
|
|
|
|
|
} |
258
|
|
|
|
|
|
|
} |
259
|
|
|
|
|
|
|
elsif (is_scalarref($_)) { |
260
|
1639
|
100
|
66
|
|
|
7310
|
next if !defined $$_ || readonly $$_; |
261
|
1224
|
|
|
|
|
2381
|
my $cowrefcnt = _CowREFCNT($$_); |
262
|
1224
|
100
|
100
|
|
|
4152
|
goto FREE_REF if defined $cowrefcnt && 1 < $cowrefcnt; |
263
|
|
|
|
|
|
|
# if (__PACKAGE__->can('erase_xs')) { |
264
|
|
|
|
|
|
|
# erase_xs($$_); |
265
|
|
|
|
|
|
|
# } |
266
|
|
|
|
|
|
|
# else { |
267
|
963
|
|
|
|
|
2486
|
substr($$_, 0, length($$_), "\0" x length($$_)); |
268
|
|
|
|
|
|
|
# } |
269
|
|
|
|
|
|
|
FREE_REF: { |
270
|
27
|
|
|
27
|
|
165
|
no warnings 'uninitialized'; |
|
27
|
|
|
|
|
41
|
|
|
27
|
|
|
|
|
8994
|
|
|
1224
|
|
|
|
|
1534
|
|
271
|
1224
|
|
|
|
|
7224
|
undef $$_; |
272
|
|
|
|
|
|
|
} |
273
|
|
|
|
|
|
|
} |
274
|
|
|
|
|
|
|
elsif (is_arrayref($_)) { |
275
|
65
|
|
|
|
|
187
|
erase(@$_); |
276
|
65
|
|
|
|
|
201
|
@$_ = (); |
277
|
|
|
|
|
|
|
} |
278
|
|
|
|
|
|
|
elsif (is_hashref($_)) { |
279
|
1
|
|
|
|
|
5
|
erase(values %$_); |
280
|
1
|
|
|
|
|
3
|
%$_ = (); |
281
|
|
|
|
|
|
|
} |
282
|
|
|
|
|
|
|
else { |
283
|
0
|
|
|
|
|
0
|
throw 'Cannot erase this type of scalar', type => ref $_, what => $_; |
284
|
|
|
|
|
|
|
} |
285
|
|
|
|
|
|
|
} |
286
|
|
|
|
|
|
|
} |
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
sub erase_scoped { |
290
|
828
|
50
|
|
828
|
1
|
4804
|
throw 'Programmer error: Cannot call erase_scoped in void context' if !defined wantarray; |
291
|
828
|
|
|
|
|
1023
|
my @args; |
292
|
828
|
|
|
|
|
1462
|
for (@_) { |
293
|
890
|
50
|
100
|
|
|
2212
|
!is_ref($_) || is_arrayref($_) || is_hashref($_) || is_scalarref($_) |
|
|
|
66
|
|
|
|
|
|
|
|
66
|
|
|
|
|
294
|
|
|
|
|
|
|
or throw 'Cannot erase this type of scalar', type => ref $_, what => $_; |
295
|
890
|
100
|
|
|
|
2113
|
push @args, is_ref($_) ? $_ : \$_; |
296
|
|
|
|
|
|
|
} |
297
|
828
|
|
|
|
|
8869
|
require Scope::Guard; |
298
|
828
|
|
|
828
|
|
9366
|
return Scope::Guard->new(sub { erase(@args) }); |
|
828
|
|
|
|
|
6060
|
|
299
|
|
|
|
|
|
|
} |
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
sub extends { |
303
|
118
|
|
|
118
|
1
|
309
|
my $parent = shift; |
304
|
118
|
|
|
|
|
287
|
my $caller = caller; |
305
|
118
|
|
|
|
|
809
|
load $parent; |
306
|
27
|
|
|
27
|
|
170
|
no strict 'refs'; ## no critic (ProhibitNoStrict) |
|
27
|
|
|
|
|
45
|
|
|
27
|
|
|
|
|
123656
|
|
307
|
118
|
|
|
|
|
19270
|
@{"${caller}::ISA"} = $parent; |
|
118
|
|
|
|
|
2533
|
|
308
|
|
|
|
|
|
|
} |
309
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
sub has { |
312
|
1063
|
|
|
1063
|
1
|
2231
|
my $name = shift; |
313
|
1063
|
100
|
|
|
|
3948
|
my %args = @_ % 2 == 1 ? (default => shift, @_) : @_; |
314
|
|
|
|
|
|
|
|
315
|
1063
|
|
|
|
|
3401
|
my ($package, $file, $line) = caller; |
316
|
|
|
|
|
|
|
|
317
|
1063
|
|
|
|
|
1870
|
my $d = $args{default}; |
318
|
1063
|
100
|
|
244
|
|
2701
|
my $default = is_arrayref($d) ? sub { [@$d] } : is_hashref($d) ? sub { +{%$d} } : $d; |
|
144
|
100
|
|
|
|
634
|
|
|
736
|
|
|
|
|
3710
|
|
319
|
1063
|
|
|
|
|
1327
|
my $coerce = $args{coerce}; |
320
|
1063
|
|
100
|
|
|
2935
|
my $is = $args{is} || 'rw'; |
321
|
|
|
|
|
|
|
|
322
|
1063
|
|
|
|
|
1324
|
my $store = $args{store}; |
323
|
1063
|
100
|
|
|
|
3437
|
($store, $name) = split(/\./, $name, 2) if $name =~ /\./; |
324
|
|
|
|
|
|
|
|
325
|
1063
|
|
100
|
|
|
3347
|
my @path = split(/\./, $args{path} || ''); |
326
|
1063
|
|
|
|
|
1473
|
my $last = pop @path; |
327
|
1063
|
100
|
|
|
|
2844
|
my $path = $last ? join('', map { qq{->$_} } @path) . qq{->{'$last'}} |
|
30
|
100
|
|
|
|
127
|
|
328
|
|
|
|
|
|
|
: $store ? qq{->$store\->{'$name'}} : qq{->{'$name'}}; |
329
|
1063
|
|
|
|
|
1591
|
my $member = qq{\$_[0]$path}; |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
|
332
|
1063
|
100
|
|
|
|
2074
|
my $default_code = is_coderef $default ? q{scalar $default->($_[0])} |
|
|
100
|
|
|
|
|
|
333
|
|
|
|
|
|
|
: defined $default ? q{$default} |
334
|
|
|
|
|
|
|
: q{undef}; |
335
|
1063
|
|
|
|
|
1531
|
my $get = qq{$member //= $default_code;}; |
336
|
|
|
|
|
|
|
|
337
|
1063
|
|
|
|
|
1254
|
my $set = ''; |
338
|
1063
|
100
|
|
|
|
1954
|
if ($is eq 'rw') { |
339
|
966
|
50
|
|
|
|
1928
|
$set = is_coderef $coerce ? qq{$member = scalar \$coerce->(\@_[1..\$#_]) if \$#_;} |
|
|
100
|
|
|
|
|
|
340
|
|
|
|
|
|
|
: defined $coerce ? qq{$member = do { local @_ = (\@_[1..\$#_]); $coerce } if \$#_;} |
341
|
|
|
|
|
|
|
: qq{$member = \$_[1] if \$#_;}; |
342
|
|
|
|
|
|
|
} |
343
|
|
|
|
|
|
|
|
344
|
1063
|
|
100
|
|
|
1206
|
push @{$ATTRIBUTES{$package} //= []}, $name; |
|
1063
|
|
|
|
|
3065
|
|
345
|
1063
|
|
|
|
|
1542
|
$line -= 4; |
346
|
1063
|
|
|
|
|
2731
|
my $code = <
|
347
|
|
|
|
|
|
|
# line $line "$file" |
348
|
|
|
|
|
|
|
sub ${package}::${name} { |
349
|
|
|
|
|
|
|
return $default_code if !Scalar::Util::blessed(\$_[0]); |
350
|
|
|
|
|
|
|
$set |
351
|
|
|
|
|
|
|
$get |
352
|
|
|
|
|
|
|
} |
353
|
|
|
|
|
|
|
END |
354
|
1063
|
|
|
|
|
105612
|
eval $code; ## no critic (ProhibitStringyEval) |
355
|
|
|
|
|
|
|
} |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
sub format_uuid { |
359
|
346
|
|
50
|
346
|
1
|
2945
|
local $_ = shift // "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; |
360
|
346
|
|
100
|
|
|
1034
|
my $delim = shift // ''; |
361
|
346
|
50
|
|
|
|
730
|
length($_) == 16 or throw 'Must provide a 16-bytes UUID', size => length($_), str => $_; |
362
|
346
|
|
|
|
|
2255
|
return uc(join($delim, unpack('H8 H4 H4 H4 H12', $_))); |
363
|
|
|
|
|
|
|
} |
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
sub generate_uuid { |
367
|
191
|
100
|
|
191
|
1
|
1976
|
my $set = @_ % 2 == 1 ? shift : undef; |
368
|
191
|
|
|
|
|
315
|
my %args = @_; |
369
|
191
|
|
66
|
|
|
614
|
my $test = $set //= $args{test}; |
370
|
191
|
100
|
|
1
|
|
402
|
$test = sub { !$set->{$_} } if is_hashref($test); |
|
1
|
|
|
|
|
135
|
|
371
|
191
|
|
100
|
189
|
|
1174
|
$test //= sub { 1 }; |
|
189
|
|
|
|
|
2843
|
|
372
|
191
|
|
100
|
|
|
609
|
my $printable = $args{printable} // $args{print}; |
373
|
191
|
|
|
|
|
319
|
local $_ = ''; |
374
|
191
|
|
|
|
|
267
|
do { |
375
|
191
|
100
|
|
|
|
673
|
$_ = $printable ? random_string(16) : random_bytes(16); |
376
|
|
|
|
|
|
|
} while (!$test->($_)); |
377
|
191
|
|
|
|
|
971
|
return $_; |
378
|
|
|
|
|
|
|
} |
379
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
sub gunzip { |
382
|
0
|
|
|
0
|
1
|
0
|
load_optional('Compress::Raw::Zlib'); |
383
|
0
|
|
|
|
|
0
|
local $_ = shift; |
384
|
0
|
|
|
|
|
0
|
my ($i, $status) = Compress::Raw::Zlib::Inflate->new(-WindowBits => 31); |
385
|
0
|
0
|
|
|
|
0
|
$status == Compress::Raw::Zlib::Z_OK() |
386
|
|
|
|
|
|
|
or throw 'Failed to initialize compression library', status => $status; |
387
|
0
|
|
|
|
|
0
|
$status = $i->inflate($_, my $out); |
388
|
0
|
0
|
|
|
|
0
|
$status == Compress::Raw::Zlib::Z_STREAM_END() |
389
|
|
|
|
|
|
|
or throw 'Failed to decompress data', status => $status; |
390
|
0
|
|
|
|
|
0
|
return $out; |
391
|
|
|
|
|
|
|
} |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
sub gzip { |
395
|
2
|
|
|
2
|
1
|
5
|
load_optional('Compress::Raw::Zlib'); |
396
|
2
|
|
|
|
|
3
|
local $_ = shift; |
397
|
2
|
|
|
|
|
10
|
my ($d, $status) = Compress::Raw::Zlib::Deflate->new(-WindowBits => 31, -AppendOutput => 1); |
398
|
2
|
50
|
|
|
|
1131
|
$status == Compress::Raw::Zlib::Z_OK() |
399
|
|
|
|
|
|
|
or throw 'Failed to initialize compression library', status => $status; |
400
|
2
|
|
|
|
|
37
|
$status = $d->deflate($_, my $out); |
401
|
2
|
50
|
|
|
|
6
|
$status == Compress::Raw::Zlib::Z_OK() |
402
|
|
|
|
|
|
|
or throw 'Failed to compress data', status => $status; |
403
|
2
|
|
|
|
|
44
|
$status = $d->flush($out); |
404
|
2
|
50
|
|
|
|
9
|
$status == Compress::Raw::Zlib::Z_OK() |
405
|
|
|
|
|
|
|
or throw 'Failed to compress data', status => $status; |
406
|
2
|
|
|
|
|
60
|
return $out; |
407
|
|
|
|
|
|
|
} |
408
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
sub int64 { |
411
|
30
|
|
|
30
|
1
|
197
|
require Config; |
412
|
30
|
50
|
|
|
|
2277
|
if ($Config::Config{ivsize} < 8) { |
413
|
0
|
|
|
|
|
0
|
require Math::BigInt; |
414
|
0
|
|
|
|
|
0
|
return Math::BigInt->new(@_); |
415
|
|
|
|
|
|
|
} |
416
|
30
|
|
|
|
|
2025
|
return 0 + shift; |
417
|
|
|
|
|
|
|
} |
418
|
|
|
|
|
|
|
|
419
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
sub pack_Ql { |
421
|
317
|
|
|
317
|
1
|
17182
|
my $num = shift; |
422
|
317
|
|
|
|
|
1314
|
require Config; |
423
|
317
|
50
|
|
|
|
2226
|
if ($Config::Config{ivsize} < 8) { |
424
|
0
|
0
|
0
|
|
|
0
|
if (blessed $num && $num->can('as_hex')) { |
425
|
0
|
|
|
|
|
0
|
require Math::BigInt; |
426
|
0
|
0
|
|
|
|
0
|
return "\xff\xff\xff\xff\xff\xff\xff\xff" if Math::BigInt->new('18446744073709551615') <= $num; |
427
|
0
|
0
|
|
|
|
0
|
return "\x00\x00\x00\x00\x00\x00\x00\x80" if $num <= Math::BigInt->new('-9223372036854775808'); |
428
|
0
|
|
|
|
|
0
|
my $neg; |
429
|
0
|
0
|
|
|
|
0
|
if ($num < 0) { |
430
|
0
|
|
|
|
|
0
|
$neg = 1; |
431
|
0
|
|
|
|
|
0
|
$num = -$num; |
432
|
|
|
|
|
|
|
} |
433
|
0
|
|
|
|
|
0
|
my $hex = $num->as_hex; |
434
|
0
|
|
|
|
|
0
|
$hex =~ s/^0x/000000000000000/; |
435
|
0
|
|
|
|
|
0
|
my $bytes = reverse pack('H16', substr($hex, -16)); |
436
|
0
|
0
|
|
|
|
0
|
$bytes .= "\0" x (8 - length $bytes) if length $bytes < 8; |
437
|
0
|
0
|
|
|
|
0
|
if ($neg) { |
438
|
|
|
|
|
|
|
# two's compliment |
439
|
0
|
|
|
|
|
0
|
$bytes = join('', map { chr(~ord($_) & 0xff) } split(//, $bytes)); |
|
0
|
|
|
|
|
0
|
|
440
|
0
|
|
|
|
|
0
|
substr($bytes, 0, 1, chr(ord(substr($bytes, 0, 1)) + 1)); |
441
|
|
|
|
|
|
|
} |
442
|
0
|
|
|
|
|
0
|
return $bytes; |
443
|
|
|
|
|
|
|
} |
444
|
|
|
|
|
|
|
else { |
445
|
0
|
0
|
|
|
|
0
|
my $pad = $num < 0 ? "\xff" : "\0"; |
446
|
0
|
|
|
|
|
0
|
return pack('L<', $num) . ($pad x 4); |
447
|
|
|
|
|
|
|
}; |
448
|
|
|
|
|
|
|
} |
449
|
317
|
|
|
|
|
1256
|
return pack('Q<', $num); |
450
|
|
|
|
|
|
|
} |
451
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
|
453
|
0
|
|
|
0
|
1
|
0
|
sub pack_ql { goto &pack_Ql } |
454
|
|
|
|
|
|
|
|
455
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
sub unpack_Ql { |
457
|
174
|
|
|
174
|
1
|
5838
|
my $bytes = shift; |
458
|
174
|
|
|
|
|
667
|
require Config; |
459
|
174
|
50
|
|
|
|
1272
|
if ($Config::Config{ivsize} < 8) { |
460
|
0
|
|
|
|
|
0
|
require Math::BigInt; |
461
|
0
|
|
|
|
|
0
|
return Math::BigInt->new('0x' . unpack('H*', scalar reverse $bytes)); |
462
|
|
|
|
|
|
|
} |
463
|
174
|
|
|
|
|
660
|
return unpack('Q<', $bytes); |
464
|
|
|
|
|
|
|
} |
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
sub unpack_ql { |
468
|
8
|
|
|
8
|
1
|
5753
|
my $bytes = shift; |
469
|
8
|
|
|
|
|
35
|
require Config; |
470
|
8
|
50
|
|
|
|
48
|
if ($Config::Config{ivsize} < 8) { |
471
|
0
|
|
|
|
|
0
|
require Math::BigInt; |
472
|
0
|
0
|
|
|
|
0
|
if (ord(substr($bytes, -1, 1)) & 128) { |
473
|
0
|
0
|
|
|
|
0
|
return Math::BigInt->new('-9223372036854775808') if $bytes eq "\x00\x00\x00\x00\x00\x00\x00\x80"; |
474
|
|
|
|
|
|
|
# two's compliment |
475
|
0
|
|
|
|
|
0
|
substr($bytes, 0, 1, chr(ord(substr($bytes, 0, 1)) - 1)); |
476
|
0
|
|
|
|
|
0
|
$bytes = join('', map { chr(~ord($_) & 0xff) } split(//, $bytes)); |
|
0
|
|
|
|
|
0
|
|
477
|
0
|
|
|
|
|
0
|
return -Math::BigInt->new('0x' . unpack('H*', scalar reverse $bytes)); |
478
|
|
|
|
|
|
|
} |
479
|
|
|
|
|
|
|
else { |
480
|
0
|
|
|
|
|
0
|
return Math::BigInt->new('0x' . unpack('H*', scalar reverse $bytes)); |
481
|
|
|
|
|
|
|
} |
482
|
|
|
|
|
|
|
} |
483
|
8
|
|
|
|
|
28
|
return unpack('q<', $bytes); |
484
|
|
|
|
|
|
|
} |
485
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
|
487
|
0
|
0
|
0
|
0
|
1
|
0
|
sub is_uuid { defined $_[0] && !is_ref($_[0]) && length($_[0]) == 16 } |
488
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
|
490
|
|
|
|
|
|
|
sub list_attributes { |
491
|
223
|
|
|
223
|
1
|
381
|
my $package = shift; |
492
|
223
|
|
50
|
|
|
308
|
return @{$ATTRIBUTES{$package} // []}; |
|
223
|
|
|
|
|
2646
|
|
493
|
|
|
|
|
|
|
} |
494
|
|
|
|
|
|
|
|
495
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
sub load_optional { |
497
|
96
|
|
|
96
|
1
|
208
|
for my $module (@_) { |
498
|
96
|
|
|
|
|
145
|
eval { load $module }; |
|
96
|
|
|
|
|
281
|
|
499
|
96
|
50
|
|
|
|
222721
|
if (my $err = $@) { |
500
|
0
|
|
|
|
|
0
|
throw "Missing dependency: Please install $module to use this feature.\n", |
501
|
|
|
|
|
|
|
module => $module, |
502
|
|
|
|
|
|
|
error => $err; |
503
|
|
|
|
|
|
|
} |
504
|
|
|
|
|
|
|
} |
505
|
96
|
50
|
|
|
|
271
|
return wantarray ? @_ : $_[0]; |
506
|
|
|
|
|
|
|
} |
507
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
sub memoize { |
510
|
346
|
|
|
346
|
1
|
444
|
my $func = shift; |
511
|
346
|
|
|
|
|
576
|
my @args = @_; |
512
|
346
|
|
|
|
|
457
|
my %cache; |
513
|
346
|
|
100
|
88
|
|
1209
|
return sub { $cache{join("\0", grep { defined } @_)} //= $func->(@args, @_) }; |
|
88
|
|
|
|
|
130
|
|
|
218
|
|
|
|
|
687
|
|
514
|
|
|
|
|
|
|
} |
515
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
sub pad_pkcs7 { |
518
|
16
|
|
66
|
16
|
1
|
4099
|
my $data = shift // throw 'Must provide a string to pad'; |
519
|
15
|
100
|
|
|
|
48
|
my $size = shift or throw 'Must provide block size'; |
520
|
|
|
|
|
|
|
|
521
|
13
|
50
|
33
|
|
|
84
|
0 <= $size && $size < 256 |
522
|
|
|
|
|
|
|
or throw 'Cannot add PKCS7 padding to a large block size', size => $size; |
523
|
|
|
|
|
|
|
|
524
|
13
|
|
|
|
|
45
|
my $pad_len = $size - length($data) % $size; |
525
|
13
|
|
|
|
|
147
|
$data .= chr($pad_len) x $pad_len; |
526
|
|
|
|
|
|
|
} |
527
|
|
|
|
|
|
|
|
528
|
|
|
|
|
|
|
|
529
|
65
|
|
|
65
|
1
|
1372
|
sub query { _query(undef, '-or', \@_) } |
530
|
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
|
532
|
|
|
|
|
|
|
sub query_any { |
533
|
395
|
|
|
395
|
1
|
532
|
my $code = shift; |
534
|
|
|
|
|
|
|
|
535
|
395
|
100
|
66
|
|
|
959
|
if (is_coderef($code) || overload::Method($code, '&{}')) { |
|
|
100
|
|
|
|
|
|
536
|
359
|
|
|
|
|
715
|
return $code; |
537
|
|
|
|
|
|
|
} |
538
|
|
|
|
|
|
|
elsif (is_scalarref($code)) { |
539
|
2
|
|
|
|
|
41
|
return simple_expression_query($$code, @_); |
540
|
|
|
|
|
|
|
} |
541
|
|
|
|
|
|
|
else { |
542
|
34
|
|
|
|
|
943
|
return query($code, @_); |
543
|
|
|
|
|
|
|
} |
544
|
|
|
|
|
|
|
} |
545
|
|
|
|
|
|
|
|
546
|
|
|
|
|
|
|
|
547
|
|
|
|
|
|
|
sub read_all($$$;$) { ## no critic (ProhibitSubroutinePrototypes) |
548
|
1306
|
50
|
|
1306
|
1
|
313808
|
my $result = @_ == 3 ? read($_[0], $_[1], $_[2]) |
549
|
|
|
|
|
|
|
: read($_[0], $_[1], $_[2], $_[3]); |
550
|
1306
|
50
|
|
|
|
4801
|
return if !defined $result; |
551
|
1306
|
100
|
|
|
|
1925
|
return if $result != $_[2]; |
552
|
1305
|
|
|
|
|
2638
|
return $result; |
553
|
|
|
|
|
|
|
} |
554
|
|
|
|
|
|
|
|
555
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
sub recurse_limit { |
557
|
23
|
|
|
23
|
1
|
32
|
my $func = shift; |
558
|
23
|
|
50
|
|
|
55
|
my $max_depth = shift // 200; |
559
|
23
|
|
50
|
0
|
|
38
|
my $error = shift // sub {}; |
560
|
23
|
|
|
|
|
26
|
my $depth = 0; |
561
|
23
|
100
|
|
42
|
|
72
|
return sub { return $error->(@_) if $max_depth < ++$depth; $func->(@_) }; |
|
42
|
|
|
|
|
92
|
|
|
41
|
|
|
|
|
92
|
|
562
|
|
|
|
|
|
|
}; |
563
|
|
|
|
|
|
|
|
564
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
sub search { |
566
|
36
|
|
|
36
|
1
|
35872
|
my $list = shift; |
567
|
36
|
|
|
|
|
59
|
my $query = query_any(@_); |
568
|
|
|
|
|
|
|
|
569
|
36
|
|
|
|
|
46
|
my @match; |
570
|
36
|
|
|
|
|
51
|
for my $item (@$list) { |
571
|
144
|
100
|
|
|
|
204
|
push @match, $item if $query->($item); |
572
|
|
|
|
|
|
|
} |
573
|
36
|
|
|
|
|
175
|
return \@match; |
574
|
|
|
|
|
|
|
} |
575
|
|
|
|
|
|
|
|
576
|
|
|
|
|
|
|
|
577
|
|
|
|
|
|
|
sub simple_expression_query { |
578
|
21
|
|
|
21
|
1
|
17055
|
my $expr = shift; |
579
|
21
|
100
|
66
|
|
|
132
|
my $op = @_ && ($OPS{$_[0] || ''} || 0) == 2 ? shift : '=~'; |
580
|
|
|
|
|
|
|
|
581
|
21
|
|
|
|
|
45
|
my $neg_op = $OP_NEG{$op}; |
582
|
21
|
|
66
|
|
|
47
|
my $is_re = $op eq '=~' || $op eq '!~'; |
583
|
|
|
|
|
|
|
|
584
|
21
|
|
|
|
|
1545
|
require Text::ParseWords; |
585
|
21
|
|
|
|
|
3512
|
my @terms = Text::ParseWords::shellwords($expr); |
586
|
|
|
|
|
|
|
|
587
|
21
|
|
|
|
|
1366
|
my @query = qw(-and); |
588
|
|
|
|
|
|
|
|
589
|
21
|
|
|
|
|
37
|
for my $term (@terms) { |
590
|
28
|
|
|
|
|
44
|
my @subquery = qw(-or); |
591
|
|
|
|
|
|
|
|
592
|
28
|
|
|
|
|
58
|
my $neg = $term =~ s/^-//; |
593
|
28
|
100
|
|
|
|
263
|
my $condition = [($neg ? $neg_op : $op) => ($is_re ? qr/\Q$term\E/i : $term)]; |
|
|
100
|
|
|
|
|
|
594
|
|
|
|
|
|
|
|
595
|
28
|
|
|
|
|
60
|
for my $field (@_) { |
596
|
32
|
|
|
|
|
54
|
push @subquery, $field => $condition; |
597
|
|
|
|
|
|
|
} |
598
|
|
|
|
|
|
|
|
599
|
28
|
|
|
|
|
48
|
push @query, \@subquery; |
600
|
|
|
|
|
|
|
} |
601
|
|
|
|
|
|
|
|
602
|
21
|
|
|
|
|
46
|
return query(\@query); |
603
|
|
|
|
|
|
|
} |
604
|
|
|
|
|
|
|
|
605
|
|
|
|
|
|
|
|
606
|
|
|
|
|
|
|
sub snakify { |
607
|
3450
|
|
|
3450
|
1
|
7808
|
local $_ = shift; |
608
|
3450
|
|
|
|
|
4927
|
s/UserName/Username/g; |
609
|
3450
|
|
|
|
|
18295
|
s/([a-z])([A-Z0-9])/${1}_${2}/g; |
610
|
3450
|
|
|
|
|
9267
|
s/([A-Z0-9]+)([A-Z0-9])(?![A-Z0-9]|$)/${1}_${2}/g; |
611
|
3450
|
|
|
|
|
8842
|
return lc($_); |
612
|
|
|
|
|
|
|
} |
613
|
|
|
|
|
|
|
|
614
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
sub split_url { |
616
|
0
|
|
|
0
|
1
|
0
|
local $_ = shift; |
617
|
0
|
|
|
|
|
0
|
my ($scheme, $auth, $host, $port, $path, $query, $hash) =~ m! |
618
|
|
|
|
|
|
|
^([^:/\?\#]+) :// |
619
|
|
|
|
|
|
|
(?:([^\@]+)\@) |
620
|
|
|
|
|
|
|
([^:/\?\#]*) |
621
|
|
|
|
|
|
|
(?::(\d+))? |
622
|
|
|
|
|
|
|
([^\?\#]*) |
623
|
|
|
|
|
|
|
(\?[^\#]*)? |
624
|
|
|
|
|
|
|
(\#(.*))? |
625
|
|
|
|
|
|
|
!x; |
626
|
|
|
|
|
|
|
|
627
|
0
|
|
|
|
|
0
|
$scheme = lc($scheme); |
628
|
|
|
|
|
|
|
|
629
|
0
|
|
0
|
|
|
0
|
$host ||= 'localhost'; |
630
|
0
|
|
|
|
|
0
|
$host = lc($host); |
631
|
|
|
|
|
|
|
|
632
|
0
|
0
|
|
|
|
0
|
$path = "/$path" if $path !~ m!^/!; |
633
|
|
|
|
|
|
|
|
634
|
0
|
0
|
0
|
|
|
0
|
$port ||= $scheme eq 'http' ? 80 : $scheme eq 'https' ? 433 : undef; |
|
|
0
|
|
|
|
|
|
635
|
|
|
|
|
|
|
|
636
|
0
|
|
|
|
|
0
|
my ($username, $password) = split($auth, ':', 2); |
637
|
|
|
|
|
|
|
|
638
|
0
|
|
|
|
|
0
|
return ($scheme, $auth, $host, $port, $path, $query, $hash, $username, $password); |
639
|
|
|
|
|
|
|
} |
640
|
|
|
|
|
|
|
|
641
|
|
|
|
|
|
|
|
642
|
66
|
|
50
|
66
|
1
|
262
|
sub to_bool { $_[0] // return; boolean($_[0]) } |
|
66
|
|
|
|
|
461
|
|
643
|
98
|
|
50
|
98
|
1
|
209
|
sub to_number { $_[0] // return; 0+$_[0] } |
|
98
|
|
|
|
|
222
|
|
644
|
203
|
|
100
|
203
|
1
|
410
|
sub to_string { $_[0] // return; "$_[0]" } |
|
200
|
|
|
|
|
648
|
|
645
|
|
|
|
|
|
|
sub to_time { |
646
|
32
|
|
50
|
32
|
1
|
83
|
$_[0] // return; |
647
|
32
|
50
|
|
|
|
493
|
return scalar gmtime($_[0]) if looks_like_number($_[0]); |
648
|
32
|
100
|
|
|
|
360
|
return scalar gmtime if $_[0] eq 'now'; |
649
|
31
|
100
|
|
|
|
495
|
return Time::Piece->strptime($_[0], '%Y-%m-%d %H:%M:%S') if !blessed $_[0]; |
650
|
30
|
|
|
|
|
82
|
return $_[0]; |
651
|
|
|
|
|
|
|
} |
652
|
2
|
|
50
|
2
|
1
|
5
|
sub to_tristate { $_[0] // return; boolean($_[0]) } |
|
2
|
|
|
|
|
11
|
|
653
|
|
|
|
|
|
|
sub to_uuid { |
654
|
10
|
|
100
|
10
|
1
|
24
|
my $str = to_string(@_) // return; |
655
|
7
|
50
|
|
|
|
23
|
return sprintf('%016s', $str) if length($str) < 16; |
656
|
7
|
50
|
|
|
|
18
|
return substr($str, 0, 16) if 16 < length($str); |
657
|
7
|
|
|
|
|
20
|
return $str; |
658
|
|
|
|
|
|
|
} |
659
|
|
|
|
|
|
|
|
660
|
|
|
|
|
|
|
|
661
|
|
|
|
|
|
|
sub trim($) { ## no critic (ProhibitSubroutinePrototypes) |
662
|
1790
|
|
100
|
1790
|
1
|
3298
|
local $_ = shift // return; |
663
|
1712
|
|
|
|
|
5166
|
s/^\s*//; |
664
|
1712
|
|
|
|
|
6237
|
s/\s*$//; |
665
|
1712
|
|
|
|
|
3393
|
return $_; |
666
|
|
|
|
|
|
|
} |
667
|
|
|
|
|
|
|
|
668
|
|
|
|
|
|
|
|
669
|
|
|
|
|
|
|
sub try_load_optional { |
670
|
2
|
|
|
2
|
1
|
4
|
for my $module (@_) { |
671
|
2
|
|
|
|
|
4
|
eval { load $module }; |
|
2
|
|
|
|
|
6
|
|
672
|
2
|
50
|
|
|
|
16801
|
if (my $err = $@) { |
673
|
0
|
|
|
|
|
0
|
warn $err if 3 <= DEBUG; |
674
|
0
|
|
|
|
|
0
|
return; |
675
|
|
|
|
|
|
|
} |
676
|
|
|
|
|
|
|
} |
677
|
2
|
|
|
|
|
8
|
return @_; |
678
|
|
|
|
|
|
|
} |
679
|
|
|
|
|
|
|
|
680
|
|
|
|
|
|
|
|
681
|
|
|
|
|
|
|
my %ESC = map { chr($_) => sprintf('%%%02X', $_) } 0..255; |
682
|
|
|
|
|
|
|
sub uri_escape_utf8 { |
683
|
148
|
|
100
|
148
|
1
|
273
|
local $_ = shift // return; |
684
|
146
|
|
|
|
|
268
|
$_ = encode('UTF-8', $_); |
685
|
|
|
|
|
|
|
# RFC 3986 section 2.3 unreserved characters |
686
|
146
|
|
|
|
|
4705
|
s/([^A-Za-z0-9\-\._~])/$ESC{$1}/ge; |
|
4
|
|
|
|
|
16
|
|
687
|
146
|
|
|
|
|
348
|
return $_; |
688
|
|
|
|
|
|
|
} |
689
|
|
|
|
|
|
|
|
690
|
|
|
|
|
|
|
|
691
|
|
|
|
|
|
|
sub uri_unescape_utf8 { |
692
|
24
|
|
50
|
24
|
1
|
55
|
local $_ = shift // return; |
693
|
24
|
|
|
|
|
49
|
s/\%([A-Fa-f0-9]{2})/chr(hex($1))/; |
694
|
24
|
|
|
|
|
106
|
return decode('UTF-8', $_); |
695
|
|
|
|
|
|
|
} |
696
|
|
|
|
|
|
|
|
697
|
|
|
|
|
|
|
|
698
|
|
|
|
|
|
|
sub uuid { |
699
|
12
|
|
50
|
12
|
1
|
4994
|
local $_ = shift // return "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; |
700
|
12
|
|
|
|
|
29
|
s/-//g; |
701
|
12
|
50
|
|
|
|
45
|
/^[A-Fa-f0-9]{32}$/ or throw 'Must provide a formatted 128-bit UUID'; |
702
|
12
|
|
|
|
|
85
|
return pack('H32', $_); |
703
|
|
|
|
|
|
|
|
704
|
|
|
|
|
|
|
} |
705
|
|
|
|
|
|
|
|
706
|
|
|
|
|
|
|
|
707
|
|
|
|
|
|
|
sub UUID_NULL() { "\0" x 16 } |
708
|
|
|
|
|
|
|
|
709
|
|
|
|
|
|
|
### -------------------------------------------------------------------------- |
710
|
|
|
|
|
|
|
|
711
|
|
|
|
|
|
|
# Determine if an array looks like keypairs from a hash. |
712
|
|
|
|
|
|
|
sub _looks_like_keypairs { |
713
|
216
|
|
|
216
|
|
242
|
my $arr = shift; |
714
|
216
|
100
|
|
|
|
407
|
return 0 if @$arr % 2 == 1; |
715
|
149
|
|
|
|
|
260
|
for (my $i = 0; $i < @$arr; $i += 2) { |
716
|
161
|
100
|
|
|
|
377
|
return 0 if is_ref($arr->[$i]); |
717
|
|
|
|
|
|
|
} |
718
|
142
|
|
|
|
|
246
|
return 1; |
719
|
|
|
|
|
|
|
} |
720
|
|
|
|
|
|
|
|
721
|
|
|
|
|
|
|
sub _is_operand_plain { |
722
|
298
|
|
|
298
|
|
348
|
local $_ = shift; |
723
|
298
|
|
100
|
|
|
918
|
return !(is_hashref($_) || is_arrayref($_)); |
724
|
|
|
|
|
|
|
} |
725
|
|
|
|
|
|
|
|
726
|
|
|
|
|
|
|
sub _query { |
727
|
|
|
|
|
|
|
# dumper \@_; |
728
|
287
|
|
|
287
|
|
324
|
my $subject = shift; |
729
|
287
|
|
33
|
|
|
439
|
my $op = shift // throw 'Must specify a query operator'; |
730
|
287
|
|
|
|
|
326
|
my $operand = shift; |
731
|
|
|
|
|
|
|
|
732
|
287
|
50
|
66
|
|
|
886
|
return _query_simple($op, $subject) if defined $subject && !is_ref($op) && ($OPS{$subject} || 2) < 2; |
|
|
|
50
|
|
|
|
|
|
|
|
66
|
|
|
|
|
733
|
287
|
100
|
|
|
|
386
|
return _query_simple($subject, $op, $operand) if _is_operand_plain($operand); |
734
|
212
|
100
|
66
|
|
|
573
|
return _query_inverse(_query($subject, '-or', $operand)) if $op eq '-not' || $op eq '-false'; |
735
|
210
|
100
|
|
|
|
363
|
return _query($subject, '-and', [%$operand]) if is_hashref($operand); |
736
|
|
|
|
|
|
|
|
737
|
197
|
|
|
|
|
212
|
my @queries; |
738
|
|
|
|
|
|
|
|
739
|
197
|
|
|
|
|
313
|
my @atoms = @$operand; |
740
|
197
|
|
|
|
|
316
|
while (@atoms) { |
741
|
216
|
100
|
|
|
|
311
|
if (_looks_like_keypairs(\@atoms)) { |
742
|
142
|
|
|
|
|
263
|
my ($atom, $operand) = splice @atoms, 0, 2; |
743
|
142
|
100
|
|
|
|
351
|
if (my $op_type = $OPS{$atom}) { |
|
|
50
|
|
|
|
|
|
744
|
67
|
100
|
100
|
|
|
165
|
if ($op_type == 1 && _is_operand_plain($operand)) { # unary |
745
|
9
|
|
|
|
|
17
|
push @queries, _query_simple($operand, $atom); |
746
|
|
|
|
|
|
|
} |
747
|
|
|
|
|
|
|
else { |
748
|
58
|
|
|
|
|
107
|
push @queries, _query($subject, $atom, $operand); |
749
|
|
|
|
|
|
|
} |
750
|
|
|
|
|
|
|
} |
751
|
|
|
|
|
|
|
elsif (!is_ref($atom)) { |
752
|
75
|
|
|
|
|
177
|
push @queries, _query($atom, 'eq', $operand); |
753
|
|
|
|
|
|
|
} |
754
|
|
|
|
|
|
|
} |
755
|
|
|
|
|
|
|
else { |
756
|
74
|
|
|
|
|
102
|
my $atom = shift @atoms; |
757
|
74
|
100
|
|
|
|
144
|
if ($OPS{$atom}) { # apply new operator over the rest |
758
|
35
|
|
|
|
|
70
|
push @queries, _query($subject, $atom, \@atoms); |
759
|
35
|
|
|
|
|
57
|
last; |
760
|
|
|
|
|
|
|
} |
761
|
|
|
|
|
|
|
else { # apply original operator over this one |
762
|
39
|
|
|
|
|
74
|
push @queries, _query($subject, $op, $atom); |
763
|
|
|
|
|
|
|
} |
764
|
|
|
|
|
|
|
} |
765
|
|
|
|
|
|
|
} |
766
|
|
|
|
|
|
|
|
767
|
197
|
100
|
|
|
|
334
|
if (@queries == 1) { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
768
|
178
|
|
|
|
|
516
|
return $queries[0]; |
769
|
|
|
|
|
|
|
} |
770
|
|
|
|
|
|
|
elsif ($op eq '-and') { |
771
|
12
|
|
|
|
|
27
|
return _query_all(@queries); |
772
|
|
|
|
|
|
|
} |
773
|
|
|
|
|
|
|
elsif ($op eq '-or') { |
774
|
7
|
|
|
|
|
14
|
return _query_any(@queries); |
775
|
|
|
|
|
|
|
} |
776
|
0
|
|
|
|
|
0
|
throw 'Malformed query'; |
777
|
|
|
|
|
|
|
} |
778
|
|
|
|
|
|
|
|
779
|
|
|
|
|
|
|
sub _query_simple { |
780
|
84
|
|
|
84
|
|
124
|
my $subject = shift; |
781
|
84
|
|
50
|
|
|
139
|
my $op = shift // 'eq'; |
782
|
84
|
|
|
|
|
100
|
my $operand = shift; |
783
|
|
|
|
|
|
|
|
784
|
|
|
|
|
|
|
# these special operators can also act as simple operators |
785
|
84
|
100
|
|
|
|
145
|
$op = '!!' if $op eq '-true'; |
786
|
84
|
100
|
|
|
|
137
|
$op = '!' if $op eq '-false'; |
787
|
84
|
50
|
|
|
|
132
|
$op = '!' if $op eq '-not'; |
788
|
|
|
|
|
|
|
|
789
|
84
|
50
|
|
|
|
139
|
defined $subject or throw 'Subject is not set in query'; |
790
|
84
|
50
|
|
|
|
169
|
$OPS{$op} >= 0 or throw 'Cannot use a non-simple operator in a simple query'; |
791
|
84
|
100
|
|
|
|
157
|
if (empty($operand)) { |
792
|
11
|
100
|
66
|
|
|
32
|
if ($OPS{$op} < 2) { |
|
|
100
|
33
|
|
|
|
|
|
|
50
|
|
|
|
|
|
793
|
|
|
|
|
|
|
# no operand needed |
794
|
|
|
|
|
|
|
} |
795
|
|
|
|
|
|
|
# Allow field => undef and field => {'ne' => undef} to do the (arguably) right thing. |
796
|
|
|
|
|
|
|
elsif ($op eq 'eq' || $op eq '==') { |
797
|
1
|
|
|
|
|
2
|
$op = '-empty'; |
798
|
|
|
|
|
|
|
} |
799
|
|
|
|
|
|
|
elsif ($op eq 'ne' || $op eq '!=') { |
800
|
1
|
|
|
|
|
2
|
$op = '-nonempty'; |
801
|
|
|
|
|
|
|
} |
802
|
|
|
|
|
|
|
else { |
803
|
0
|
|
|
|
|
0
|
throw 'Operand is required'; |
804
|
|
|
|
|
|
|
} |
805
|
|
|
|
|
|
|
} |
806
|
|
|
|
|
|
|
|
807
|
84
|
100
|
66
|
224
|
|
323
|
my $field = sub { blessed $_[0] && $_[0]->can($subject) ? $_[0]->$subject : $_[0]->{$subject} }; |
|
224
|
|
|
|
|
779
|
|
808
|
|
|
|
|
|
|
|
809
|
|
|
|
|
|
|
my %map = ( |
810
|
78
|
100
|
|
78
|
|
114
|
'eq' => sub { local $_ = $field->(@_); defined && $_ eq $operand }, |
|
78
|
|
|
|
|
367
|
|
811
|
2
|
50
|
|
2
|
|
3
|
'ne' => sub { local $_ = $field->(@_); defined && $_ ne $operand }, |
|
2
|
|
|
|
|
13
|
|
812
|
0
|
0
|
|
0
|
|
0
|
'lt' => sub { local $_ = $field->(@_); defined && $_ lt $operand }, |
|
0
|
|
|
|
|
0
|
|
813
|
0
|
0
|
|
0
|
|
0
|
'gt' => sub { local $_ = $field->(@_); defined && $_ gt $operand }, |
|
0
|
|
|
|
|
0
|
|
814
|
0
|
0
|
|
0
|
|
0
|
'le' => sub { local $_ = $field->(@_); defined && $_ le $operand }, |
|
0
|
|
|
|
|
0
|
|
815
|
0
|
0
|
|
0
|
|
0
|
'ge' => sub { local $_ = $field->(@_); defined && $_ ge $operand }, |
|
0
|
|
|
|
|
0
|
|
816
|
4
|
50
|
|
4
|
|
8
|
'==' => sub { local $_ = $field->(@_); defined && $_ == $operand }, |
|
4
|
|
|
|
|
15
|
|
817
|
4
|
50
|
|
4
|
|
5
|
'!=' => sub { local $_ = $field->(@_); defined && $_ != $operand }, |
|
4
|
|
|
|
|
14
|
|
818
|
0
|
0
|
|
0
|
|
0
|
'<' => sub { local $_ = $field->(@_); defined && $_ < $operand }, |
|
0
|
|
|
|
|
0
|
|
819
|
0
|
0
|
|
0
|
|
0
|
'>' => sub { local $_ = $field->(@_); defined && $_ > $operand }, |
|
0
|
|
|
|
|
0
|
|
820
|
0
|
0
|
|
0
|
|
0
|
'<=' => sub { local $_ = $field->(@_); defined && $_ <= $operand }, |
|
0
|
|
|
|
|
0
|
|
821
|
4
|
50
|
|
4
|
|
6
|
'>=' => sub { local $_ = $field->(@_); defined && $_ >= $operand }, |
|
4
|
|
|
|
|
17
|
|
822
|
81
|
50
|
|
81
|
|
121
|
'=~' => sub { local $_ = $field->(@_); defined && $_ =~ $operand }, |
|
81
|
|
|
|
|
515
|
|
823
|
7
|
50
|
|
7
|
|
11
|
'!~' => sub { local $_ = $field->(@_); defined && $_ !~ $operand }, |
|
7
|
|
|
|
|
40
|
|
824
|
11
|
|
|
11
|
|
14
|
'!' => sub { local $_ = $field->(@_); ! $_ }, |
|
11
|
|
|
|
|
36
|
|
825
|
9
|
|
|
9
|
|
14
|
'!!' => sub { local $_ = $field->(@_); !!$_ }, |
|
9
|
|
|
|
|
33
|
|
826
|
4
|
|
|
4
|
|
6
|
'-defined' => sub { local $_ = $field->(@_); defined $_ }, |
|
4
|
|
|
|
|
10
|
|
827
|
4
|
|
|
4
|
|
6
|
'-undef' => sub { local $_ = $field->(@_); !defined $_ }, |
|
4
|
|
|
|
|
9
|
|
828
|
8
|
|
|
8
|
|
11
|
'-nonempty' => sub { local $_ = $field->(@_); nonempty $_ }, |
|
8
|
|
|
|
|
14
|
|
829
|
8
|
|
|
8
|
|
12
|
'-empty' => sub { local $_ = $field->(@_); empty $_ }, |
|
8
|
|
|
|
|
25
|
|
830
|
84
|
|
|
|
|
1852
|
); |
831
|
|
|
|
|
|
|
|
832
|
84
|
|
33
|
|
|
1779
|
return $map{$op} // throw "Unexpected operator in query: $op", |
833
|
|
|
|
|
|
|
subject => $subject, |
834
|
|
|
|
|
|
|
operator => $op, |
835
|
|
|
|
|
|
|
operand => $operand; |
836
|
|
|
|
|
|
|
} |
837
|
|
|
|
|
|
|
|
838
|
|
|
|
|
|
|
sub _query_inverse { |
839
|
2
|
|
|
2
|
|
2
|
my $query = shift; |
840
|
2
|
|
|
7
|
|
10
|
return sub { !$query->(@_) }; |
|
7
|
|
|
|
|
10
|
|
841
|
|
|
|
|
|
|
} |
842
|
|
|
|
|
|
|
|
843
|
|
|
|
|
|
|
sub _query_all { |
844
|
12
|
|
|
12
|
|
22
|
my @queries = @_; |
845
|
|
|
|
|
|
|
return sub { |
846
|
44
|
|
|
44
|
|
49
|
my $val = shift; |
847
|
44
|
|
|
|
|
122
|
all { $_->($val) } @queries; |
|
57
|
|
|
|
|
93
|
|
848
|
12
|
|
|
|
|
48
|
}; |
849
|
|
|
|
|
|
|
} |
850
|
|
|
|
|
|
|
|
851
|
|
|
|
|
|
|
sub _query_any { |
852
|
7
|
|
|
7
|
|
12
|
my @queries = @_; |
853
|
|
|
|
|
|
|
return sub { |
854
|
26
|
|
|
26
|
|
29
|
my $val = shift; |
855
|
26
|
|
|
|
|
62
|
any { $_->($val) } @queries; |
|
43
|
|
|
|
|
65
|
|
856
|
7
|
|
|
|
|
25
|
}; |
857
|
|
|
|
|
|
|
} |
858
|
|
|
|
|
|
|
|
859
|
|
|
|
|
|
|
1; |
860
|
|
|
|
|
|
|
|
861
|
|
|
|
|
|
|
__END__ |