From 675efe1e431f0ac423f2e4ff8ff63ef99ff16aef Mon Sep 17 00:00:00 2001 From: joyqi Date: Sat, 4 Sep 2021 21:12:14 +0800 Subject: [PATCH] add sandbox --- var/Typecho/Request.php | 37 +++++++++++++++- var/Typecho/Response.php | 70 +++++++++++++++++++++++++++---- var/Typecho/Widget.php | 54 ++++++++++++++++-------- var/Typecho/Widget/Request.php | 5 +-- var/Typecho/Widget/Response.php | 49 +++------------------- var/Typecho/Widget/Sandbox.php | 58 +++++++++++++++++++++++++ var/Typecho/Widget/Terminal.php | 14 +++++++ var/Utils/Upgrade.php | 6 ++- var/Widget/Contents/Post/Edit.php | 3 +- var/Widget/Service.php | 6 ++- var/Widget/XmlRpc.php | 19 +++++---- 11 files changed, 237 insertions(+), 84 deletions(-) create mode 100644 var/Typecho/Widget/Sandbox.php create mode 100644 var/Typecho/Widget/Terminal.php diff --git a/var/Typecho/Request.php b/var/Typecho/Request.php index 736e1b33..ac398baf 100644 --- a/var/Typecho/Request.php +++ b/var/Typecho/Request.php @@ -18,7 +18,15 @@ class Request private static $instance; /** - * 内部参数 + * 沙箱参数 + * + * @access private + * @var Config|null + */ + private $sandbox; + + /** + * 用户参数 * * @access private * @var Config|null @@ -92,6 +100,25 @@ class Request * * @return $this */ + public function beginSandbox(Config $sandbox): Request + { + $this->sandbox = $sandbox; + return $this; + } + + /** + * @return $this + */ + public function endSandbox(): Request + { + $this->sandbox = null; + return $this; + } + + /** + * @param Config $params + * @return $this + */ public function proxy(Config $params): Request { $this->params = $params; @@ -117,6 +144,14 @@ class Request case isset($this->params) && isset($this->params[$key]): $value = $this->params[$key]; break; + case isset($this->sandbox): + if (isset($this->sandbox[$key])) { + $value = $this->sandbox[$key]; + } else { + $value = $default; + $exists = false; + } + break; case isset($_GET[$key]): $value = $_GET[$key]; break; diff --git a/var/Typecho/Response.php b/var/Typecho/Response.php index 14b25273..906fab09 100644 --- a/var/Typecho/Response.php +++ b/var/Typecho/Response.php @@ -2,6 +2,8 @@ namespace Typecho; +use Typecho\Widget\Terminal; + /** * Typecho公用方法 * @@ -107,6 +109,11 @@ class Response */ private $enableAutoSendHeaders = true; + /** + * @var bool + */ + private $sandbox = false; + /** * init responder */ @@ -129,6 +136,24 @@ class Response return self::$instance; } + /** + * @return $this + */ + public function beginSandbox(): Response + { + $this->sandbox = true; + return $this; + } + + /** + * @return $this + */ + public function endSandbox(): Response + { + $this->sandbox = false; + return $this; + } + /** * @param bool $enable */ @@ -154,6 +179,10 @@ class Response */ public function sendHeaders() { + if ($this->sandbox) { + return; + } + header('HTTP/1.1 ' . $this->status . ' ' . self::HTTP_CODE[$this->status], true, $this->status); // set header @@ -177,9 +206,14 @@ class Response /** * respond data + * @throws Terminal */ public function respond() { + if ($this->sandbox) { + throw new Terminal(); + } + if ($this->enableAutoSendHeaders) { $this->sendHeaders(); } @@ -200,7 +234,10 @@ class Response */ public function setStatus(int $code): Response { - $this->status = $code; + if (!$this->sandbox) { + $this->status = $code; + } + return $this; } @@ -213,8 +250,11 @@ class Response */ public function setHeader(string $name, string $value): Response { - $name = str_replace(' ', '-', ucwords(str_replace('-', ' ', $name))); - $this->headers[$name] = $value; + if (!$this->sandbox) { + $name = str_replace(' ', '-', ucwords(str_replace('-', ' ', $name))); + $this->headers[$name] = $value; + } + return $this; } @@ -235,7 +275,10 @@ class Response string $path = '/', string $domain = null ): Response { - $this->cookies[] = [$key, $value, $timeout, $path, $domain]; + if (!$this->sandbox) { + $this->cookies[] = [$key, $value, $timeout, $path, $domain]; + } + return $this; } @@ -247,8 +290,11 @@ class Response */ public function setContentType(string $contentType = 'text/html'): Response { - $this->contentType = $contentType; - $this->setHeader('Content-Type', $this->contentType . '; charset=' . $this->charset); + if (!$this->sandbox) { + $this->contentType = $contentType; + $this->setHeader('Content-Type', $this->contentType . '; charset=' . $this->charset); + } + return $this; } @@ -270,8 +316,11 @@ class Response */ public function setCharset(string $charset): Response { - $this->charset = $charset; - $this->setHeader('Content-Type', $this->contentType . '; charset=' . $this->charset); + if (!$this->sandbox) { + $this->charset = $charset; + $this->setHeader('Content-Type', $this->contentType . '; charset=' . $this->charset); + } + return $this; } @@ -283,7 +332,10 @@ class Response */ public function addResponder(callable $responder): Response { - $this->responders[] = $responder; + if (!$this->sandbox) { + $this->responders[] = $responder; + } + return $this; } } diff --git a/var/Typecho/Widget.php b/var/Typecho/Widget.php index 80cbafc9..c1bcf696 100644 --- a/var/Typecho/Widget.php +++ b/var/Typecho/Widget.php @@ -5,6 +5,7 @@ namespace Typecho; use Typecho\Widget\Helper\EmptyClass; use Typecho\Widget\Request as WidgetRequest; use Typecho\Widget\Response as WidgetResponse; +use Typecho\Widget\Terminal; /** * Typecho组件基类 @@ -117,14 +118,14 @@ abstract class Widget * @param class-string $alias 组件别名 * @param mixed $params 传递的参数 * @param mixed $request 前端参数 - * @param boolean $enableResponse 是否允许http回执 + * @param bool|callable $call 回调 * @return Widget */ public static function widget( string $alias, $params = null, $request = null, - bool $enableResponse = true + $call = true ): Widget { [$className] = explode('@', $alias); $key = Common::nativeClassName($alias); @@ -133,17 +134,36 @@ abstract class Widget $className = self::$widgetAlias[$className]; } - if (!isset(self::$widgetPool[$key])) { - /** 初始化request */ - $requestObject = new WidgetRequest(Request::getInstance(), Config::factory($request)); + $sandbox = false; - /** 初始化response */ - $responseObject = new WidgetResponse(Request::getInstance(), Response::getInstance(), $enableResponse); + if (isset($request) || $call === true || is_callable($call)) { + $sandbox = true; + Request::getInstance()->beginSandbox(new Config($request)); + Response::getInstance()->beginSandbox(); + } - /** 初始化组件 */ - $widget = new $className($requestObject, $responseObject, $params); + if ($sandbox || !isset(self::$widgetPool[$key])) { + $requestObject = new WidgetRequest(Request::getInstance()); + $responseObject = new WidgetResponse(Request::getInstance(), Response::getInstance()); + + try { + $widget = new $className($requestObject, $responseObject, $params); + $widget->execute(); + + if ($sandbox && is_callable($call)) { + call_user_func($call, $widget); + } + } catch (Terminal $e) { + $widget = null; + } finally { + if ($sandbox) { + Response::getInstance()->endSandbox(); + Request::getInstance()->endSandbox(); + + return $widget; + } + } - $widget->execute(); self::$widgetPool[$key] = $widget; } @@ -155,12 +175,12 @@ abstract class Widget * * @param mixed $params * @param mixed $request - * @param bool $enableResponse + * @param mixed $call * @return $this */ - public static function alloc($params = null, $request = null, bool $enableResponse = true): Widget + public static function alloc($params = null, $request = null, $call = true): Widget { - return self::widget(static::class, $params, $request, $enableResponse); + return self::widget(static::class, $params, $request, $call); } /** @@ -169,16 +189,16 @@ abstract class Widget * @param string $alias * @param mixed $params * @param mixed $request - * @param bool $enableResponse + * @param mixed $call * @return $this */ public static function allocWithAlias( string $alias, $params = null, $request = null, - bool $enableResponse = true + $call = true ): Widget { - return self::widget(static::class . '@' . $alias, $params, $request, $enableResponse); + return self::widget(static::class . '@' . $alias, $params, $request, $call); } /** @@ -240,7 +260,7 @@ abstract class Widget * 将类本身赋值 * * @param mixed $variable 变量名 - * @return Widget + * @return $this */ public function to(&$variable): Widget { diff --git a/var/Typecho/Widget/Request.php b/var/Typecho/Widget/Request.php index 751fe396..690cde5c 100644 --- a/var/Typecho/Widget/Request.php +++ b/var/Typecho/Widget/Request.php @@ -45,12 +45,11 @@ class Request /** * @param HttpRequest $request - * @param Config $params */ - public function __construct(HttpRequest $request, Config $params) + public function __construct(HttpRequest $request) { $this->request = $request; - $this->params = $params; + $this->params = new Config(); } /** diff --git a/var/Typecho/Widget/Response.php b/var/Typecho/Widget/Response.php index e87ad7ce..6351f8ea 100644 --- a/var/Typecho/Widget/Response.php +++ b/var/Typecho/Widget/Response.php @@ -21,21 +21,14 @@ class Response */ private $response; - /** - * @var bool - */ - private $enabled; - /** * @param HttpRequest $request * @param HttpResponse $response - * @param bool $enabled */ - public function __construct(HttpRequest $request, HttpResponse $response, bool $enabled = true) + public function __construct(HttpRequest $request, HttpResponse $response) { $this->request = $request; $this->response = $response; - $this->enabled = $enabled; } /** @@ -44,9 +37,7 @@ class Response */ public function setStatus(int $code): Response { - if ($this->enabled) { - $this->response->setStatus($code); - } + $this->response->setStatus($code); return $this; } @@ -57,9 +48,7 @@ class Response */ public function setHeader(string $name, $value): Response { - if ($this->enabled) { - $this->response->setHeader($name, (string)$value); - } + $this->response->setHeader($name, (string)$value); return $this; } @@ -71,9 +60,7 @@ class Response */ public function setCharset(string $charset): Response { - if ($this->enabled) { - $this->response->setCharset($charset); - } + $this->response->setCharset($charset); return $this; } @@ -83,9 +70,7 @@ class Response */ public function setContentType(string $contentType = 'text/html'): Response { - if ($this->enabled) { - $this->response->setContentType($contentType); - } + $this->response->setContentType($contentType); return $this; } @@ -95,10 +80,6 @@ class Response */ public function throwContent(string $content, string $contentType = 'text/html') { - if (!$this->enabled) { - return; - } - $this->response->setContentType($contentType) ->addResponder(function () use ($content) { echo $content; @@ -111,10 +92,6 @@ class Response */ public function throwXml(string $message) { - if (!$this->enabled) { - return; - } - $this->response->setContentType('text/xml') ->addResponder(function () use ($message) { echo 'response->getCharset() . '"?>', @@ -132,10 +109,6 @@ class Response */ public function throwJson($message) { - if (!$this->enabled) { - return; - } - /** 设置http头信息 */ $this->response->setContentType('application/json') ->addResponder(function () use ($message) { @@ -150,10 +123,6 @@ class Response */ public function throwFile($file, ?string $contentType = null) { - if (!$this->enabled) { - return; - } - if (!empty($contentType)) { $this->response->setContentType($contentType); } @@ -173,10 +142,6 @@ class Response */ public function redirect(string $location, bool $isPermanently = false) { - if (!$this->enabled) { - return; - } - $location = Common::safeUrl($location); $this->response->setStatus($isPermanently ? 301 : 302) @@ -192,10 +157,6 @@ class Response */ public function goBack(string $suffix = null, string $default = null) { - if (!$this->enabled) { - return; - } - //获取来源 $referer = $this->request->getReferer(); diff --git a/var/Typecho/Widget/Sandbox.php b/var/Typecho/Widget/Sandbox.php new file mode 100644 index 00000000..9e1502af --- /dev/null +++ b/var/Typecho/Widget/Sandbox.php @@ -0,0 +1,58 @@ +params = $params; + } + + /** + * @param mixed $params + * @return Sandbox + */ + public static function factory($params = null): Sandbox + { + return new self(new Config($params)); + } + + /** + * run function in a sandbox + * + * @param callable $call + * @return mixed + */ + public function run(callable $call) + { + HttpRequest::getInstance()->beginSandbox($this->params); + HttpResponse::getInstance()->beginSandbox(); + + try { + $result = call_user_func($call); + } catch (Terminal $e) { + $result = null; + } finally { + HttpResponse::getInstance()->endSandbox(); + HttpRequest::getInstance()->endSandbox(); + } + + return $result; + } +} diff --git a/var/Typecho/Widget/Terminal.php b/var/Typecho/Widget/Terminal.php new file mode 100644 index 00000000..5ed223a9 --- /dev/null +++ b/var/Typecho/Widget/Terminal.php @@ -0,0 +1,14 @@ +timezone); */ public static function v0_8r10_5_17($db, $options) { - Edit::alloc(null, 'change=' . $options->theme, false)->action(); + Sandbox::factory('change=' . $options->theme) + ->run(function () { + Edit::alloc()->action(); + }); } diff --git a/var/Widget/Contents/Post/Edit.php b/var/Widget/Contents/Post/Edit.php index 8002f1cc..13eccde4 100644 --- a/var/Widget/Contents/Post/Edit.php +++ b/var/Widget/Contents/Post/Edit.php @@ -627,7 +627,8 @@ class Edit extends Contents implements ActionInterface if (!empty($attachments)) { foreach ($attachments as $key => $attachment) { $this->db->query($this->db->update('table.contents')->rows([ - 'parent' => $cid, 'status' => 'publish', + 'parent' => $cid, + 'status' => 'publish', 'order' => $key + 1 ])->where('cid = ? AND type = ?', $attachment, 'attachment')); } diff --git a/var/Widget/Service.php b/var/Widget/Service.php index 1bae1723..6bd806c7 100644 --- a/var/Widget/Service.php +++ b/var/Widget/Service.php @@ -7,6 +7,7 @@ use Typecho\Http\Client; use Typecho\Plugin; use Typecho\Response; use Typecho\Widget\Exception; +use Typecho\Widget\Sandbox; use Widget\Base\Options as BaseOptions; if (!defined('__TYPECHO_ROOT_DIR__')) { @@ -53,7 +54,10 @@ class Service extends BaseOptions implements ActionInterface } /** 获取post */ - $post = Archive::alloc("type=post", "cid={$this->request->cid}", false); + $post = Sandbox::factory("cid={$this->request->cid}") + ->run(function () { + return Archive::alloc('type=post'); + }); if ($post->have() && preg_match_all("|]*href=[\"'](.*?)[\"'][^>]*>(.*?)|", $post->text, $matches)) { $links = array_unique($matches[1]); diff --git a/var/Widget/XmlRpc.php b/var/Widget/XmlRpc.php index 6ff58900..9cecc5f6 100644 --- a/var/Widget/XmlRpc.php +++ b/var/Widget/XmlRpc.php @@ -439,11 +439,13 @@ class XmlRpc extends Contents implements ActionInterface, Hook /** 调用已有组件 */ if ('page' == $type) { - $widget = PageEdit::alloc(null, $input, false); - $widget->writePage(); + $widget = PageEdit::alloc(null, $input, function (PageEdit $page) { + $page->writePage(); + }); } else { - $widget = PostEdit::alloc(null, $input, false); - $widget->writePost(); + $widget = PostEdit::alloc(null, $input, function (PostEdit $post) { + $post->writePost(); + }); } return $widget->cid; @@ -469,8 +471,9 @@ class XmlRpc extends Contents implements ActionInterface, Hook $input['description'] = $category['description'] ?? $category['name']; /** 调用已有组件 */ - $categoryWidget = CategoryEdit::alloc(null, $input, false); - $categoryWidget->insertCategory(); + $categoryWidget = CategoryEdit::alloc(null, $input, function (CategoryEdit $category) { + $category->insertCategory(); + }); return $categoryWidget->mid; } @@ -486,7 +489,9 @@ class XmlRpc extends Contents implements ActionInterface, Hook */ public function wpDeletePage(int $blogId, string $userName, string $password, int $pageId): bool { - PageEdit::alloc(null, ['cid' => $pageId], false)->deletePage(); + PageEdit::alloc(null, ['cid' => $pageId], function (PageEdit $page) { + $page->deletePage(); + }); return true; }