<?php
	// by Edd Dumbill (C) 1999-2001
	// <edd@usefulinc.com>
	// xmlrpc.inc,v 1.18 2001/07/06 18:23:57 edmundd

	// License is granted to use or modify this software ('XML-RPC for PHP')
	// for commercial or non-commercial use provided the copyright of the author
	// is preserved in any distributed or derivative work.

	// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESSED OR
	// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
	// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
	// IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
	// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
	// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
	// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
	// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
	// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
	// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

	/* $Id$ */

	class xmlrpcmsg
	{
		var $payload;
		var $methodname;
		var $params = array();
		var $debug  = 0;

		function xmlrpcmsg($meth, $pars=0)
		{
			$this->methodname=$meth;
			if(is_array($pars) && sizeof($pars)>0)
			{
				for($i=0; $i<sizeof($pars); $i++)
				{
					$this->addParam($pars[$i]);
				}
			}
		}

		function xml_header()
		{
			return "<?xml version=\"1.0\"?" . ">\n<methodCall>\n";
		}

		function xml_footer()
		{
			return "</methodCall>\n";
		}

		function createPayload()
		{
			$this->payload=$this->xml_header();
			$this->payload.='<methodName>' . $this->methodname . "</methodName>\n";
			//	if(sizeof($this->params)) {
			$this->payload.="<params>\n";
			for($i=0; $i<sizeof($this->params); $i++)
			{
				$p=$this->params[$i];
				$this->payload.="<param>\n" . $p->serialize() .
				"</param>\n";
			}
			$this->payload.="</params>\n";
			// }
			$this->payload.=$this->xml_footer();
			//$this->payload=str_replace("\n", "\r\n", $this->payload);
		}

		function method($meth='')
		{
			if($meth!='')
			{
				$this->methodname=$meth;
			}
			return $this->methodname;
		}

		function serialize()
		{
			$this->createPayload();
			return $this->payload;
		}

		function addParam($par)
		{
			// add check: do not add to self params which are not xmlrpcvals
			if(is_object($par) && (get_class($par) == 'xmlrpcval' || is_subclass_of($par, 'xmlrpcval')))
			{
				$this->params[]=$par;
				return true;
			}
			else
			{
				return false;
			}
		}

		function getParam($i) { return $this->params[$i]; }
		function getNumParams() { return sizeof($this->params); }

		function &parseResponseFile($fp)
		{
			$ipd='';
			while($data=fread($fp, 32768))
			{
				$ipd.=$data;
			}
			//fclose($fp);
			$r =& $this->parseResponse($ipd);
			return $r;
		}

		function &parseResponse($data='', $headers_processed=false)
		{
			$parser = xml_parser_create($GLOBALS['xmlrpc_defencoding']);

			$hdrfnd = 0;
			if($this->debug)
			{
				//by maHo, replaced htmlspecialchars with htmlentities
				print "<PRE>---GOT---\n" . htmlentities($data) . "\n---END---\n</PRE>";
			}

			if($data == '')
			{
				error_log('No response received from server.');
				$r =& CreateObject('phpgwapi.xmlrpcresp',0, $GLOBALS['xmlrpcerr']['no_data'], $GLOBALS['xmlrpcstr']['no_data']);
				return $r;
			}
			// see if we got an HTTP 200 OK, else bomb
			// but only do this if we're using the HTTP protocol.
			if(ereg("^HTTP",$data))
			{
				// Strip HTTP 1.1 100 Continue header if present
				while(ereg('^HTTP/1.1 1[0-9]{2} ', $data))
				{
					$pos = strpos($data, 'HTTP', 12);
					// server sent a Continue header without any (valid) content following...
					// give the client a chance to know it
					if(!$pos && !is_int($pos)) // works fine in php 3, 4 and 5
					{
						break;
					}
					$data = substr($data, $pos);
				}
				if(!ereg("^HTTP/[0-9\\.]+ 200 ", $data))
				{
					$errstr= substr($data, 0, strpos($data, "\n")-1);
					error_log('HTTP error, got response: ' .$errstr);
					$r=& CreateObject('phpgwapi.xmlrpcresp',0, $GLOBALS['xmlrpcerr']['http_error'], $GLOBALS['xmlrpcstr']['http_error']. ' (' . $errstr . ')');
					return $r;
				}
			}

			$GLOBALS['_xh'][$parser] = array();
			$GLOBALS['_xh'][$parser]['headers'] = array();
			$GLOBALS['_xh'][$parser]['stack'] = array();
			$GLOBALS['_xh'][$parser]['valuestack'] = array();

			// separate HTTP headers from data
			if(ereg("^HTTP", $data))
			{
				// be tolerant to usage of \n instead of \r\n to separate headers and data
				// (even though it is not valid http)
				$pos = strpos($data,"\r\n\r\n");
				if($pos || is_int($pos))
				{
					$bd = $pos+4;
				}
				else
				{
					$pos = strpos($data,"\n\n");
					if($pos || is_int($pos))
					{
						$bd = $pos+2;
					}
					else
					{
						// No separation between response headers and body: fault?
						$bd = 0;
					}
				}
				// be tolerant to line endings, and extra empty lines
				$ar = split("\r?\n", trim(substr($data, 0, $pos)));
				while(list(,$line) = @each($ar))
				{
					// take care of multi-line headers
					$arr = explode(':',$line);
					if(count($arr) > 1)
					{
						$header_name = strtolower(trim($arr[0]));
						// TO DO: some headers (the ones that allow a CSV list of values)
						// do allow many values to be passed using multiple header lines.
						// We should add content to $GLOBALS['_xh'][$parser]['headers'][$header_name]
						// instead of replacing it for those...
						$GLOBALS['_xh'][$parser]['headers'][$header_name] = $arr[1];
						for($i = 2; $i < count($arr); $i++)
						{
							$GLOBALS['_xh'][$parser]['headers'][$header_name] .= ':'.$arr[$i];
						} // while
						$GLOBALS['_xh'][$parser]['headers'][$header_name] = trim($GLOBALS['_xh'][$parser]['headers'][$header_name]);
					}
					elseif(isset($header_name))
					{
						$GLOBALS['_xh'][$parser]['headers'][$header_name] .= ' ' . trim($line);
					}
				}
				$data = substr($data, $bd);

				if($this->debug && count($GLOBALS['_xh'][$parser]['headers']))
				{
					print '<PRE>';
					foreach($GLOBALS['_xh'][$parser]['headers'] as $header => $value)
					{
						print "HEADER: $header: $value\n";
					}
					print "</PRE>\n";
				}
			}

			// if CURL was used for the call, http headers have been processed,
			// and dechunking + reinflating have been carried out
			if(!$headers_processed)
			{
				// Decode chunked encoding sent by http 1.1 servers
				if(isset($GLOBALS['_xh'][$parser]['headers']['transfer-encoding']) && $GLOBALS['_xh'][$parser]['headers']['transfer-encoding'] == 'chunked')
				{
					if(!$data = decode_chunked($data))
					{
						error_log('Errors occurred when trying to rebuild the chunked data received from server');
						$r =& CreateObject('phpgwapi.xmlrpcresp',0, $GLOBALS['xmlrpcerr']['dechunk_fail'], $GLOBALS['xmlrpcstr']['dechunk_fail']);
						return $r;
					}
				}

				// Decode gzip-compressed stuff
				// code shamelessly inspired from nusoap library by Dietrich Ayala
				if(isset($GLOBALS['_xh'][$parser]['headers']['content-encoding']))
				{
					if($GLOBALS['_xh'][$parser]['headers']['content-encoding'] == 'deflate' || $GLOBALS['_xh'][$parser]['headers']['content-encoding'] == 'gzip')
					{
						// if decoding works, use it. else assume data wasn't gzencoded
						if(function_exists('gzinflate'))
						{
							if($GLOBALS['_xh'][$parser]['headers']['content-encoding'] == 'deflate' && $degzdata = @gzinflate($data))
							{
								$data = $degzdata;
								if($this->debug)
								print "<PRE>---INFLATED RESPONSE---[".strlen($data)." chars]---\n" . htmlentities($data) . "\n---END---</PRE>";
							}
							elseif($GLOBALS['_xh'][$parser]['headers']['content-encoding'] == 'gzip' && $degzdata = @gzinflate(substr($data, 10)))
							{
								$data = $degzdata;
								if($this->debug)
								print "<PRE>---INFLATED RESPONSE---[".strlen($data)." chars]---\n" . htmlentities($data) . "\n---END---</PRE>";
							}
							else
							{
								error_log('Errors occurred when trying to decode the deflated data received from server');
								$r =& CreateObject('phpgwapi.xmlrpcresp',0, $GLOBALS['xmlrpcerr']['decompress_fail'], $GLOBALS['xmlrpcstr']['decompress_fail']);
								return $r;
							}
						}
						else
						{
							error_log('The server sent deflated data. Your php install must have the Zlib extension compiled in to support this.');
							$r =& CreateObject('phpgwapi.xmlrpcresp',0, $GLOBALS['xmlrpcerr']['cannot_decompress'], $GLOBALS['xmlrpcstr']['cannot_decompress']);
							return $r;
						}
					}
				}
			} // end of 'de-chunk, re-inflate response'

			// be tolerant of extra whitespace in response body
			$data = trim($data);

			// be tolerant of junk after methodResponse (e.g. javascript automatically inserted by free hosts)
			// idea from Luca Mariano <luca.mariano@email.it> originally in PEARified version of the lib
			$bd = false;
			$pos = strpos($data, '</methodResponse>');
			while($pos || is_int($pos))
			{
				$bd = $pos+17;
				$pos = strpos($data, '</methodResponse>', $bd);
			}
			if($bd)
			{
				$data = substr($data, 0, $bd);
			}

			$GLOBALS['_xh'][$parser]['isf']=0;
			$GLOBALS['_xh'][$parser]['isf_reason']='';
			$GLOBALS['_xh'][$parser]['ac']='';
			$GLOBALS['_xh'][$parser]['qt']='';

			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
			xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $GLOBALS['xmlrpc_internalencoding']);

			xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee');
			xml_set_character_data_handler($parser, 'xmlrpc_cd');
			xml_set_default_handler($parser, 'xmlrpc_dh');
			//$xmlrpc_value=new xmlrpcval;

			if(!xml_parse($parser, $data, sizeof($data)))
			{
				// thanks to Peter Kocks <peter.kocks@baygate.com>
				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',
						xml_error_string(xml_get_error_code($parser)),
						xml_get_current_line_number($parser));
				}
				error_log($errstr);
				$r =& CreateObject('phpgwapi.xmlrpcresp',0, $GLOBALS['xmlrpcerr']['invalid_return'], $GLOBALS['xmlrpcstr']['invalid_return'].' ('.$errstr.')');
				xml_parser_free($parser);
				if($this->debug)
				{
					print $errstr;
				}
				$r->hdrs = $GLOBALS['_xh'][$parser]['headers'];
				return $r;
			}
			xml_parser_free($parser);
			if($GLOBALS['_xh'][$parser]['isf'] > 1)
			{
				if ($this->debug)
				{
					///@todo echo something for user?
				}

				$r =& CreateObject('phpgwapi.xmlrpcresp',0, $GLOBALS['xmlrpcerr']['invalid_return'],
				$GLOBALS['xmlrpcstr']['invalid_return'] . ' ' . $GLOBALS['_xh'][$parser]['isf_reason']);
			}
			elseif (!is_object($GLOBALS['_xh'][$parser]['value']))
			{
				// then something odd has happened
				// and it's time to generate a client side error
				// indicating something odd went on
				$r=& CreateObject('phpgwapi.xmlrpcresp',0, $GLOBALS['xmlrpcerr']['invalid_return'],
				$GLOBALS['xmlrpcstr']['invalid_return']);
			}
			else
			{
				if ($this->debug)
				{
					print "<PRE>---PARSED---\n" ;
					var_dump($GLOBALS['_xh'][$parser]['value']);
					print "\n---END---</PRE>";
				}				// note that using =& will raise an error if $GLOBALS['_xh'][$parser]['st'] does not generate an object.

				$v = $GLOBALS['_xh'][$parser]['value'];

				if($GLOBALS['_xh'][$parser]['isf'])
				{
					$errno_v = $v->structmem('faultCode');
					$errstr_v = $v->structmem('faultString');
					$errno = $errno_v->scalarval();

					if($errno == 0)
					{
						// FAULT returned, errno needs to reflect that
						$errno = -1;
					}

					$r =& CreateObject('phpgwapi.xmlrpcresp',$v, $errno, $errstr_v->scalarval());
				}
				else
				{
					$r=& CreateObject('phpgwapi.xmlrpcresp',$v);
				}
			}

			$r->hdrs = $GLOBALS['_xh'][$parser]['headers'];
			return $r;
		}
	}
?>