first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+396
View File
@@ -0,0 +1,396 @@
<?php
namespace PhpXmlRpc\Helper;
use PhpXmlRpc\Exception\ValueErrorException;
use PhpXmlRpc\PhpXmlRpc;
use PhpXmlRpc\Traits\DeprecationLogger;
/**
* @todo implement an interface
*/
class Charset
{
use DeprecationLogger;
// tables used for transcoding different charsets into us-ascii xml
protected $xml_iso88591_Entities = array("in" => array(), "out" => array());
//protected $xml_cp1252_Entities = array('in' => array(), out' => array());
protected $charset_supersets = array(
'US-ASCII' => array('ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', 'ISO-8859-4',
'ISO-8859-5', 'ISO-8859-6', 'ISO-8859-7', 'ISO-8859-8',
'ISO-8859-9', 'ISO-8859-10', 'ISO-8859-11', 'ISO-8859-12',
'ISO-8859-13', 'ISO-8859-14', 'ISO-8859-15', 'UTF-8',
'EUC-JP', 'EUC-', 'EUC-KR', 'EUC-CN',),
);
/** @var Charset $instance */
protected static $instance = null;
/**
* This class is singleton for performance reasons.
*
* @return Charset
*
* @todo should we just make $xml_iso88591_Entities a static variable instead ?
*/
public static function instance()
{
if (self::$instance === null) {
self::$instance = new static();
}
return self::$instance;
}
/**
* Force usage as singleton.
*/
protected function __construct()
{
}
/**
* @param string $tableName
* @return void
*
* @throws ValueErrorException for unsupported $tableName
*
* @todo add support for cp1252 as well as latin-2 .. latin-10
* Optimization creep: instead of building all those tables on load, keep them ready-made php files
* which are not even included until needed
* @todo should we add to the latin-1 table the characters from cp_1252 range, i.e. 128 to 159 ?
* Those will NOT be present in true ISO-8859-1, but will save the unwary windows user from sending junk
* (though no luck when receiving them...)
* Note also that, apparently, while 'ISO/IEC 8859-1' has no characters defined for bytes 128 to 159,
* IANA ISO-8859-1 does have well-defined 'C1' control codes for those - wikipedia's page on latin-1 says:
* "ISO-8859-1 is the IANA preferred name for this standard when supplemented with the C0 and C1 control codes
* from ISO/IEC 6429." Check what mbstring/iconv do by default with those?
*/
protected function buildConversionTable($tableName)
{
switch ($tableName) {
case 'xml_iso88591_Entities':
if (count($this->xml_iso88591_Entities['in'])) {
return;
}
for ($i = 0; $i < 32; $i++) {
$this->xml_iso88591_Entities["in"][] = chr($i);
$this->xml_iso88591_Entities["out"][] = "&#{$i};";
}
/// @todo to be 'print safe', should we encode as well character 127 (DEL) ?
for ($i = 160; $i < 256; $i++) {
$this->xml_iso88591_Entities["in"][] = chr($i);
$this->xml_iso88591_Entities["out"][] = "&#{$i};";
}
break;
/*case 'xml_cp1252_Entities':
if (count($this->xml_cp1252_Entities['in'])) {
return;
}
for ($i = 128; $i < 160; $i++)
{
$this->xml_cp1252_Entities['in'][] = chr($i);
}
$this->xml_cp1252_Entities['out'] = array(
'&#x20AC;', '?', '&#x201A;', '&#x0192;',
'&#x201E;', '&#x2026;', '&#x2020;', '&#x2021;',
'&#x02C6;', '&#x2030;', '&#x0160;', '&#x2039;',
'&#x0152;', '?', '&#x017D;', '?',
'?', '&#x2018;', '&#x2019;', '&#x201C;',
'&#x201D;', '&#x2022;', '&#x2013;', '&#x2014;',
'&#x02DC;', '&#x2122;', '&#x0161;', '&#x203A;',
'&#x0153;', '?', '&#x017E;', '&#x0178;'
);
$this->buildConversionTable('xml_iso88591_Entities');
break;*/
default:
throw new ValueErrorException('Unsupported table: ' . $tableName);
}
}
/**
* Convert a string to the correct XML representation in a target charset.
* This involves:
* - character transformation for all characters which have a different representation in source and dest charsets
* - using 'charset entity' representation for all characters which are outside the target charset
*
* To help correct communication of non-ascii chars inside strings, regardless of the charset used when sending
* requests, parsing them, sending responses and parsing responses, an option is to convert all non-ascii chars
* present in the message into their equivalent 'charset entity'. Charset entities enumerated this way are
* independent of the charset encoding used to transmit them, and all XML parsers are bound to understand them.
*
* Note that when not sending a charset encoding mime type along with http headers, we are bound by RFC 3023 to emit
* strict us-ascii for 'text/xml' payloads (but we should review RFC 7303, which seems to have changed the rules...)
*
* @param string $data
* @param string $srcEncoding
* @param string $destEncoding
* @return string
*
* @todo do a bit of basic benchmarking: strtr vs. str_replace, str_replace vs htmlspecialchars, hand-coded conversion
* vs mbstring when that is enabled
* @todo make use of iconv when it is available and mbstring is not
* @todo support aliases for charset names, eg ASCII, LATIN1, ISO-88591 (see f.e. polyfill-iconv for a list),
* but then take those into account as well in other methods, ie. isValidCharset)
* @todo when converting to ASCII, allow to choose whether to escape the range 0-31,127 (non-print chars) or not
* @todo allow picking different strategies to deal w. invalid chars? eg. source in latin-1 and chars 128-159
* @todo add support for escaping using CDATA sections? (add cdata start and end tokens, replace only ']]>' with ']]]]><![CDATA[>')
*/
public function encodeEntities($data, $srcEncoding = '', $destEncoding = '')
{
if ($srcEncoding == '') {
// lame, but we know no better...
$srcEncoding = PhpXmlRpc::$xmlrpc_internalencoding;
}
if ($destEncoding == '') {
$destEncoding = 'US-ASCII';
}
// in case there is transcoding going on, let's upscale to UTF8
/// @todo we should do this as well when $srcEncoding == $destEncoding and the encoding is not supported by
/// htmlspecialchars
if (!in_array($srcEncoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII')) && $srcEncoding != $destEncoding &&
function_exists('mb_convert_encoding')) {
$data = mb_convert_encoding($data, 'UTF-8', str_replace('US-ASCII', 'ASCII', $srcEncoding));
$srcEncoding = 'UTF-8';
}
$conversion = strtoupper($srcEncoding . '_' . $destEncoding);
// list ordered with (expected) most common scenarios first
switch ($conversion) {
case 'UTF-8_UTF-8':
case 'ISO-8859-1_ISO-8859-1':
case 'US-ASCII_UTF-8':
case 'US-ASCII_US-ASCII':
case 'US-ASCII_ISO-8859-1':
//case 'CP1252_CP1252':
$escapedData = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
break;
case 'UTF-8_US-ASCII':
case 'UTF-8_ISO-8859-1':
// NB: this will choke on invalid UTF-8, going most likely beyond EOF
$escapedData = '';
// be kind to users creating string xml-rpc values out of different php types
$data = (string)$data;
$ns = strlen($data);
for ($nn = 0; $nn < $ns; $nn++) {
$ch = $data[$nn];
$ii = ord($ch);
// 7 bits in 1 byte: 0bbbbbbb (127)
if ($ii < 32) {
if ($conversion == 'UTF-8_US-ASCII') {
$escapedData .= sprintf('&#%d;', $ii);
} else {
$escapedData .= $ch;
}
}
else if ($ii < 128) {
/// @todo shall we replace this with a (supposedly) faster str_replace?
/// @todo to be 'print safe', should we encode as well character 127 (DEL) ?
switch ($ii) {
case 34:
$escapedData .= '&quot;';
break;
case 38:
$escapedData .= '&amp;';
break;
case 39:
$escapedData .= '&apos;';
break;
case 60:
$escapedData .= '&lt;';
break;
case 62:
$escapedData .= '&gt;';
break;
default:
$escapedData .= $ch;
} // switch
} // 11 bits in 2 bytes: 110bbbbb 10bbbbbb (2047)
elseif ($ii >> 5 == 6) {
$b1 = ($ii & 31);
$b2 = (ord($data[$nn + 1]) & 63);
$ii = ($b1 * 64) + $b2;
$escapedData .= sprintf('&#%d;', $ii);
$nn += 1;
} // 16 bits in 3 bytes: 1110bbbb 10bbbbbb 10bbbbbb
elseif ($ii >> 4 == 14) {
$b1 = ($ii & 15);
$b2 = (ord($data[$nn + 1]) & 63);
$b3 = (ord($data[$nn + 2]) & 63);
$ii = ((($b1 * 64) + $b2) * 64) + $b3;
$escapedData .= sprintf('&#%d;', $ii);
$nn += 2;
} // 21 bits in 4 bytes: 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
elseif ($ii >> 3 == 30) {
$b1 = ($ii & 7);
$b2 = (ord($data[$nn + 1]) & 63);
$b3 = (ord($data[$nn + 2]) & 63);
$b4 = (ord($data[$nn + 3]) & 63);
$ii = ((((($b1 * 64) + $b2) * 64) + $b3) * 64) + $b4;
$escapedData .= sprintf('&#%d;', $ii);
$nn += 3;
}
}
// when converting to latin-1, do not be so eager with using entities for characters 160-255
if ($conversion == 'UTF-8_ISO-8859-1') {
$this->buildConversionTable('xml_iso88591_Entities');
$escapedData = str_replace(array_slice($this->xml_iso88591_Entities['out'], 32), array_slice($this->xml_iso88591_Entities['in'], 32), $escapedData);
}
break;
case 'ISO-8859-1_UTF-8':
$escapedData = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
/// @todo if on php >= 8.2, prefer using mbstring or iconv. Also: suppress the warning!
if (function_exists('mb_convert_encoding')) {
$escapedData = mb_convert_encoding($escapedData, 'UTF-8', 'ISO-8859-1');
} else {
$escapedData = utf8_encode($escapedData);
}
break;
case 'ISO-8859-1_US-ASCII':
$this->buildConversionTable('xml_iso88591_Entities');
$escapedData = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
$escapedData = str_replace($this->xml_iso88591_Entities['in'], $this->xml_iso88591_Entities['out'], $escapedData);
break;
/*
case 'CP1252_US-ASCII':
$this->buildConversionTable('xml_cp1252_Entities');
$escapedData = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
$escapedData = str_replace($this->xml_iso88591_Entities']['in'], $this->xml_iso88591_Entities['out'], $escapedData);
$escapedData = str_replace($this->xml_cp1252_Entities['in'], $this->xml_cp1252_Entities['out'], $escapedData);
break;
case 'CP1252_UTF-8':
$this->buildConversionTable('xml_cp1252_Entities');
$escapedData = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
/// @todo we could use real UTF8 chars here instead of xml entities... (note that utf_8 encode all alone will NOT convert them)
$escapedData = str_replace($this->xml_cp1252_Entities['in'], $this->xml_cp1252_Entities['out'], $escapedData);
$escapedData = utf8_encode($escapedData);
break;
case 'CP1252_ISO-8859-1':
$this->buildConversionTable('xml_cp1252_Entities');
$escapedData = str_replace(array('&', '"', "'", '<', '>'), array('&amp;', '&quot;', '&apos;', '&lt;', '&gt;'), $data);
// we might as well replace all funky chars with a '?' here, but we are kind and leave it to the receiving application layer to decide what to do with these weird entities...
$escapedData = str_replace($this->xml_cp1252_Entities['in'], $this->xml_cp1252_Entities['out'], $escapedData);
break;
*/
default:
if (function_exists('mb_convert_encoding')) {
// If reaching where, there are only 2 cases possible: UTF8->XXX or XXX->XXX
// If src is UTF8, we run htmlspecialchars before converting to the target charset, as
// htmlspecialchars has limited charset support, but it groks utf8
if ($srcEncoding === 'UTF-8') {
$data = htmlspecialchars($data, defined('ENT_XML1') ? ENT_XML1 | ENT_QUOTES : ENT_QUOTES, 'UTF-8');
}
if ($srcEncoding !== $destEncoding) {
try {
// php 7.4 and lower: a warning is generated. php 8.0 and up: an Error is thrown. So much for BC...
$data = @mb_convert_encoding($data, str_replace('US-ASCII', 'ASCII', $destEncoding), str_replace('US-ASCII', 'ASCII', $srcEncoding));
} catch (\ValueError $e) {
$data = false;
}
}
if ($data === false) {
$escapedData = '';
$this->getLogger()->error('XML-RPC: ' . __METHOD__ . ": Converting from $srcEncoding to $destEncoding via mbstring: failed...");
} else {
if ($srcEncoding === 'UTF-8') {
$escapedData = $data;
} else {
$escapedData = htmlspecialchars($data, defined('ENT_XML1') ? ENT_XML1 | ENT_QUOTES : ENT_QUOTES, $destEncoding);
}
}
} else {
$escapedData = '';
$this->getLogger()->error('XML-RPC: ' . __METHOD__ . ": Converting from $srcEncoding to $destEncoding: not supported...");
}
}
return $escapedData;
}
/**
* @return string[]
*/
public function knownCharsets()
{
$knownCharsets = array('UTF-8', 'ISO-8859-1', 'US-ASCII');
// Add all charsets which mbstring can handle, but remove junk not found in IANA registry at
// http://www.iana.org/assignments/character-sets/character-sets.xhtml
if (function_exists('mb_list_encodings')) {
$knownCharsets = array_unique(array_merge($knownCharsets, array_diff(mb_list_encodings(), array(
'pass', 'auto', 'wchar', 'BASE64', 'UUENCODE', 'ASCII', 'HTML-ENTITIES', 'Quoted-Printable',
'7bit','8bit', 'byte2be', 'byte2le', 'byte4be', 'byte4le'
))));
}
return $knownCharsets;
}
// *** BC layer ***
/**
* Checks if a given charset encoding is present in a list of encodings or if it is a valid subset of any encoding
* in the list.
* @deprecated kept around for BC, as it is not in use by the lib
*
* @param string $encoding charset to be tested
* @param string|array $validList comma separated list of valid charsets (or array of charsets)
* @return bool
*/
public function isValidCharset($encoding, $validList)
{
$this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
if (is_string($validList)) {
$validList = explode(',', $validList);
}
if (in_array(strtoupper($encoding), $validList)) {
return true;
} else {
if (array_key_exists($encoding, $this->charset_supersets)) {
foreach ($validList as $allowed) {
if (in_array($allowed, $this->charset_supersets[$encoding])) {
return true;
}
}
}
return false;
}
}
/**
* Used only for backwards compatibility (the .inc shims).
* @deprecated
*
* @param string $charset
* @return array
* @throws ValueErrorException for unknown/unsupported charsets
*/
public function getEntities($charset)
{
$this->logDeprecation('Method ' . __METHOD__ . ' is deprecated');
switch ($charset)
{
case 'iso88591':
return $this->xml_iso88591_Entities;
default:
throw new ValueErrorException('Unsupported charset: ' . $charset);
}
}
}
+64
View File
@@ -0,0 +1,64 @@
<?php
namespace PhpXmlRpc\Helper;
use PhpXmlRpc\PhpXmlRpc;
/**
* Helps to convert timestamps to the xml-rpc date format.
*
* Feature creep -- add support for custom TZs
*/
class Date
{
/**
* Given a timestamp, return the corresponding ISO8601 encoded string.
*
* Really, timezones ought to be supported but the XML-RPC spec says:
*
* "Don't assume a timezone. It should be specified by the server in its documentation what assumptions it makes
* about timezones."
*
* This routine always encodes to local time unless $utc is set to 1, in which case UTC output is produced and an
* adjustment for the local timezone's offset is made
*
* @param int|\DateTimeInterface $timet timestamp or datetime
* @param bool|int $utc (0 or 1)
* @return string
*/
public static function iso8601Encode($timet, $utc = 0)
{
if (is_a($timet, 'DateTimeInterface') || is_a($timet, 'DateTime')) {
$timet = $timet->getTimestamp();
}
if (!$utc) {
$t = date('Ymd\TH:i:s', $timet);
} else {
$t = gmdate('Ymd\TH:i:s', $timet);
}
return $t;
}
/**
* Given an ISO8601 date string, return a timestamp in the localtime, or UTC.
*
* @param string $idate
* @param bool|int $utc either 0 (assume date is in local time) or 1 (assume date is in UTC)
*
* @return int (timestamp) 0 if the source string does not match the xml-rpc dateTime format
*/
public static function iso8601Decode($idate, $utc = 0)
{
$t = 0;
if (preg_match(PhpXmlRpc::$xmlrpc_datetime_format, $idate, $regs)) {
if ($utc) {
$t = gmmktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);
} else {
$t = mktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);
}
}
return $t;
}
}
+283
View File
@@ -0,0 +1,283 @@
<?php
namespace PhpXmlRpc\Helper;
use PhpXmlRpc\Exception\HttpException;
use PhpXmlRpc\PhpXmlRpc;
use PhpXmlRpc\Traits\LoggerAware;
class Http
{
use LoggerAware;
/**
* Decode a string that is encoded with "chunked" transfer encoding as defined in rfc2068 par. 19.4.6.
* Code shamelessly stolen from nusoap library by Dietrich Ayala.
* @internal this function will become protected in the future
*
* @param string $buffer the string to be decoded
* @return string
*/
public static function decodeChunked($buffer)
{
// length := 0
$length = 0;
$new = '';
// read chunk-size, chunk-extension (if any) and crlf
// get the position of the linebreak
$chunkEnd = strpos($buffer, "\r\n") + 2;
$temp = substr($buffer, 0, $chunkEnd);
$chunkSize = hexdec(trim($temp));
$chunkStart = $chunkEnd;
while ($chunkSize > 0) {
$chunkEnd = strpos($buffer, "\r\n", $chunkStart + $chunkSize);
// just in case we got a broken connection
if ($chunkEnd == false) {
$chunk = substr($buffer, $chunkStart);
// append chunk-data to entity-body
$new .= $chunk;
$length += strlen($chunk);
break;
}
// read chunk-data and crlf
$chunk = substr($buffer, $chunkStart, $chunkEnd - $chunkStart);
// append chunk-data to entity-body
$new .= $chunk;
// length := length + chunk-size
$length += strlen($chunk);
// read chunk-size and crlf
$chunkStart = $chunkEnd + 2;
$chunkEnd = strpos($buffer, "\r\n", $chunkStart) + 2;
if ($chunkEnd == false) {
break; // just in case we got a broken connection
}
$temp = substr($buffer, $chunkStart, $chunkEnd - $chunkStart);
$chunkSize = hexdec(trim($temp));
$chunkStart = $chunkEnd;
}
return $new;
}
/**
* Parses HTTP an http response's headers and separates them from the body.
*
* @param string $data the http response, headers and body. It will be stripped of headers
* @param bool $headersProcessed when true, we assume that response inflating and dechunking has been already carried out
* @param int $debug when > 0, logs to screen messages detailing info about the parsed data
* @return array with keys 'headers', 'cookies', 'raw_data' and 'status_code'
* @throws HttpException
*
* @todo if $debug is < 0, we could avoid populating 'raw_data' and 'headers' in the returned value - but that would
* be a weird API...
*/
public function parseResponseHeaders(&$data, $headersProcessed = false, $debug = 0)
{
$httpResponse = array('raw_data' => $data, 'headers'=> array(), 'cookies' => array(), 'status_code' => null);
// Support "web-proxy-tunnelling" connections for https through proxies
if (preg_match('/^HTTP\/1\.[0-1] 200 Connection established/', $data)) {
// Look for CR/LF or simple LF as line separator (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;
}
}
if ($bd) {
// this filters out all http headers from proxy. maybe we could take them into account, too?
$data = substr($data, $bd);
} else {
$this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': HTTPS via proxy error, tunnel connection possibly failed');
throw new HttpException(PhpXmlRpc::$xmlrpcstr['http_error'] . ' (HTTPS via proxy error, tunnel connection possibly failed)', PhpXmlRpc::$xmlrpcerr['http_error']);
}
}
// Strip HTTP 1.1 100 Continue header if present
while (preg_match('/^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)) {
/// @todo this construct works fine in php 3, 4 and 5 - 8; would it not be enough to have !== false now ?
break;
}
$data = substr($data, $pos);
}
// When using Curl to query servers using Digest Auth, we get back a double set of http headers.
// Same when following redirects
// We strip out the 1st...
/// @todo we should let the caller know that there was a redirect involved
if ($headersProcessed && preg_match('/^HTTP\/[0-9](?:\.[0-9])? (?:401|30[1278]) /', $data)) {
if (preg_match('/(\r?\n){2}HTTP\/[0-9](?:\.[0-9])? 200 /', $data)) {
$data = preg_replace('/^HTTP\/[0-9](?:\.[0-9])? (?:401|30[1278]) .+?(?:\r?\n){2}(HTTP\/[0-9.]+ 200 )/s', '$1', $data, 1);
}
}
if (preg_match('/^HTTP\/([0-9](?:\.[0-9])?) ([0-9]{3}) /', $data, $matches)) {
$httpResponse['protocol_version'] = $matches[1];
$httpResponse['status_code'] = $matches[2];
}
if ($httpResponse['status_code'] !== '200') {
$errstr = substr($data, 0, strpos($data, "\n") - 1);
$this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': HTTP error, got response: ' . $errstr);
throw new HttpException(PhpXmlRpc::$xmlrpcstr['http_error'] . ' (' . $errstr . ')', PhpXmlRpc::$xmlrpcerr['http_error'], null, $httpResponse['status_code']);
}
// 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?
// we could take some action here instead of going on...
$bd = 0;
}
}
// be tolerant to line endings, and extra empty lines
$ar = preg_split("/\r?\n/", trim(substr($data, 0, $pos)));
foreach ($ar as $line) {
// take care of (multi-line) headers and cookies
$arr = explode(':', $line, 2);
if (count($arr) > 1) {
/// @todo according to https://www.rfc-editor.org/rfc/rfc7230#section-3.2.4, we should reject with error
/// 400 any messages where a space is present between the header name and colon
$headerName = strtolower(trim($arr[0]));
if ($headerName == 'set-cookie') {
$cookie = $arr[1];
// glue together all received cookies, using a comma to separate them (same as php does with getallheaders())
if (isset($httpResponse['headers'][$headerName])) {
$httpResponse['headers'][$headerName] .= ', ' . trim($cookie);
} else {
$httpResponse['headers'][$headerName] = trim($cookie);
}
// parse cookie attributes, in case user wants to correctly honour them
// @todo support for server sending multiple time cookie with same name, but using different PATHs
$cookie = explode(';', $cookie);
foreach ($cookie as $pos => $val) {
$val = explode('=', $val, 2);
$tag = trim($val[0]);
$val = isset($val[1]) ? trim($val[1]) : '';
if ($pos === 0) {
$cookieName = $tag;
// if present, we have strip leading and trailing " chars from $val
if (preg_match('/^"(.*)"$/', $val, $matches)) {
$val = $matches[1];
}
$httpResponse['cookies'][$cookieName] = array('value' => urldecode($val));
} else {
$httpResponse['cookies'][$cookieName][$tag] = $val;
}
}
} else {
/// @todo some other 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 $xmlrpc->_xh['headers'][$headerName] instead of replacing it for those...
$httpResponse['headers'][$headerName] = trim($arr[1]);
}
} elseif (isset($headerName)) {
/// @todo improve this: 1. check that the line starts with a space or tab; 2. according to
/// https://www.rfc-editor.org/rfc/rfc7230#section-3.2.4, we should flat out refuse these messages
$httpResponse['headers'][$headerName] .= ' ' . trim($line);
}
}
$data = substr($data, $bd);
if ($debug && count($httpResponse['headers'])) {
$msg = '';
foreach ($httpResponse['headers'] as $header => $value) {
$msg .= "HEADER: $header: $value\n";
}
foreach ($httpResponse['cookies'] as $header => $value) {
$msg .= "COOKIE: $header={$value['value']}\n";
}
$this->getLogger()->debug($msg);
}
// if CURL was used for the call, http headers have been processed, and dechunking + reinflating have been carried out
if (!$headersProcessed) {
// Decode chunked encoding sent by http 1.1 servers
if (isset($httpResponse['headers']['transfer-encoding']) && $httpResponse['headers']['transfer-encoding'] == 'chunked') {
if (!$data = static::decodeChunked($data)) {
$this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': errors occurred when trying to rebuild the chunked data received from server');
throw new HttpException(PhpXmlRpc::$xmlrpcstr['dechunk_fail'], PhpXmlRpc::$xmlrpcerr['dechunk_fail'], null, $httpResponse['status_code']);
}
}
// Decode gzip-compressed stuff
// code shamelessly inspired from nusoap library by Dietrich Ayala
if (isset($httpResponse['headers']['content-encoding'])) {
$httpResponse['headers']['content-encoding'] = str_replace('x-', '', $httpResponse['headers']['content-encoding']);
if ($httpResponse['headers']['content-encoding'] == 'deflate' || $httpResponse['headers']['content-encoding'] == 'gzip') {
// if decoding works, use it. else assume data wasn't gzencoded
if (function_exists('gzinflate')) {
if ($httpResponse['headers']['content-encoding'] == 'deflate' && $degzdata = @gzuncompress($data)) {
$data = $degzdata;
if ($debug) {
$this->getLogger()->debug("---INFLATED RESPONSE---[" . strlen($data) . " chars]---\n$data\n---END---");
}
} elseif ($httpResponse['headers']['content-encoding'] == 'gzip' && $degzdata = @gzinflate(substr($data, 10))) {
$data = $degzdata;
if ($debug) {
$this->getLogger()->debug("---INFLATED RESPONSE---[" . strlen($data) . " chars]---\n$data\n---END---");
}
} else {
$this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': errors occurred when trying to decode the deflated data received from server');
throw new HttpException(PhpXmlRpc::$xmlrpcstr['decompress_fail'], PhpXmlRpc::$xmlrpcerr['decompress_fail'], null, $httpResponse['status_code']);
}
} else {
$this->getLogger()->error('XML-RPC: ' . __METHOD__ . ': the server sent deflated data. Your php install must have the Zlib extension compiled in to support this.');
throw new HttpException(PhpXmlRpc::$xmlrpcstr['cannot_decompress'], PhpXmlRpc::$xmlrpcerr['cannot_decompress'], null, $httpResponse['status_code']);
}
}
}
} // end of 'if needed, de-chunk, re-inflate response'
return $httpResponse;
}
/**
* Parses one of the http headers which can have a list of values with quality param.
* @see https://www.rfc-editor.org/rfc/rfc7231#section-5.3.1
*
* @param string $header
* @return string[]
*/
public function parseAcceptHeader($header)
{
$accepted = array();
foreach(explode(',', $header) as $c) {
if (preg_match('/^([^;]+); *q=([0-9.]+)/', $c, $matches)) {
$c = $matches[1];
$w = $matches[2];
} else {
$c = preg_replace('/;.*/', '', $c);
$w = 1;
}
$accepted[(trim($c))] = $w;
}
arsort($accepted);
return array_keys($accepted);
}
}
+42
View File
@@ -0,0 +1,42 @@
<?php
namespace PhpXmlRpc\Helper;
/**
* A helper dedicated to support Interoperability features
*/
class Interop
{
/// @todo review - should we use the range -32099 .. -32000 for some server erors?
public static $xmlrpcerr = array(
'unknown_method' => -32601,
'invalid_return' => 2,
'incorrect_params' => -32602,
'introspect_unknown' => -32601, // this shares the same code but has a separate meaning from 'unknown_method'...
'http_error' => -32300,
'no_data' => -32700,
'no_ssl' => -32400,
'curl_fail' => -32400,
'invalid_request' => -32600,
'no_curl' => -32400,
'server_error' => -32500,
'multicall_error' => -32700,
'multicall_notstruct' => -32600,
'multicall_nomethod' => -32601,
'multicall_notstring' => -32600,
'multicall_recursion' => -32603,
'multicall_noparams' => -32602,
'multicall_notarray' => -32600,
'no_http2' => -32400,
'invalid_xml' => -32700,
'xml_not_compliant' => -32700,
'xml_parsing_error' => -32700,
'cannot_decompress' => -32400,
'decompress_fail' => -32300,
'dechunk_fail' => -32300,
'server_cannot_decompress' => -32300,
'server_decompress_fail' => -32300,
);
public static $xmlrpcerruser = -32000;
}
+123
View File
@@ -0,0 +1,123 @@
<?php
namespace PhpXmlRpc\Helper;
/**
* @todo implement an interface
* @todo make constructor private to force users to go through `instance()` ?
*/
class Logger
{
protected static $instance = null;
/**
* This class can be used as singleton, so that later we can move to DI patterns (ish...)
*
* @return Logger
*/
public static function instance()
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
// *** Implement the same interface as PSR/LOG, for the sake of interoperability ***
/**
* NB: unlike other "traditional" loggers, this one echoes to screen the debug messages instead of logging them.
*
* @param string $message
* @param array $context known key: 'encoding'
* @return void
*/
public function debug($message, $context = array())
{
if (isset($context['encoding'])) {
$this->debugMessage($message, $context['encoding']);
} else {
$this->debugMessage($message);
}
}
/**
* Following the general principle of 'never break stdout', the default behaviour
*
* @param string $message
* @param $context
* @return void
*/
public function warning($message, $context = array())
{
$this->errorLog(preg_replace('/^XML-RPC :/', 'XML-RPC Warning: ', $message));
}
/**
* Triggers the writing of a message to php's error log
*
* @param string $message
* @param array $context
* @return void
*/
public function error($message, $context = array())
{
$this->errorLog(preg_replace('/^XML-RPC :/', 'XML-RPC Error: ', $message));
}
// BC interface
/**
* Echoes a debug message, taking care of escaping it when not in console mode.
* NB: if the encoding of the message is not known or wrong, and we are working in web mode, there is no guarantee
* of 100% accuracy, which kind of defeats the purpose of debugging
*
* @param string $message
* @param string $encoding deprecated
* @return void
*
* @internal left in purely for BC
*/
public function debugMessage($message, $encoding = null)
{
// US-ASCII is a warning for PHP and a fatal for HHVM
if ($encoding == 'US-ASCII') {
$encoding = 'UTF-8';
}
if (PHP_SAPI != 'cli') {
$flags = ENT_COMPAT;
// avoid warnings on php < 5.4...
if (defined('ENT_HTML401')) {
$flags = $flags | ENT_HTML401;
}
if (defined('ENT_SUBSTITUTE')) {
$flags = $flags | ENT_SUBSTITUTE;
}
if ($encoding != null) {
print "<PRE>\n".htmlentities($message, $flags, $encoding)."\n</PRE>";
} else {
print "<PRE>\n".htmlentities($message, $flags)."\n</PRE>";
}
} else {
print "\n$message\n";
}
// let the user see this now in case there's a time-out later...
flush();
}
/**
* Writes a message to the error log.
*
* @param string $message
* @return void
*
* @internal left in purely for BC
*/
public function errorLog($message)
{
error_log($message);
}
}
File diff suppressed because it is too large Load Diff