File-KDBX

 view release on metacpan or  search on metacpan

lib/File/KDBX/Loader/XML.pm  view on Meta::CPAN

    } : ref $args eq 'ARRAY' ? sub {
        my ($key, $val) = @_;
        push @$args, $val;
    } : sub {};

    my $pattern = XML::LibXML::Pattern->new("${path}|${path}/*");
    while ($reader->nextPatternMatch($pattern) == 1) {
        last if $reader->nodePath eq $path && $reader->nodeType == XML_READER_TYPE_END_ELEMENT;
        next if $reader->nodeType != XML_READER_TYPE_ELEMENT;

        my $name = $reader->localName;
        my $key  = snakify($name);
        my $type = $spec{$name};
        ($key, $type) = @$type if ref $type eq 'ARRAY';

        if (!defined $type) {
            exists $spec{$name} or alert "Ignoring unknown element: $name",
                node => $reader->nodePath,
                line => $reader->lineNumber;
            next;
        }

        if (ref $type eq 'CODE') {
            my @result = $self->$type($args, $reader->nodePath);
            if (@result == 2) {
                $store->(@result);
            }
            elsif (@result == 1) {
                $store->($key, @result);
            }
        }
        else {
            $store->($key, $self->_read_xml_content($type));
        }
    }

    return $args;
}

sub _read_xml_attribute {
    my $self = shift;
    my $name = shift;
    my $type = shift // 'text';
    my $default = shift;
    my $reader = $self->_reader;

    return $default if !$reader->hasAttributes;

    my $value = trim($reader->getAttribute($name));
    if (!defined $value) {
        # try again after reading in all the attributes
        $reader->moveToFirstAttribute;
        while ($self->_reader->readAttributeValue == 1) {}
        $reader->moveToElement;

        $value = trim($reader->getAttribute($name));
    }

    return $default if !defined $value;

    my $decoded = eval { _decode_primitive($value, $type) };
    if (my $err = $@) {
        ref $err and $err->details(attribute => $name, node => $reader->nodePath, line => $reader->lineNumber);
        throw $err
    }

    return $decoded;
}

sub _read_xml_content {
    my $self = shift;
    my $type = shift;
    my $reader = $self->_reader;

    $reader->read if !$reader->isEmptyElement;  # step into element
    return '' if !$reader->hasValue;

    my $content = trim($reader->value);

    my $decoded = eval { _decode_primitive($content, $type) };
    if (my $err = $@) {
        ref $err and $err->details(node => $reader->nodePath, line => $reader->lineNumber);
        throw $err;
    }

    return $decoded;
}

##############################################################################

sub _decode_primitive { goto &{__PACKAGE__."::_decode_$_[1]"} }

sub _decode_binary {
    local $_ = shift;
    return '' if !defined || (ref && !defined $$_);
    $_ = eval { decode_b64(ref $_ ? $$_ : $_) };
    my $err = $@;
    my $cleanup = erase_scoped $_;
    $err and throw 'Failed to parse binary', error => $err;
    return $_;
}

sub _decode_bool {
    local $_ = shift;
    return true  if /^True$/i;
    return false if /^False$/i;
    return false if length($_) == 0;
    throw 'Expected boolean', text => $_;
}

sub _decode_datetime {
    local $_ = shift;

    if (/^[A-Za-z0-9\+\/\=]+$/) {
        my $binary = eval { decode_b64($_) };
        if (my $err = $@) {
            throw 'Failed to parse binary datetime', text => $_, error => $err;
        }
        throw $@ if $@;
        $binary .= \0 x (8 - length($binary)) if length($binary) < 8;
        my ($seconds_since_ad1) = unpack_Ql($binary);
        my $epoch = $seconds_since_ad1 - TIME_SECONDS_AD1_TO_UNIX_EPOCH;
        return gmtime($epoch);
    }

    my $dt = eval { Time::Piece->strptime($_, '%Y-%m-%dT%H:%M:%SZ') };
    if (my $err = $@) {
        throw 'Failed to parse datetime', text => $_, error => $err;
    }
    return $dt;
}

sub _decode_tristate {
    local $_ = shift;
    return undef if /^null$/i;
    my $tristate = eval { _decode_bool($_) };
    $@ and throw 'Expected tristate', text => $_, error => $@;
    return $tristate;
}

sub _decode_number {
    local $_ = shift;
    $_ = _decode_text($_);
    looks_like_number($_) or throw 'Expected number', text => $_;
    return $_+0;
}



( run in 0.519 second using v1.01-cache-2.11-cpan-39bf76dae61 )