1 package File
::KDBX
::Util
;
2 # ABSTRACT: Utility functions for working with KDBX files
7 use Crypt
::PRNG
qw(random_bytes random_string);
8 use Encode
qw(decode encode);
9 use Exporter
qw(import);
10 use File
::KDBX
::Error
;
11 use List
::Util
1.33 qw(any all);
13 use Ref
::Util
qw(is_arrayref is_coderef is_hashref is_ref is_refref is_scalarref);
14 use Scalar
::Util
qw(blessed looks_like_number readonly);
17 use namespace
::clean
-except
=> 'import';
19 our $VERSION = '999.999'; # VERSION
22 assert
=> [qw(DEBUG assert)],
23 class => [qw(extends has list_attributes)],
24 clone
=> [qw(clone clone_nomagic)],
25 coercion
=> [qw(to_bool to_number to_string to_time to_tristate to_uuid)],
26 crypt => [qw(pad_pkcs7)],
27 debug
=> [qw(DEBUG dumper)],
28 fork => [qw(can_fork)],
29 function
=> [qw(memoize recurse_limit)],
30 empty
=> [qw(empty nonempty)],
31 erase
=> [qw(erase erase_scoped)],
32 gzip
=> [qw(gzip gunzip)],
33 int => [qw(int64 pack_ql pack_Ql unpack_ql unpack_Ql)],
35 load
=> [qw(load_optional load_xs try_load_optional)],
36 search
=> [qw(query query_any search simple_expression_query)],
37 text
=> [qw(snakify trim)],
38 uuid
=> [qw(format_uuid generate_uuid is_uuid uuid UUID_NULL)],
39 uri
=> [qw(split_url uri_escape_utf8 uri_unescape_utf8)],
42 $EXPORT_TAGS{all
} = [map { @$_ } values %EXPORT_TAGS];
43 our @EXPORT_OK = @{$EXPORT_TAGS{all
}};
46 my $debug = $ENV{DEBUG
};
47 $debug = looks_like_number
($debug) ? (0 + $debug) : ($debug ? 1 : 0);
48 *DEBUG
= $debug == 1 ? sub() { 1 } :
49 $debug == 2 ? sub() { 2 } :
50 $debug == 3 ? sub() { 3 } :
51 $debug == 4 ? sub() { 4 } : sub() { 0 };
71 '-not' => 1, # special
102 $bool = load_xs
($version);
104 Attempt to load L
<File
::KDBX
::XS
>. Return truthy
if it
is loaded
. If C
<$version> is given, it will check that
105 at least the
given version
is loaded
.
113 goto IS_LOADED
if defined $XS_LOADED;
115 if ($ENV{PERL_ONLY
} || (exists $ENV{PERL_FILE_KDBX_XS
} && !$ENV{PERL_FILE_KDBX_XS
})) {
116 return $XS_LOADED = !1;
119 $XS_LOADED = !!eval { require File
::KDBX
::XS
; 1 };
124 return $XS_LOADED if !$version;
125 return !!eval { File
::KDBX
::XS-
>VERSION($version); 1 };
133 Write an executable comment
. Only executed
if C
<DEBUG
> is set
in the environment
.
137 sub assert
(&) { ## no critic (ProhibitSubroutinePrototypes)
142 (undef, my $file, my $line) = caller;
143 $file =~ s!([^/\\]+)$!$1!;
145 if (try_load_optional
('B::Deparse')) {
146 my $deparse = B
::Deparse-
>new(qw{-P -x9});
147 $assertion = $deparse->coderef2text($code);
148 $assertion =~ s/^\{(?:\s*(?:package[^;]+|use[^;]+);)*\s*(.*?);\s*\}$/$1/s;
149 $assertion =~ s/\s+/ /gs;
150 $assertion = ": $assertion";
152 die "$0: $file:$line: Assertion failed$assertion\n";
159 Determine
if perl can
fork, with logic lifted from L
<Test2
::Util
/CAN_FORK
>.
165 return 1 if $Config::Config
{d_fork
};
166 return 0 if $^O ne 'MSWin32' && $^O ne 'NetWare';
167 return 0 if !$Config::Config
{useithreads
};
168 return 0 if $Config::Config
{ccflags
} !~ /-DPERL_IMPLICIT_SYS/;
169 return 0 if $] < 5.008001;
170 if ($] == 5.010000 && $Config::Config
{ccname
} eq 'gcc' && $Config::Config
{gccversion
}) {
171 return 0 if $Config::Config
{gccversion
} !~ m/^(\d+)\.(\d+)/;
172 my @parts = split(/[\.\s]+/, $Config::Config
{gccversion
});
173 return 0 if $parts[0] > 4 || ($parts[0] == 4 && $parts[1] >= 8);
175 return 0 if $INC{'Devel/Cover.pm'};
181 $clone = clone
($thing);
183 Clone deeply
. This
is an unadorned alias to L
<Storable
> C
<dclone
>.
189 goto &Storable
::dclone
;
194 $clone = clone_nomagic
($thing);
196 Clone deeply without keeping
[most of
] the magic
.
198 B
<WARNING
:> At the moment the implementation
is naïve
and won
't respond well to nontrivial data or recursive
205 if (is_arrayref($thing)) {
206 my @arr = map { clone_nomagic($_) } @$thing;
209 elsif (is_hashref($thing)) {
211 $hash{$_} = clone_nomagic($thing->{$_}) for keys %$thing;
214 elsif (is_ref($thing)) {
215 return clone($thing);
222 Constant number indicating the level of debuggingness.
226 $str = dumper $thing;
227 dumper $thing; # in void context, prints to STDERR
229 Like L<Data::Dumper> but slightly terser in some cases relevent to L<File::KDBX>.
234 require Data::Dumper;
235 # avoid "once" warnings
236 local $Data::Dumper::Deepcopy = $Data::Dumper::Deepcopy = 1;
237 local $Data::Dumper::Deparse = $Data::Dumper::Deparse = 1;
238 local $Data::Dumper::Indent = 1;
239 local $Data::Dumper::Quotekeys = 0;
240 local $Data::Dumper::Sortkeys = 1;
241 local $Data::Dumper::Terse = 1;
242 local $Data::Dumper::Trailingcomma = 1;
243 local $Data::Dumper::Useqq = 1;
246 for my $struct (@_) {
247 my $str = Data::Dumper::Dumper($struct);
250 $str =~ s/bless\( do\{\\\(my \$o = ([01])\)\}, 'boolean' \)/boolean($1)/gs;
252 $str =~ s/bless\([^\)]+?(\d+)'?,\s
+\d
+,?\s
+\
], 'Time::Piece' \
),/
253 "scalar gmtime($1), # " . scalar gmtime($1)->datetime/ges
;
255 print STDERR
$str if !defined wantarray;
259 return join("\n", @dumps);
266 $bool = empty
$thing;
268 $bool = nonempty
$thing;
270 Test whether a thing
is empty
(or nonempty
). An empty thing
is one of these
:
277 * hash with zero keys
278 * reference to an empty thing (recursive)
280 Note in particular that zero C<0> is not considered empty because it is an actual value.
284 sub empty
{ _empty
(@_) }
285 sub nonempty
{ !_empty
(@_) }
292 || (is_arrayref
($_) && @$_ == 0)
293 || (is_hashref
($_) && keys %$_ == 0)
294 || (is_scalarref
($_) && (!defined $$_ || $$_ eq ''))
295 || (is_refref
($_) && _empty
($$_));
301 erase
(\
$string, ...);
303 Overwrite the memory used by one
or more string
.
309 *_CowREFCNT
= \
&File
::KDBX
::XS
::CowREFCNT
;
311 elsif (eval { require B
::COW
; 1 }) {
312 *_CowREFCNT
= \
&B
::COW
::cowrefcnt
;
315 *_CowREFCNT
= sub { undef };
320 # Only bother zeroing out memory if we have the last SvPV COW reference, otherwise we'll end up just
321 # creating a copy and erasing the copy.
322 # TODO - Is this worth doing? Need some benchmarking.
325 next if !defined $_ || readonly
$_;
326 my $cowrefcnt = _CowREFCNT
($_);
327 goto FREE_NONREF
if defined $cowrefcnt && 1 < $cowrefcnt;
328 # if (__PACKAGE__->can('erase_xs')) {
332 substr($_, 0, length($_), "\0" x
length($_));
335 no warnings
'uninitialized';
339 elsif (is_scalarref
($_)) {
340 next if !defined $$_ || readonly
$$_;
341 my $cowrefcnt = _CowREFCNT
($$_);
342 goto FREE_REF
if defined $cowrefcnt && 1 < $cowrefcnt;
343 # if (__PACKAGE__->can('erase_xs')) {
347 substr($$_, 0, length($$_), "\0" x
length($$_));
350 no warnings
'uninitialized';
354 elsif (is_arrayref
($_)) {
358 elsif (is_hashref
($_)) {
363 throw
'Cannot erase this type of scalar', type
=> ref $_, what
=> $_;
370 $scope_guard = erase_scoped
($string, ...);
371 $scope_guard = erase_scoped
(\
$string, ...);
372 undef $scope_guard; # erase happens here
374 Get a scope guard that will cause scalars to be erased later
(i
.e
. when the scope ends
). This
is useful
if you
375 want to make sure a string gets erased after you
're done with it, even if the scope ends abnormally.
382 throw 'Programmer error
: Cannot call erase_scoped
in void context
' if !defined wantarray;
385 !is_ref($_) || is_arrayref($_) || is_hashref($_) || is_scalarref($_)
386 or throw 'Cannot erase this type of
scalar', type => ref $_, what => $_;
387 push @args, is_ref($_) ? $_ : \$_;
389 require Scope::Guard;
390 return Scope::Guard->new(sub { erase(@args) });
397 Set up the current module to inheret from another module.
405 no strict 'refs
'; ## no critic (ProhibitNoStrict)
406 @{"${caller}::ISA"} = $parent;
411 has $name => %options;
413 Create an attribute getter/setter. Possible options:
416 * C<is> - Either "rw" (default) or "ro"
417 * C<default> - Default value
418 * C<coerce> - Coercive function
424 my %args = @_ % 2 == 1 ? (default => shift, @_) : @_;
426 my ($package, $file, $line) = caller;
428 my $d = $args{default};
429 my $default = is_arrayref($d) ? sub { [@$d] } : is_hashref($d) ? sub { +{%$d} } : $d;
430 my $coerce = $args{coerce};
431 my $is = $args{is} || 'rw';
433 my $store = $args{store};
434 ($store, $name) = split(/\./, $name, 2) if $name =~ /\./;
436 my @path = split(/\./, $args{path} || '');
437 my $last = pop @path;
438 my $path = $last ? join('', map { qq{->$_} } @path) . qq{->{'$last'}}
439 : $store ? qq{->$store\->{'$name'}} : qq{->{'$name'}};
440 my $member = qq{\$_[0]$path};
443 my $default_code = is_coderef
$default ? q{scalar $default->($_[0])}
444 : defined $default ? q{$default}
446 my $get = qq{$member //= $default_code;};
450 $set = is_coderef
$coerce ? qq{$member = scalar \$coerce->(\@_[1..\$#_]) if \$#_;}
451 : defined $coerce ? qq{$member = do { local @_ = (\@_[1..\$#_]); $coerce } if \
$#_;}
452 : qq{$member = \$_[1] if \$#_;};
455 push @{$ATTRIBUTES{$package} //= []}, $name;
459 sub ${package}::${name} {
460 return $default_code if !Scalar::Util::blessed(\$_[0]);
465 eval $code; ## no critic (ProhibitStringyEval)
470 $string_uuid = format_uuid
($raw_uuid);
471 $string_uuid = format_uuid
($raw_uuid, $delimiter);
473 Format a
128-bit UUID
(given as a string of
16 octets
) into a hexidecimal string
, optionally with a delimiter
474 to
break up the UUID visually into five parts
. Examples
:
476 my $uuid = uuid
('01234567-89AB-CDEF-0123-456789ABCDEF');
477 say format_uuid
($uuid); # -> 0123456789ABCDEF0123456789ABCDEF
478 say format_uuid
($uuid, '-'); # -> 01234567-89AB-CDEF-0123-456789ABCDEF
480 This
is the inverse of L
</uuid
>.
485 local $_ = shift // "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
486 my $delim = shift // '';
487 length($_) == 16 or throw
'Must provide a 16-bytes UUID', size
=> length($_), str
=> $_;
488 return uc(join($delim, unpack('H8 H4 H4 H4 H12', $_)));
493 $uuid = generate_uuid
;
494 $uuid = generate_uuid
(\
%set);
495 $uuid = generate_uuid
(\
&test_uuid
);
497 Generate a new random UUID
. It
's pretty unlikely that this will generate a repeat, but if you're worried about
498 that you can provide either a set of existing UUIDs
(as a hashref where the
keys are the elements of a set
) or
499 a function to check
for existing UUIDs
, and this will be sure to
not return a UUID already
in provided set
.
500 Perhaps an example will make it clear
:
503 uuid
('12345678-9ABC-DEFG-1234-56789ABCDEFG') => 'whatever',
505 $uuid = generate_uuid
(\
%uuid_set);
507 $uuid = generate_uuid
(sub { !$uuid_set{$_} });
509 Here
, C
<$uuid> can
't be "12345678-9ABC-DEFG-1234-56789ABCDEFG". This example uses L</uuid> to easily pack
510 a 16-byte UUID from a literal, but it otherwise is not a consequential part of the example.
515 my $set = @_ % 2 == 1 ? shift : undef;
517 my $test = $set //= $args{test};
518 $test = sub { !$set->{$_} } if is_hashref($test);
520 my $printable = $args{printable} // $args{print};
523 $_ = $printable ? random_string(16) : random_bytes(16);
524 } while (!$test->($_));
530 $unzipped = gunzip($string);
532 Decompress an octet stream.
537 load_optional('Compress
::Raw
::Zlib
');
539 my ($i, $status) = Compress::Raw::Zlib::Inflate->new(-WindowBits => 31);
540 $status == Compress::Raw::Zlib::Z_OK()
541 or throw 'Failed to initialize compression library
', status => $status;
542 $status = $i->inflate($_, my $out);
543 $status == Compress::Raw::Zlib::Z_STREAM_END()
544 or throw 'Failed to decompress data
', status => $status;
550 $zipped = gzip($string);
552 Compress an octet stream.
557 load_optional('Compress
::Raw
::Zlib
');
559 my ($d, $status) = Compress::Raw::Zlib::Deflate->new(-WindowBits => 31, -AppendOutput => 1);
560 $status == Compress::Raw::Zlib::Z_OK()
561 or throw 'Failed to initialize compression library
', status => $status;
562 $status = $d->deflate($_, my $out);
563 $status == Compress::Raw::Zlib::Z_OK()
564 or throw 'Failed to compress data
', status => $status;
565 $status = $d->flush($out);
566 $status == Compress::Raw::Zlib::Z_OK()
567 or throw 'Failed to compress data
', status => $status;
573 $int = int64($string);
575 Get a scalar integer capable of holding 64-bit values, initialized with a given default value. On a 64-bit
576 perl, it will return a regular SvIV. On a 32-bit perl it will return a L<Math::BigInt>.
582 if ($Config::Config{ivsize} < 8) {
583 require Math::BigInt;
584 return Math::BigInt->new(@_);
591 $bytes = pack_Ql($int);
593 Like C<pack('QE
<lt>', $int)>, but also works on 32-bit perls.
600 if ($Config::Config{ivsize} < 8) {
601 if (blessed $num && $num->can('as_hex
')) {
602 return "\xff\xff\xff\xff\xff\xff\xff\xff" if Math::BigInt->new('18446744073709551615') <= $num;
603 return "\x00\x00\x00\x00\x00\x00\x00\x80" if $num <= Math::BigInt->new('-9223372036854775808');
609 my $hex = $num->as_hex;
610 $hex =~ s/^0x/000000000000000/;
611 my $bytes = reverse pack('H16
', substr($hex, -16));
612 $bytes .= "\0" x (8 - length $bytes) if length $bytes < 8;
615 $bytes = join('', map { chr(~ord($_) & 0xff) } split(//, $bytes));
616 substr($bytes, 0, 1, chr(ord(substr($bytes, 0, 1)) + 1));
621 my $pad = $num < 0 ? "\xff" : "\0";
622 return pack('L<', $num) . ($pad x
4);
625 return pack('Q<', $num);
630 $bytes = pack_ql
($int);
632 Like C
<pack('qE<lt>', $int)>, but also works on
32-bit perls
.
636 sub pack_ql
{ goto &pack_Ql
}
640 $int = unpack_Ql
($bytes);
642 Like C
<unpack('QE<lt>', $bytes)>, but also works on
32-bit perls
.
649 if ($Config::Config
{ivsize
} < 8) {
650 require Math
::BigInt
;
651 return Math
::BigInt-
>new('0x' . unpack('H*', scalar reverse $bytes));
653 return unpack('Q<', $bytes);
658 $int = unpack_ql
($bytes);
660 Like C
<unpack('qE<lt>', $bytes)>, but also works on
32-bit perls
.
667 if ($Config::Config
{ivsize
} < 8) {
668 require Math
::BigInt
;
669 if (ord(substr($bytes, -1, 1)) & 128) {
670 return Math
::BigInt-
>new('-9223372036854775808') if $bytes eq "\x00\x00\x00\x00\x00\x00\x00\x80";
672 substr($bytes, 0, 1, chr(ord(substr($bytes, 0, 1)) - 1));
673 $bytes = join('', map { chr(~ord($_) & 0xff) } split(//, $bytes));
674 return -Math
::BigInt-
>new('0x' . unpack('H*', scalar reverse $bytes));
677 return Math
::BigInt-
>new('0x' . unpack('H*', scalar reverse $bytes));
680 return unpack('q<', $bytes);
685 $bool = is_uuid
($thing);
687 Check
if a thing
is a UUID
(i
.e
. scalar string of
length 16).
691 sub is_uuid
{ defined $_[0] && !is_ref
($_[0]) && length($_[0]) == 16 }
693 =func list_attributes
695 @attributes = list_attributes
($package);
697 Get a list of attributes
for a
class.
701 sub list_attributes
{
703 return @{$ATTRIBUTES{$package} // []};
708 $package = load_optional
($package);
710 Load a module that isn
't required but can provide extra functionality. Throw if the module is not available.
715 for my $module (@_) {
716 eval { load $module };
718 throw "Missing dependency: Please install $module to use this feature.\n",
723 return wantarray ? @_ : $_[0];
728 \&memoized_code = memoize(\&code, ...);
730 Memoize a function. Extra arguments are passed through to C<&code> when it is called.
738 return sub { $cache{join("\0", grep { defined } @_)} //= $func->(@args, @_) };
743 $padded_string = pad_pkcs7($string, $block_size),
745 Pad a block using the PKCS#7 method.
750 my $data = shift // throw 'Must provide a string to pad
';
751 my $size = shift or throw 'Must provide block size
';
753 0 <= $size && $size < 256
754 or throw 'Cannot add PKCS7 padding to a large block size
', size => $size;
756 my $pad_len = $size - length($data) % $size;
757 $data .= chr($pad_len) x $pad_len;
762 $query = query(@where);
765 Generate a function that will run a series of tests on a passed hashref and return true or false depending on
766 if the data record in the hash matched the specified logic.
768 The logic can be specified in a manner similar to L<SQL::Abstract/"WHERE CLAUSES"> which was the inspiration
769 for this function, but this code is distinct, supporting an overlapping but not identical feature set and
772 See L<File::KDBX/"Declarative Syntax"> for examples.
776 sub query { _query(undef, '-or', \@_) }
780 Get either a L</query> or L</simple_expression_query>, depending on the arguments.
787 if (is_coderef($code) || overload::Method($code, '&{}')) {
790 elsif (is_scalarref($code)) {
791 return simple_expression_query($$code, @_);
794 return query($code, @_);
800 $size = read_all($fh, my $buffer, $size);
801 $size = read_all($fh, my $buffer, $size, $offset);
803 Like L<perlfunc/"read FILEHANDLE,SCALAR,LENGTH,OFFSET"> but returns C<undef> if not all C<$size> bytes are
804 read. This is considered an error, distinguishable from other errors by C<$!> not being set.
808 sub read_all($$$;$) { ## no critic (ProhibitSubroutinePrototypes)
809 my $result = @_ == 3 ? read($_[0], $_[1], $_[2])
810 : read($_[0], $_[1], $_[2], $_[3]);
811 return if !defined $result;
812 return if $result != $_[2];
818 \&limited_code = recurse_limit(\&code);
819 \&limited_code = recurse_limit(\&code, $max_depth);
820 \&limited_code = recurse_limit(\&code, $max_depth, \&error_handler);
822 Wrap a function with a guard to prevent deep recursion.
828 my $max_depth = shift // 200;
829 my $error = shift // sub {};
831 return sub { return $error->(@_) if $max_depth < ++$depth; $func->(@_) };
836 # Generate a query on-the-fly:
837 \@matches = search(\@records, @where);
839 # Use a pre-compiled query:
840 $query = query(@where);
841 \@matches = search(\@records, $query);
843 # Use a simple expression:
844 \@matches = search(\@records, \'query terms', @fields);
845 \
@matches = search
(\
@records, \'query terms
', $operator, @fields);
847 # Use your own subroutine:
848 \@matches = search(\@records, \&query);
849 \@matches = search(\@records, sub { $record = shift; ... });
851 Execute a linear search over an array of records using a L</query>. A "record" is usually a hash.
857 my $query = query_any(@_);
860 for my $item (@$list) {
861 push @match, $item if $query->($item);
866 =func simple_expression_query
868 $query = simple_expression_query($expression, @fields);
869 $query = simple_expression_query($expression, $operator, @fields);
871 Generate a query, like L</query>, to be used with L</search> but built from a "simple expression" as
872 L<described here|https://keepass.info/help/base/search.html#mode_se>.
874 An expression is a string with one or more space-separated terms. Terms with spaces can be enclosed in double
875 quotes. Terms are negated if they are prefixed with a minus sign. A record must match every term on at least
876 one of the given fields.
880 sub simple_expression_query {
882 my $op = @_ && ($OPS{$_[0] || ''} || 0) == 2 ? shift : '=~';
884 my $neg_op = $OP_NEG{$op};
885 my $is_re = $op eq '=~' || $op eq '!~';
887 require Text::ParseWords;
888 my @terms = Text::ParseWords::shellwords($expr);
890 my @query = qw(-and);
892 for my $term (@terms) {
893 my @subquery = qw(-or);
895 my $neg = $term =~ s/^-//;
896 my $condition = [($neg ? $neg_op : $op) => ($is_re ? qr/\Q$term\E/i : $term)];
899 push @subquery, $field => $condition;
902 push @query, \
@subquery;
905 return query
(\
@query);
910 $string = snakify
($string);
912 Turn a CamelCase string into snake_case
.
918 s/UserName/Username/g;
919 s/([a-z])([A-Z0-9])/${1}_${2}/g;
920 s/([A-Z0-9]+)([A-Z0-9])(?![A-Z0-9]|$)/${1}_${2}/g;
926 ($scheme, $auth, $host, $port, $path, $query, $hash, $usename, $password) = split_url
($url);
928 Split a URL into its parts
.
930 For example
, C
<http
://user
:pass
@localhost:4000/path
?query
#hash> gets split like:
947 my ($scheme, $auth, $host, $port, $path, $query, $hash) =~ m
!
957 $scheme = lc($scheme);
959 $host ||= 'localhost';
962 $path = "/$path" if $path !~ m
!^/!;
964 $port ||= $scheme eq 'http' ? 80 : $scheme eq 'https' ? 433 : undef;
966 my ($username, $password) = split($auth, ':', 2);
968 return ($scheme, $auth, $host, $port, $path, $query, $hash, $username, $password);
983 Various typecasting
/ coercive functions
.
987 sub to_bool
{ $_[0] // return; boolean
($_[0]) }
988 sub to_number
{ $_[0] // return; 0+$_[0] }
989 sub to_string
{ $_[0] // return; "$_[0]" }
992 return scalar gmtime($_[0]) if looks_like_number
($_[0]);
993 return scalar gmtime if $_[0] eq 'now';
994 return Time
::Piece-
>strptime($_[0], '%Y-%m-%d %H:%M:%S') if !blessed
$_[0];
997 sub to_tristate
{ $_[0] // return; boolean
($_[0]) }
999 my $str = to_string
(@_) // return;
1000 return sprintf('%016s', $str) if length($str) < 16;
1001 return substr($str, 0, 16) if 16 < length($str);
1007 $string = trim
($string);
1009 The ubiquitous C
<trim
> function
. Removes all whitespace from both ends of a string
.
1013 sub trim
($) { ## no critic (ProhibitSubroutinePrototypes)
1014 local $_ = shift // return;
1020 =func try_load_optional
1022 $package = try_load_optional
($package);
1024 Try to load a module that isn
't required but can provide extra functionality, and return true if successful.
1028 sub try_load_optional {
1029 for my $module (@_) {
1030 eval { load $module };
1032 warn $err if 3 <= DEBUG;
1039 =func uri_escape_utf8
1041 $string = uri_escape_utf8($string);
1043 Percent-encode arbitrary text strings, like for a URI.
1047 my %ESC = map { chr($_) => sprintf('%%%02X', $_) } 0..255;
1048 sub uri_escape_utf8 {
1049 local $_ = shift // return;
1050 $_ = encode('UTF-8
', $_);
1051 # RFC 3986 section 2.3 unreserved characters
1052 s/([^A-Za-z0-9\-\._~])/$ESC{$1}/ge;
1056 =func uri_unescape_utf8
1058 $string = uri_unescape_utf8($string);
1060 Inverse of L</uri_escape_utf8>.
1064 sub uri_unescape_utf8 {
1065 local $_ = shift // return;
1066 s/\%([A-Fa-f0-9]{2})/chr(hex($1))/;
1067 return decode('UTF-8
', $_);
1072 $raw_uuid = uuid($string_uuid);
1074 Pack a 128-bit UUID (given as a hexidecimal string with optional C<->'s
, like
1075 C
<12345678-9ABC-DEFG-1234-56789ABCDEFG
>) into a string of exactly
16 octets
.
1077 This
is the inverse of L
</format_uuid
>.
1082 local $_ = shift // return "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
1084 /^[A-Fa-f0-9]{32}$/ or throw
'Must provide a formatted 128-bit UUID';
1085 return pack('H32', $_);
1091 Get the null UUID
(i
.e
. string of
16 null bytes
).
1095 sub UUID_NULL
() { "\0" x
16 }
1097 ### --------------------------------------------------------------------------
1099 # Determine if an array looks like keypairs from a hash.
1100 sub _looks_like_keypairs
{
1102 return 0 if @$arr % 2 == 1;
1103 for (my $i = 0; $i < @$arr; $i += 2) {
1104 return 0 if is_ref
($arr->[$i]);
1109 sub _is_operand_plain
{
1111 return !(is_hashref
($_) || is_arrayref
($_));
1116 my $subject = shift;
1117 my $op = shift // throw
'Must specify a query operator';
1118 my $operand = shift;
1120 return _query_simple
($op, $subject) if defined $subject && !is_ref
($op) && ($OPS{$subject} || 2) < 2;
1121 return _query_simple
($subject, $op, $operand) if _is_operand_plain
($operand);
1122 return _query_inverse
(_query
($subject, '-or', $operand)) if $op eq '-not' || $op eq '-false';
1123 return _query
($subject, '-and', [%$operand]) if is_hashref
($operand);
1127 my @atoms = @$operand;
1129 if (_looks_like_keypairs
(\
@atoms)) {
1130 my ($atom, $operand) = splice @atoms, 0, 2;
1131 if (my $op_type = $OPS{$atom}) {
1132 if ($op_type == 1 && _is_operand_plain
($operand)) { # unary
1133 push @queries, _query_simple
($operand, $atom);
1136 push @queries, _query
($subject, $atom, $operand);
1139 elsif (!is_ref
($atom)) {
1140 push @queries, _query
($atom, 'eq', $operand);
1144 my $atom = shift @atoms;
1145 if ($OPS{$atom}) { # apply new operator over the rest
1146 push @queries, _query
($subject, $atom, \
@atoms);
1149 else { # apply original operator over this one
1150 push @queries, _query
($subject, $op, $atom);
1155 if (@queries == 1) {
1158 elsif ($op eq '-and') {
1159 return _query_all
(@queries);
1161 elsif ($op eq '-or') {
1162 return _query_any
(@queries);
1164 throw
'Malformed query';
1168 my $subject = shift;
1169 my $op = shift // 'eq';
1170 my $operand = shift;
1172 # these special operators can also act as simple operators
1173 $op = '!!' if $op eq '-true';
1174 $op = '!' if $op eq '-false';
1175 $op = '!' if $op eq '-not';
1177 defined $subject or throw
'Subject is not set in query';
1178 $OPS{$op} >= 0 or throw
'Cannot use a non-simple operator in a simple query';
1179 if (empty
($operand)) {
1180 if ($OPS{$op} < 2) {
1183 # Allow field => undef and field => {'ne' => undef} to do the (arguably) right thing.
1184 elsif ($op eq 'eq' || $op eq '==') {
1187 elsif ($op eq 'ne' || $op eq '!=') {
1191 throw
'Operand is required';
1195 my $field = sub { blessed
$_[0] && $_[0]->can($subject) ? $_[0]->$subject : $_[0]->{$subject} };
1198 'eq' => sub { local $_ = $field->(@_); defined && $_ eq $operand },
1199 'ne' => sub { local $_ = $field->(@_); defined && $_ ne $operand },
1200 'lt' => sub { local $_ = $field->(@_); defined && $_ lt $operand },
1201 'gt' => sub { local $_ = $field->(@_); defined && $_ gt $operand },
1202 'le' => sub { local $_ = $field->(@_); defined && $_ le $operand },
1203 'ge' => sub { local $_ = $field->(@_); defined && $_ ge $operand },
1204 '==' => sub { local $_ = $field->(@_); defined && $_ == $operand },
1205 '!=' => sub { local $_ = $field->(@_); defined && $_ != $operand },
1206 '<' => sub { local $_ = $field->(@_); defined && $_ < $operand },
1207 '>' => sub { local $_ = $field->(@_); defined && $_ > $operand },
1208 '<=' => sub { local $_ = $field->(@_); defined && $_ <= $operand },
1209 '>=' => sub { local $_ = $field->(@_); defined && $_ >= $operand },
1210 '=~' => sub { local $_ = $field->(@_); defined && $_ =~ $operand },
1211 '!~' => sub { local $_ = $field->(@_); defined && $_ !~ $operand },
1212 '!' => sub { local $_ = $field->(@_); ! $_ },
1213 '!!' => sub { local $_ = $field->(@_); !!$_ },
1214 '-defined' => sub { local $_ = $field->(@_); defined $_ },
1215 '-undef' => sub { local $_ = $field->(@_); !defined $_ },
1216 '-nonempty' => sub { local $_ = $field->(@_); nonempty
$_ },
1217 '-empty' => sub { local $_ = $field->(@_); empty
$_ },
1220 return $map{$op} // throw
"Unexpected operator in query: $op",
1221 subject
=> $subject,
1223 operand
=> $operand;
1226 sub _query_inverse
{
1228 return sub { !$query->(@_) };
1235 all
{ $_->($val) } @queries;
1243 any
{ $_->($val) } @queries;