* 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>
355 lines
7.8 KiB
PHP
355 lines
7.8 KiB
PHP
<?php
|
|
|
|
namespace Typecho;
|
|
|
|
use Typecho\Widget\Terminal;
|
|
|
|
/**
|
|
* Typecho公用方法
|
|
*
|
|
* @category typecho
|
|
* @package Response
|
|
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
|
|
* @license GNU General Public License 2.0
|
|
*/
|
|
class Response
|
|
{
|
|
/**
|
|
* http code
|
|
*
|
|
* @access private
|
|
* @var array
|
|
*/
|
|
private const HTTP_CODE = [
|
|
100 => 'Continue',
|
|
101 => 'Switching Protocols',
|
|
200 => 'OK',
|
|
201 => 'Created',
|
|
202 => 'Accepted',
|
|
203 => 'Non-Authoritative Information',
|
|
204 => 'No Content',
|
|
205 => 'Reset Content',
|
|
206 => 'Partial Content',
|
|
300 => 'Multiple Choices',
|
|
301 => 'Moved Permanently',
|
|
302 => 'Found',
|
|
303 => 'See Other',
|
|
304 => 'Not Modified',
|
|
305 => 'Use Proxy',
|
|
307 => 'Temporary Redirect',
|
|
400 => 'Bad Request',
|
|
401 => 'Unauthorized',
|
|
402 => 'Payment Required',
|
|
403 => 'Forbidden',
|
|
404 => 'Not Found',
|
|
405 => 'Method Not Allowed',
|
|
406 => 'Not Acceptable',
|
|
407 => 'Proxy Authentication Required',
|
|
408 => 'Request Timeout',
|
|
409 => 'Conflict',
|
|
410 => 'Gone',
|
|
411 => 'Length Required',
|
|
412 => 'Precondition Failed',
|
|
413 => 'Request Entity Too Large',
|
|
414 => 'Request-URI Too Long',
|
|
415 => 'Unsupported Media Type',
|
|
416 => 'Requested Range Not Satisfiable',
|
|
417 => 'Expectation Failed',
|
|
500 => 'Internal Server Error',
|
|
501 => 'Not Implemented',
|
|
502 => 'Bad Gateway',
|
|
503 => 'Service Unavailable',
|
|
504 => 'Gateway Timeout',
|
|
505 => 'HTTP Version Not Supported'
|
|
];
|
|
|
|
//默认的字符编码
|
|
/**
|
|
* 单例句柄
|
|
*
|
|
* @access private
|
|
* @var Response
|
|
*/
|
|
private static Response $instance;
|
|
|
|
/**
|
|
* 字符编码
|
|
*
|
|
* @var string
|
|
*/
|
|
private string $charset = 'UTF-8';
|
|
|
|
/**
|
|
* @var string
|
|
*/
|
|
private string $contentType = 'text/html';
|
|
|
|
/**
|
|
* @var callable[]
|
|
*/
|
|
private array $responders = [];
|
|
|
|
/**
|
|
* @var array
|
|
*/
|
|
private array $cookies = [];
|
|
|
|
/**
|
|
* @var array
|
|
*/
|
|
private array $headers = [];
|
|
|
|
/**
|
|
* @var int
|
|
*/
|
|
private int $status = 200;
|
|
|
|
/**
|
|
* @var bool
|
|
*/
|
|
private bool $enableAutoSendHeaders = true;
|
|
|
|
/**
|
|
* @var bool
|
|
*/
|
|
private bool $sandbox = false;
|
|
|
|
/**
|
|
* init responder
|
|
*/
|
|
public function __construct()
|
|
{
|
|
$this->clean();
|
|
}
|
|
|
|
/**
|
|
* 获取单例句柄
|
|
*
|
|
* @return Response
|
|
*/
|
|
public static function getInstance(): Response
|
|
{
|
|
if (!isset(self::$instance)) {
|
|
self::$instance = new self();
|
|
}
|
|
|
|
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
|
|
*/
|
|
public function enableAutoSendHeaders(bool $enable = true)
|
|
{
|
|
$this->enableAutoSendHeaders = $enable;
|
|
}
|
|
|
|
/**
|
|
* clean all
|
|
*/
|
|
public function clean()
|
|
{
|
|
$this->headers = [];
|
|
$this->cookies = [];
|
|
$this->status = 200;
|
|
$this->responders = [];
|
|
$this->setContentType('text/html');
|
|
}
|
|
|
|
/**
|
|
* send all headers
|
|
*/
|
|
public function sendHeaders()
|
|
{
|
|
if ($this->sandbox) {
|
|
return;
|
|
}
|
|
|
|
$sentHeaders = [];
|
|
foreach (headers_list() as $header) {
|
|
[$key] = explode(':', $header, 2);
|
|
$sentHeaders[] = strtolower(trim($key));
|
|
}
|
|
|
|
header('HTTP/1.1 ' . $this->status . ' ' . self::HTTP_CODE[$this->status], true, $this->status);
|
|
|
|
// set header
|
|
foreach ($this->headers as $name => $value) {
|
|
if (!in_array(strtolower($name), $sentHeaders)) {
|
|
header($name . ': ' . $value);
|
|
}
|
|
}
|
|
|
|
// set cookie
|
|
foreach ($this->cookies as $cookie) {
|
|
[$key, $value, $timeout, $path, $domain, $secure, $httponly] = $cookie;
|
|
|
|
if ($timeout > 0) {
|
|
$now = time();
|
|
$timeout += $timeout > $now - 86400 ? 0 : $now;
|
|
} elseif ($timeout < 0) {
|
|
$timeout = 1;
|
|
}
|
|
|
|
setrawcookie($key, rawurlencode($value), $timeout, $path, $domain, $secure, $httponly);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* respond data
|
|
* @throws Terminal
|
|
*/
|
|
public function respond()
|
|
{
|
|
if ($this->sandbox) {
|
|
throw new Terminal('sandbox mode');
|
|
}
|
|
|
|
if ($this->enableAutoSendHeaders) {
|
|
$this->sendHeaders();
|
|
}
|
|
|
|
foreach ($this->responders as $responder) {
|
|
call_user_func($responder, $this);
|
|
}
|
|
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* 设置HTTP状态
|
|
*
|
|
* @access public
|
|
* @param integer $code http代码
|
|
* @return $this
|
|
*/
|
|
public function setStatus(int $code): Response
|
|
{
|
|
if (!$this->sandbox) {
|
|
$this->status = $code;
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* 设置http头
|
|
*
|
|
* @param string $name 名称
|
|
* @param string $value 对应值
|
|
* @return $this
|
|
*/
|
|
public function setHeader(string $name, string $value): Response
|
|
{
|
|
if (!$this->sandbox) {
|
|
$name = str_replace(' ', '-', ucwords(str_replace('-', ' ', $name)));
|
|
$this->headers[$name] = $value;
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* 设置指定的COOKIE值
|
|
*
|
|
* @param string $key 指定的参数
|
|
* @param mixed $value 设置的值
|
|
* @param integer $timeout 过期时间,默认为0,表示随会话时间结束
|
|
* @param string $path 路径信息
|
|
* @param string|null $domain 域名信息
|
|
* @param bool $secure 是否仅可通过安全的 HTTPS 连接传给客户端
|
|
* @param bool $httponly 是否仅可通过 HTTP 协议访问
|
|
* @return $this
|
|
*/
|
|
public function setCookie(
|
|
string $key,
|
|
$value,
|
|
int $timeout = 0,
|
|
string $path = '/',
|
|
string $domain = '',
|
|
bool $secure = false,
|
|
bool $httponly = false
|
|
): Response {
|
|
if (!$this->sandbox) {
|
|
$this->cookies[] = [$key, $value, $timeout, $path, $domain, $secure, $httponly];
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* 在http头部请求中声明类型和字符集
|
|
*
|
|
* @param string $contentType 文档类型
|
|
* @return $this
|
|
*/
|
|
public function setContentType(string $contentType): Response
|
|
{
|
|
if (!$this->sandbox) {
|
|
$this->contentType = $contentType;
|
|
$this->setHeader('Content-Type', $this->contentType . '; charset=' . $this->charset);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* 获取字符集
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getCharset(): string
|
|
{
|
|
return $this->charset;
|
|
}
|
|
|
|
/**
|
|
* 设置默认回执编码
|
|
*
|
|
* @param string $charset 字符集
|
|
* @return $this
|
|
*/
|
|
public function setCharset(string $charset): Response
|
|
{
|
|
if (!$this->sandbox) {
|
|
$this->charset = $charset;
|
|
$this->setHeader('Content-Type', $this->contentType . '; charset=' . $this->charset);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* add responder
|
|
*
|
|
* @param callable $responder
|
|
* @return $this
|
|
*/
|
|
public function addResponder(callable $responder): Response
|
|
{
|
|
if (!$this->sandbox) {
|
|
$this->responders[] = $responder;
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
}
|