RPC-XML

 view release on metacpan or  search on metacpan

lib/RPC/XML/Parser/XMLParser.pm  view on Meta::CPAN

            $robj->[M_CDATA] = $fh;
            $robj->[M_SPOOLING_BASE64_DATA]= 1;
        }
    }
    else
    {
        push @{$robj->[M_STACK]},
            "Unknown tag encountered: $elem", PARSE_ERROR;
        $self->finish;
    }

    return;
}

# Very simple error-text generator, just to eliminate heavy reduncancy in the
# next sub:
sub error
{
    my ($robj, $self, $mesg, $elem) = @_;
    my $msg;

    if ($elem)
    {
        $msg = sprintf
            '%s at document line %d, column %d (byte %d, closing tag %s)',
            $mesg, $self->current_line, $self->current_column,
            $self->current_byte, $elem;
    }
    else
    {
        $msg = sprintf '%s at document line %d, column %d (byte %d)',
            $mesg, $self->current_line, $self->current_column,
            $self->current_byte;
    }

    push @{$robj->[M_STACK]}, $msg, PARSE_ERROR;
    $self->finish;

    return;
}

# A shorter-cut for stack integrity errors
sub stack_error
{
    my ($robj, $self, $elem) = @_;

    return error($robj, $self, 'Stack corruption detected', $elem);
}

# This is a hairy subroutine-- what to do at the end-tag. The actions range
# from simply new-ing a datatype all the way to building the final object.
sub tag_end ## no critic (ProhibitExcessComplexity)
{
    my ($robj, $self, $elem) = @_;

    my ($op, $newobj, $class, $list, $name);

    # This should always be one of the stack machine ops defined above
    $op = pop @{$robj->[M_STACK]};

    my $cdata = q{};
    if ($robj->[M_SPOOLING_BASE64_DATA])
    {
        $cdata = $robj->[M_CDATA];
        seek $cdata, 0, 0;
    }
    elsif ($robj->[M_CDATA])
    {
        $cdata = join q{} => @{$robj->[M_CDATA]};
    }

    # Decide what to do from here
    if (VALIDTYPES->{$elem}) ## no critic (ProhibitCascadingIfElse)
    {
        # This is the closing tag of one of the data-types.
        $class = $elem;
        # Cheaper than the regex that was here, and more locale-portable
        if ($class eq 'dateTime.iso8601')
        {
            $class = 'datetime_iso8601';
        }
        # Some minimal data-integrity checking
        if ($class eq 'int' or $class eq 'i4' or $class eq 'i8')
        {
            if ($cdata !~ /^[-+]?\d+$/)
            {
                return error($robj, $self, 'Bad integer data read');
            }
        }
        elsif ($class eq 'double')
        {
            if ($cdata !~
                # Taken from perldata(1)
                /^[+-]?(?=\d|[.]\d)\d*(?:[.]\d*)?(?:[Ee](?:[+-]?\d+))?$/x)
            {
                return error($robj, $self, 'Bad floating-point data read');
            }
        }
        elsif ($class eq 'nil')
        {
            # We now allow parsing of <nil/> at all times.
            # By definition though, it must be, well... nil.
            if ($cdata !~ /^\s*$/)
            {
                return error($robj, $self, '<nil /> element must be empty');
            }
        }

        $class = "RPC::XML::$class";
        # The string at the end is only seen by the RPC::XML::base64 class
        $newobj = $class->new($cdata, 'base64 is encoded, nil is allowed');
        push @{$robj->[M_STACK]}, $newobj, DATAOBJECT;
        if ($robj->[M_SPOOLING_BASE64_DATA])
        {
            $robj->[M_SPOOLING_BASE64_DATA] = 0;
            $robj->[M_CDATA] = undef; # Won't close FH, $newobj still holds it
        }
    }
    elsif ($elem eq 'value')
    {
        # For <value></value>, there should already be a dataobject, or else
        # the marker token in which case the CDATA is used as a string value.
        if ($op == DATAOBJECT)
        {
            ($op, $newobj) = splice @{$robj->[M_STACK]}, -2;
            if ($op != VALUEMARKER)
            {
                return stack_error($robj, $self, $elem);
            }
        }
        else
        {
            $newobj = RPC::XML::string->new($cdata);
        }

        push @{$robj->[M_STACK]}, $newobj, DATAOBJECT;
    }
    elsif ($elem eq 'param')
    {
        # Almost like above, since this is really a NOP anyway. But it also
        # puts PARAMENT on the stack, so that the closing tag of <params />
        # can check for bad content.
        if ($op != DATAOBJECT)
        {
            return error($robj, $self,
                         'No <value> found within <param> container');
        }
        ($op, $newobj) = splice @{$robj->[M_STACK]}, -2;
        if ($op != PARAM)
        {
            return error($robj, $self, "Illegal content in $elem tag");
        }
        push @{$robj->[M_STACK]}, $newobj, PARAMENT;
    }
    elsif ($elem eq 'params')
    {
        # At this point, there should be zero or more PARAMENT tokens on the
        # stack, each with an object right below it.
        $list = [];
        if ($op != PARAMENT && $op != PARAMSTART)
        {
            return error($robj, $self, "Illegal content in $elem tag");
        }
        while ($op == PARAMENT)
        {
            unshift @{$list}, pop @{$robj->[M_STACK]};
            $op = pop @{$robj->[M_STACK]};
        }
        # Now that we see something ! PARAMENT, it needs to be PARAMSTART
        if ($op != PARAMSTART)
        {
            return error($robj, $self, "Illegal content in $elem tag");
        }
        push @{$robj->[M_STACK]}, $list, PARAMLIST;
    }
    elsif ($elem eq 'fault')
    {
        # If we're finishing up a fault definition, there needs to be a struct
        # on the stack.
        if ($op != DATAOBJECT)
        {
            return stack_error($robj, $self, $elem);
        }
        ($op, $newobj) = splice @{$robj->[M_STACK]}, -2;
        if (! $newobj->isa('RPC::XML::struct'))
        {
            return error($robj, $self,
                         'Only a <struct> value may be within a <fault>');
        }
        $newobj = RPC::XML::fault->new($newobj);
        if (! $newobj)
        {
            return error($robj, $self, 'Unable to instantiate fault object: ' .
                         $RPC::XML::ERROR);
        }

        push @{$robj->[M_STACK]}, $newobj, FAULTENT;
    }
    elsif ($elem eq 'member')
    {
        # We need to see a DATAOBJECT followed by a STRUCTNAME
        if ($op != DATAOBJECT)
        {
            return error(
                $robj, $self, 'Element mismatch, expected to see value'
            );
        }
        ($op, $newobj) = splice @{$robj->[M_STACK]}, -2;
        if ($op != STRUCTNAME)
        {
            return error(
                $robj, $self, 'Element mismatch, expected to see name'
            );
        }
        # Get the name off the stack to clear the way for the STRUCTMEM marker
        # under it
        ($op, $name) = splice @{$robj->[M_STACK]}, -2;
        # Push the name back on, with the value and the new marker (STRUCTMEM)
        push @{$robj->[M_STACK]}, $name, $newobj, STRUCTMEM;
    }
    elsif ($elem eq 'name')
    {
        # Fairly simple: just push the current content of CDATA on w/ a marker
        push @{$robj->[M_STACK]}, $cdata, STRUCTNAME;
    }
    elsif ($elem eq 'struct')
    {
        # Create the hash table in-place, then pass the ref to the constructor
        $list = {};
        # First off the stack needs to be STRUCTMEM or STRUCT
        if (! ($op == STRUCTMEM or $op == STRUCT))
        {
            return error(
                $robj, $self, 'Element mismatch, expected to see member'
            );
        }
        while ($op == STRUCTMEM)
        {
            # Next on stack (in list-order): name, value
            ($name, $newobj) = splice @{$robj->[M_STACK]}, -2;
            $list->{$name} = $newobj;
            $op = pop @{$robj->[M_STACK]};
        }
        # Now that we see something ! STRUCTMEM, it needs to be STRUCT
        if ($op != STRUCT)
        {
            return error($robj, $self, 'Bad content inside struct block');
        }
        $newobj = RPC::XML::struct->new($list);

        push @{$robj->[M_STACK]}, $newobj, DATAOBJECT;
    }
    elsif ($elem eq 'data')
    {
        # The <data></data> block within an <array></array> declaration serves
        # to gather together all the <value /> elements that will make up the
        # resulting list.
        #
        # Go down the stack, gathering DATAOBJECT markers until we see the
        # DATASTART marker.
        $list = [];
        # Only DATAOBJECT and DATASTART should be visible
        if ($op != DATASTART && $op != DATAOBJECT)
        {
            return error($robj, $self, 'Bad content inside data block');
        }
        while ($op == DATAOBJECT)
        {
            unshift @{$list}, pop @{$robj->[M_STACK]};
            $op = pop @{$robj->[M_STACK]};
        }

        # Now that we see something ! DATAOBJECT, it needs to be DATASTART
        if ($op != DATASTART)
        {
            return error($robj, $self, "Illegal content in $elem tag");
        }

        # We might as well instantiate the RPC::XML::array object here, and
        # put it on the stack with a DATAOBJECT marker. Then the end-tag of
        # the <array /> can just look to make sure there is exactly one
        # DATAOBJECT/value pair between it and the start of the array.
        $newobj = RPC::XML::array->new(from => $list);

        push @{$robj->[M_STACK]}, $newobj, DATAOBJECT;
    }
    elsif ($elem eq 'array')
    {
        # Now that we process the <data /> block directly (I used to just
        # ignore it), handling the closing tag of <array /> is just a matter
        # of making sure $op is DATAOBJECT and that we have an array object
        # on the stack with an ARRAY marker just below it.

        # Only DATAOBJECT or ARRAY should be visible
        if ($op == DATAOBJECT)
        {
            ($op, $newobj) = splice @{$robj->[M_STACK]}, -2;
        }

        # Now only ARRAY should be
        if ($op != ARRAY)
        {
            return error($robj, $self, "Illegal content in $elem tag");
        }

        # Technically, this is a little redundant, since we had these two right
        # here on the stack when we started. But at this point we've validated
        # the form of the <array /> block and removed the ARRAY marker from the
        # stack.
        push @{$robj->[M_STACK]}, $newobj, DATAOBJECT;
    }
    elsif ($elem eq 'methodName')
    {
        if ($robj->[M_STACK]->[$#{$robj->[M_STACK]}] != METHOD)
        {
            return error(
                $robj, $self,
                "$elem tag must immediately follow a methodCall tag"
            );
        }
        push @{$robj->[M_STACK]}, $cdata, NAMEVAL;
    }
    elsif ($elem eq 'methodCall')
    {
        # A methodCall closing should have on the stack an optional PARAMLIST
        # marker, a NAMEVAL marker, then the METHOD token from the
        # opening tag.
        if ($op == PARAMLIST)
        {
            ($op, $list) = splice @{$robj->[M_STACK]}, -2;
        }
        else
        {
            $list = [];
        }
        if ($op == NAMEVAL)
        {
            ($op, $name) = splice @{$robj->[M_STACK]}, -2;
        }
        elsif ($op != METHOD)
        {
            return error(
                $robj, $self,
                'Extra content in "methodCall" block detected'
            );
        }
        if (! $name)
        {
            return error(
                $robj, $self,
                'No methodName tag detected during methodCall parsing'
            );
        }

        # Create the request object and push it on the stack
        $newobj = RPC::XML::request->new($name, @{$list});
        if (! $newobj)
        {
            return error($robj, $self,
                         "Error creating request object: $RPC::XML::ERROR");
        }

        push @{$robj->[M_STACK]}, $newobj, METHODENT;
    }
    elsif ($elem eq 'methodResponse')
    {
        # A methodResponse closing should have on the stack only the
        # DATAOBJECT marker, then the RESPONSE token from the opening tag.
        if ($op == PARAMLIST)
        {
            # To my knowledge, the XML-RPC spec limits the params list for
            # a response to exactly one object. Extract it from the listref
            # and put it back.
            $list = pop @{$robj->[M_STACK]};
            if (@{$list} > 1)
            {
                return error(
                    $robj, $self,
                    "Params list for $elem tag invalid: too many params"
                );
            }



( run in 1.475 second using v1.01-cache-2.11-cpan-5b529ec07f3 )