/** * Convert the xml representation of a method response, method request or single * xmlrpc value into the appropriate object (a.k.a. deserialize). * * @param string $xmlVal * @param array $options * * @return mixed false on error, or an instance of either Value, Request or Response */ public function decodeXml($xmlVal, $options = array()) { // 'guestimate' encoding $valEncoding = XMLParser::guessEncoding('', $xmlVal); if ($valEncoding != '') { // Since parsing will fail if charset is not specified in the xml prologue, // the encoding is not UTF8 and there are non-ascii chars in the text, we try to work round that... // The following code might be better for mb_string enabled installs, but // makes the lib about 200% slower... //if (!is_valid_charset($valEncoding, array('UTF-8')) if (!in_array($valEncoding, array('UTF-8', 'US-ASCII')) && !XMLParser::hasEncoding($xmlVal)) { if ($valEncoding == 'ISO-8859-1') { $xmlVal = utf8_encode($xmlVal); } else { if (extension_loaded('mbstring')) { $xmlVal = mb_convert_encoding($xmlVal, 'UTF-8', $valEncoding); } else { error_log('XML-RPC: ' . __METHOD__ . ': invalid charset encoding of xml text: ' . $valEncoding); } } } } $parser = xml_parser_create(); xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true); // What if internal encoding is not in one of the 3 allowed? // we use the broadest one, ie. utf8! if (!in_array(PhpXmlRpc::$xmlrpc_internalencoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) { xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8'); } else { xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, PhpXmlRpc::$xmlrpc_internalencoding); } $xmlRpcParser = new XMLParser(); xml_set_object($parser, $xmlRpcParser); xml_set_element_handler($parser, 'xmlrpc_se_any', 'xmlrpc_ee'); xml_set_character_data_handler($parser, 'xmlrpc_cd'); xml_set_default_handler($parser, 'xmlrpc_dh'); if (!xml_parse($parser, $xmlVal, 1)) { $errstr = sprintf('XML error: %s at line %d, column %d', xml_error_string(xml_get_error_code($parser)), xml_get_current_line_number($parser), xml_get_current_column_number($parser)); error_log($errstr); xml_parser_free($parser); return false; } xml_parser_free($parser); if ($xmlRpcParser->_xh['isf'] > 1) { // test that $xmlrpc->_xh['value'] is an obj, too??? error_log($xmlRpcParser->_xh['isf_reason']); return false; } switch ($xmlRpcParser->_xh['rt']) { case 'methodresponse': $v =& $xmlRpcParser->_xh['value']; if ($xmlRpcParser->_xh['isf'] == 1) { $vc = $v['faultCode']; $vs = $v['faultString']; $r = new Response(0, $vc->scalarval(), $vs->scalarval()); } else { $r = new Response($v); } return $r; case 'methodcall': $req = new Request($xmlRpcParser->_xh['method']); for ($i = 0; $i < count($xmlRpcParser->_xh['params']); $i++) { $req->addParam($xmlRpcParser->_xh['params'][$i]); } return $req; case 'value': return $xmlRpcParser->_xh['value']; default: return false; } }
/** * Parse an xml chunk containing an xmlrpc request and execute the corresponding * php function registered with the server. * * @param string $data the xml request * @param string $reqEncoding (optional) the charset encoding of the xml request * * @return Response * * @throws \Exception in case the executed method does throw an exception (and depending on server configuration) */ public function parseRequest($data, $reqEncoding = '') { // decompose incoming XML into request structure if ($reqEncoding != '') { // Since parsing will fail if charset is not specified in the xml prologue, // the encoding is not UTF8 and there are non-ascii chars in the text, we try to work round that... // The following code might be better for mb_string enabled installs, but // makes the lib about 200% slower... //if (!is_valid_charset($reqEncoding, array('UTF-8'))) if (!in_array($reqEncoding, array('UTF-8', 'US-ASCII')) && !XMLParser::hasEncoding($data)) { if ($reqEncoding == 'ISO-8859-1') { $data = utf8_encode($data); } else { if (extension_loaded('mbstring')) { $data = mb_convert_encoding($data, 'UTF-8', $reqEncoding); } else { error_log('XML-RPC: ' . __METHOD__ . ': invalid charset encoding of received request: ' . $reqEncoding); } } } } $parser = xml_parser_create(); xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true); // G. Giunta 2005/02/13: PHP internally uses ISO-8859-1, so we have to tell // the xml parser to give us back data in the expected charset // What if internal encoding is not in one of the 3 allowed? // we use the broadest one, ie. utf8 // This allows to send data which is native in various charset, // by extending xmlrpc_encode_entities() and setting xmlrpc_internalencoding if (!in_array(PhpXmlRpc::$xmlrpc_internalencoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) { xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8'); } else { xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, PhpXmlRpc::$xmlrpc_internalencoding); } $xmlRpcParser = new XMLParser(); xml_set_object($parser, $xmlRpcParser); if ($this->functions_parameters_type != 'xmlrpcvals') { xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast'); } else { xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee'); } xml_set_character_data_handler($parser, 'xmlrpc_cd'); xml_set_default_handler($parser, 'xmlrpc_dh'); if (!xml_parse($parser, $data, 1)) { // return XML error as a faultCode $r = new Response(0, PhpXmlRpc::$xmlrpcerrxml + xml_get_error_code($parser), sprintf('XML error: %s at line %d, column %d', xml_error_string(xml_get_error_code($parser)), xml_get_current_line_number($parser), xml_get_current_column_number($parser))); xml_parser_free($parser); } elseif ($xmlRpcParser->_xh['isf']) { xml_parser_free($parser); $r = new Response(0, PhpXmlRpc::$xmlrpcerr['invalid_request'], PhpXmlRpc::$xmlrpcstr['invalid_request'] . ' ' . $xmlRpcParser->_xh['isf_reason']); } else { xml_parser_free($parser); // small layering violation in favor of speed and memory usage: // we should allow the 'execute' method handle this, but in the // most common scenario (xmlrpc values type server with some methods // registered as phpvals) that would mean a useless encode+decode pass if ($this->functions_parameters_type != 'xmlrpcvals' || isset($this->dmap[$xmlRpcParser->_xh['method']]['parameters_type']) && $this->dmap[$xmlRpcParser->_xh['method']]['parameters_type'] == 'phpvals') { if ($this->debug > 1) { $this->debugmsg("\n+++PARSED+++\n" . var_export($xmlRpcParser->_xh['params'], true) . "\n+++END+++"); } $r = $this->execute($xmlRpcParser->_xh['method'], $xmlRpcParser->_xh['params'], $xmlRpcParser->_xh['pt']); } else { // build a Request object with data parsed from xml $req = new Request($xmlRpcParser->_xh['method']); // now add parameters in for ($i = 0; $i < count($xmlRpcParser->_xh['params']); $i++) { $req->addParam($xmlRpcParser->_xh['params'][$i]); } if ($this->debug > 1) { $this->debugmsg("\n+++PARSED+++\n" . var_export($req, true) . "\n+++END+++"); } $r = $this->execute($req); } } return $r; }
/** * Parse the xmlrpc response contained in the string $data and return a Response object. * * When $this->debug has been set to a value greater than 0, will echo debug messages to screen while decoding. * * @param string $data the xmlrpc response, possibly including http headers * @param bool $headersProcessed when true prevents parsing HTTP headers for interpretation of content-encoding and * consequent decoding * @param string $returnType decides return type, i.e. content of response->value(). Either 'xmlrpcvals', 'xml' or * 'phpvals' * * @return Response */ public function parseResponse($data = '', $headersProcessed = false, $returnType = 'xmlrpcvals') { if ($this->debug) { Logger::instance()->debugMessage("---GOT---\n{$data}\n---END---"); } $this->httpResponse = array('raw_data' => $data, 'headers' => array(), 'cookies' => array()); if ($data == '') { error_log('XML-RPC: ' . __METHOD__ . ': no response received from server.'); return new Response(0, PhpXmlRpc::$xmlrpcerr['no_data'], PhpXmlRpc::$xmlrpcstr['no_data']); } // parse the HTTP headers of the response, if present, and separate them from data if (substr($data, 0, 4) == 'HTTP') { $httpParser = new Http(); try { $this->httpResponse = $httpParser->parseResponseHeaders($data, $headersProcessed, $this->debug); } catch (\Exception $e) { $r = new Response(0, $e->getCode(), $e->getMessage()); // failed processing of HTTP response headers // save into response obj the full payload received, for debugging $r->raw_data = $data; return $r; } } // be tolerant of extra whitespace in response body $data = trim($data); /// @todo return an error msg if $data=='' ? // be tolerant of junk after methodResponse (e.g. javascript ads automatically inserted by free hosts) // idea from Luca Mariano <*****@*****.**> originally in PEARified version of the lib $pos = strrpos($data, '</methodResponse>'); if ($pos !== false) { $data = substr($data, 0, $pos + 17); } // try to 'guestimate' the character encoding of the received response $respEncoding = XMLParser::guessEncoding(@$this->httpResponse['headers']['content-type'], $data); if ($this->debug) { $start = strpos($data, '<!-- SERVER DEBUG INFO (BASE64 ENCODED):'); if ($start) { $start += strlen('<!-- SERVER DEBUG INFO (BASE64 ENCODED):'); $end = strpos($data, '-->', $start); $comments = substr($data, $start, $end - $start); Logger::instance()->debugMessage("---SERVER DEBUG INFO (DECODED) ---\n\t" . str_replace("\n", "\n\t", base64_decode($comments)) . "\n---END---", $respEncoding); } } // if user wants back raw xml, give it to him if ($returnType == 'xml') { $r = new Response($data, 0, '', 'xml'); $r->hdrs = $this->httpResponse['headers']; $r->_cookies = $this->httpResponse['cookies']; $r->raw_data = $this->httpResponse['raw_data']; return $r; } if ($respEncoding != '') { // Since parsing will fail if charset is not specified in the xml prologue, // the encoding is not UTF8 and there are non-ascii chars in the text, we try to work round that... // The following code might be better for mb_string enabled installs, but // makes the lib about 200% slower... //if (!is_valid_charset($respEncoding, array('UTF-8'))) if (!in_array($respEncoding, array('UTF-8', 'US-ASCII')) && !XMLParser::hasEncoding($data)) { if ($respEncoding == 'ISO-8859-1') { $data = utf8_encode($data); } else { if (extension_loaded('mbstring')) { $data = mb_convert_encoding($data, 'UTF-8', $respEncoding); } else { error_log('XML-RPC: ' . __METHOD__ . ': invalid charset encoding of received response: ' . $respEncoding); } } } } $parser = xml_parser_create(); xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true); // G. Giunta 2005/02/13: PHP internally uses ISO-8859-1, so we have to tell // the xml parser to give us back data in the expected charset. // What if internal encoding is not in one of the 3 allowed? // we use the broadest one, ie. utf8 // This allows to send data which is native in various charset, // by extending xmlrpc_encode_entities() and setting xmlrpc_internalencoding if (!in_array(PhpXmlRpc::$xmlrpc_internalencoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) { xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8'); } else { xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, PhpXmlRpc::$xmlrpc_internalencoding); } $xmlRpcParser = new XMLParser(); xml_set_object($parser, $xmlRpcParser); if ($returnType == 'phpvals') { xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast'); } else { xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee'); } xml_set_character_data_handler($parser, 'xmlrpc_cd'); xml_set_default_handler($parser, 'xmlrpc_dh'); // first error check: xml not well formed if (!xml_parse($parser, $data, count($data))) { // thanks to Peter Kocks <*****@*****.**> if (xml_get_current_line_number($parser) == 1) { $errStr = 'XML error at line 1, check URL'; } else { $errStr = sprintf('XML error: %s at line %d, column %d', xml_error_string(xml_get_error_code($parser)), xml_get_current_line_number($parser), xml_get_current_column_number($parser)); } error_log($errStr); $r = new Response(0, PhpXmlRpc::$xmlrpcerr['invalid_return'], PhpXmlRpc::$xmlrpcstr['invalid_return'] . ' (' . $errStr . ')'); xml_parser_free($parser); if ($this->debug) { print $errStr; } $r->hdrs = $this->httpResponse['headers']; $r->_cookies = $this->httpResponse['cookies']; $r->raw_data = $this->httpResponse['raw_data']; return $r; } xml_parser_free($parser); // second error check: xml well formed but not xml-rpc compliant if ($xmlRpcParser->_xh['isf'] > 1) { if ($this->debug) { /// @todo echo something for user? } $r = new Response(0, PhpXmlRpc::$xmlrpcerr['invalid_return'], PhpXmlRpc::$xmlrpcstr['invalid_return'] . ' ' . $xmlRpcParser->_xh['isf_reason']); } elseif ($returnType == 'xmlrpcvals' && !is_object($xmlRpcParser->_xh['value'])) { // something odd has happened // and it's time to generate a client side error // indicating something odd went on $r = new Response(0, PhpXmlRpc::$xmlrpcerr['invalid_return'], PhpXmlRpc::$xmlrpcstr['invalid_return']); } else { if ($this->debug > 1) { Logger::instance()->debugMessage("---PARSED---\n" . var_export($xmlRpcParser->_xh['value'], true) . "\n---END---"); } // note that using =& will raise an error if $xmlRpcParser->_xh['st'] does not generate an object. $v =& $xmlRpcParser->_xh['value']; if ($xmlRpcParser->_xh['isf']) { /// @todo we should test here if server sent an int and a string, and/or coerce them into such... if ($returnType == 'xmlrpcvals') { $errNo_v = $v['faultCode']; $errStr_v = $v['faultString']; $errNo = $errNo_v->scalarval(); $errStr = $errStr_v->scalarval(); } else { $errNo = $v['faultCode']; $errStr = $v['faultString']; } if ($errNo == 0) { // FAULT returned, errno needs to reflect that $errNo = -1; } $r = new Response(0, $errNo, $errStr); } else { $r = new Response($v, 0, '', $returnType); } } $r->hdrs = $this->httpResponse['headers']; $r->_cookies = $this->httpResponse['cookies']; $r->raw_data = $this->httpResponse['raw_data']; return $r; }