first commit
This commit is contained in:
38
var/IXR/Base64.php
Executable file
38
var/IXR/Base64.php
Executable file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace IXR;
|
||||
|
||||
/**
|
||||
* IXR Base64编码
|
||||
*
|
||||
* @package IXR
|
||||
*/
|
||||
class Base64
|
||||
{
|
||||
/**
|
||||
* 编码数据
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private string $data;
|
||||
|
||||
/**
|
||||
* 初始化数据
|
||||
*
|
||||
* @param string $data
|
||||
*/
|
||||
public function __construct(string $data)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取XML数据
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getXml()
|
||||
{
|
||||
return '<base64>' . base64_encode($this->data) . '</base64>';
|
||||
}
|
||||
}
|
||||
199
var/IXR/Client.php
Executable file
199
var/IXR/Client.php
Executable file
@@ -0,0 +1,199 @@
|
||||
<?php
|
||||
|
||||
namespace IXR;
|
||||
|
||||
use Typecho\Http\Client as HttpClient;
|
||||
|
||||
/**
|
||||
* IXR客户端
|
||||
* reload by typecho team(http://www.typecho.org)
|
||||
*
|
||||
* @package IXR
|
||||
*/
|
||||
class Client
|
||||
{
|
||||
/** 默认客户端 */
|
||||
private const DEFAULT_USERAGENT = 'Typecho XML-RPC PHP Library';
|
||||
|
||||
/**
|
||||
* 地址
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private string $url;
|
||||
|
||||
/**
|
||||
* 消息体
|
||||
*
|
||||
* @var Message
|
||||
*/
|
||||
private Message $message;
|
||||
|
||||
/**
|
||||
* 调试开关
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
private bool $debug = false;
|
||||
|
||||
/**
|
||||
* 请求前缀
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
private ?string $prefix;
|
||||
|
||||
/**
|
||||
* @var Error
|
||||
*/
|
||||
private Error $error;
|
||||
|
||||
/**
|
||||
* 客户端构造函数
|
||||
*
|
||||
* @param string $url 服务端地址
|
||||
* @param string|null $prefix
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(
|
||||
string $url,
|
||||
?string $prefix = null
|
||||
) {
|
||||
$this->url = $url;
|
||||
$this->prefix = $prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置调试模式
|
||||
* @deprecated
|
||||
*/
|
||||
public function setDebug()
|
||||
{
|
||||
$this->debug = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行请求
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $args
|
||||
* @return bool
|
||||
*/
|
||||
private function rpcCall(string $method, array $args): bool
|
||||
{
|
||||
$request = new Request($method, $args);
|
||||
$xml = $request->getXml();
|
||||
|
||||
$client = HttpClient::get();
|
||||
if (!$client) {
|
||||
$this->error = new Error(-32300, 'transport error - could not open socket');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$client->setHeader('Content-Type', 'text/xml')
|
||||
->setHeader('User-Agent', self::DEFAULT_USERAGENT)
|
||||
->setData($xml)
|
||||
->send($this->url);
|
||||
} catch (HttpClient\Exception $e) {
|
||||
$this->error = new Error(-32700, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
$contents = $client->getResponseBody();
|
||||
|
||||
if ($this->debug) {
|
||||
echo '<pre>' . htmlspecialchars($contents) . "\n</pre>\n\n";
|
||||
}
|
||||
|
||||
// Now parse what we've got back
|
||||
$this->message = new Message($contents);
|
||||
if (!$this->message->parse()) {
|
||||
// XML error
|
||||
$this->error = new Error(-32700, 'parse error. not well formed');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is the message a fault?
|
||||
if ($this->message->messageType == 'fault') {
|
||||
$this->error = new Error($this->message->faultCode, $this->message->faultString);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Message must be OK
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加前缀
|
||||
* <code>
|
||||
* $rpc->metaWeblog->newPost();
|
||||
* </code>
|
||||
*
|
||||
* @param string $prefix 前缀
|
||||
* @return Client
|
||||
*/
|
||||
public function __get(string $prefix): Client
|
||||
{
|
||||
return new self($this->url, $this->prefix . $prefix . '.');
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加魔术特性
|
||||
* by 70
|
||||
*
|
||||
* @return mixed
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __call($method, $args)
|
||||
{
|
||||
$return = $this->rpcCall($this->prefix . $method, $args);
|
||||
|
||||
if ($return) {
|
||||
return $this->getResponse();
|
||||
} else {
|
||||
throw new Exception($this->getErrorMessage(), $this->getErrorCode());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得返回值
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getResponse()
|
||||
{
|
||||
// methodResponses can only have one param - return that
|
||||
return $this->message->params[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为错误
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isError(): bool
|
||||
{
|
||||
return isset($this->error);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取错误代码
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function getErrorCode(): int
|
||||
{
|
||||
return $this->error->code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取错误消息
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getErrorMessage(): string
|
||||
{
|
||||
return $this->error->message;
|
||||
}
|
||||
}
|
||||
90
var/IXR/Date.php
Executable file
90
var/IXR/Date.php
Executable file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace IXR;
|
||||
|
||||
/**
|
||||
* IXR日期
|
||||
*
|
||||
* @package IXR
|
||||
*/
|
||||
class Date
|
||||
{
|
||||
private string $year;
|
||||
|
||||
private string $month;
|
||||
|
||||
private string $day;
|
||||
|
||||
private string $hour;
|
||||
|
||||
private string $minute;
|
||||
|
||||
private string $second;
|
||||
|
||||
private string $timezone;
|
||||
|
||||
/**
|
||||
* @param int|string $time
|
||||
*/
|
||||
public function __construct($time)
|
||||
{
|
||||
// $time can be a PHP timestamp or an ISO one
|
||||
if (is_numeric($time)) {
|
||||
$this->parseTimestamp(intval($time));
|
||||
} else {
|
||||
$this->parseIso($time);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $timestamp
|
||||
*/
|
||||
private function parseTimestamp(int $timestamp)
|
||||
{
|
||||
$this->year = gmdate('Y', $timestamp);
|
||||
$this->month = gmdate('m', $timestamp);
|
||||
$this->day = gmdate('d', $timestamp);
|
||||
$this->hour = gmdate('H', $timestamp);
|
||||
$this->minute = gmdate('i', $timestamp);
|
||||
$this->second = gmdate('s', $timestamp);
|
||||
$this->timezone = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $iso
|
||||
*/
|
||||
private function parseIso(string $iso)
|
||||
{
|
||||
$this->year = substr($iso, 0, 4);
|
||||
$this->month = substr($iso, 4, 2);
|
||||
$this->day = substr($iso, 6, 2);
|
||||
$this->hour = substr($iso, 9, 2);
|
||||
$this->minute = substr($iso, 12, 2);
|
||||
$this->second = substr($iso, 15, 2);
|
||||
$this->timezone = substr($iso, 17);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getIso(): string
|
||||
{
|
||||
return $this->year . $this->month . $this->day . 'T' . $this->hour . ':' . $this->minute . ':' . $this->second . $this->timezone;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getXml(): string
|
||||
{
|
||||
return '<dateTime.iso8601>' . $this->getIso() . '</dateTime.iso8601>';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return false|int
|
||||
*/
|
||||
public function getTimestamp()
|
||||
{
|
||||
return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year);
|
||||
}
|
||||
}
|
||||
67
var/IXR/Error.php
Executable file
67
var/IXR/Error.php
Executable file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace IXR;
|
||||
|
||||
/**
|
||||
* IXR错误
|
||||
*
|
||||
* @package IXR
|
||||
*/
|
||||
class Error
|
||||
{
|
||||
/**
|
||||
* 错误代码
|
||||
*
|
||||
* @access public
|
||||
* @var integer
|
||||
*/
|
||||
public int $code;
|
||||
|
||||
/**
|
||||
* 错误消息
|
||||
*
|
||||
* @access public
|
||||
* @var string|null
|
||||
*/
|
||||
public ?string $message;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param integer $code 错误代码
|
||||
* @param string $message 错误消息
|
||||
*/
|
||||
public function __construct(int $code, string $message)
|
||||
{
|
||||
$this->code = $code;
|
||||
$this->message = $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取xml
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getXml(): string
|
||||
{
|
||||
return <<<EOD
|
||||
<methodResponse>
|
||||
<fault>
|
||||
<value>
|
||||
<struct>
|
||||
<member>
|
||||
<name>faultCode</name>
|
||||
<value><int>{$this->code}</int></value>
|
||||
</member>
|
||||
<member>
|
||||
<name>faultString</name>
|
||||
<value><string>{$this->message}</string></value>
|
||||
</member>
|
||||
</struct>
|
||||
</value>
|
||||
</fault>
|
||||
</methodResponse>
|
||||
|
||||
EOD;
|
||||
}
|
||||
}
|
||||
12
var/IXR/Exception.php
Executable file
12
var/IXR/Exception.php
Executable file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace IXR;
|
||||
|
||||
/**
|
||||
* IXR异常类
|
||||
*
|
||||
* @package IXR
|
||||
*/
|
||||
class Exception extends \Exception
|
||||
{
|
||||
}
|
||||
25
var/IXR/Hook.php
Executable file
25
var/IXR/Hook.php
Executable file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace IXR;
|
||||
|
||||
use ReflectionMethod;
|
||||
|
||||
/**
|
||||
* hook rpc call
|
||||
*/
|
||||
interface Hook
|
||||
{
|
||||
/**
|
||||
* @param string $methodName
|
||||
* @param ReflectionMethod $reflectionMethod
|
||||
* @param array $parameters
|
||||
* @return mixed
|
||||
*/
|
||||
public function beforeRpcCall(string $methodName, ReflectionMethod $reflectionMethod, array $parameters);
|
||||
|
||||
/**
|
||||
* @param string $methodName
|
||||
* @param mixed $result
|
||||
*/
|
||||
public function afterRpcCall(string $methodName, &$result): void;
|
||||
}
|
||||
218
var/IXR/Message.php
Executable file
218
var/IXR/Message.php
Executable file
@@ -0,0 +1,218 @@
|
||||
<?php
|
||||
|
||||
namespace IXR;
|
||||
|
||||
/**
|
||||
* IXR消息
|
||||
*
|
||||
* @package IXR
|
||||
*/
|
||||
class Message
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public string $message;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public string $messageType; // methodCall / methodResponse / fault
|
||||
|
||||
public int $faultCode;
|
||||
|
||||
public string $faultString;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public string $methodName;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public array $params = [];
|
||||
|
||||
// Current variable stacks
|
||||
private array $arrayStructs = []; // The stack used to keep track of the current array/struct
|
||||
|
||||
private array $arrayStructsTypes = []; // Stack keeping track of if things are structs or array
|
||||
|
||||
private array $currentStructName = []; // A stack as well
|
||||
|
||||
private string $currentTagContents = '';
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
*/
|
||||
public function __construct(string $message)
|
||||
{
|
||||
$this->message = $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function parse(): bool
|
||||
{
|
||||
// first remove the XML declaration
|
||||
$this->message = preg_replace('/<\?xml(.*)?\?' . '>/', '', $this->message);
|
||||
if (trim($this->message) == '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// remove the DOCTYPE, avoid using a regexp, so we can save memory
|
||||
$count = 0;
|
||||
while (true) {
|
||||
// Fail if there is an endless loop
|
||||
if ($count >= 10) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$pos = strpos($this->message, '<!DOCTYPE');
|
||||
if ($pos !== false) {
|
||||
$this->message = substr($this->message, 0, $pos)
|
||||
. substr($this->message, strpos($this->message, '>', $pos) + 1);
|
||||
$count ++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$parser = xml_parser_create();
|
||||
// Set XML parser to take the case of tags in to account
|
||||
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);
|
||||
// Set XML parser callback functions
|
||||
xml_set_object($parser, $this);
|
||||
xml_set_element_handler($parser, [$this, 'tagOpen'], [$this, 'tagClose']);
|
||||
xml_set_character_data_handler($parser, [$this, 'cdata']);
|
||||
if (!xml_parse($parser, $this->message)) {
|
||||
/* die(sprintf('XML error: %s at line %d',
|
||||
xml_error_string(xml_get_error_code($this->parser)),
|
||||
xml_get_current_line_number($this->parser))); */
|
||||
return false;
|
||||
}
|
||||
xml_parser_free($parser);
|
||||
// Grab the error messages, if any
|
||||
if ($this->messageType == 'fault') {
|
||||
$this->faultCode = intval($this->params[0]['faultCode']);
|
||||
$this->faultString = $this->params[0]['faultString'];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $parser
|
||||
* @param string $tag
|
||||
* @param $attr
|
||||
*/
|
||||
private function tagOpen($parser, string $tag, $attr)
|
||||
{
|
||||
switch ($tag) {
|
||||
case 'methodCall':
|
||||
case 'methodResponse':
|
||||
case 'fault':
|
||||
$this->messageType = $tag;
|
||||
break;
|
||||
/* Deal with stacks of arrays and structs */
|
||||
case 'data': // data is to all intents and puposes more interesting than array
|
||||
$this->arrayStructsTypes[] = 'array';
|
||||
$this->arrayStructs[] = [];
|
||||
break;
|
||||
case 'struct':
|
||||
$this->arrayStructsTypes[] = 'struct';
|
||||
$this->arrayStructs[] = [];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $parser
|
||||
* @param string $cdata
|
||||
*/
|
||||
private function cdata($parser, string $cdata)
|
||||
{
|
||||
$this->currentTagContents .= $cdata;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $parser
|
||||
* @param string $tag
|
||||
*/
|
||||
private function tagClose($parser, string $tag)
|
||||
{
|
||||
switch ($tag) {
|
||||
case 'int':
|
||||
case 'i4':
|
||||
$value = (int) trim($this->currentTagContents);
|
||||
$this->currentTagContents = '';
|
||||
break;
|
||||
case 'double':
|
||||
$value = (double) trim($this->currentTagContents);
|
||||
$this->currentTagContents = '';
|
||||
break;
|
||||
case 'string':
|
||||
$value = trim($this->currentTagContents);
|
||||
$this->currentTagContents = '';
|
||||
break;
|
||||
case 'dateTime.iso8601':
|
||||
$value = new Date(trim($this->currentTagContents));
|
||||
// $value = $iso->getTimestamp();
|
||||
$this->currentTagContents = '';
|
||||
break;
|
||||
case 'value':
|
||||
// "If no type is indicated, the type is string."
|
||||
if (trim($this->currentTagContents) != '') {
|
||||
$value = $this->currentTagContents;
|
||||
$this->currentTagContents = '';
|
||||
}
|
||||
break;
|
||||
case 'boolean':
|
||||
$value = (bool) trim($this->currentTagContents);
|
||||
$this->currentTagContents = '';
|
||||
break;
|
||||
case 'base64':
|
||||
$value = base64_decode($this->currentTagContents);
|
||||
$this->currentTagContents = '';
|
||||
break;
|
||||
/* Deal with stacks of arrays and structs */
|
||||
case 'data':
|
||||
case 'struct':
|
||||
$value = array_pop($this->arrayStructs);
|
||||
array_pop($this->arrayStructsTypes);
|
||||
break;
|
||||
case 'member':
|
||||
array_pop($this->currentStructName);
|
||||
break;
|
||||
case 'name':
|
||||
$this->currentStructName[] = trim($this->currentTagContents);
|
||||
$this->currentTagContents = '';
|
||||
break;
|
||||
case 'methodName':
|
||||
$this->methodName = trim($this->currentTagContents);
|
||||
$this->currentTagContents = '';
|
||||
break;
|
||||
}
|
||||
if (isset($value)) {
|
||||
/*
|
||||
if (!is_array($value) && !is_object($value)) {
|
||||
$value = trim($value);
|
||||
}
|
||||
*/
|
||||
if (count($this->arrayStructs) > 0) {
|
||||
// Add value to struct or array
|
||||
if ($this->arrayStructsTypes[count($this->arrayStructsTypes) - 1] == 'struct') {
|
||||
// Add to struct
|
||||
$this->arrayStructs[count($this->arrayStructs) - 1]
|
||||
[$this->currentStructName[count($this->currentStructName) - 1]] = $value;
|
||||
} else {
|
||||
// Add to array
|
||||
$this->arrayStructs[count($this->arrayStructs) - 1][] = $value;
|
||||
}
|
||||
} else {
|
||||
// Just add as a paramater
|
||||
$this->params[] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
120
var/IXR/Pingback.php
Executable file
120
var/IXR/Pingback.php
Executable file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace IXR;
|
||||
|
||||
use Typecho\Common;
|
||||
use Typecho\Http\Client as HttpClient;
|
||||
use Typecho\Http\Client\Exception as HttpException;
|
||||
|
||||
/**
|
||||
* fetch pingback
|
||||
*/
|
||||
class Pingback
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private string $html;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private string $target;
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @param string $target
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct(string $url, string $target)
|
||||
{
|
||||
$client = HttpClient::get();
|
||||
$this->target = $target;
|
||||
|
||||
if (!isset($client)) {
|
||||
throw new Exception('No available http client', 50);
|
||||
}
|
||||
|
||||
try {
|
||||
$client->setTimeout(5)
|
||||
->send($url);
|
||||
} catch (HttpException $e) {
|
||||
throw new Exception('Pingback http error', 50);
|
||||
}
|
||||
|
||||
if ($client->getResponseStatus() != 200) {
|
||||
throw new Exception('Pingback wrong http status', 50);
|
||||
}
|
||||
|
||||
$response = $client->getResponseBody();
|
||||
$encoding = 'UTF-8';
|
||||
$contentType = $client->getResponseHeader('Content-Type');
|
||||
|
||||
if (!empty($contentType) && preg_match("/charset=([_a-z0-9-]+)/i", $contentType, $matches)) {
|
||||
$encoding = strtoupper($matches[1]);
|
||||
} elseif (preg_match("/<meta\s+charset=\"([_a-z0-9-]+)\"/i", $response, $matches)) {
|
||||
$encoding = strtoupper($matches[1]);
|
||||
}
|
||||
|
||||
$this->html = $encoding == 'UTF-8' ? $response : mb_convert_encoding($response, 'UTF-8', $encoding);
|
||||
|
||||
if (
|
||||
!$client->getResponseHeader('X-Pingback') &&
|
||||
!preg_match_all("/<link[^>]*rel=[\"']pingback[\"'][^>]+href=[\"']([^\"']*)[\"'][^>]*>/i", $this->html)
|
||||
) {
|
||||
throw new Exception("Source server doesn't support pingback", 50);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
if (preg_match("/<title>([^<]*?)<\/title>/is", $this->html, $matchTitle)) {
|
||||
return Common::subStr(Common::removeXSS(trim(strip_tags($matchTitle[1]))), 0, 150, '...');
|
||||
}
|
||||
|
||||
return parse_url($this->target, PHP_URL_HOST);
|
||||
}
|
||||
|
||||
/**
|
||||
* get content
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getContent(): string
|
||||
{
|
||||
/** 干掉html tag,只留下<a>*/
|
||||
$text = Common::stripTags($this->html, '<a href="">');
|
||||
|
||||
/** 此处将$target quote,留着后面用*/
|
||||
$pregLink = preg_quote($this->target);
|
||||
|
||||
/** 找出含有target链接的最长的一行作为$finalText*/
|
||||
$finalText = null;
|
||||
$lines = explode("\n", $text);
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
if (null != $line) {
|
||||
if (preg_match("|<a[^>]*href=[\"']{$pregLink}[\"'][^>]*>(.*?)</a>|", $line)) {
|
||||
if (strlen($line) > strlen($finalText)) {
|
||||
/** <a>也要干掉,*/
|
||||
$finalText = Common::stripTags($line);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($finalText)) {
|
||||
throw new Exception("Source page doesn't have target url", 50);
|
||||
}
|
||||
|
||||
return '[...]' . Common::subStr($finalText, 0, 200, '') . '[...]';
|
||||
}
|
||||
}
|
||||
55
var/IXR/Request.php
Executable file
55
var/IXR/Request.php
Executable file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace IXR;
|
||||
|
||||
/**
|
||||
* IXR请求体
|
||||
*
|
||||
* @package IXR
|
||||
*/
|
||||
class Request
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private string $xml;
|
||||
|
||||
/**
|
||||
* @param string $method
|
||||
* @param array $args
|
||||
*/
|
||||
public function __construct(string $method, array $args)
|
||||
{
|
||||
$this->xml = <<<EOD
|
||||
<?xml version="1.0"?>
|
||||
<methodCall>
|
||||
<methodName>{$method}</methodName>
|
||||
<params>
|
||||
|
||||
EOD;
|
||||
foreach ($args as $arg) {
|
||||
$this->xml .= '<param><value>';
|
||||
$v = new Value($arg);
|
||||
$this->xml .= $v->getXml();
|
||||
$this->xml .= "</value></param>\n";
|
||||
}
|
||||
|
||||
$this->xml .= '</params></methodCall>';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getLength(): int
|
||||
{
|
||||
return strlen($this->xml);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getXml(): string
|
||||
{
|
||||
return $this->xml;
|
||||
}
|
||||
}
|
||||
337
var/IXR/Server.php
Executable file
337
var/IXR/Server.php
Executable file
@@ -0,0 +1,337 @@
|
||||
<?php
|
||||
|
||||
namespace IXR;
|
||||
|
||||
use Typecho\Widget\Exception as WidgetException;
|
||||
|
||||
/**
|
||||
* IXR服务器
|
||||
*
|
||||
* @package IXR
|
||||
*/
|
||||
class Server
|
||||
{
|
||||
/**
|
||||
* 回调函数
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private array $callbacks;
|
||||
|
||||
/**
|
||||
* 默认参数
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private array $capabilities;
|
||||
|
||||
/**
|
||||
* @var Hook
|
||||
*/
|
||||
private Hook $hook;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param array $callbacks 回调函数
|
||||
*/
|
||||
public function __construct(array $callbacks = [])
|
||||
{
|
||||
$this->setCapabilities();
|
||||
$this->callbacks = $callbacks;
|
||||
$this->setCallbacks();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认参数
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getCapabilities(): array
|
||||
{
|
||||
return $this->capabilities;
|
||||
}
|
||||
|
||||
/**
|
||||
* 列出所有方法
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function listMethods(): array
|
||||
{
|
||||
// Returns a list of methods - uses array_reverse to ensure user defined
|
||||
// methods are listed before server defined methods
|
||||
return array_reverse(array_keys($this->callbacks));
|
||||
}
|
||||
|
||||
/**
|
||||
* 一次处理多个请求
|
||||
*
|
||||
* @param array $methodcalls
|
||||
* @return array
|
||||
*/
|
||||
public function multiCall(array $methodcalls): array
|
||||
{
|
||||
// See http://www.xmlrpc.com/discuss/msgReader$1208
|
||||
$return = [];
|
||||
foreach ($methodcalls as $call) {
|
||||
$method = $call['methodName'];
|
||||
$params = $call['params'];
|
||||
if ($method == 'system.multicall') {
|
||||
$result = new Error(-32600, 'Recursive calls to system.multicall are forbidden');
|
||||
} else {
|
||||
$result = $this->call($method, $params);
|
||||
}
|
||||
if (is_a($result, 'Error')) {
|
||||
$return[] = [
|
||||
'faultCode' => $result->code,
|
||||
'faultString' => $result->message
|
||||
];
|
||||
} else {
|
||||
$return[] = [$result];
|
||||
}
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $methodName
|
||||
* @return string|Error
|
||||
*/
|
||||
public function methodHelp(string $methodName)
|
||||
{
|
||||
if (!$this->hasMethod($methodName)) {
|
||||
return new Error(-32601, 'server error. requested method ' . $methodName . ' does not exist.');
|
||||
}
|
||||
|
||||
[$object, $method] = $this->callbacks[$methodName];
|
||||
|
||||
try {
|
||||
$ref = new \ReflectionMethod($object, $method);
|
||||
$doc = $ref->getDocComment();
|
||||
|
||||
return $doc ?: '';
|
||||
} catch (\ReflectionException $e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Hook $hook
|
||||
*/
|
||||
public function setHook(Hook $hook)
|
||||
{
|
||||
$this->hook = $hook;
|
||||
}
|
||||
|
||||
/**
|
||||
* 呼叫内部方法
|
||||
*
|
||||
* @param string $methodName 方法名
|
||||
* @param array $args 参数
|
||||
* @return mixed
|
||||
*/
|
||||
private function call(string $methodName, array $args)
|
||||
{
|
||||
if (!$this->hasMethod($methodName)) {
|
||||
return new Error(-32601, 'server error. requested method ' . $methodName . ' does not exist.');
|
||||
}
|
||||
$method = $this->callbacks[$methodName];
|
||||
|
||||
if (!is_callable($method)) {
|
||||
return new Error(
|
||||
-32601,
|
||||
'server error. requested class method "' . $methodName . '" does not exist.'
|
||||
);
|
||||
}
|
||||
|
||||
[$object, $objectMethod] = $method;
|
||||
|
||||
try {
|
||||
$ref = new \ReflectionMethod($object, $objectMethod);
|
||||
$requiredArgs = $ref->getNumberOfRequiredParameters();
|
||||
if (count($args) < $requiredArgs) {
|
||||
return new Error(
|
||||
-32602,
|
||||
'server error. requested class method "' . $methodName . '" require ' . $requiredArgs . ' params.'
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($ref->getParameters() as $key => $parameter) {
|
||||
if ($parameter->hasType() && !settype($args[$key], $parameter->getType()->getName())) {
|
||||
return new Error(
|
||||
-32602,
|
||||
'server error. requested class method "'
|
||||
. $methodName . '" ' . $key . ' param has wrong type.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->hook)) {
|
||||
$result = $this->hook->beforeRpcCall($methodName, $ref, $args);
|
||||
|
||||
if (isset($result)) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
$result = call_user_func_array($method, $args);
|
||||
|
||||
if (isset($this->hook)) {
|
||||
$this->hook->afterRpcCall($methodName, $result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
} catch (\ReflectionException $e) {
|
||||
return new Error(
|
||||
-32601,
|
||||
'server error. requested class method "' . $methodName . '" does not exist.'
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
return new Error(
|
||||
$e->getCode(),
|
||||
$e->getMessage()
|
||||
);
|
||||
} catch (WidgetException $e) {
|
||||
return new Error(
|
||||
-32001,
|
||||
$e->getMessage()
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
return new Error(
|
||||
-32001,
|
||||
'server error. requested class method "' . $methodName . '" failed.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 抛出错误
|
||||
*
|
||||
* @access private
|
||||
* @param integer|Error $error 错误代码
|
||||
* @param string|null $message 错误消息
|
||||
* @return void
|
||||
*/
|
||||
private function error($error, ?string $message = null)
|
||||
{
|
||||
// Accepts either an error object or an error code and message
|
||||
if (!$error instanceof Error) {
|
||||
$error = new Error($error, $message);
|
||||
}
|
||||
|
||||
$this->output($error->getXml());
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出xml
|
||||
*
|
||||
* @access private
|
||||
* @param string $xml 输出xml
|
||||
*/
|
||||
private function output(string $xml)
|
||||
{
|
||||
$xml = '<?xml version="1.0"?>' . "\n" . $xml;
|
||||
$length = strlen($xml);
|
||||
header('Connection: close');
|
||||
header('Content-Length: ' . $length);
|
||||
header('Content-Type: text/xml');
|
||||
header('Date: ' . date('r'));
|
||||
echo $xml;
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否存在方法
|
||||
*
|
||||
* @access private
|
||||
* @param string $method 方法名
|
||||
* @return bool
|
||||
*/
|
||||
private function hasMethod(string $method): bool
|
||||
{
|
||||
return in_array($method, array_keys($this->callbacks));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认参数
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
private function setCapabilities()
|
||||
{
|
||||
// Initialises capabilities array
|
||||
$this->capabilities = [
|
||||
'xmlrpc' => [
|
||||
'specUrl' => 'http://www.xmlrpc.com/spec',
|
||||
'specVersion' => 1
|
||||
],
|
||||
'faults_interop' => [
|
||||
'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',
|
||||
'specVersion' => 20010516
|
||||
],
|
||||
'system.multicall' => [
|
||||
'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',
|
||||
'specVersion' => 1
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认方法
|
||||
*
|
||||
* @access private
|
||||
* @return void
|
||||
*/
|
||||
private function setCallbacks()
|
||||
{
|
||||
$this->callbacks['system.getCapabilities'] = [$this, 'getCapabilities'];
|
||||
$this->callbacks['system.listMethods'] = [$this, 'listMethods'];
|
||||
$this->callbacks['system.multicall'] = [$this, 'multiCall'];
|
||||
$this->callbacks['system.methodHelp'] = [$this, 'methodHelp'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务入口
|
||||
*/
|
||||
public function serve()
|
||||
{
|
||||
$message = new Message(file_get_contents('php://input') ?: '');
|
||||
|
||||
if (!$message->parse()) {
|
||||
$this->error(-32700, 'parse error. not well formed');
|
||||
} elseif ($message->messageType != 'methodCall') {
|
||||
$this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall');
|
||||
}
|
||||
|
||||
$result = $this->call($message->methodName, $message->params);
|
||||
// Is the result an error?
|
||||
if ($result instanceof Error) {
|
||||
$this->error($result);
|
||||
}
|
||||
|
||||
// Encode the result
|
||||
$r = new Value($result);
|
||||
$resultXml = $r->getXml();
|
||||
|
||||
// Create the XML
|
||||
$xml = <<<EOD
|
||||
<methodResponse>
|
||||
<params>
|
||||
<param>
|
||||
<value>
|
||||
$resultXml
|
||||
</value>
|
||||
</param>
|
||||
</params>
|
||||
</methodResponse>
|
||||
|
||||
EOD;
|
||||
|
||||
// Send it
|
||||
$this->output($xml);
|
||||
}
|
||||
}
|
||||
123
var/IXR/Value.php
Executable file
123
var/IXR/Value.php
Executable file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
namespace IXR;
|
||||
|
||||
/**
|
||||
* IXR值
|
||||
*
|
||||
* @package IXR
|
||||
*/
|
||||
class Value
|
||||
{
|
||||
private $data;
|
||||
|
||||
private ?string $type;
|
||||
|
||||
/**
|
||||
* @param mixed $data
|
||||
* @param string|null $type
|
||||
*/
|
||||
public function __construct($data, ?string $type = null)
|
||||
{
|
||||
$this->data = $data;
|
||||
if (!$type) {
|
||||
$type = $this->calculateType();
|
||||
}
|
||||
$this->type = $type;
|
||||
if ($type == 'struct') {
|
||||
/* Turn all the values in the array in to new IXR_Value objects */
|
||||
foreach ($this->data as $key => $value) {
|
||||
$this->data[$key] = new Value($value);
|
||||
}
|
||||
}
|
||||
if ($type == 'array') {
|
||||
for ($i = 0, $j = count($this->data); $i < $j; $i++) {
|
||||
$this->data[$i] = new Value($this->data[$i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getXml(): string
|
||||
{
|
||||
/* Return XML for this value */
|
||||
switch ($this->type) {
|
||||
case 'boolean':
|
||||
return '<boolean>' . (($this->data) ? '1' : '0') . '</boolean>';
|
||||
case 'int':
|
||||
return '<int>' . $this->data . '</int>';
|
||||
case 'double':
|
||||
return '<double>' . $this->data . '</double>';
|
||||
case 'string':
|
||||
return '<string>' . htmlspecialchars($this->data) . '</string>';
|
||||
case 'array':
|
||||
$return = '<array><data>' . "\n";
|
||||
foreach ($this->data as $item) {
|
||||
$return .= ' <value>' . $item->getXml() . "</value>\n";
|
||||
}
|
||||
$return .= '</data></array>';
|
||||
return $return;
|
||||
case 'struct':
|
||||
$return = '<struct>' . "\n";
|
||||
foreach ($this->data as $name => $value) {
|
||||
$return .= " <member><name>$name</name><value>";
|
||||
$return .= $value->getXml() . "</value></member>\n";
|
||||
}
|
||||
$return .= '</struct>';
|
||||
return $return;
|
||||
case 'date':
|
||||
case 'base64':
|
||||
return $this->data->getXml();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private function calculateType(): string
|
||||
{
|
||||
if ($this->data === true || $this->data === false) {
|
||||
return 'boolean';
|
||||
}
|
||||
if (is_integer($this->data)) {
|
||||
return 'int';
|
||||
}
|
||||
if (is_double($this->data)) {
|
||||
return 'double';
|
||||
}
|
||||
// Deal with IXR object types base64 and date
|
||||
if (is_object($this->data) && is_a($this->data, 'IXR_Date')) {
|
||||
return 'date';
|
||||
}
|
||||
if (is_object($this->data) && is_a($this->data, 'IXR_Base64')) {
|
||||
return 'base64';
|
||||
}
|
||||
// If it is a normal PHP object convert it in to a struct
|
||||
if (is_object($this->data)) {
|
||||
$this->data = get_object_vars($this->data);
|
||||
return 'struct';
|
||||
}
|
||||
if (!is_array($this->data)) {
|
||||
return 'string';
|
||||
}
|
||||
/* We have an array - is it an array or a struct ? */
|
||||
if ($this->isStruct($this->data)) {
|
||||
return 'struct';
|
||||
} else {
|
||||
return 'array';
|
||||
}
|
||||
}
|
||||
|
||||
private function isStruct($array): bool
|
||||
{
|
||||
/* Nasty function to check if an array is a struct or not */
|
||||
$expected = 0;
|
||||
foreach ($array as $key => $value) {
|
||||
if ((string)$key != (string)$expected) {
|
||||
return true;
|
||||
}
|
||||
$expected++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user