From f98eabb2b1ec1e1ce051f63934cc960aaddf5c25 Mon Sep 17 00:00:00 2001 From: Chen Date: Fri, 18 Apr 2025 16:37:37 +0800 Subject: [PATCH 01/18] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=EF=BC=9A=E8=8E=B7=E5=8F=96=E6=A0=87=E7=AD=BE=E3=80=81=E5=8F=91?= =?UTF-8?q?=E8=A1=A8=E8=AF=84=E8=AE=BA=EF=BC=88=E4=BC=A0=E5=8F=82=E6=9C=89?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=EF=BC=89=20=E6=96=B0=E5=A2=9E=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=EF=BC=9A=E7=94=A8=E6=88=B7=E5=88=97=E8=A1=A8=E3=80=81?= =?UTF-8?q?=E5=8F=91=E8=A1=A8=E6=96=87=E7=AB=A0=E3=80=81=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E5=88=86=E7=B1=BB/=E6=A0=87=E7=AD=BE=20=E6=96=B0=E5=A2=9EapiTo?= =?UTF-8?q?ken=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + .travis.yml | 2 +- Action.php | 310 ++++++++++++++++++++++++++++++++++------------------ Plugin.php | 4 + README.md | 135 +++++++++++++++-------- 5 files changed, 295 insertions(+), 157 deletions(-) diff --git a/.gitignore b/.gitignore index 5a11b0d..de499fb 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ vendor/ tmp composer.lock +.idea diff --git a/.travis.yml b/.travis.yml index 10e5a27..428c79e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: php php: - - 7.2 + - 7.4 services: - mysql diff --git a/Action.php b/Action.php index 3691e8e..cb2b4fe 100644 --- a/Action.php +++ b/Action.php @@ -1,4 +1,9 @@ request->getServer('REQUEST_METHOD')) == 'options') { - Typecho_Response::setStatus(204); +// Typecho_Response::setStatus(204); $this->response->setHeader('Access-Control-Allow-Headers', 'Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type'); $this->response->setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); exit; @@ -145,7 +151,7 @@ private function getParams($key, $default = null) */ private function throwError($message = 'unknown', $status = 400) { - Typecho_Response::setStatus($status); +// Typecho_Response::setStatus($status); $this->response->throwJson(array( 'status' => 'error', 'message' => $message, @@ -177,6 +183,7 @@ private function throwData($data) private function lockMethod($method) { $method = strtolower($method); + if (strtolower($this->request->getServer('REQUEST_METHOD')) != $method) { $this->throwError('method not allowed', 405); } @@ -194,6 +201,10 @@ private function checkState($route) if (!$state) { $this->throwError('This API has been disabled.', 403); } + $token = $this->request->getHeader('token'); + if ($token != $this->config->apiToken) { + $this->throwError('apiToken is invalid', 403); + } } /** @@ -270,8 +281,8 @@ public function postsAction() if (count($cids) == 0) { $this->throwData(array( - 'page' => (int) $page, - 'pageSize' => (int) $pageSize, + 'page' => (int)$page, + 'pageSize' => (int)$pageSize, 'pages' => 0, 'count' => 0, 'dataSet' => array(), @@ -317,14 +328,14 @@ public function postsAction() $result[$key] = $this->filter($result[$key]); } else if ($showDigest === 'excerpt') { // if you use 'excerpt', plugin will truncate for certain number of text - $limit = (int) trim($this->getParams('limit', '200')); + $limit = (int)trim($this->getParams('limit', '200')); $result[$key] = $this->filter($result[$key]); $result[$key]['digest'] = mb_substr( - htmlspecialchars_decode(strip_tags($result[$key]['text'])), - 0, - $limit, - 'utf-8' - ) . "..."; + htmlspecialchars_decode(strip_tags($result[$key]['text'])), + 0, + $limit, + 'utf-8' + ) . "..."; } else { $result[$key] = $this->filter($result[$key]); } @@ -336,8 +347,8 @@ public function postsAction() } $this->throwData(array( - 'page' => (int) $page, - 'pageSize' => (int) $pageSize, + 'page' => (int)$page, + 'pageSize' => (int)$pageSize, 'pages' => ceil($count / $pageSize), 'count' => $count, 'dataSet' => $result, @@ -403,15 +414,16 @@ public function tagsAction() $this->lockMethod('get'); $this->checkState('tags'); - $this->widget('Widget_Metas_Tag_Cloud')->to($tags); - - if ($tags->have()) { - while ($tags->next()) { - $this->throwData($tags->stack); - } + $tags = $this->db + ->select('mid', 'name', 'slug', 'description', 'count', 'order', 'parent') + ->from('table.metas') + ->where("type = 'tag'"); + $result = $this->db->fetchAll($tags); + if (count($result) != 0) { + $this->throwData($result); + } else { + $this->throwError('no tag', 404); } - - $this->throwError('no tag', 404); } /** @@ -428,7 +440,7 @@ public function postAction() $cid = $this->getParams('cid', ''); $select = $this->db - ->select('title','cid', 'created', 'type', 'slug', 'commentsNum', 'text') + ->select('title', 'cid', 'created', 'type', 'slug', 'commentsNum', 'text') ->from('table.contents') ->where('password IS NULL'); @@ -533,8 +545,8 @@ public function commentsAction() } $this->throwData(array( - 'page' => (int) $page, - 'pageSize' => (int) $pageSize, + 'page' => (int)$page, + 'pageSize' => (int)$pageSize, 'pages' => ceil($count / $pageSize), 'count' => $count, 'dataSet' => $finalResult, @@ -551,19 +563,20 @@ public function commentAction() $this->lockMethod('post'); $this->checkState('comment'); + $comments = new Comments($this->request, $this->response); + $check_key = [ + 'text', 'mail', 'author', 'token' + ]; + foreach ($check_key as $key) { + if (!$this->getParams($key, '')) { + $this->throwError('missing ' . $key); + } + } + $slug = $this->getParams('slug', ''); $cid = $this->getParams('cid', ''); $token = $this->getParams('token', ''); - // administrator - $uid = $this->getParams('uid', null); - $authCode = $this->getParams('authCode', null); - $cookie = Typecho_Cookie::get('__typecho_uid'); - if (!empty($cookie)) { - $uid = $cookie; - $authCode = Typecho_Cookie::get('__typecho_authCode'); - } - $select = $this->db->select('cid', 'created', 'type', 'slug', 'commentsNum', 'text') ->from('table.contents') ->where('password IS NULL'); @@ -585,77 +598,34 @@ public function commentAction() $this->throwError('token invalid'); } - $commentUrl = Typecho_Router::url( - 'feedback', - array('type' => 'comment', 'permalink' => $result['pathinfo']), - $this->options->index - ); - if (defined('IN_PHPUNIT_SERVER')) { - $commentUrl = str_replace(':' . WEB_SERVER_PORT, ':' . FORKED_WEB_SERVER_PORT, $commentUrl); - } - - $postData = empty($authCode) ? array( + $postData = array( 'text' => $this->getParams('text', ''), - 'author' => $this->getParams('author', ''), 'mail' => $this->getParams('mail', ''), - 'url' => $this->getParams('url', ''), - ) : array( - 'text' => $this->getParams('text'), + 'cid' => $result['cid'], + 'author' => $this->getParams('author', ''), ); - - // Typecho 0.9- has no anti-spam security - if (file_exists(__TYPECHO_ROOT_DIR__ . '/var/Widget/Security.php')) { - $postData['_'] = $this->widget('Widget_Security')->getToken($result['permalink']); - } - $parent = $this->getParams('parent', ''); if (is_numeric($parent)) { $postData['parent'] = $parent; } - - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $commentUrl); - curl_setopt($ch, CURLOPT_HTTPHEADER, array( - 'X-TYPECHO-RESTFUL-IP: ' . $this->request->getIp(), - )); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 2); - curl_setopt($ch, CURLOPT_TIMEOUT, 10); - curl_setopt($ch, CURLOPT_USERAGENT, $this->request->getAgent()); - curl_setopt($ch, CURLOPT_REFERER, $result['permalink']); - curl_setopt($ch, CURLOPT_POST, 1); - curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData)); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // no verify ssl - - if (!empty($authCode)) { - $cookiePrefix = Typecho_Cookie::getPrefix(); - $cookieText = $cookiePrefix . '__typecho_uid=' . $uid . '; ' . $cookiePrefix . '__typecho_authCode=' . $authCode . ';'; - curl_setopt($ch, CURLOPT_COOKIE, $cookieText); + $authorId = $this->getParams('authorId', ''); + if (is_numeric($authorId)) { + $postData['authorId'] = $authorId; } - - $data = curl_exec($ch); - - if (curl_error($ch)) { - $this->throwError('comment failed'); + $ownerId = $this->getParams('ownerId', ''); + if (is_numeric($ownerId)) { + $postData['ownerId'] = $ownerId; } - - curl_close($ch); - - preg_match('!(]*>)(.*)()!i', $data, $matches); - if (isset($matches[2]) && $matches[2] == 'Error') { - preg_match('/
(.*?)<\/div>/s', $data, $matches); - if (isset($matches[1])) { - $this->throwError(trim($matches[1])); - } + $url = $this->getParams('url', ''); + if (is_numeric($url)) { + $postData['url'] = $url; } - - $query = $this->db->select('coid', 'status') + $comments->insert($postData); + $query = $this->db->select() ->from('table.comments') - ->where('text = ?', $text) + ->where('author = ?', $this->getParams('author', '')) ->order('created', Typecho_Db::SORT_DESC); $res = $this->db->fetchRow($query); - $this->throwData($res); } @@ -705,6 +675,9 @@ public function usersAction() $uid = $this->getParams('uid', ''); $name = $this->getParams('name', ''); + if (empty($uid) && empty($name)) { + $this->throwError('uid or name is required'); + } $select = $this->db->select('uid', 'mail', 'url', 'screenName') ->from('table.users'); @@ -776,18 +749,16 @@ public function archivesAction() ); $post = $this->filter($post); - } - else if ($showDigest === 'excerpt') { - $limit = (int) trim($this->getParams('limit', '200')); + } else if ($showDigest === 'excerpt') { + $limit = (int)trim($this->getParams('limit', '200')); $post = $this->filter($post); $post['digest'] = mb_substr( - htmlspecialchars_decode(strip_tags($post['text'])), - 0, - $limit, - 'utf-8' - ) . "..."; - } - else { + htmlspecialchars_decode(strip_tags($post['text'])), + 0, + $limit, + 'utf-8' + ) . "..."; + } else { $post = $this->filter($post); } @@ -800,8 +771,8 @@ public function archivesAction() $month = date('m', $date); $archives[$year] = isset($archives[$year]) ? $archives[$year] : array(); $archives[$year][$month] = isset($archives[$year][$month]) - ? $archives[$year][$month] - : array(); + ? $archives[$year][$month] + : array(); array_push($archives[$year][$month], $post); } @@ -824,6 +795,126 @@ public function archivesAction() )); } + /** + * 获取用户列表 + * + * @return void + */ + public function userListAction() + { + $this->lockMethod('get'); + $this->checkState('userList'); + + $select = $this->db->select('uid', 'mail', 'url', 'screenName') + ->from('table.users'); + $result = $this->db->fetchAll($select); + $this->throwData($result); + } + + /** + * 发表文章 + */ + public function postArticleAction() + { + $this->lockMethod('post'); + $this->checkState('postArticle'); + + $contents = new Contents($this->request, $this->response); + + $check_key = [ + 'title', 'text', 'authorId' + ]; + foreach ($check_key as $key) { + if (!$this->getParams($key, '')) { + $this->throwError('missing ' . $key); + } + } + $title = $this->getParams('title', ''); + $text = $this->getParams('text', ''); + $authorId = $this->getParams('authorId', ''); + $slug = $this->getParams('slug', ''); + $mid = $this->getParams('mid', ''); + try { + $article = $this->db->select('cid', 'created', 'type', 'slug', 'commentsNum', 'text') + ->from('table.contents') + ->where('authorId = ?', $authorId); + if (!empty($slug)) { + $article->where('slug = ?', $slug); + } else { + $article->where('title = ?', $title); + } + $articleData = $this->db->fetchRow($article); + + $postData = [ + 'title' => $title, + 'text' => $text, + 'authorId' => $authorId, + 'slug' => $slug, + ]; + $type = 'add'; + if (!empty($articleData)) { + // 更新 + $postData['modified'] = $this->options->time; + $res = $this->db->query($this->db->sql()->where('cid = ?', $articleData['cid'])->update('table.contents')->rows($postData)); + $cid = $articleData['cid']; + $type = 'update'; + } else { + // 新增 + $res = $cid = $contents->insert($postData); + } + // 分类/标签 + if (!empty($mid)) { + if ($type == 'update') { + $metas = $this->db->fetchAll($this->db->select('mid')->from('table.metas')); + $sql = $this->db->sql() + ->delete('table.relationships') + ->where('cid = ?', $cid); + if (!empty($metas)) { + $sql = $sql->where('mid IN (' . implode(',', array_column($metas, 'mid')) . ')'); + } + $this->db->query($sql); + } + + $midArray = explode(',', $mid); + $values = []; + foreach ($midArray as $mid) { + $values[] = '(' . $cid . ', ' . $mid . ')'; + } + $valuesString = implode(', ', $values); + $sql = "INSERT INTO " . $this->db->getPrefix() . "relationships (`cid`, `mid`) VALUES " . $valuesString . ";"; + $this->db->query($sql); + } + + $this->throwData($res); + } catch (\Typecho\Db\Exception $e) { + $this->throwError($e->getMessage()); + } + } + + /** + * 新增标签/分类 + */ + public function addMetasAction() + { + $this->lockMethod('post'); + $this->checkState('addMetas'); + $name = $this->getParams('name', ''); + $type = $this->getParams('type', ''); + $slug = $this->getParams('slug', ''); + if (empty($name) || empty($type)) { + $this->throwError('missing name or type'); + } + if ($type != 'category' && $type != 'tag') { + $this->throwError('type must be category or tag'); + } + $res = $this->db->query($this->db->insert('table.metas')->rows([ + 'name' => $name, + 'type' => $type, + 'slug' => empty($slug) ? $name : $slug, + ])); + $this->throwData($res); + } + /** * 插件更新接口 * @@ -873,7 +964,7 @@ public function upgradeAction() /** * 构造文章评论关系树 * - * @param array $raw 评论的集合 + * @param array $raw 评论的集合 * @return array 返回构造后的评论关系数组 */ private function buildNodes($comments) @@ -886,7 +977,7 @@ private function buildNodes($comments) $comments[$index]['mailHash'] = md5($comment['mail']); unset($comments[$index]['mail']); // avoid exposing users' email to public - $parent = (int) $comment['parent']; + $parent = (int)$comment['parent']; if ($parent !== 0) { if (!isset($childMap[$parent])) { $childMap[$parent] = array(); @@ -905,8 +996,8 @@ private function buildNodes($comments) * 通过递归构建评论父子关系 * * @param array $comments 评论集合 - * @param array $parents 父评论的 key 集合 - * @param array $map 子评论与父评论的映射关系 + * @param array $parents 父评论的 key 集合 + * @param array $map 子评论与父评论的映射关系 * @return array 返回处理后的结果集合 */ private function recursion($comments, $parents, $map) @@ -915,7 +1006,7 @@ private function recursion($comments, $parents, $map) foreach ($parents as $parent) { $item = &$comments[$parent]; - $coid = (int) $item['coid']; + $coid = (int)$item['coid']; if (isset($map[$coid])) { $item['children'] = $this->recursion($comments, $map[$coid], $map); } else { @@ -932,7 +1023,8 @@ private function recursion($comments, $parents, $map) * @param string|null $content * @return string */ - private function safelyParseMarkdown($content) { + private function safelyParseMarkdown($content) + { if (is_null($content) || empty($content)) { return ''; } diff --git a/Plugin.php b/Plugin.php index a82ae4e..02bf4ab 100644 --- a/Plugin.php +++ b/Plugin.php @@ -91,6 +91,10 @@ public static function config(Typecho_Widget_Helper_Form $form) /* CSRF token salt */ $csrfSalt = new Typecho_Widget_Helper_Form_Element_Text('csrfSalt', null, '05faabd6637f7e30c797973a558d4372', _t('CSRF加密盐'), _t('请务必修改本参数,以防止跨站攻击。')); $form->addInput($csrfSalt); + + /* API token */ + $apiToken = new Typecho_Widget_Helper_Form_Element_Text('apiToken', null, '123456', _t('APITOKEN'), _t('api请求需要携带的token。')); + $form->addInput($apiToken); ?> -Plugin.php Action.php tests + + tests/tmp/* diff --git a/phpunit.xml b/phpunit.xml index 06aac20..ab91570 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -24,7 +24,7 @@ - + diff --git a/tests/Util.php b/tests/Util.php index c2163ff..f2d7a4b 100644 --- a/tests/Util.php +++ b/tests/Util.php @@ -18,7 +18,7 @@ class Util * * @var string */ - private static $tmpPath = __DIR__ . '/../tmp/'; + private static $tmpPath = __DIR__ . '/tmp/'; /** * Typecho根目录 @@ -36,13 +36,11 @@ public static function downloadTypecho() { $progressBar = new Manager(0, 1000); -// $url = 'https://github.com/typecho/typecho/archive/master.tar.gz'; - $url = 'https://github.com/typecho/typecho/releases/latest/download/typecho.zip'; + $url = 'https://github.com/typecho/typecho/archive/refs/tags/v1.2.1.tar.gz'; $fileName = basename($url); $filePath = self::$tmpPath . $fileName; -// self::$typechoDir = self::$tmpPath . 'typecho-master'; - self::$typechoDir = self::$tmpPath; + self::$typechoDir = self::$tmpPath . 'typecho-1.2.1'; if (is_dir(self::$typechoDir)) { echo 'typecho already downloaded' . PHP_EOL; @@ -53,7 +51,8 @@ public static function downloadTypecho() try { self::deleteDir(self::$tmpPath); - } catch (Exception $e) {} + } catch (Exception $e) { + } self::mkdirs(self::$tmpPath); @@ -85,6 +84,8 @@ public static function downloadTypecho() echo 'extracting' . PHP_EOL; $pharData = new PharData($filePath); + $pharData->decompress(); + $pharData = new PharData(substr($filePath, 0, -3)); $pharData->extractTo(self::$tmpPath); } @@ -187,7 +188,8 @@ public static function installTypecho() $pluginDir = self::$typechoDir . '/usr/plugins/Restful'; try { self::deleteDir($pluginDir); - } catch (Exception $e) {} + } catch (Exception $e) { + } self::mkdirs($pluginDir); copy(__DIR__ . '/../Plugin.php', $pluginDir . '/Plugin.php'); copy(__DIR__ . '/../Action.php', $pluginDir . '/Action.php'); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index de0f402..3d5384f 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,4 +1,5 @@ getenv('WEB_SERVER_HOST'), 'port' => getenv('WEB_SERVER_PORT'), - 'document_root' => getenv('WEB_SERVER_DOCROOT'), -)); -$server[] = new Serve(array( - 'address' => getenv('WEB_SERVER_HOST'), - 'port' => getenv('FORKED_WEB_SERVER_PORT'), - 'document_root' => getenv('WEB_SERVER_DOCROOT'), + 'document_root' => dirname(__FILE__) . getenv('WEB_SERVER_DOCROOT'), )); -$server[0]->start(); -$server[1]->start(); +$server->start(); // Kill the web server when the process ends register_shutdown_function(function () use ($server) { - $server[0]->stop(); - $server[1]->stop(); + $server->stop(); }); Util::installTypecho(); From 3f906205041c1f7058a12642de43a36587f0cc27 Mon Sep 17 00:00:00 2001 From: Chen <2810582161@qq.com> Date: Sun, 4 May 2025 17:17:16 +0800 Subject: [PATCH 09/18] =?UTF-8?q?=E5=8F=91=E5=B8=83=E8=AF=84=E8=AE=BA?= =?UTF-8?q?=E6=81=A2=E5=A4=8D=E5=8E=9F=E6=9D=A5=E9=80=BB=E8=BE=91=20?= =?UTF-8?q?=E5=8F=91=E5=B8=83=E6=96=87=E7=AB=A0=E3=80=81=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E6=A0=87=E7=AD=BE/=E5=88=86=E7=B1=BB=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=20=E5=A2=9E=E5=8A=A0=E5=8F=AF=E9=80=89?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=E9=AB=98=E6=95=8F=E6=8E=A5=E5=8F=A3=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Action.php | 75 ++++++++++++++++++------------------------- CHANGELOG.md | 11 +++++-- Plugin.php | 9 +++++- README.md | 66 +++++++++++++++---------------------- tests/RestfulTest.php | 12 ------- 5 files changed, 74 insertions(+), 99 deletions(-) diff --git a/Action.php b/Action.php index dfa1a08..8cc4f87 100644 --- a/Action.php +++ b/Action.php @@ -94,6 +94,7 @@ public function action() private function sendCORS() { $httpOrigin = $this->request->getServer('HTTP_ORIGIN'); + $this->response->setHeader('Access-Control-Allow-Credentials', 'true'); $allowedHttpOrigins = explode("\n", str_replace("\r", "", $this->config->origin)); if (!$httpOrigin) { @@ -207,10 +208,7 @@ private function checkState($route) $this->throwError('This API has been disabled.', 403); } $token = $this->request->getHeader('token'); - if (empty($token)) { - $this->throwError('apiToken is empty', 403); - } - if ($token != $this->config->apiToken) { + if (!empty($token) && $token != $this->config->apiToken) { $this->throwError('apiToken is invalid', 403); } } @@ -340,11 +338,11 @@ public function postsAction() $limit = (int)trim($this->getParams('limit', '200')); $result[$key] = $this->filter($result[$key]); $result[$key]['digest'] = mb_substr( - htmlspecialchars_decode(strip_tags($result[$key]['text'])), - 0, - $limit, - 'utf-8' - ) . "..."; + htmlspecialchars_decode(strip_tags($result[$key]['text'])), + 0, + $limit, + 'utf-8' + ) . "..."; } else { $result[$key] = $this->filter($result[$key]); } @@ -462,6 +460,7 @@ public function postAction() $result = $this->db->fetchRow($select); if (count($result) != 0) { $result = $this->filter($result); + $result['csrfToken'] = $this->generateCsrfToken($result['permalink']); $this->throwData($result); } else { $this->throwError('post not exists', 404); @@ -616,20 +615,27 @@ public function commentAction() 'cid' => $result['cid'], 'author' => $this->getParams('author', ''), ); + $parent = $this->getParams('parent', ''); + $authorId = $this->getParams('authorId', ''); + $ownerId = $this->getParams('ownerId', ''); + $url = $this->getParams('url', ''); + + $uid = Typecho_Cookie::get('__typecho_uid'); // 登录的话忽略传值 + if (!empty($uid)) { + $authorId = $uid; + } + if (is_numeric($parent)) { $postData['parent'] = $parent; } - $authorId = $this->getParams('authorId', ''); if (is_numeric($authorId)) { $postData['authorId'] = $authorId; } - $ownerId = $this->getParams('ownerId', ''); if (is_numeric($ownerId)) { $postData['ownerId'] = $ownerId; } - $url = $this->getParams('url', ''); - if (is_numeric($url)) { + if (is_string($url)) { $postData['url'] = $url; } $comments->insert($postData); @@ -766,11 +772,11 @@ public function archivesAction() $limit = (int)trim($this->getParams('limit', '200')); $post = $this->filter($post); $post['digest'] = mb_substr( - htmlspecialchars_decode(strip_tags($post['text'])), - 0, - $limit, - 'utf-8' - ) . "..."; + htmlspecialchars_decode(strip_tags($post['text'])), + 0, + $limit, + 'utf-8' + ) . "..."; } else { $post = $this->filter($post); } @@ -832,10 +838,14 @@ public function postArticleAction() $this->lockMethod('post'); $this->checkState('postArticle'); + if ($this->config->validateLogin == 1 && !$this->widget('Widget_User')->hasLogin()) { + $this->throwError('User must be logged in', 401); + } + $contents = new Contents($this->request, $this->response); $check_key = [ - 'title', 'text', 'authorId', 'token' + 'title', 'text', 'authorId' ]; foreach ($check_key as $key) { if (!$this->getParams($key, '')) { @@ -847,11 +857,6 @@ public function postArticleAction() $authorId = $this->getParams('authorId', ''); $slug = $this->getParams('slug', ''); $mid = $this->getParams('mid', ''); - $token = $this->getParams('token', ''); - - if (!$this->checkCsrfToken($title, $token)) { - $this->throwError('token invalid'); - } try { $article = $this->db->select('cid', 'created', 'type', 'slug', 'commentsNum', 'text') @@ -919,16 +924,15 @@ public function addMetasAction() { $this->lockMethod('post'); $this->checkState('addMetas'); + if ($this->config->validateLogin == 1 && !$this->widget('Widget_User')->hasLogin()) { + $this->throwError('User must be logged in', 401); + } $name = $this->getParams('name', ''); $type = $this->getParams('type', ''); $slug = $this->getParams('slug', ''); - $token = $this->getParams('token', ''); if (empty($name) || empty($type)) { $this->throwError('missing name or type'); } - if (!$this->checkCsrfToken($name, $token)) { - $this->throwError('token invalid'); - } if ($type != 'category' && $type != 'tag') { $this->throwError('type must be category or tag'); } @@ -940,21 +944,6 @@ public function addMetasAction() $this->throwData($res); } - /** - * 获取 CSRF Token - */ - public function getCsrfTokenAction() - { - $this->lockMethod('get'); - $this->checkState('getCsrfToken'); - $key = $this->getParams('key'); - if (empty($key)) { - $this->throwError('missing key'); - } - $token = $this->generateCsrfToken($key); - $this->throwData(['csrfToken' => $token]); - } - /** * 插件更新接口 * diff --git a/CHANGELOG.md b/CHANGELOG.md index e674ac1..8c16499 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,14 @@ # 变更日志 -### 2025-05-03 -- 兼容typecho1.2.x -- 新增apiToken校验(必填) +### 2025-05-04 + +- 适配typecho1.2.x +- 新增apiToken校验(非必填) + - 为空就不校验 +- 新增是否校验高敏接口(发布文章、新增标签/分类) + - 开启后需要携带cookie访问接口 - 修复接口:获取标签、发表评论(传参有修改) - 新增接口:用户列表、发表文章、新增分类/标签 - 不兼容Typecho1.2以前版本 + ------ diff --git a/Plugin.php b/Plugin.php index f9a5e08..bdf8e3d 100644 --- a/Plugin.php +++ b/Plugin.php @@ -93,8 +93,15 @@ public static function config(Typecho_Widget_Helper_Form $form) $form->addInput($csrfSalt); /* API token */ - $apiToken = new Typecho_Widget_Helper_Form_Element_Text('apiToken', null, '123456', _t('APITOKEN'), _t('api请求需要携带的token。')); + $apiToken = new Typecho_Widget_Helper_Form_Element_Text('apiToken', null, '123456', _t('APITOKEN'), _t('api请求需要携带的token,设置为空就不校验。')); $form->addInput($apiToken); + + /* 高敏接口是否校验登录用户 */ + $validateLogin = new Typecho_Widget_Helper_Form_Element_Radio('validateLogin', array( + 0 => _t('否'), + 1 => _t('是'), + ), 0, _t('高敏接口是否校验登录'), _t('开启后,高敏接口需要携带Cookie才能访问')); + $form->addInput($validateLogin); ?>