示例#1
0
/**
 * Given a user-defined PHP function, create a PHP 'wrapper' function that can
 * be exposed as xmlrpc method from an xmlrpc_server object and called from remote
 * clients.
 *
 * Since php is a typeless language, to infer types of input and output parameters,
 * it relies on parsing the javadoc-style comment block associated with the given
 * function. Usage of xmlrpc native types (such as datetime.dateTime.iso8601 and base64)
 * in the @param tag is also allowed, if you need the php function to receive/send
 * data in that particular format (note that base64 enncoding/decoding is transparently
 * carried out by the lib, while datetime vals are passed around as strings)
 *
 * Known limitations:
 * - requires PHP 5.0.3 +
 * - only works for user-defined functions, not for PHP internal functions
 *   (reflection does not support retrieving number/type of params for those)
 * - functions returning php objects will generate special xmlrpc responses:
 *   when the xmlrpc decoding of those responses is carried out by this same lib, using
 *   the appropriate param in php_xmlrpc_decode, the php objects will be rebuilt.
 *   In short: php objects can be serialized, too (except for their resource members),
 *   using this function.
 *   Other libs might choke on the very same xml that will be generated in this case
 *   (i.e. it has a nonstandard attribute on struct element tags)
 * - usage of javadoc @param tags using param names in a different order from the
 *   function prototype is not considered valid (to be fixed?)
 *
 * @param string $funcname the name of the PHP user function to be exposed as xmlrpc method; array($obj, 'methodname') might be ok too, in the future...
 * @return false on error, or an array containing the name of the new php function,
 *         its signature and docs, to be used in the server dispatch map
 *
 * @todo decide how to deal with params passed by ref: bomb out or allow?
 * @todo finish using javadoc info to build method sig if all params are named but out of order
 * @done switch to some automagic object encoding scheme
 * @todo add a check for params of 'resource' type
 * @todo add some trigger_errors when returning false?
 * @todo what to do when the PHP function returns NULL? we are currently returning bogus responses!!!
 */
function wrap_php_function($funcname, $newfuncname = '')
{
    if (version_compare(phpversion(), '5.0.3') == -1) {
        // up to php 5.0.3 some useful reflection methods were missing
        return false;
    }
    if (is_array($funcname) && !method_exists($funcname[0], $funcname[1]) || !function_exists($funcname)) {
        return false;
    } else {
        // determine name of new php function
        if ($newfuncname == '') {
            if (is_array($funcname)) {
                $xmlrpcfuncname = "xmlrpc_" . implode('_', $funcname);
            } else {
                $xmlrpcfuncname = "xmlrpc_{$funcname}";
            }
        } else {
            $xmlrpcfuncname = $newfuncname;
        }
        while (function_exists($xmlrpcfuncname)) {
            $xmlrpcfuncname .= 'x';
        }
        $code = "function {$xmlrpcfuncname}(\$msg) {\n";
        // start to introspect PHP code
        $func =& new ReflectionFunction($funcname);
        if ($func->isInternal()) {
            // Note: from PHP 5.1.0 onward, we will possibly be able to use invokeargs
            // instead of getparameters to fully reflect internal php functions ?
            return false;
        }
        // retrieve parameter names, types and description from javadoc comments
        // function description
        $desc = '';
        // type of return val: by default 'any'
        $returns = $GLOBALS['xmlrpcValue'];
        // type + name of function parameters
        $paramDocs = array();
        $docs = $func->getDocComment();
        if ($docs != '') {
            $docs = explode("\n", $docs);
            $i = 0;
            foreach ($docs as $doc) {
                $doc = trim($doc, " \r\t/*");
                if (strlen($doc) && strpos($doc, '@') !== 0 && !$i) {
                    if ($desc) {
                        $desc .= "\n";
                    }
                    $desc .= $doc;
                } elseif (strpos($doc, '@param') === 0) {
                    // syntax: @param type [$name] desc
                    if (preg_match('/@param\\s+(\\S+)(\\s+\\$\\S+)?\\s+(.+)/', $doc, $matches)) {
                        if (strpos($matches[1], '|')) {
                            //$paramDocs[$i]['type'] = explode('|', $matches[1]);
                            $paramDocs[$i]['type'] = 'mixed';
                        } else {
                            $paramDocs[$i]['type'] = $matches[1];
                        }
                        $paramDocs[$i]['name'] = trim($matches[2]);
                        $paramDocs[$i]['doc'] = $matches[3];
                    }
                    $i++;
                } elseif (strpos($doc, '@return') === 0) {
                    $returns = preg_split("/\\s+/", $doc);
                    if (isset($returns[1])) {
                        $returns = php_2_xmlrpc_type($returns[1]);
                    }
                }
            }
        }
        // start introspection of actual function prototype and building of PHP code
        // to be eval'd
        $params = $func->getParameters();
        $innercode = '';
        $i = 0;
        $parsvariations = array();
        $pars = array();
        $pnum = count($params);
        foreach ($params as $param) {
            if (isset($paramDocs[$i]['name']) && $paramDocs[$i]['name'] && strtolower($paramDocs[$i]['name']) != '$' . strtolower($param->getName())) {
                // param name from phpdoc info does not match param definition!
                $paramDocs[$i]['type'] = 'mixed';
            }
            if ($param->isOptional()) {
                // this particular parameter is optional. save as valid previous list of parameters
                $innercode .= "if (\$paramcount > {$i}) {\n";
                $parsvariations[] = $pars;
            }
            $innercode .= "\$p{$i} = \$msg->getParam({$i});\n";
            $innercode .= "if (\$p{$i}->kindOf() == 'scalar') \$p{$i} = \$p{$i}->scalarval(); else \$p{$i} = php_xmlrpc_decode(\$p{$i});\n";
            $pars[] = "\$p{$i}";
            $i++;
            if ($param->isOptional()) {
                $innercode .= "}\n";
            }
            if ($i == $pnum) {
                // last allowed parameters combination
                $parsvariations[] = $pars;
            }
        }
        $sigs = array();
        if (count($parsvariations) == 0) {
            // only known good synopsis = no parameters
            $parsvariations[] = array();
            $minpars = 0;
        } else {
            $minpars = count($parsvariations[0]);
        }
        if ($minpars) {
            // add to code the check for min params number
            // NB: this check needs to be done BEFORE decoding param values
            $innercode = "\$paramcount = \$msg->getNumParams();\n" . "if (\$paramcount < {$minpars}) return new xmlrpcresp(0, {$GLOBALS['xmlrpcerr']['incorrect_params']}, '{$GLOBALS['xmlrpcstr']['incorrect_params']}');\n" . $innercode;
        } else {
            $innercode = "\$paramcount = \$msg->getNumParams();\n" . $innercode;
        }
        $innercode .= "\$np = false;";
        foreach ($parsvariations as $pars) {
            $innercode .= "if (\$paramcount == " . count($pars) . ") \$retval = {$funcname}(" . implode(',', $pars) . "); else\n";
            // build a 'generic' signature (only use an appropriate return type)
            $sig = array($returns);
            for ($i = 0; $i < count($pars); $i++) {
                if (isset($paramDocs[$i]['type'])) {
                    $sig[] = php_2_xmlrpc_type($paramDocs[$i]['type']);
                } else {
                    $sig[] = $GLOBALS['xmlrpcValue'];
                }
            }
            $sigs[] = $sig;
        }
        $innercode .= "\$np = true;\n";
        $innercode .= "if (\$np) return new xmlrpcresp(0, {$GLOBALS['xmlrpcerr']['incorrect_params']}, '{$GLOBALS['xmlrpcstr']['incorrect_params']}'); else\n";
        //$innercode .= "if (\$_xmlrpcs_error_occurred) return new xmlrpcresp(0, $GLOBALS['xmlrpcerr']user, \$_xmlrpcs_error_occurred); else\n";
        if ($returns == $GLOBALS['xmlrpcDateTime'] || $returns == $GLOBALS['xmlrpcBase64']) {
            $innercode .= "return new xmlrpcresp(new xmlrpcval(\$retval, '{$returns}'));";
        } else {
            $innercode .= "return new xmlrpcresp(php_xmlrpc_encode(\$retval, array('encode_php_objs')));";
        }
        // shall we exclude functions returning by ref?
        // if($func->returnsReference())
        //  return false;
        $code = $code . $innercode . "\n}\n \$allOK=1;";
        //print_r($code);
        $allOK = 0;
        eval($code);
        // alternative
        //$xmlrpcfuncname = create_function('$m', $innercode);
        if (!$allOK) {
            return false;
        }
        /// @todo examine if $paramDocs matches $parsvariations and build array for
        /// usage as method signature, plus put together a nice string for docs
        $ret = array('function' => $xmlrpcfuncname, 'signature' => $sigs, 'docstring' => $desc);
        return $ret;
    }
}
示例#2
0
文件: xmlrpcs.php 项目: Tommar/vino2
function _xmlrpcs_multicall_do_call_phpvals($server, $call)
{
    if (!is_array($call)) {
        return _xmlrpcs_multicall_error('notstruct');
    }
    if (!array_key_exists('methodName', $call)) {
        return _xmlrpcs_multicall_error('nomethod');
    }
    if (!is_string($call['methodName'])) {
        return _xmlrpcs_multicall_error('notstring');
    }
    if ($call['methodName'] == 'system.multicall') {
        return _xmlrpcs_multicall_error('recursion');
    }
    if (!array_key_exists('params', $call)) {
        return _xmlrpcs_multicall_error('noparams');
    }
    if (!is_array($call['params'])) {
        return _xmlrpcs_multicall_error('notarray');
    }
    // this is a real dirty and simplistic hack, since we might have received a
    // base64 or datetime values, but they will be listed as strings here...
    $numParams = count($call['params']);
    $pt = array();
    foreach ($call['params'] as $val) {
        $pt[] = php_2_xmlrpc_type(gettype($val));
    }
    $result = $server->execute($call['methodName'], $call['params'], $pt);
    if ($result->faultCode() != 0) {
        return _xmlrpcs_multicall_error($result);
        // Method returned fault.
    }
    return new xmlrpcval(array($result->value()), 'array');
}
/**
 * Given a user-defined PHP function, create a PHP 'wrapper' function that can
 * be exposed as xmlrpc method from an xmlrpc_server object and called from remote
 * clients (as well as its corresponding signature info).
 *
 * Since php is a typeless language, to infer types of input and output parameters,
 * it relies on parsing the javadoc-style comment block associated with the given
 * function. Usage of xmlrpc native types (such as datetime.dateTime.iso8601 and base64)
 * in the @param tag is also allowed, if you need the php function to receive/send
 * data in that particular format (note that base64 encoding/decoding is transparently
 * carried out by the lib, while datetime vals are passed around as strings)
 *
 * Known limitations:
 * - requires PHP 5.0.3 +
 * - only works for user-defined functions, not for PHP internal functions
 *   (reflection does not support retrieving number/type of params for those)
 * - functions returning php objects will generate special xmlrpc responses:
 *   when the xmlrpc decoding of those responses is carried out by this same lib, using
 *   the appropriate param in php_xmlrpc_decode, the php objects will be rebuilt.
 *   In short: php objects can be serialized, too (except for their resource members),
 *   using this function.
 *   Other libs might choke on the very same xml that will be generated in this case
 *   (i.e. it has a nonstandard attribute on struct element tags)
 * - usage of javadoc @param tags using param names in a different order from the
 *   function prototype is not considered valid (to be fixed?)
 *
 * Note that since rel. 2.0RC3 the preferred method to have the server call 'standard'
 * php functions (ie. functions not expecting a single xmlrpcmsg obj as parameter)
 * is by making use of the functions_parameters_type class member.
 *
 * @param string $funcname the name of the PHP user function to be exposed as xmlrpc method; array($obj, 'methodname') and array('class', 'methodname') are ok too
 * @param string $newfuncname (optional) name for function to be created
 * @param array $extra_options (optional) array of options for conversion. valid values include:
 *        bool  return_source when true, php code w. function definition will be returned, not evaluated
 *        bool  encode_php_objs let php objects be sent to server using the 'improved' xmlrpc notation, so server can deserialize them as php objects
 *        bool  decode_php_objs --- WARNING !!! possible security hazard. only use it with trusted servers ---
 *        bool  suppress_warnings  remove from produced xml any runtime warnings due to the php function being invoked
 * @return false on error, or an array containing the name of the new php function,
 *         its signature and docs, to be used in the server dispatch map
 *
 * @todo decide how to deal with params passed by ref: bomb out or allow?
 * @todo finish using javadoc info to build method sig if all params are named but out of order
 * @todo add a check for params of 'resource' type
 * @todo add some trigger_errors / error_log when returning false?
 * @todo what to do when the PHP function returns NULL? we are currently returning an empty string value...
 * @todo add an option to suppress php warnings in invocation of user function, similar to server debug level 3?
 * @todo if $newfuncname is empty, we could use create_user_func instead of eval, as it is possibly faster
 * @todo add a verbatim_object_copy parameter to allow avoiding the same obj instance?
 */
function wrap_php_function($funcname, $newfuncname = '', $extra_options = array())
{
    $buildit = isset($extra_options['return_source']) ? !$extra_options['return_source'] : true;
    $prefix = isset($extra_options['prefix']) ? $extra_options['prefix'] : 'xmlrpc';
    $encode_php_objects = isset($extra_options['encode_php_objs']) ? (bool) $extra_options['encode_php_objs'] : false;
    $decode_php_objects = isset($extra_options['decode_php_objs']) ? (bool) $extra_options['decode_php_objs'] : false;
    $catch_warnings = isset($extra_options['suppress_warnings']) && $extra_options['suppress_warnings'] ? '@' : '';
    if (version_compare(phpversion(), '5.0.3') == -1) {
        // up to php 5.0.3 some useful reflection methods were missing
        error_log('XML-RPC: cannot not wrap php functions unless running php version bigger than 5.0.3');
        return false;
    }
    $exists = false;
    if (is_string($funcname) && strpos($funcname, '::') !== false) {
        $funcname = explode('::', $funcname);
    }
    if (is_array($funcname)) {
        if (count($funcname) < 2 || !is_string($funcname[0]) && !is_object($funcname[0])) {
            error_log('XML-RPC: syntax for function to be wrapped is wrong');
            return false;
        }
        if (is_string($funcname[0])) {
            $plainfuncname = implode('::', $funcname);
        } elseif (is_object($funcname[0])) {
            $plainfuncname = get_class($funcname[0]) . '->' . $funcname[1];
        }
        $exists = method_exists($funcname[0], $funcname[1]);
        if (!$exists && version_compare(phpversion(), '5.1') < 0) {
            // workaround for php 5.0: static class methods are not seen by method_exists
            $exists = is_callable($funcname);
        }
    } else {
        $plainfuncname = $funcname;
        $exists = function_exists($funcname);
    }
    if (!$exists) {
        error_log('XML-RPC: function to be wrapped is not defined: ' . $plainfuncname);
        return false;
    } else {
        // determine name of new php function
        if ($newfuncname == '') {
            if (is_array($funcname)) {
                if (is_string($funcname[0])) {
                    $xmlrpcfuncname = "{$prefix}_" . implode('_', $funcname);
                } else {
                    $xmlrpcfuncname = "{$prefix}_" . get_class($funcname[0]) . '_' . $funcname[1];
                }
            } else {
                $xmlrpcfuncname = "{$prefix}_{$funcname}";
            }
        } else {
            $xmlrpcfuncname = $newfuncname;
        }
        while ($buildit && function_exists($xmlrpcfuncname)) {
            $xmlrpcfuncname .= 'x';
        }
        // start to introspect PHP code
        if (is_array($funcname)) {
            $func = new ReflectionMethod($funcname[0], $funcname[1]);
            if ($func->isPrivate()) {
                error_log('XML-RPC: method to be wrapped is private: ' . $plainfuncname);
                return false;
            }
            if ($func->isProtected()) {
                error_log('XML-RPC: method to be wrapped is protected: ' . $plainfuncname);
                return false;
            }
            if ($func->isConstructor()) {
                error_log('XML-RPC: method to be wrapped is the constructor: ' . $plainfuncname);
                return false;
            }
            // php 503 always says isdestructor = true...
            if (version_compare(phpversion(), '5.0.3') != 0 && $func->isDestructor()) {
                error_log('XML-RPC: method to be wrapped is the destructor: ' . $plainfuncname);
                return false;
            }
            if ($func->isAbstract()) {
                error_log('XML-RPC: method to be wrapped is abstract: ' . $plainfuncname);
                return false;
            }
            /// @todo add more checks for static vs. nonstatic?
        } else {
            $func = new ReflectionFunction($funcname);
        }
        if ($func->isInternal()) {
            // Note: from PHP 5.1.0 onward, we will possibly be able to use invokeargs
            // instead of getparameters to fully reflect internal php functions ?
            error_log('XML-RPC: function to be wrapped is internal: ' . $plainfuncname);
            return false;
        }
        // retrieve parameter names, types and description from javadoc comments
        // function description
        $desc = '';
        // type of return val: by default 'any'
        $returns = $GLOBALS['xmlrpcValue'];
        // desc of return val
        $returnsDocs = '';
        // type + name of function parameters
        $paramDocs = array();
        $docs = $func->getDocComment();
        if ($docs != '') {
            $docs = explode("\n", $docs);
            $i = 0;
            foreach ($docs as $doc) {
                $doc = trim($doc, " \r\t/*");
                if (strlen($doc) && strpos($doc, '@') !== 0 && !$i) {
                    if ($desc) {
                        $desc .= "\n";
                    }
                    $desc .= $doc;
                } elseif (strpos($doc, '@param') === 0) {
                    // syntax: @param type [$name] desc
                    if (preg_match('/@param\\s+(\\S+)(\\s+\\$\\S+)?\\s+(.+)/', $doc, $matches)) {
                        if (strpos($matches[1], '|')) {
                            //$paramDocs[$i]['type'] = explode('|', $matches[1]);
                            $paramDocs[$i]['type'] = 'mixed';
                        } else {
                            $paramDocs[$i]['type'] = $matches[1];
                        }
                        $paramDocs[$i]['name'] = trim($matches[2]);
                        $paramDocs[$i]['doc'] = $matches[3];
                    }
                    $i++;
                } elseif (strpos($doc, '@return') === 0) {
                    // syntax: @return type desc
                    //$returns = preg_split('/\s+/', $doc);
                    if (preg_match('/@return\\s+(\\S+)\\s+(.+)/', $doc, $matches)) {
                        $returns = php_2_xmlrpc_type($matches[1]);
                        if (isset($matches[2])) {
                            $returnsDocs = $matches[2];
                        }
                    }
                }
            }
        }
        // execute introspection of actual function prototype
        $params = array();
        $i = 0;
        foreach ($func->getParameters() as $paramobj) {
            $params[$i] = array();
            $params[$i]['name'] = '$' . $paramobj->getName();
            $params[$i]['isoptional'] = $paramobj->isOptional();
            $i++;
        }
        // start  building of PHP code to be eval'd
        $innercode = '';
        $i = 0;
        $parsvariations = array();
        $pars = array();
        $pnum = count($params);
        foreach ($params as $param) {
            if (isset($paramDocs[$i]['name']) && $paramDocs[$i]['name'] && strtolower($paramDocs[$i]['name']) != strtolower($param['name'])) {
                // param name from phpdoc info does not match param definition!
                $paramDocs[$i]['type'] = 'mixed';
            }
            if ($param['isoptional']) {
                // this particular parameter is optional. save as valid previous list of parameters
                $innercode .= "if (\$paramcount > {$i}) {\n";
                $parsvariations[] = $pars;
            }
            $innercode .= "\$p{$i} = \$msg->getParam({$i});\n";
            if ($decode_php_objects) {
                $innercode .= "if (\$p{$i}->kindOf() == 'scalar') \$p{$i} = \$p{$i}->scalarval(); else \$p{$i} = php_{$prefix}_decode(\$p{$i}, array('decode_php_objs'));\n";
            } else {
                $innercode .= "if (\$p{$i}->kindOf() == 'scalar') \$p{$i} = \$p{$i}->scalarval(); else \$p{$i} = php_{$prefix}_decode(\$p{$i});\n";
            }
            $pars[] = "\$p{$i}";
            $i++;
            if ($param['isoptional']) {
                $innercode .= "}\n";
            }
            if ($i == $pnum) {
                // last allowed parameters combination
                $parsvariations[] = $pars;
            }
        }
        $sigs = array();
        $psigs = array();
        if (count($parsvariations) == 0) {
            // only known good synopsis = no parameters
            $parsvariations[] = array();
            $minpars = 0;
        } else {
            $minpars = count($parsvariations[0]);
        }
        if ($minpars) {
            // add to code the check for min params number
            // NB: this check needs to be done BEFORE decoding param values
            $innercode = "\$paramcount = \$msg->getNumParams();\n" . "if (\$paramcount < {$minpars}) return new {$prefix}resp(0, {$GLOBALS['xmlrpcerr']['incorrect_params']}, '{$GLOBALS['xmlrpcstr']['incorrect_params']}');\n" . $innercode;
        } else {
            $innercode = "\$paramcount = \$msg->getNumParams();\n" . $innercode;
        }
        $innercode .= "\$np = false;\n";
        // since there are no closures in php, if we are given an object instance,
        // we store a pointer to it in a global var...
        if (is_array($funcname) && is_object($funcname[0])) {
            $GLOBALS['xmlrpcWPFObjHolder'][$xmlrpcfuncname] =& $funcname[0];
            $innercode .= "\$obj =& \$GLOBALS['xmlrpcWPFObjHolder']['{$xmlrpcfuncname}'];\n";
            $realfuncname = '$obj->' . $funcname[1];
        } else {
            $realfuncname = $plainfuncname;
        }
        foreach ($parsvariations as $pars) {
            $innercode .= "if (\$paramcount == " . count($pars) . ") \$retval = {$catch_warnings}{$realfuncname}(" . implode(',', $pars) . "); else\n";
            // build a 'generic' signature (only use an appropriate return type)
            $sig = array($returns);
            $psig = array($returnsDocs);
            for ($i = 0; $i < count($pars); $i++) {
                if (isset($paramDocs[$i]['type'])) {
                    $sig[] = php_2_xmlrpc_type($paramDocs[$i]['type']);
                } else {
                    $sig[] = $GLOBALS['xmlrpcValue'];
                }
                $psig[] = isset($paramDocs[$i]['doc']) ? $paramDocs[$i]['doc'] : '';
            }
            $sigs[] = $sig;
            $psigs[] = $psig;
        }
        $innercode .= "\$np = true;\n";
        $innercode .= "if (\$np) return new {$prefix}resp(0, {$GLOBALS['xmlrpcerr']['incorrect_params']}, '{$GLOBALS['xmlrpcstr']['incorrect_params']}'); else {\n";
        //$innercode .= "if (\$_xmlrpcs_error_occurred) return new xmlrpcresp(0, $GLOBALS['xmlrpcerr']user, \$_xmlrpcs_error_occurred); else\n";
        $innercode .= "if (is_a(\$retval, '{$prefix}resp')) return \$retval; else\n";
        if ($returns == $GLOBALS['xmlrpcDateTime'] || $returns == $GLOBALS['xmlrpcBase64']) {
            $innercode .= "return new {$prefix}resp(new {$prefix}val(\$retval, '{$returns}'));";
        } else {
            if ($encode_php_objects) {
                $innercode .= "return new {$prefix}resp(php_{$prefix}_encode(\$retval, array('encode_php_objs')));\n";
            } else {
                $innercode .= "return new {$prefix}resp(php_{$prefix}_encode(\$retval));\n";
            }
        }
        // shall we exclude functions returning by ref?
        // if($func->returnsReference())
        // 	return false;
        $code = "function {$xmlrpcfuncname}(\$msg) {\n" . $innercode . "}\n}";
        //print_r($code);
        if ($buildit) {
            $allOK = 0;
            eval($code . '$allOK=1;');
            // alternative
            //$xmlrpcfuncname = create_function('$m', $innercode);
            if (!$allOK) {
                error_log('XML-RPC: could not create function ' . $xmlrpcfuncname . ' to wrap php function ' . $plainfuncname);
                return false;
            }
        }
        /// @todo examine if $paramDocs matches $parsvariations and build array for
        /// usage as method signature, plus put together a nice string for docs
        $ret = array('function' => $xmlrpcfuncname, 'signature' => $sigs, 'docstring' => $desc, 'signature_docs' => $psigs, 'source' => $code);
        return $ret;
    }
}