2 // by Edd Dumbill (C) 1999-2002
4 // $Id: xmlrpc.inc.php,v 1.6 2007-03-22 08:32:11 kimitake Exp $
5 // $NucleusJP: xmlrpc.inc.php,v 1.5 2005/08/13 07:20:34 kimitake Exp $
7 // Copyright (c) 1999,2000,2002 Edd Dumbill.
8 // All rights reserved.
10 // Redistribution and use in source and binary forms, with or without
11 // modification, are permitted provided that the following conditions
14 // * Redistributions of source code must retain the above copyright
15 // notice, this list of conditions and the following disclaimer.
17 // * Redistributions in binary form must reproduce the above
18 // copyright notice, this list of conditions and the following
19 // disclaimer in the documentation and/or other materials provided
20 // with the distribution.
22 // * Neither the name of the "XML-RPC for PHP" nor the names of its
23 // contributors may be used to endorse or promote products derived
24 // from this software without specific prior written permission.
26 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
29 // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
30 // REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
31 // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
32 // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
33 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
35 // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
37 // OF THE POSSIBILITY OF SUCH DAMAGE.
39 if (!function_exists('xml_parser_create'))
41 // Win 32 fix. From: 'Leo West' <lwest@imaginet.fr>
52 // G. Giunta 2005/01/29: declare global these variables,
53 // so that xmlrpc.inc will work even if included from within a function
54 // NB: it will give warnings in PHP3, so we comment it out
55 // Milosch: Next round, maybe we should explicitly request these via $GLOBALS where used.
56 if (phpversion() >= '4')
62 global $xmlrpcDateTime;
71 global $xmlrpc_defencoding;
72 global $xmlrpc_internalencoding;
74 global $xmlrpcVersion;
75 global $xmlrpcerruser;
77 global $xmlrpc_backslash;
82 $xmlrpcBoolean='boolean';
83 $xmlrpcDouble='double';
84 $xmlrpcString='string';
85 $xmlrpcDateTime='dateTime.iso8601';
86 $xmlrpcBase64='base64';
88 $xmlrpcStruct='struct';
110 $xmlrpcerr['unknown_method']=1;
111 $xmlrpcstr['unknown_method']='Unknown method';
112 $xmlrpcerr['invalid_return']=2;
113 $xmlrpcstr['invalid_return']='Invalid return payload: enable debugging to examine incoming payload';
114 $xmlrpcerr['incorrect_params']=3;
115 $xmlrpcstr['incorrect_params']='Incorrect parameters passed to method';
116 $xmlrpcerr['introspect_unknown']=4;
117 $xmlrpcstr['introspect_unknown']="Can't introspect: method unknown";
118 $xmlrpcerr['http_error']=5;
119 $xmlrpcstr['http_error']="Didn't receive 200 OK from remote server.";
120 $xmlrpcerr['no_data']=6;
121 $xmlrpcstr['no_data']='No data received from server.';
122 $xmlrpcerr['no_ssl']=7;
123 $xmlrpcstr['no_ssl']='No SSL support compiled in.';
124 $xmlrpcerr['curl_fail']=8;
125 $xmlrpcstr['curl_fail']='CURL error';
128 $xmlrpcerr['multicall_notstruct'] = 9;
129 $xmlrpcstr['multicall_notstruct'] = 'system.multicall expected struct';
130 $xmlrpcerr['multicall_nomethod'] = 10;
131 $xmlrpcstr['multicall_nomethod'] = 'missing methodName';
132 $xmlrpcerr['multicall_notstring'] = 11;
133 $xmlrpcstr['multicall_notstring'] = 'methodName is not a string';
134 $xmlrpcerr['multicall_recursion'] = 12;
135 $xmlrpcstr['multicall_recursion'] = 'recursive system.multicall forbidden';
136 $xmlrpcerr['multicall_noparams'] = 13;
137 $xmlrpcstr['multicall_noparams'] = 'missing params';
138 $xmlrpcerr['multicall_notarray'] = 14;
139 $xmlrpcstr['multicall_notarray'] = 'params is not an array';
141 // The charset encoding expected by the server for received messages and
142 // by the client for received responses
143 $xmlrpc_defencoding='UTF-8';
144 // The encoding used by PHP.
145 // String values received will be converted to this.
146 $xmlrpc_internalencoding='ISO-8859-1';
148 $xmlrpcName='XML-RPC for PHP';
149 $xmlrpcVersion='1.1.1';
151 // let user errors start at 800
153 // let XML parse errors start at 100
156 // formulate backslashes for escaping regexp
157 $xmlrpc_backslash=chr(92).chr(92);
159 // used to store state during parsing
160 // quick explanation of components:
161 // st - used to build up a string for evaluation
162 // ac - used to accumulate values
163 // qt - used to decide if quotes are needed for evaluation
164 // cm - used to denote struct or array (comma needed)
165 // isf - used to indicate a fault
166 // lv - used to indicate "looking for a value": implements
167 // the logic to allow values with no types to be strings
168 // params - used to store parameters in method calls
169 // method - used to store method name
174 * To help correct communication of non-ascii chars inside strings, regardless
175 * of the charset used when sending requests, parsing them, sending responses
176 * and parsing responses, convert all non-ascii chars present in the message
177 * into their equivalent 'charset entity'. Charset entities enumerated this way
178 * are independent of the charset encoding used to transmit them, and all XML
179 * parsers are bound to understand them.
181 function xmlrpc_entity_decode($string)
183 $top=split('&', $string);
186 while($i<sizeof($top))
188 if (ereg("^([#a-zA-Z0-9]+);", $top[$i], $regs))
190 $op.=ereg_replace("^[#a-zA-Z0-9]+;",
191 xmlrpc_lookup_entity($regs[1]),
210 function xmlrpc_lookup_entity($ent)
214 if (isset($xmlEntities[strtolower($ent)]))
216 return $xmlEntities[strtolower($ent)];
218 if (ereg("^#([0-9]+)$", $ent, $regs))
220 return chr($regs[1]);
226 * These entities originate from HTML specs (1.1, proposed 2.0, etc),
227 * and are taken directly from php-4.3.1/ext/mbstring/html_entities.c.
228 * Until php provides functionality to translate these entities in its
229 * core library, use this function.
231 function xmlrpc_html_entity_xlate($data = '')
234 " " => " ",
235 "¡" => "¡",
236 "¢" => "¢",
237 "£" => "£",
238 "¤" => "¤",
240 "¦" => "¦",
241 "§" => "§",
243 "©" => "©",
244 "ª" => "ª",
245 "«" => "«",
249 "¯" => "¯",
251 "±" => "±",
252 "²" => "²",
253 "³" => "³",
254 "´" => "´",
255 "µ" => "µ",
256 "¶" => "¶",
257 "·" => "·",
258 "¸" => "¸",
259 "¹" => "¹",
260 "º" => "º",
261 "»" => "»",
262 "¼" => "¼",
263 "½" => "½",
264 "¾" => "¾",
265 "¿" => "¿",
266 "À" => "À",
267 "Á" => "Á",
268 "Â" => "Â",
269 "Ã" => "Ã",
270 "Ä" => "Ä",
271 "Å" => "Å",
272 "Æ" => "Æ",
273 "Ç" => "Ç",
274 "È" => "È",
275 "É" => "É",
276 "Ê" => "Ê",
277 "Ë" => "Ë",
278 "Ì" => "Ì",
279 "Í" => "Í",
280 "Î" => "Î",
281 "Ï" => "Ï",
283 "Ñ" => "Ñ",
284 "Ò" => "Ò",
285 "Ó" => "Ó",
286 "Ô" => "Ô",
287 "Õ" => "Õ",
288 "Ö" => "Ö",
289 "×" => "×",
290 "Ø" => "Ø",
291 "Ù" => "Ù",
292 "Ú" => "Ú",
293 "Û" => "Û",
294 "Ü" => "Ü",
295 "Ý" => "Ý",
296 "Þ" => "Þ",
297 "ß" => "ß",
298 "à" => "à",
299 "á" => "á",
300 "â" => "â",
301 "ã" => "ã",
302 "ä" => "ä",
303 "å" => "å",
304 "æ" => "æ",
305 "ç" => "ç",
306 "è" => "è",
307 "é" => "é",
308 "ê" => "ê",
309 "ë" => "ë",
310 "ì" => "ì",
311 "í" => "í",
312 "î" => "î",
313 "ï" => "ï",
315 "ñ" => "ñ",
316 "ò" => "ò",
317 "ó" => "ó",
318 "ô" => "ô",
319 "õ" => "õ",
320 "ö" => "ö",
321 "÷" => "÷",
322 "ø" => "ø",
323 "ù" => "ù",
324 "ú" => "ú",
325 "û" => "û",
326 "ü" => "ü",
327 "ý" => "ý",
328 "þ" => "þ",
329 "ÿ" => "ÿ",
330 "Œ" => "Œ",
331 "œ" => "œ",
332 "Š" => "Š",
333 "š" => "š",
334 "Ÿ" => "Ÿ",
335 "ƒ" => "ƒ",
336 "ˆ" => "ˆ",
337 "˜" => "˜",
338 "Α" => "Α",
339 "Β" => "Β",
340 "Γ" => "Γ",
341 "Δ" => "Δ",
342 "Ε" => "Ε",
343 "Ζ" => "Ζ",
345 "Θ" => "Θ",
346 "Ι" => "Ι",
347 "Κ" => "Κ",
348 "Λ" => "Λ",
352 "Ο" => "Ο",
355 "Σ" => "Σ",
357 "Υ" => "Υ",
361 "Ω" => "Ω",
362 "β" => "β",
363 "γ" => "γ",
364 "δ" => "δ",
365 "ε" => "ε",
366 "ζ" => "ζ",
368 "θ" => "θ",
369 "ι" => "ι",
370 "κ" => "κ",
371 "λ" => "λ",
375 "ο" => "ο",
378 "ς" => "ς",
379 "σ" => "σ",
381 "υ" => "υ",
385 "ω" => "ω",
386 "ϑ" => "ϑ",
387 "ϒ" => "ϒ",
389 " " => " ",
390 " " => " ",
391 " " => " ",
392 "‌" => "‌",
393 "‍" => "‍",
394 "‎" => "‎",
395 "‏" => "‏",
396 "–" => "–",
397 "—" => "—",
398 "‘" => "‘",
399 "’" => "’",
400 "‚" => "‚",
401 "“" => "“",
402 "”" => "”",
403 "„" => "„",
404 "†" => "†",
405 "‡" => "‡",
406 "•" => "•",
407 "…" => "…",
408 "‰" => "‰",
409 "′" => "′",
410 "″" => "″",
411 "‹" => "‹",
412 "›" => "›",
413 "‾" => "‾",
414 "⁄" => "⁄",
415 "€" => "€",
416 "℘" => "℘",
417 "ℑ" => "ℑ",
418 "ℜ" => "ℜ",
419 "™" => "™",
420 "ℵ" => "ℵ",
421 "←" => "←",
422 "↑" => "↑",
423 "→" => "→",
424 "↓" => "↓",
425 "↔" => "↔",
426 "↵" => "↵",
427 "⇐" => "⇐",
428 "⇑" => "⇑",
429 "⇒" => "⇒",
430 "⇓" => "⇓",
431 "⇔" => "⇔",
432 "∀" => "∀",
433 "∂" => "∂",
434 "∃" => "∃",
435 "∅" => "∅",
436 "∇" => "∇",
437 "∈" => "∈",
438 "∉" => "∉",
440 "∏" => "∏",
441 "∑" => "∑",
442 "−" => "−",
443 "∗" => "∗",
444 "√" => "√",
445 "∝" => "∝",
446 "∞" => "∞",
447 "∠" => "∠",
448 "∧" => "∧",
450 "∩" => "∩",
451 "∪" => "∪",
452 "∫" => "∫",
453 "∴" => "∴",
454 "∼" => "∼",
455 "≅" => "≅",
456 "≈" => "≈",
458 "≡" => "≡",
461 "⊂" => "⊂",
462 "⊃" => "⊃",
463 "⊄" => "⊄",
464 "⊆" => "⊆",
465 "⊇" => "⊇",
466 "⊕" => "⊕",
467 "⊗" => "⊗",
468 "⊥" => "⊥",
469 "⋅" => "⋅",
470 "⌈" => "⌈",
471 "⌉" => "⌉",
472 "⌊" => "⌊",
473 "⌋" => "⌋",
474 "⟨" => "〈",
475 "⟩" => "〉",
476 "◊" => "◊",
477 "♠" => "♠",
478 "♣" => "♣",
479 "♥" => "♥",
480 "♦" => "♦");
481 return strtr($data, $entities);
484 function xmlrpc_encode_entitites($data)
486 $length = strlen($data);
488 for($position = 0; $position < $length; $position++)
490 $character = substr($data, $position, 1);
491 $code = Ord($character);
494 $character = """;
497 $character = "&";
500 $character = "'";
509 if ($code < 32 || $code > 159)
510 $character = ("&#".strval($code).";");
513 $escapeddata .= $character;
518 function xmlrpc_se($parser, $name, $attrs)
520 global $_xh, $xmlrpcDateTime, $xmlrpcString;
526 $_xh[$parser]['st'].='array(';
527 $_xh[$parser]['cm']++;
528 // this last line turns quoting off
529 // this means if we get an empty array we'll
530 // simply get a bit of whitespace in the eval
531 $_xh[$parser]['qt']=0;
534 $_xh[$parser]['st'].='"';
535 $_xh[$parser]['ac']='';
538 $_xh[$parser]['isf']=1;
541 $_xh[$parser]['st']='';
544 $_xh[$parser]['st'].='new xmlrpcval(';
545 $_xh[$parser]['vt']=$xmlrpcString;
546 $_xh[$parser]['ac']='';
547 $_xh[$parser]['qt']=0;
548 $_xh[$parser]['lv']=1;
549 // look for a value: if this is still 1 by the
550 // time we reach the first data segment then the type is string
551 // by implication and we need to add in a quote
558 case 'DATETIME.ISO8601':
560 $_xh[$parser]['ac']=''; // reset the accumulator
562 if ($name=='DATETIME.ISO8601' || $name=='STRING')
564 $_xh[$parser]['qt']=1;
565 if ($name=='DATETIME.ISO8601')
567 $_xh[$parser]['vt']=$xmlrpcDateTime;
570 elseif ($name=='BASE64')
572 $_xh[$parser]['qt']=2;
576 // No quoting is required here -- but
577 // at the end of the element we must check
578 // for data format errors.
579 $_xh[$parser]['qt']=0;
583 $_xh[$parser]['ac']='';
591 $_xh[$parser]['lv']=0;
595 function xmlrpc_ee($parser, $name)
597 global $_xh,$xmlrpcTypes,$xmlrpcString;
603 if ($_xh[$parser]['cm'] && substr($_xh[$parser]['st'], -1) ==',')
605 $_xh[$parser]['st']=substr($_xh[$parser]['st'],0,-1);
607 $_xh[$parser]['st'].=')';
608 $_xh[$parser]['vt']=strtolower($name);
609 $_xh[$parser]['cm']--;
612 $_xh[$parser]['st'].= $_xh[$parser]['ac'] . '" => ';
615 // special case here: we translate boolean 1 or 0 into PHP
616 // constants true or false
617 // NB: this simple checks helps a lot sanitizing input, ie no
618 // security problems around here
619 if ($_xh[$parser]['ac']=='1')
621 $_xh[$parser]['ac']='true';
625 $_xh[$parser]['ac']='false';
627 $_xh[$parser]['vt']=strtolower($name);
628 // Drop through intentionally.
633 case 'DATETIME.ISO8601':
635 if ($_xh[$parser]['qt']==1)
637 // we use double quotes rather than single so backslashification works OK
638 $_xh[$parser]['st'].='"'. $_xh[$parser]['ac'] . '"';
640 elseif ($_xh[$parser]['qt']==2)
642 $_xh[$parser]['st'].='base64_decode("'. $_xh[$parser]['ac'] . '")';
644 elseif ($name=='BOOLEAN')
646 $_xh[$parser]['st'].=$_xh[$parser]['ac'];
648 elseif ($name=='DOUBLE')
651 // we must check that only 0123456789-.<space> are characters here
652 if (!ereg("^[+-]?[eE0123456789 \\t\\.]+$", $_xh[$parser]['ac']))
654 // TODO: find a better way of throwing an error
656 error_log('XML-RPC: non numeric value received in DOUBLE: '.$_xh[$parser]['ac']);
657 $_xh[$parser]['st'].="'ERROR_NON_NUMERIC_FOUND'";
661 // it's ok, add it on
662 $_xh[$parser]['st'].=(double)$_xh[$parser]['ac'];
668 // we must check that only 0123456789-<space> are characters here
669 if (!ereg("^[+-]?[0123456789 \\t]+$", $_xh[$parser]['ac']))
671 // TODO: find a better way of throwing an error
673 error_log('XML-RPC: non numeric value received in INT: '.$_xh[$parser]['ac']);
674 $_xh[$parser]['st'].="'ERROR_NON_NUMERIC_FOUND'";
678 // it's ok, add it on
679 $_xh[$parser]['st'].=(int)$_xh[$parser]['ac'];
682 $_xh[$parser]['ac']='';
683 $_xh[$parser]['qt']=0;
684 $_xh[$parser]['lv']=3; // indicate we've found a value
687 // deal with a string value
688 if (strlen($_xh[$parser]['ac'])>0 &&
689 $_xh[$parser]['vt']==$xmlrpcString)
691 $_xh[$parser]['st'].='"'. $_xh[$parser]['ac'] . '"';
693 // This if() detects if no scalar was inside <VALUE></VALUE>
694 // and pads an empty ''.
695 if($_xh[$parser]['st'][strlen($_xh[$parser]['st'])-1] == '(')
697 $_xh[$parser]['st'].= '""';
699 // G. Giunta 2005/03/12 save some chars in the reconstruction of string vals...
700 if ($_xh[$parser]['vt'] != $xmlrpcString)
701 $_xh[$parser]['st'].=", '" . $_xh[$parser]['vt'] . "')";
703 $_xh[$parser]['st'].=")";
704 if ($_xh[$parser]['cm'])
706 $_xh[$parser]['st'].=',';
710 $_xh[$parser]['ac']='';
711 $_xh[$parser]['qt']=0;
714 $_xh[$parser]['ac']='';
715 $_xh[$parser]['qt']=0;
718 $_xh[$parser]['params'][]=$_xh[$parser]['st'];
721 $_xh[$parser]['method']=ereg_replace("^[\n\r\t ]+", '', $_xh[$parser]['ac']);
723 // BOOLEAN HAS BEEN ENUMERATED ABOVE!
725 // special case here: we translate boolean 1 or 0 into PHP
726 // constants true or false
727 if ($_xh[$parser]['ac']=='1')
729 $_xh[$parser]['ac']='true';
733 $_xh[$parser]['ac']='false';
734 $_xh[$parser]['vt']=strtolower($name);
740 // if it's a valid type name, set the type
741 if (isset($xmlrpcTypes[strtolower($name)]))
743 $_xh[$parser]['vt']=strtolower($name);
747 function xmlrpc_cd($parser, $data)
749 global $_xh, $xmlrpc_backslash;
751 //if (ereg("^[\n\r \t]+$", $data)) return;
752 // print "adding [${data}]\n";
754 if ($_xh[$parser]['lv']!=3)
756 // "lookforvalue==3" means that we've found an entire value
757 // and should discard any further character data
758 if ($_xh[$parser]['lv']==1)
760 // if we've found text and we're just in a <value> then
761 // turn quoting on, as this will be a string
762 $_xh[$parser]['qt']=1;
763 // and say we've found a value
764 $_xh[$parser]['lv']=2;
766 if(!@isset($_xh[$parser]['ac']))
768 $_xh[$parser]['ac'] = '';
770 $_xh[$parser]['ac'].=str_replace('$', '\$', str_replace('"', '\"', str_replace(chr(92),$xmlrpc_backslash, $data)));
774 function xmlrpc_dh($parser, $data)
776 global $_xh, $xmlrpc_backslash;
777 if (substr($data, 0, 1) == '&' && substr($data, -1, 1) == ';')
779 if ($_xh[$parser]['lv']==1)
781 $_xh[$parser]['qt']=1;
782 $_xh[$parser]['lv']=2;
784 $_xh[$parser]['ac'].=str_replace('$', '\$', str_replace('"', '\"', str_replace(chr(92),$xmlrpc_backslash, $data)));
802 var $no_multicall=false;
804 function xmlrpc_client($path, $server, $port=0)
806 $this->port=$port; $this->server=$server; $this->path=$path;
809 function setDebug($in)
821 function setCredentials($u, $p)
827 function setCertificate($cert, $certpass)
830 $this->certpass = $certpass;
833 function setSSLVerifyPeer($i)
835 $this->verifypeer = $i;
838 function setSSLVerifyHost($i)
840 $this->verifyhost = $i;
843 function send($msg, $timeout=0, $method='http')
847 // $msg is an array of xmlrpcmsg's
848 return $this->multicall($msg, $timeout, $method);
851 // where msg is an xmlrpcmsg
852 $msg->debug=$this->debug;
854 if ($method == 'https')
856 return $this->sendPayloadHTTPS($msg,
858 $this->port, $timeout,
859 $this->username, $this->password,
865 return $this->sendPayloadHTTP10($msg, $this->server, $this->port,
866 $timeout, $this->username,
871 function sendPayloadHTTP10($msg, $server, $port, $timeout=0,$username='', $password='')
873 global $xmlrpcerr, $xmlrpcstr, $xmlrpcName, $xmlrpcVersion, $xmlrpc_defencoding;
880 $fp=@fsockopen($server, $port,$this->errno, $this->errstr, $timeout);
884 $fp=@fsockopen($server, $port,$this->errno, $this->errstr);
888 if ($timeout>0 && function_exists('stream_set_timeout'))
889 stream_set_timeout($fp, $timeout);
893 $this->errstr='Connect error';
894 $r=new xmlrpcresp(0, $xmlrpcerr['http_error'],$xmlrpcstr['http_error']);
897 // Only create the payload if it was not created previously
898 if(empty($msg->payload))
900 $msg->createPayload();
903 // thanks to Grant Rauscher <grant7@firstworld.net>
908 $credentials='Authorization: Basic ' . base64_encode($username . ':' . $password) . "\r\n";
911 $op= "POST " . $this->path. " HTTP/1.0\r\n" .
912 "User-Agent: " . $xmlrpcName . " " . $xmlrpcVersion . "\r\n" .
913 "Host: ". $server . "\r\n" .
915 "Accept-Charset: " . $xmlrpc_defencoding . "\r\n" .
916 "Content-Type: text/xml\r\nContent-Length: " .
917 strlen($msg->payload) . "\r\n\r\n" .
920 if (!fputs($fp, $op, strlen($op)))
922 $this->errstr='Write error';
923 $r=new xmlrpcresp(0, $xmlrpcerr['http_error'], $xmlrpcstr['http_error']);
926 $resp=$msg->parseResponseFile($fp);
931 // contributed by Justin Miller <justin@voxel.net>
932 // requires curl to be built into PHP
933 function sendPayloadHTTPS($msg, $server, $port, $timeout=0,$username='', $password='', $cert='',$certpass='')
935 global $xmlrpcerr, $xmlrpcstr, $xmlrpcVersion, $xmlrpc_internalencoding;
941 // Only create the payload if it was not created previously
942 if(empty($msg->payload))
944 $msg->createPayload();
947 if (!function_exists('curl_init'))
949 $this->errstr='SSL unavailable on this install';
950 $r=new xmlrpcresp(0, $xmlrpcerr['no_ssl'], $xmlrpcstr['no_ssl']);
954 $curl = curl_init('https://' . $server . ':' . $port . $this->path);
956 curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
957 // results into variable
960 curl_setopt($curl, CURLOPT_VERBOSE, 1);
962 curl_setopt($curl, CURLOPT_USERAGENT, 'PHP XMLRPC '.$xmlrpcVersion);
963 // required for XMLRPC
964 curl_setopt($curl, CURLOPT_POST, 1);
966 curl_setopt($curl, CURLOPT_POSTFIELDS, $msg->payload);
968 curl_setopt($curl, CURLOPT_HEADER, 1);
969 // return the header too
970 curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/xml', 'Accept-Charset: '.$xmlrpc_internalencoding));
971 // whether to verify remote host's cert
972 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verifypeer);
973 // whether to verify cert's common name (CN); 0 for no, 1 to verify that it exists, and 2 to verify that it matches the hostname used
974 curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $this->verifyhost);
975 // required for XMLRPC
978 curl_setopt($curl, CURLOPT_TIMEOUT, $timeout == 1 ? 1 : $timeout - 1);
981 if ($username && $password)
983 curl_setopt($curl, CURLOPT_USERPWD,"$username:$password");
988 curl_setopt($curl, CURLOPT_SSLCERT, $cert);
993 curl_setopt($curl, CURLOPT_SSLCERTPASSWD,$certpass);
997 $result = curl_exec($curl);
1001 $this->errstr='no response';
1002 $resp=new xmlrpcresp(0, $xmlrpcerr['curl_fail'], $xmlrpcstr['curl_fail']. ': '. curl_error($curl));
1008 $resp = $msg->parseResponse($result);
1013 function multicall($msgs, $timeout=0, $method='http')
1017 if (! $this->no_multicall)
1019 $results = $this->_try_multicall($msgs, $timeout, $method);
1020 /* TODO - this is not php3-friendly */
1021 // if($results !== false)
1022 if(is_array($results))
1024 // Either the system.multicall succeeded, or the send
1025 // failed (e.g. due to HTTP timeout). In either case,
1026 // we're done for now.
1031 // system.multicall unsupported by server,
1032 // don't try it next time...
1033 $this->no_multicall = true;
1037 // system.multicall is unupported by server:
1038 // Emulate multicall via multiple requests
1040 //foreach($msgs as $msg)
1042 while(list(,$msg) = @each($msgs))
1044 $results[] = $this->send($msg, $timeout, $method);
1049 // Attempt to boxcar $msgs via system.multicall.
1050 function _try_multicall($msgs, $timeout, $method)
1052 // Construct multicall message
1054 //foreach($msgs as $msg)
1056 while(list(,$msg) = @each($msgs))
1058 $call['methodName'] = new xmlrpcval($msg->method(),'string');
1059 $numParams = $msg->getNumParams();
1061 for ($i = 0; $i < $numParams; $i++)
1063 $params[$i] = $msg->getParam($i);
1065 $call['params'] = new xmlrpcval($params, 'array');
1066 $calls[] = new xmlrpcval($call, 'struct');
1068 $multicall = new xmlrpcmsg('system.multicall');
1069 $multicall->addParam(new xmlrpcval($calls, 'array'));
1072 $result = $this->send($multicall, $timeout, $method);
1073 if(!is_object($result))
1075 return ($result || 0); // transport failed
1078 if($result->faultCode() != 0)
1080 return false; // system.multicall failed
1083 // Unpack responses.
1084 $rets = $result->value();
1085 if($rets->kindOf() != 'array')
1087 return false; // bad return type from system.multicall
1089 $numRets = $rets->arraysize();
1090 if($numRets != count($msgs))
1092 return false; // wrong number of return values.
1095 $response = array();
1096 for ($i = 0; $i < $numRets; $i++)
1098 $val = $rets->arraymem($i);
1099 switch ($val->kindOf())
1102 if($val->arraysize() != 1)
1104 return false; // Bad value
1106 // Normal return value
1107 $response[$i] = new xmlrpcresp($val->arraymem(0));
1110 $code = $val->structmem('faultCode');
1111 if($code->kindOf() != 'scalar' || $code->scalartyp() != 'int')
1115 $str = $val->structmem('faultString');
1116 if($str->kindOf() != 'scalar' || $str->scalartyp() != 'string')
1120 $response[$i] = new xmlrpcresp(0, $code->scalarval(), $str->scalarval());
1128 } // end class xmlrpc_client
1135 var $hdrs = array();
1137 function xmlrpcresp($val, $fcode = 0, $fstr = '')
1142 $this->errno = $fcode;
1143 $this->errstr = $fstr;
1144 //$this->errstr = htmlspecialchars($fstr); // XXX: encoding probably shouldn't be done here; fix later.
1146 elseif (!is_object($val))
1149 error_log("Invalid type '" . gettype($val) . "' (value: $val) passed to xmlrpcresp. Defaulting to empty value.");
1150 $this->val = new xmlrpcval();
1159 function faultCode()
1161 return $this->errno;
1164 function faultString()
1166 return $this->errstr;
1174 function serialize()
1176 global $xmlrpc_defencoding;
1177 $result = "<methodResponse>\n";
1180 // G. Giunta 2005/2/13: let non-ASCII response messages be tolerated by clients
1185 <name>faultCode</name>
1186 <value><int>' . $this->errno . '</int></value>
1189 <name>faultString</name>
1190 <value><string>' . mb_convert_encoding(xmlrpc_encode_entitites($this->errstr), $xmlrpc_defencoding, _CHARSET) . '</string></value>
1198 $result .= "<params>\n<param>\n" .
1199 $this->val->serialize() .
1200 "</param>\n</params>";
1202 $result .= "\n</methodResponse>";
1211 var $params=array();
1214 function xmlrpcmsg($meth, $pars=0)
1216 $this->methodname=$meth;
1217 if (is_array($pars) && sizeof($pars)>0)
1219 for($i=0; $i<sizeof($pars); $i++)
1221 $this->addParam($pars[$i]);
1226 function xml_header()
1228 return "<?xml version=\"1.0\"?" . ">\n<methodCall>\n";
1231 function xml_footer()
1233 return "</methodCall>\n";
1236 function createPayload()
1238 $this->payload=$this->xml_header();
1239 $this->payload.='<methodName>' . $this->methodname . "</methodName>\n";
1240 // if (sizeof($this->params)) {
1241 $this->payload.="<params>\n";
1242 for($i=0; $i<sizeof($this->params); $i++)
1244 $p=$this->params[$i];
1245 $this->payload.="<param>\n" . $p->serialize() .
1248 $this->payload.="</params>\n";
1250 $this->payload.=$this->xml_footer();
1251 //$this->payload=str_replace("\n", "\r\n", $this->payload);
1254 function method($meth='')
1258 $this->methodname=$meth;
1260 return $this->methodname;
1263 function serialize()
1265 $this->createPayload();
1266 return $this->payload;
1269 function addParam($par) { $this->params[]=$par; }
1270 function getParam($i) { return $this->params[$i]; }
1271 function getNumParams() { return sizeof($this->params); }
1273 function parseResponseFile($fp)
1276 while($data=fread($fp, 32768))
1280 return $this->parseResponse($ipd);
1283 function parseResponse($data='')
1285 global $_xh,$xmlrpcerr,$xmlrpcstr;
1286 global $xmlrpc_defencoding, $xmlrpc_internalencoding;
1291 //by maHo, replaced htmlspecialchars with htmlentities
1292 print "<PRE>---GOT---\n" . htmlentities($data) . "\n---END---\n</PRE>";
1297 error_log('No response received from server.');
1298 $r = new xmlrpcresp(0, $xmlrpcerr['no_data'], $xmlrpcstr['no_data']);
1301 // see if we got an HTTP 200 OK, else bomb
1302 // but only do this if we're using the HTTP protocol.
1303 if(ereg("^HTTP",$data))
1305 // Strip HTTP 1.1 100 Continue header if present
1306 while (ereg('^HTTP/1.1 1[0-9]{2}', $data))
1308 $pos = strpos($data, 'HTTP', 12);
1309 // server sent a Continue header without any (valid) content following...
1310 // give the client a chance to know it
1311 if (!$pos && !is_int($pos)) // works fine in php 3, 4 and 5
1313 $data = substr($data, $pos);
1315 if (!ereg("^HTTP/[0-9\\.]+ 200 ", $data))
1317 $errstr= substr($data, 0, strpos($data, "\n")-1);
1318 error_log('HTTP error, got response: ' .$errstr);
1319 $r=new xmlrpcresp(0, $xmlrpcerr['http_error'], $xmlrpcstr['http_error']. ' (' . $errstr . ')');
1323 $parser = xml_parser_create($xmlrpc_defencoding);
1325 // G. Giunta 2004/04/06
1326 // Clean up the accumulator, or it will grow indefinitely long
1327 // if making xmlrpc calls for a while
1329 $_xh[$parser]=array();
1330 $_xh[$parser]['headers'] = array();
1332 // separate HTTP headers from data
1333 if (ereg("^HTTP", $data))
1335 // be tolerant to usage of \n instead of \r\n to separate headers and data
1336 // (even though it is not valid http)
1337 $pos = strpos($data,"\r\n\r\n");
1338 if($pos || is_int($pos))
1342 $pos = strpos($data,"\n\n");
1343 if($pos || is_int($pos))
1347 // No separation between response headers and body: fault?
1351 // be tolerant to line endings, and extra empty lines
1352 $ar = split("\r?\n", trim(substr($data, 0, $pos)));
1353 while (list(,$line) = @each($ar))
1355 // take care of multi-line headers
1356 $arr = explode(':',$line);
1359 $header_name = trim($arr[0]);
1360 // TO DO: some headers (the ones that allow a CSV list of values)
1361 // do allow many values to be passed using multiple header lines.
1362 // We should add content to $_xh[$parser]['headers'][$header_name]
1363 // instead of replacing it for those...
1364 $_xh[$parser]['headers'][$header_name] = $arr[1];
1365 for ($i = 2; $i < count($arr); $i++)
1367 $_xh[$parser]['headers'][$header_name] .= ':'.$arr[$i];
1369 $_xh[$parser]['headers'][$header_name] = trim($_xh[$parser]['headers'][$header_name]);
1370 } else if (isset($header_name))
1372 $_xh[$parser]['headers'][$header_name] .= ' ' . trim($line);
1375 $data = substr($data, $bd);
1377 if ($this->debug && count($_xh[$parser]['headers']))
1380 //foreach ($_xh[$parser]['headers'] as $header)
1381 @reset($_xh[$parser]['headers']);
1382 while(list($header, $value) = @each($_xh[$parser]['headers']))
1384 print "HEADER: $header: $value\n";
1390 // be tolerant of extra whitespace in response body
1391 $data = trim($data);
1393 // be tolerant of junk after methodResponse (e.g. javascript automatically inserted by free hosts)
1394 // idea from Luca Mariano <luca.mariano@email.it> originally in PEARified version of the lib
1396 $pos = strpos($data, "</methodResponse>");
1397 while ($pos || is_int($pos))
1400 $pos = strpos($data, "</methodResponse>", $bd);
1403 $data = substr($data, 0, $bd);
1405 $_xh[$parser]['st']='';
1406 $_xh[$parser]['cm']=0;
1407 $_xh[$parser]['isf']=0;
1408 $_xh[$parser]['ac']='';
1409 $_xh[$parser]['qt']='';
1411 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true);
1412 // G. Giunta 2005/02/13: PHP internally uses ISO-8859-1, so we have to tell
1413 // the xml parser to give us back data in the expected charset
1414 xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $xmlrpc_internalencoding);
1416 xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee');
1417 xml_set_character_data_handler($parser, 'xmlrpc_cd');
1418 xml_set_default_handler($parser, 'xmlrpc_dh');
1419 //$xmlrpc_value=new xmlrpcval;
1421 if (!xml_parse($parser, $data, sizeof($data)))
1423 // thanks to Peter Kocks <peter.kocks@baygate.com>
1424 if((xml_get_current_line_number($parser)) == 1)
1426 $errstr = 'XML error at line 1, check URL';
1430 $errstr = sprintf('XML error: %s at line %d',
1431 xml_error_string(xml_get_error_code($parser)),
1432 xml_get_current_line_number($parser));
1435 $r=new xmlrpcresp(0, $xmlrpcerr['invalid_return'], $xmlrpcstr['invalid_return'].' ('.$errstr.')');
1436 xml_parser_free($parser);
1439 $r->hdrs = $_xh[$parser]['headers'];
1442 xml_parser_free($parser);
1445 print "<PRE>---EVALING---[" .
1446 strlen($_xh[$parser]['st']) . " chars]---\n" .
1447 htmlspecialchars($_xh[$parser]['st']) . ";\n---END---</PRE>";
1449 if (strlen($_xh[$parser]['st'])==0)
1451 // then something odd has happened
1452 // and it's time to generate a client side error
1453 // indicating something odd went on
1454 $r=new xmlrpcresp(0, $xmlrpcerr['invalid_return'],
1455 $xmlrpcstr['invalid_return']);
1460 @eval('$v=' . $_xh[$parser]['st'] . '; $allOK=1;');
1463 $r = new xmlrpcresp(0, $xmlrpcerr['invalid_return'], $xmlrpcstr['invalid_return']);
1466 if ($_xh[$parser]['isf'])
1468 $errno_v = $v->structmem('faultCode');
1469 $errstr_v = $v->structmem('faultString');
1470 $errno = $errno_v->scalarval();
1474 // FAULT returned, errno needs to reflect that
1478 $r = new xmlrpcresp($v, $errno, $errstr_v->scalarval());
1482 $r=new xmlrpcresp($v);
1486 $r->hdrs = $_xh[$parser]['headers'];
1496 function xmlrpcval($val=-1, $type='')
1498 global $xmlrpcTypes;
1501 if ($val!=-1 || !is_int($val) || $type!='')
1507 if ($xmlrpcTypes[$type]==1)
1509 $this->addScalar($val,$type);
1511 elseif ($xmlrpcTypes[$type]==2)
1513 $this->addArray($val);
1515 elseif ($xmlrpcTypes[$type]==3)
1517 $this->addStruct($val);
1522 function addScalar($val, $type='string')
1524 global $xmlrpcTypes, $xmlrpcBoolean, $xmlrpc_defencoding;
1526 if ($this->mytype==1)
1528 echo '<B>xmlrpcval</B>: scalar can have only one value<BR>';
1531 $typeof=$xmlrpcTypes[$type];
1534 echo '<B>xmlrpcval</B>: not a scalar type (${typeof})<BR>';
1538 if ($type==$xmlrpcBoolean)
1540 if (strcasecmp($val,'true')==0 || $val==1 || ($val==true && strcasecmp($val,'false')))
1550 if ($this->mytype==2)
1552 // we're adding to an array here
1553 $ar=$this->me['array'];
1554 $ar[]=new xmlrpcval($val, $type);
1555 $this->me['array']=$ar;
1559 // a scalar, so set the value and remember we're scalar
1560 $this->me[$type]=$val;
1561 $this->mytype=$typeof;
1566 function addArray($vals)
1568 global $xmlrpcTypes;
1569 if ($this->mytype!=0)
1571 echo '<B>xmlrpcval</B>: already initialized as a [' . $this->kindOf() . ']<BR>';
1575 $this->mytype=$xmlrpcTypes['array'];
1576 $this->me['array']=$vals;
1580 function addStruct($vals)
1582 global $xmlrpcTypes;
1583 if ($this->mytype!=0)
1585 echo '<B>xmlrpcval</B>: already initialized as a [' . $this->kindOf() . ']<BR>';
1588 $this->mytype=$xmlrpcTypes['struct'];
1589 $this->me['struct']=$vals;
1596 while ( list( $key, $val ) = each( $ar ) )
1598 echo "$key => $val<br>";
1599 if ($key == 'array')
1601 while ( list( $key2, $val2 ) = each( $val ) )
1603 echo "-- $key2 => $val2<br>";
1611 switch($this->mytype)
1627 function serializedata($typ, $val)
1630 global $xmlrpcTypes, $xmlrpcBase64, $xmlrpcString,
1631 $xmlrpcBoolean, $xmlrpc_defencoding;
1632 switch(@$xmlrpcTypes[$typ])
1638 while(list($key2, $val2)=each($val))
1640 $rs.="<member><name>${key2}</name>\n";
1641 $rs.=$this->serializeval($val2);
1648 $rs.="<array>\n<data>\n";
1649 for($i=0; $i<sizeof($val); $i++)
1651 $rs.=$this->serializeval($val[$i]);
1653 $rs.="</data>\n</array>";
1659 $rs.="<${typ}>" . base64_encode($val) . "</${typ}>";
1661 case $xmlrpcBoolean:
1662 $rs.="<${typ}>" . ($val ? '1' : '0') . "</${typ}>";
1665 // G. Giunta 2005/2/13: do NOT use htmlentities, since
1666 // it will produce named html entities, which are invalid xml
1667 // $rs.="<${typ}>" . xmlrpc_encode_entitites($val). "</${typ}>";
1668 // $rs.="<${typ}>" . htmlentities($val). "</${typ}>";
1670 // N. Leenheer 2005/6/30: Use CDATA instead...
1671 $rs.="<${typ}><![CDATA[" . mb_convert_encoding($val, $xmlrpc_defencoding, _CHARSET). "]]></${typ}>";
1674 $rs.="<${typ}>${val}</${typ}>";
1683 function serialize()
1685 return $this->serializeval($this);
1688 function serializeval($o)
1690 //global $xmlrpcTypes;
1694 list($typ, $val) = each($ar);
1696 $rs.=$this->serializedata($typ, $val);
1701 function structmem($m)
1703 $nv=$this->me['struct'][$m];
1707 function structreset()
1709 reset($this->me['struct']);
1712 function structeach()
1714 return each($this->me['struct']);
1720 global $xmlrpcBoolean, $xmlrpcBase64;
1722 list($a,$b)=each($this->me);
1723 // contributed by I Sofer, 2001-03-24
1724 // add support for nested arrays to scalarval
1725 // i've created a new method here, so as to
1726 // preserve back compatibility
1731 while(list($id,$cont) = @each($b))
1733 $b[$id] = $cont->scalarval();
1737 // add support for structures directly encoding php objects
1740 $t = get_object_vars($b);
1742 while(list($id,$cont) = @each($t))
1744 $t[$id] = $cont->scalarval();
1747 while(list($id,$cont) = @each($t))
1749 eval('$b->'.$id.' = $cont;');
1756 function scalarval()
1758 //global $xmlrpcBoolean, $xmlrpcBase64;
1760 list($a,$b)=each($this->me);
1764 function scalartyp()
1766 global $xmlrpcI4, $xmlrpcInt;
1768 list($a,$b)=each($this->me);
1776 function arraymem($m)
1778 $nv=$this->me['array'][$m];
1782 function arraysize()
1785 list($a,$b)=each($this->me);
1791 function iso8601_encode($timet, $utc=0)
1793 // return an ISO8601 encoded string
1794 // really, timezones ought to be supported
1795 // but the XML-RPC spec says:
1797 // "Don't assume a timezone. It should be specified by the server in its
1798 // documentation what assumptions it makes about timezones."
1800 // these routines always assume localtime unless
1801 // $utc is set to 1, in which case UTC is assumed
1802 // and an adjustment for locale is made when encoding
1805 $t=strftime("%Y%m%dT%H:%M:%S", $timet);
1809 if (function_exists('gmstrftime'))
1811 // gmstrftime doesn't exist in some versions
1813 $t=gmstrftime("%Y%m%dT%H:%M:%S", $timet);
1817 $t=strftime("%Y%m%dT%H:%M:%S", $timet-date('Z'));
1823 function iso8601_decode($idate, $utc=0)
1825 // return a timet in the localtime, or UTC
1827 if (ereg("([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})", $idate, $regs))
1831 $t=gmmktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);
1835 $t=mktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);
1841 /****************************************************************
1842 * xmlrpc_decode takes a message in PHP xmlrpc object format and *
1843 * tranlates it into native PHP types. *
1845 * author: Dan Libby (dan@libby.com) *
1846 ****************************************************************/
1847 function php_xmlrpc_decode($xmlrpc_val)
1849 $kind = $xmlrpc_val->kindOf();
1851 if($kind == 'scalar')
1853 return $xmlrpc_val->scalarval();
1855 elseif($kind == 'array')
1857 $size = $xmlrpc_val->arraysize();
1860 for($i = 0; $i < $size; $i++)
1862 $arr[] = php_xmlrpc_decode($xmlrpc_val->arraymem($i));
1866 elseif($kind == 'struct')
1868 $xmlrpc_val->structreset();
1871 while(list($key,$value)=$xmlrpc_val->structeach())
1873 $arr[$key] = php_xmlrpc_decode($value);
1879 if(function_exists('xmlrpc_decode'))
1881 define('XMLRPC_EPI_ENABLED','1');
1885 define('XMLRPC_EPI_ENABLED','0');
1886 function xmlrpc_decode($xmlrpc_val)
1888 $kind = $xmlrpc_val->kindOf();
1890 if($kind == 'scalar')
1892 return $xmlrpc_val->scalarval();
1894 elseif($kind == 'array')
1896 $size = $xmlrpc_val->arraysize();
1899 for($i = 0; $i < $size; $i++)
1901 $arr[]=xmlrpc_decode($xmlrpc_val->arraymem($i));
1905 elseif($kind == 'struct')
1907 $xmlrpc_val->structreset();
1910 while(list($key,$value)=$xmlrpc_val->structeach())
1912 $arr[$key] = xmlrpc_decode($value);
1919 /****************************************************************
1920 * xmlrpc_encode takes native php types and encodes them into *
1921 * xmlrpc PHP object format. *
1922 * BUG: All sequential arrays are turned into structs. I don't *
1923 * know of a good way to determine if an array is sequential *
1926 * feature creep -- could support more types via optional type *
1929 * author: Dan Libby (dan@libby.com) *
1930 ****************************************************************/
1931 function php_xmlrpc_encode($php_val)
1934 global $xmlrpcDouble;
1935 global $xmlrpcString;
1936 global $xmlrpcArray;
1937 global $xmlrpcStruct;
1938 global $xmlrpcBoolean;
1940 $type = gettype($php_val);
1941 $xmlrpc_val = new xmlrpcval;
1948 while (list($k,$v) = each($php_val))
1950 $arr[$k] = php_xmlrpc_encode($v);
1952 $xmlrpc_val->addStruct($arr);
1955 $xmlrpc_val->addScalar($php_val, $xmlrpcInt);
1958 $xmlrpc_val->addScalar($php_val, $xmlrpcDouble);
1961 $xmlrpc_val->addScalar($php_val, $xmlrpcString);
1963 // <G_Giunta_2001-02-29>
1964 // Add support for encoding/decoding of booleans, since they are supported in PHP
1966 $xmlrpc_val->addScalar($php_val, $xmlrpcBoolean);
1968 // </G_Giunta_2001-02-29>
1969 // catch "resource", "NULL", "user function", "unknown type"
1970 //case 'unknown type':
1972 // giancarlo pinerolo <ping@alt.it>
1974 // an empty object in case (which is already
1975 // at this point), not a boolean.
1981 if(XMLRPC_EPI_ENABLED == '0')
1983 function xmlrpc_encode($php_val)
1986 global $xmlrpcDouble;
1987 global $xmlrpcString;
1988 global $xmlrpcArray;
1989 global $xmlrpcStruct;
1990 global $xmlrpcBoolean;
1992 $type = gettype($php_val);
1993 $xmlrpc_val = new xmlrpcval;
2000 while (list($k,$v) = each($php_val))
2002 $arr[$k] = xmlrpc_encode($v);
2004 $xmlrpc_val->addStruct($arr);
2007 $xmlrpc_val->addScalar($php_val, $xmlrpcInt);
2010 $xmlrpc_val->addScalar($php_val, $xmlrpcDouble);
2013 $xmlrpc_val->addScalar($php_val, $xmlrpcString);
2015 // <G_Giunta_2001-02-29>
2016 // Add support for encoding/decoding of booleans, since they are supported in PHP
2018 $xmlrpc_val->addScalar($php_val, $xmlrpcBoolean);
2020 // </G_Giunta_2001-02-29>
2021 //case 'unknown type':
2023 // giancarlo pinerolo <ping@alt.it>
2025 // an empty object in case (which is already
2026 // at this point), not a boolean.