* Add feed widget * add feed render * Add CommentPage widget * New theme (#1390) * 调整忽略目录 * add theme * fix theme scss build Co-authored-by: fen <f3nb0x@gmail.com> * s/is_writeable/is_writable/g * New upgrade method * merge new fixes from master * add pgsql ssl mode support (ref #1600) (#1623) * Feat/code refactor (#1626) * remove all magic methods, add type for class properties * refactor codes * fix all * refactor code * fix type * fix all * fix request is method * fix all * fix router * fix get page * fix 1.3.0 upgrade * [feat] support high resolution avatar * fix types in i18n component * Implement Ctrl+S or Command+S for save draft (#1628) * Implement Ctrl+S or Command+S for save draft * rename * add Typecho.savePost * fix upload file size * add new uploader * replace new uploader * fix textarea change * fix preview * refactor post edit * fix issue * fix page edit --------- Co-authored-by: joyqi <joyqi@segmentfault.com> Co-authored-by: joyqi <magike.net@gmail.com> * fix #1632 * Add svg to image types * Feat/tree pages (#1646) * add tree trait * finish category tree trait * support select fields * fix select fields * refactor admin trait * fix draft status * Add new contents type "revision" * minor refactor * add more tree view abstracts * add tree trait to pages * get ready for tree view pages * improve page edit * fix revision * fix slug * add router params delegate * fix params delegate * fix * fix * fix all * fix all * fix tree * fix page link * fix feed * fix page * fix permalink * fix permalink input * fix offset query * Support IDN (#1629) * Support IDN * use js * Optimize code * Optimize code * fix URL script * remove unnecessary use --------- Co-authored-by: joyqi <joyqi@segmentfault.com> * fix input element * fix #1651, close #1653 * Use json instead of serialize (#1624) * Use json instead of serialize * Fix Upgrade code * add tree trait * finish category tree trait * support select fields * fix select fields * refactor admin trait * fix draft status * Add new contents type "revision" * minor refactor * add more tree view abstracts * add tree trait to pages * get ready for tree view pages * improve page edit * fix revision * fix slug * add router params delegate * fix params delegate * fix * fix * fix all * fix all * fix tree * fix page link * fix feed * fix page * fix permalink * fix permalink input * fix offset query * Fix typo * remove proxy methods * remove unnecessary useage --------- Co-authored-by: joyqi <joyqi@segmentfault.com> Co-authored-by: joyqi <magike.net@gmail.com> * Fix Prevent XSS vulnerability in default theme (#1654) * Fix Prevent XSS vulnerability in default theme * Update var/Typecho/Db/Adapter/Pdo.php * fix the getter --------- Co-authored-by: joyqi <joyqi@segmentfault.com> * add throwCallback to widget response * fix: cut down fields when selecting recent posts * fix typo errors * fix typo errors * fix http client cookie * add throw finish * fix theme lang * fix default theme * fix query * add open graph and twitter card support add canonical link * fix canonical link meta * fix theme classic-22 * remove unnecessary scss file when packaging * init plugin signal * improve: remove feather-icon js file * fix: typo * improve: post detail layout * fix tags saving * improve: nav search * fix: theme screenshot * fix: theme page layout * remove php 7.2/7.3 env --------- Co-authored-by: fen <f3nb0x@gmail.com> Co-authored-by: Lu Fei <52o@qq52o.cn>
338 lines
8.6 KiB
PHP
338 lines
8.6 KiB
PHP
<?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);
|
|
}
|
|
}
|