add sandbox

This commit is contained in:
joyqi 2021-09-04 21:12:14 +08:00
parent f40c5c178e
commit 675efe1e43
11 changed files with 237 additions and 84 deletions

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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
{

View File

@ -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();
}
/**

View File

@ -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 '<?xml version="1.0" encoding="' . $this->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();

View File

@ -0,0 +1,58 @@
<?php
namespace Typecho\Widget;
use Typecho\Config;
use Typecho\Request as HttpRequest;
use Typecho\Response as HttpResponse;
/**
* sandbox env
*/
class Sandbox
{
/**
* @var Config
*/
private $params;
/**
* @param Config $params
*/
public function __construct(Config $params)
{
$this->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;
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace Typecho\Widget;
if (!defined('__TYPECHO_ROOT_DIR__')) {
exit;
}
/**
* Special exception to break executor
*/
class Terminal extends Exception
{
}

View File

@ -5,6 +5,7 @@ namespace Utils;
use Typecho\Common;
use Typecho\Db;
use Typecho\Exception;
use Typecho\Widget\Sandbox;
use Widget\Options;
use Widget\Themes\Edit;
use Widget\Upload;
@ -962,7 +963,10 @@ Typecho_Date::setTimezoneOffset($options->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();
});
}

View File

@ -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'));
}

View File

@ -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("|<a[^>]*href=[\"'](.*?)[\"'][^>]*>(.*?)</a>|", $post->text, $matches)) {
$links = array_unique($matches[1]);

View File

@ -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;
}