AC_4897, AC_4898, AC_4899: Multifile uploader fixes.
[acontent.git] / include / classes / XML / XML_HTMLSax / PEAR / Remote.php
1 <?php\r
2 /**\r
3  * PEAR_Remote\r
4  *\r
5  * PHP versions 4 and 5\r
6  *\r
7  * LICENSE: This source file is subject to version 3.0 of the PHP license\r
8  * that is available through the world-wide-web at the following URI:\r
9  * http://www.php.net/license/3_0.txt.  If you did not receive a copy of\r
10  * the PHP License and are unable to obtain it through the web, please\r
11  * send a note to license@php.net so we can mail you a copy immediately.\r
12  *\r
13  * @category   pear\r
14  * @package    PEAR\r
15  * @author     Stig Bakken <ssb@php.net>\r
16  * @author     Greg Beaver <cellog@php.net>\r
17  * @copyright  1997-2008 The PHP Group\r
18  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0\r
19  * @version    CVS: $Id: Remote.php 7208 2008-01-09 16:07:24Z greg $\r
20  * @link       http://pear.php.net/package/PEAR\r
21  * @since      File available since Release 0.1\r
22  */\r
23 \r
24 /**\r
25  * needed for PEAR_Error\r
26  */\r
27 require_once 'PEAR.php';\r
28 require_once 'PEAR/Config.php';\r
29 \r
30 /**\r
31  * This is a class for doing remote operations against the central\r
32  * PEAR database.\r
33  *\r
34  * @nodep XML_RPC_Value\r
35  * @nodep XML_RPC_Message\r
36  * @nodep XML_RPC_Client\r
37  * @category   pear\r
38  * @package    PEAR\r
39  * @author     Stig Bakken <ssb@php.net>\r
40  * @author     Greg Beaver <cellog@php.net>\r
41  * @copyright  1997-2008 The PHP Group\r
42  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0\r
43  * @version    Release: 1.4.4\r
44  * @link       http://pear.php.net/package/PEAR\r
45  * @since      Class available since Release 0.1\r
46  */\r
47 class PEAR_Remote extends PEAR\r
48 {\r
49     // {{{ properties\r
50 \r
51     var $config = null;\r
52     var $cache  = null;\r
53     /**\r
54      * @var PEAR_Registry\r
55      * @access private\r
56      */\r
57     var $_registry;\r
58 \r
59     // }}}\r
60 \r
61     // {{{ PEAR_Remote(config_object)\r
62 \r
63     function PEAR_Remote(&$config)\r
64     {\r
65         $this->PEAR();\r
66         $this->config = &$config;\r
67         $this->_registry = &$this->config->getRegistry();\r
68     }\r
69 \r
70     // }}}\r
71     // {{{ setRegistry()\r
72     \r
73     function setRegistry(&$reg)\r
74     {\r
75         $this->_registry = &$reg;\r
76     }\r
77     // }}}\r
78     // {{{ getCache()\r
79 \r
80 \r
81     function getCache($args)\r
82     {\r
83         $id       = md5(serialize($args));\r
84         $cachedir = $this->config->get('cache_dir');\r
85         $filename = $cachedir . DIRECTORY_SEPARATOR . 'xmlrpc_cache_' . $id;\r
86         if (!file_exists($filename)) {\r
87             return null;\r
88         }\r
89 \r
90         $fp = fopen($filename, 'rb');\r
91         if (!$fp) {\r
92             return null;\r
93         }\r
94         if (function_exists('file_get_contents')) {\r
95             fclose($fp);\r
96             $content = file_get_contents($filename);\r
97         } else {\r
98             $content  = fread($fp, filesize($filename));\r
99             fclose($fp);\r
100         }\r
101         $result   = array(\r
102             'age'        => time() - filemtime($filename),\r
103             'lastChange' => filemtime($filename),\r
104             'content'    => unserialize($content),\r
105             );\r
106         return $result;\r
107     }\r
108 \r
109     // }}}\r
110 \r
111     // {{{ saveCache()\r
112 \r
113     function saveCache($args, $data)\r
114     {\r
115         $id       = md5(serialize($args));\r
116         $cachedir = $this->config->get('cache_dir');\r
117         if (!file_exists($cachedir)) {\r
118             System::mkdir(array('-p', $cachedir));\r
119         }\r
120         $filename = $cachedir.'/xmlrpc_cache_'.$id;\r
121 \r
122         $fp = @fopen($filename, "wb");\r
123         if ($fp) {\r
124             fwrite($fp, serialize($data));\r
125             fclose($fp);\r
126         }\r
127     }\r
128 \r
129     // }}}\r
130 \r
131     // {{{ clearCache()\r
132 \r
133     function clearCache($method, $args)\r
134     {\r
135         array_unshift($args, $method);\r
136         array_unshift($args, $this->config->get('default_channel')); // cache by channel\r
137         $id       = md5(serialize($args));\r
138         $cachedir = $this->config->get('cache_dir');\r
139         $filename = $cachedir.'/xmlrpc_cache_'.$id;\r
140         if (file_exists($filename)) {\r
141             @unlink($filename);\r
142         }\r
143     }\r
144 \r
145     // }}}\r
146     // {{{ call(method, [args...])\r
147 \r
148     function call($method)\r
149     {\r
150         $_args = $args = func_get_args();\r
151 \r
152         $server_channel = $this->config->get('default_channel');\r
153         $channel = $this->_registry->getChannel($server_channel);\r
154         if ($channel) {\r
155             $mirror = $this->config->get('preferred_mirror');\r
156             if ($channel->getMirror($mirror)) {\r
157                 if ($channel->supports('xmlrpc', $method, $mirror)) {\r
158                     $server_channel = $server_host = $mirror; // use the preferred mirror\r
159                     $server_port = $channel->getPort($mirror);\r
160                 } elseif (!$channel->supports('xmlrpc', $method)) {\r
161                     return $this->raiseError("Channel $server_channel does not " .\r
162                         "support xml-rpc method $method");\r
163                 }\r
164             }\r
165             if (!isset($server_host)) {\r
166                 if (!$channel->supports('xmlrpc', $method)) {\r
167                     return $this->raiseError("Channel $server_channel does not support " .\r
168                         "xml-rpc method $method");\r
169                 } else {\r
170                     $server_host = $server_channel;\r
171                     $server_port = $channel->getPort();\r
172                 }\r
173             }\r
174         } else {\r
175             return $this->raiseError("Unknown channel '$server_channel'");\r
176         }\r
177 \r
178         array_unshift($_args, $server_channel); // cache by channel\r
179         $this->cache = $this->getCache($_args);\r
180         $cachettl = $this->config->get('cache_ttl');\r
181         // If cache is newer than $cachettl seconds, we use the cache!\r
182         if ($this->cache !== null && $this->cache['age'] < $cachettl) {\r
183             return $this->cache['content'];\r
184         }\r
185         if (extension_loaded("xmlrpc")) {\r
186             $result = call_user_func_array(array(&$this, 'call_epi'), $args);\r
187             if (!PEAR::isError($result)) {\r
188                 $this->saveCache($_args, $result);\r
189             }\r
190             return $result;\r
191         } elseif (!@include_once 'XML/RPC.php') {\r
192             return $this->raiseError("For this remote PEAR operation you need to load the xmlrpc extension or install XML_RPC");\r
193         }\r
194 \r
195         array_shift($args);\r
196         $username = $this->config->get('username');\r
197         $password = $this->config->get('password');\r
198         $eargs = array();\r
199         foreach($args as $arg) {\r
200             $eargs[] = $this->_encode($arg);\r
201         }\r
202         $f = new XML_RPC_Message($method, $eargs);\r
203         if ($this->cache !== null) {\r
204             $maxAge = '?maxAge='.$this->cache['lastChange'];\r
205         } else {\r
206             $maxAge = '';\r
207         }\r
208         $proxy_host = $proxy_port = $proxy_user = $proxy_pass = '';\r
209         if ($proxy = parse_url($this->config->get('http_proxy'))) {\r
210             $proxy_host = @$proxy['host'];\r
211             if (isset($proxy['scheme']) && $proxy['scheme'] == 'https') {\r
212                 $proxy_host = 'https://' . $proxy_host;\r
213             }\r
214             $proxy_port = @$proxy['port'];\r
215             $proxy_user = @urldecode(@$proxy['user']);\r
216             $proxy_pass = @urldecode(@$proxy['pass']);\r
217         }\r
218         $shost = $server_host;\r
219         if ($channel->getSSL()) {\r
220             $shost = "https://$shost";\r
221         }\r
222         $c = new XML_RPC_Client('/' . $channel->getPath('xmlrpc')\r
223             . $maxAge, $shost, $server_port, $proxy_host, $proxy_port,\r
224             $proxy_user, $proxy_pass);\r
225         if ($username && $password) {\r
226             $c->setCredentials($username, $password);\r
227         }\r
228         if ($this->config->get('verbose') >= 3) {\r
229             $c->setDebug(1);\r
230         }\r
231         $r = $c->send($f);\r
232         if (!$r) {\r
233             return $this->raiseError("XML_RPC send failed");\r
234         }\r
235         $v = $r->value();\r
236         if ($e = $r->faultCode()) {\r
237             if ($e == $GLOBALS['XML_RPC_err']['http_error'] && strstr($r->faultString(), '304 Not Modified') !== false) {\r
238                 return $this->cache['content'];\r
239             }\r
240             return $this->raiseError($r->faultString(), $e);\r
241         }\r
242 \r
243         $result = XML_RPC_decode($v);\r
244         $this->saveCache($_args, $result);\r
245         return $result;\r
246     }\r
247 \r
248     // }}}\r
249 \r
250     // {{{ call_epi(method, [args...])\r
251 \r
252     function call_epi($method)\r
253     {\r
254         do {\r
255             if (extension_loaded("xmlrpc")) {\r
256                 break;\r
257             }\r
258             if (OS_WINDOWS) {\r
259                 $ext = 'dll';\r
260             } elseif (PHP_OS == 'HP-UX') {\r
261                 $ext = 'sl';\r
262             } elseif (PHP_OS == 'AIX') {\r
263                 $ext = 'a';\r
264             } else {\r
265                 $ext = 'so';\r
266             }\r
267             $ext = OS_WINDOWS ? 'dll' : 'so';\r
268             @dl("xmlrpc-epi.$ext");\r
269             if (extension_loaded("xmlrpc")) {\r
270                 break;\r
271             }\r
272             @dl("xmlrpc.$ext");\r
273             if (extension_loaded("xmlrpc")) {\r
274                 break;\r
275             }\r
276             return $this->raiseError("unable to load xmlrpc extension");\r
277         } while (false);\r
278         $server_channel = $this->config->get('default_channel');\r
279         $channel = $this->_registry->getChannel($server_channel);\r
280         if ($channel) {\r
281             $mirror = $this->config->get('preferred_mirror');\r
282             if ($channel->getMirror($mirror)) {\r
283                 if ($channel->supports('xmlrpc', $method, $mirror)) {\r
284                     $server_channel = $server_host = $mirror; // use the preferred mirror\r
285                     $server_port = $channel->getPort($mirror);\r
286                 } elseif (!$channel->supports('xmlrpc', $method)) {\r
287                     return $this->raiseError("Channel $server_channel does not " .\r
288                         "support xml-rpc method $method");\r
289                 }\r
290             }\r
291             if (!isset($server_host)) {\r
292                 if (!$channel->supports('xmlrpc', $method)) {\r
293                     return $this->raiseError("Channel $server_channel does not support " .\r
294                         "xml-rpc method $method");\r
295                 } else {\r
296                     $server_host = $server_channel;\r
297                     $server_port = $channel->getPort();\r
298                 }\r
299             }\r
300         } else {\r
301             return $this->raiseError("Unknown channel '$server_channel'");\r
302         }\r
303         $params = func_get_args();\r
304         array_shift($params);\r
305         $method = str_replace("_", ".", $method);\r
306         $request = xmlrpc_encode_request($method, $params);\r
307         if ($http_proxy = $this->config->get('http_proxy')) {\r
308             $proxy = parse_url($http_proxy);\r
309             $proxy_host = $proxy_port = $proxy_user = $proxy_pass = '';\r
310             $proxy_host = @$proxy['host'];\r
311             if (isset($proxy['scheme']) && $proxy['scheme'] == 'https') {\r
312                 $proxy_host = 'ssl://' . $proxy_host;\r
313             }\r
314             $proxy_port = @$proxy['port'];\r
315             $proxy_user = @urldecode(@$proxy['user']);\r
316             $proxy_pass = @urldecode(@$proxy['pass']);\r
317             $fp = @fsockopen($proxy_host, $proxy_port);\r
318             $use_proxy = true;\r
319             if ($channel->getSSL()) {\r
320                 $server_host = "https://$server_host";\r
321             }\r
322         } else {\r
323             $use_proxy = false;\r
324             $ssl = $channel->getSSL();\r
325             $fp = @fsockopen(($ssl ? 'ssl://' : '') . $server_host, $server_port);\r
326             if (!$fp) {\r
327                 $server_host = "$ssl$server_host"; // for error-reporting\r
328             }\r
329         }\r
330         if (!$fp && $http_proxy) {\r
331             return $this->raiseError("PEAR_Remote::call: fsockopen(`$proxy_host', $proxy_port) failed");\r
332         } elseif (!$fp) {\r
333             return $this->raiseError("PEAR_Remote::call: fsockopen(`$server_host', $server_port) failed");\r
334         }\r
335         $len = strlen($request);\r
336         $req_headers = "Host: $server_host:$server_port\r\n" .\r
337              "Content-type: text/xml\r\n" .\r
338              "Content-length: $len\r\n";\r
339         $username = $this->config->get('username');\r
340         $password = $this->config->get('password');\r
341         if ($username && $password) {\r
342             $req_headers .= "Cookie: PEAR_USER=$username; PEAR_PW=$password\r\n";\r
343             $tmp = base64_encode("$username:$password");\r
344             $req_headers .= "Authorization: Basic $tmp\r\n";\r
345         }\r
346         if ($this->cache !== null) {\r
347             $maxAge = '?maxAge='.$this->cache['lastChange'];\r
348         } else {\r
349             $maxAge = '';\r
350         }\r
351 \r
352         if ($use_proxy && $proxy_host != '' && $proxy_user != '') {\r
353             $req_headers .= 'Proxy-Authorization: Basic '\r
354                 .base64_encode($proxy_user.':'.$proxy_pass)\r
355                 ."\r\n";\r
356         }\r
357 \r
358         if ($this->config->get('verbose') > 3) {\r
359             print "XMLRPC REQUEST HEADERS:\n";\r
360             var_dump($req_headers);\r
361             print "XMLRPC REQUEST BODY:\n";\r
362             var_dump($request);\r
363         }\r
364 \r
365         if ($use_proxy && $proxy_host != '') {\r
366             $post_string = "POST http://".$server_host;\r
367             if ($proxy_port > '') {\r
368                 $post_string .= ':'.$server_port;\r
369             }\r
370         } else {\r
371             $post_string = "POST ";\r
372         }\r
373 \r
374         $path = '/' . $channel->getPath('xmlrpc');\r
375         fwrite($fp, ($post_string . $path . "$maxAge HTTP/1.0\r\n$req_headers\r\n$request"));\r
376         $response = '';\r
377         $line1 = fgets($fp, 2048);\r
378         if (!preg_match('!^HTTP/[0-9\.]+ (\d+) (.*)!', $line1, $matches)) {\r
379             return $this->raiseError("PEAR_Remote: invalid HTTP response from XML-RPC server");\r
380         }\r
381         switch ($matches[1]) {\r
382             case "200": // OK\r
383                 break;\r
384             case "304": // Not Modified\r
385                 return $this->cache['content'];\r
386             case "401": // Unauthorized\r
387                 if ($username && $password) {\r
388                     return $this->raiseError("PEAR_Remote ($server_host:$server_port) " .\r
389                         ": authorization failed", 401);\r
390                 } else {\r
391                     return $this->raiseError("PEAR_Remote ($server_host:$server_port) " .\r
392                         ": authorization required, please log in first", 401);\r
393                 }\r
394             default:\r
395                 return $this->raiseError("PEAR_Remote ($server_host:$server_port) : " .\r
396                     "unexpected HTTP response", (int)$matches[1], null, null,\r
397                     "$matches[1] $matches[2]");\r
398         }\r
399         while (trim(fgets($fp, 2048)) != ''); // skip rest of headers\r
400         while ($chunk = fread($fp, 10240)) {\r
401             $response .= $chunk;\r
402         }\r
403         fclose($fp);\r
404         if ($this->config->get('verbose') > 3) {\r
405             print "XMLRPC RESPONSE:\n";\r
406             var_dump($response);\r
407         }\r
408         $ret = xmlrpc_decode($response);\r
409         if (is_array($ret) && isset($ret['__PEAR_TYPE__'])) {\r
410             if ($ret['__PEAR_TYPE__'] == 'error') {\r
411                 if (isset($ret['__PEAR_CLASS__'])) {\r
412                     $class = $ret['__PEAR_CLASS__'];\r
413                 } else {\r
414                     $class = "PEAR_Error";\r
415                 }\r
416                 if ($ret['code']     === '') $ret['code']     = null;\r
417                 if ($ret['message']  === '') $ret['message']  = null;\r
418                 if ($ret['userinfo'] === '') $ret['userinfo'] = null;\r
419                 if (strtolower($class) == 'db_error') {\r
420                     $ret = $this->raiseError(PEAR::errorMessage($ret['code']),\r
421                                              $ret['code'], null, null,\r
422                                              $ret['userinfo']);\r
423                 } else {\r
424                     $ret = $this->raiseError($ret['message'], $ret['code'],\r
425                                              null, null, $ret['userinfo']);\r
426                 }\r
427             }\r
428         } elseif (is_array($ret) && sizeof($ret) == 1 && isset($ret[0])\r
429                   && is_array($ret[0]) &&\r
430                   !empty($ret[0]['faultString']) &&\r
431                   !empty($ret[0]['faultCode'])) {\r
432             extract($ret[0]);\r
433             $faultString = "XML-RPC Server Fault: " .\r
434                  str_replace("\n", " ", $faultString);\r
435             return $this->raiseError($faultString, $faultCode);\r
436         } elseif (is_array($ret) && sizeof($ret) == 2 && !empty($ret['faultString']) &&\r
437               !empty($ret['faultCode'])) {\r
438             extract($ret);\r
439             $faultString = "XML-RPC Server Fault: " .\r
440                  str_replace("\n", " ", $faultString);\r
441             return $this->raiseError($faultString, $faultCode);\r
442         }\r
443         return $ret;\r
444     }\r
445 \r
446     // }}}\r
447 \r
448     // {{{ _encode\r
449 \r
450     // a slightly extended version of XML_RPC_encode\r
451     function _encode($php_val)\r
452     {\r
453         global $XML_RPC_Boolean, $XML_RPC_Int, $XML_RPC_Double;\r
454         global $XML_RPC_String, $XML_RPC_Array, $XML_RPC_Struct;\r
455 \r
456         $type = gettype($php_val);\r
457         $xmlrpcval = new XML_RPC_Value;\r
458 \r
459         switch($type) {\r
460             case "array":\r
461                 reset($php_val);\r
462                 $firstkey = key($php_val);\r
463                 end($php_val);\r
464                 $lastkey = key($php_val);\r
465                 reset($php_val);\r
466                 if ($firstkey === 0 && is_int($lastkey) &&\r
467                     ($lastkey + 1) == count($php_val)) {\r
468                     $is_continuous = true;\r
469                     reset($php_val);\r
470                     $size = count($php_val);\r
471                     for ($expect = 0; $expect < $size; $expect++, next($php_val)) {\r
472                         if (key($php_val) !== $expect) {\r
473                             $is_continuous = false;\r
474                             break;\r
475                         }\r
476                     }\r
477                     if ($is_continuous) {\r
478                         reset($php_val);\r
479                         $arr = array();\r
480                         while (list($k, $v) = each($php_val)) {\r
481                             $arr[$k] = $this->_encode($v);\r
482                         }\r
483                         $xmlrpcval->addArray($arr);\r
484                         break;\r
485                     }\r
486                 }\r
487                 // fall though if not numerical and continuous\r
488             case "object":\r
489                 $arr = array();\r
490                 while (list($k, $v) = each($php_val)) {\r
491                     $arr[$k] = $this->_encode($v);\r
492                 }\r
493                 $xmlrpcval->addStruct($arr);\r
494                 break;\r
495             case "integer":\r
496                 $xmlrpcval->addScalar($php_val, $XML_RPC_Int);\r
497                 break;\r
498             case "double":\r
499                 $xmlrpcval->addScalar($php_val, $XML_RPC_Double);\r
500                 break;\r
501             case "string":\r
502             case "NULL":\r
503                 $xmlrpcval->addScalar($php_val, $XML_RPC_String);\r
504                 break;\r
505             case "boolean":\r
506                 $xmlrpcval->addScalar($php_val, $XML_RPC_Boolean);\r
507                 break;\r
508             case "unknown type":\r
509             default:\r
510                 return null;\r
511         }\r
512         return $xmlrpcval;\r
513     }\r
514 \r
515     // }}}\r
516 \r
517 }\r
518 \r
519 ?>\r