0x01前言
一直感觉自己开发烂,想提升一下自己通过看看这些开源项目的源码来提升自己(可能有用吧,希望自己能坚持下去)
技术垃圾,如果有什么不对的地方欢迎各位师傅指出
这里选择的是Typecho 1.1(17.10.30),虽然这个月初才更新了1.2
下载地址: https://github.com/typecho/typecho/archive/refs/tags/v1.1-17.10.30-release.zip
2.分析
先来看index.php
<?php
/**
* Typecho Blog Platform
*
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
* @license GNU General Public License 2.0
* @version $Id: index.php 1153 2009-07-02 10:53:22Z magike.net $
*/
/** 载入配置支持 */
if (!defined('__TYPECHO_ROOT_DIR__') && !@include_once 'config.inc.php') {
file_exists('./install.php') ? header('Location: install.php') : print('Missing Config File');
exit;
}
/** 初始化组件 */
Typecho_Widget::widget('Widget_Init');
/** 注册一个初始化插件 */
Typecho_Plugin::factory('index.php')->begin();
/** 开始路由分发 */
Typecho_Router::dispatch();
/** 注册一个结束插件 */
Typecho_Plugin::factory('index.php')->end();
如果没有定义 TYPECHO_ROOT_DIR 常量并且 引入 config.inc.php 失败就进入if,然后检测install.php是否存在,存在就跳转过去,__TYPECHO_ROOT_DIR__这个常量是安装完后在config.inc.php里面定义的,并且没有安装也不会存在config.inc.php文件所以判断这两个就可以知道是否有安装过
/** 载入配置支持 */
if (!defined('__TYPECHO_ROOT_DIR__') && !@include_once 'config.inc.php') {
file_exists('./install.php') ? header('Location: install.php') : print('Missing Config File');
exit;
}
然后在看看install.php,先截取第一部分
<?php if (!file_exists(dirname(__FILE__) . '/config.inc.php')): ?>
<?php
/**
* Typecho Blog Platform
*
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
* @license GNU General Public License 2.0
* @version $Id$
*/
/** 定义根目录 */
define('__TYPECHO_ROOT_DIR__', dirname(__FILE__));
/** 定义插件目录(相对路径) */
define('__TYPECHO_PLUGIN_DIR__', '/usr/plugins');
/** 定义模板目录(相对路径) */
define('__TYPECHO_THEME_DIR__', '/usr/themes');
/** 后台路径(相对路径) */
define('__TYPECHO_ADMIN_DIR__', '/admin/');
/** 设置包含路径 */
@set_include_path(get_include_path() . PATH_SEPARATOR .
__TYPECHO_ROOT_DIR__ . '/var' . PATH_SEPARATOR .
__TYPECHO_ROOT_DIR__ . __TYPECHO_PLUGIN_DIR__);
/** 载入API支持 */
require_once 'Typecho/Common.php';
/** 载入Response支持 */
require_once 'Typecho/Response.php';
/** 载入配置支持 */
require_once 'Typecho/Config.php';
/** 载入异常支持 */
require_once 'Typecho/Exception.php';
/** 载入插件支持 */
require_once 'Typecho/Plugin.php';
/** 载入国际化支持 */
require_once 'Typecho/I18n.php';
/** 载入数据库支持 */
require_once 'Typecho/Db.php';
/** 载入路由器支持 */
require_once 'Typecho/Router.php';
/** 程序初始化 */
Typecho_Common::init();
else:
require_once dirname(__FILE__) . '/config.inc.php';
//判断是否已经安装
$db = Typecho_Db::get();
try {
$installed = $db->fetchRow($db->select()->from('table.options')->where('name = ?', 'installed'));
if (empty($installed) || $installed['value'] == 1) {
Typecho_Response::setStatus(404);
exit;
}
} catch (Exception $e) {
// do nothing
}
endif;
检测 install.php 同目录下是否存在 config.inc.php 文件,我们先看看存在的情况下的是怎么样的
<?php if (!file_exists(dirname(__FILE__) . '/config.inc.php')): ?>
引入 config.inc.php 配置文件紧接着查询 table.options 表,是否包含 'name = installed 的行,如果没有没有检测到就 404然后exit,如果存在就继续执行
require_once dirname(__FILE__) . '/config.inc.php';
//判断是否已经安装
$db = Typecho_Db::get();
try {
$installed = $db->fetchRow($db->select()->from('table.options')->where('name = ?', 'installed'));
if (empty($installed) || $installed['value'] == 1) {
Typecho_Response::setStatus(404);
exit;
}
} catch (Exception $e) {
// do nothing
}
然后看看如果config.inc.php不存在是执行什么,先定义各种常量比如后台路径,网站根路径等
/** 定义根目录 */
define('__TYPECHO_ROOT_DIR__', dirname(__FILE__));
/** 定义插件目录(相对路径) */
define('__TYPECHO_PLUGIN_DIR__', '/usr/plugins');
/** 定义模板目录(相对路径) */
define('__TYPECHO_THEME_DIR__', '/usr/themes');
/** 后台路径(相对路径) */
define('__TYPECHO_ADMIN_DIR__', '/admin/');
/** 设置包含路径 */
@set_include_path(get_include_path() . PATH_SEPARATOR .
__TYPECHO_ROOT_DIR__ . '/var' . PATH_SEPARATOR .
__TYPECHO_ROOT_DIR__ . __TYPECHO_PLUGIN_DIR__);
/** 载入API支持 */
require_once 'Typecho/Common.php';
/** 载入Response支持 */
require_once 'Typecho/Response.php';
/** 载入配置支持 */
require_once 'Typecho/Config.php';
/** 载入异常支持 */
require_once 'Typecho/Exception.php';
/** 载入插件支持 */
require_once 'Typecho/Plugin.php';
/** 载入国际化支持 */
require_once 'Typecho/I18n.php';
/** 载入数据库支持 */
require_once 'Typecho/Db.php';
/** 载入路由器支持 */
require_once 'Typecho/Router.php';
/** 程序初始化 */
Typecho_Common::init();
在分析下面这个代码前我们先了解一下set_include_path
/** 设置包含路径 */
@set_include_path(get_include_path() . PATH_SEPARATOR .
__TYPECHO_ROOT_DIR__ . '/var' . PATH_SEPARATOR .
__TYPECHO_ROOT_DIR__ . __TYPECHO_PLUGIN_DIR__);
set_include_path函数在脚本里动态地对PHP.ini中,include_path修改。include_path可以针对include和require的路径范围进行限定(预定义)。如果没设置这个值,则需要写完全的路径,例如:
include('123/test1.php');
include('123/test2.php');
这样会引入很多外部文件,但如设置set_include_path('123/')可用下面代码代替。
set_include_path('123/');
include('test1.php');
include('test2.php');
这个函数不仅可定义一个文件夹,还可定义很多文件夹。如下,写一个初始化函数:
function initialize()
{
set_include_path(get_include_path().PATH_SEPARATOR . 'core/');
set_include_path(get_include_path().PATH_SEPARATOR . 'app/');
set_include_path(get_include_path().PATH_SEPARATOR . 'admin/');
set_include_path(get_include_path().PATH_SEPARATOR . 'lib/');
set_include_path(get_include_path().PATH_SEPARATOR . 'include/');
set_include_path(get_include_path().PATH_SEPARATOR.'data/');
set_include_path(get_include_path().PATH_SEPARATOR.'cache/');
}
它的路径成了:
.;C:\php5\pear;core/;app/;admin/;lib/;include/;data/;cache/
这部分参考的 https://www.jianshu.com/p/303feaaeded1
PATH_SEPARATOR是分隔符,在linux系统中,该常量输出":",在windows系统中,该常量输出";"号,在这里我们输出一下他这段代码设置的路径
exit(get_include_path() . PATH_SEPARATOR .
__TYPECHO_ROOT_DIR__ . '/var' . PATH_SEPARATOR .
__TYPECHO_ROOT_DIR__ . __TYPECHO_PLUGIN_DIR__);
得到这些路径,set_include_path 和 get_include_path 配置好了系统引入路径和 typecho 自定义的引入路径,包括了 var 、插件目录,这两个字目录。以后再引入文件的时候,系统就会根据设置的这些目录,引入相关文件了。
;C:\php\pear;F:\phpstudy_pro\WWW/var;F:\phpstudy_pro\WWW/usr/plugins;F:\phpstudy_pro\WWW/var;F:\phpstudy_pro\WWW/usr/plugins
接下来就是看看程序初始化方法
/** 程序初始化 */
Typecho_Common::init();
该方法在Typecho/Common.php中,具体代码
/**
* 自动载入类
*
* @param $className
*/
public static function __autoLoad($className)
{
@include_once str_replace(array('\\', '_'), '/', $className) . '.php';
}
/**
* 程序初始化方法
*
* @access public
* @return void
*/
public static function init()
{
/** 设置自动载入函数 */
spl_autoload_register(array('Typecho_Common', '__autoLoad'));
/** 兼容php6 */
if (function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc()) {
$_GET = self::stripslashesDeep($_GET);
$_POST = self::stripslashesDeep($_POST);
$_COOKIE = self::stripslashesDeep($_COOKIE);
reset($_GET);
reset($_POST);
reset($_COOKIE);
}
/** 设置异常截获函数 */
set_exception_handler(array('Typecho_Common', 'exceptionHandle'));
}
自动载入类,也就是加载本文件Typecho下的Common.php(可能是)
spl_autoload_register(array('Typecho_Common', '__autoLoad'));
__autoLoad实现
/**
* 自动载入类
*
* @param $className
*/
public static function __autoLoad($className)
{
@include_once str_replace(array('\\', '_'), '/', $className) . '.php';
}
这部分代码是判断是否开启了get_magic_quotes_gpc,开启了就调用stripslashesDeep方法去掉反斜杠
/** 兼容php6 */
if (function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc()) {
$_GET = self::stripslashesDeep($_GET);
$_POST = self::stripslashesDeep($_POST);
$_COOKIE = self::stripslashesDeep($_COOKIE);
reset($_GET);
reset($_POST);
reset($_COOKIE);
}
然后就是异常处理部分
set_exception_handler(array('Typecho_Common', 'exceptionHandle'));
exceptionHandle,就是根据是否是 debug 两种输出模式,如果没有 exceptionHandle 的时候会调用 error 方法,进行错误输出,并且记录 error_log
/**
* 异常截获函数
*
* @access public
* @param $exception 截获的异常
* @return void
*/
public static function exceptionHandle($exception)
{
if (defined('__TYPECHO_DEBUG__')) {
echo '<pre><code>';
echo '<h1>' . htmlspecialchars($exception->getMessage()) . '</h1>';
echo htmlspecialchars($exception->__toString());
echo '</code></pre>';
} else {
@ob_end_clean();
if (404 == $exception->getCode() && !empty(self::$exceptionHandle)) {
$handleClass = self::$exceptionHandle;
new $handleClass($exception);
} else {
self::error($exception);
}
}
exit;
}
error方法
/**
* 输出错误页面
*
* @access public
* @param mixed $exception 错误信息
* @return void
*/
public static function error($exception)
{
$isException = is_object($exception);
$message = '';
if ($isException) {
$code = $exception->getCode();
$message = $exception->getMessage();
} else {
$code = $exception;
}
$charset = self::$charset;
if ($isException && $exception instanceof Typecho_Db_Exception) {
$code = 500;
@error_log($message);
//覆盖原始错误信息
$message = 'Database Server Error';
if ($exception instanceof Typecho_Db_Adapter_Exception) {
$code = 503;
$message = 'Error establishing a database connection';
} else if ($exception instanceof Typecho_Db_Query_Exception) {
$message = 'Database Query Error';
}
} else {
switch ($code) {
case 500:
$message = 'Server Error';
break;
case 404:
$message = 'Page Not Found';
break;
default:
$code = 'Error';
break;
}
}
/** 设置http code */
if (is_numeric($code) && $code > 200) {
Typecho_Response::setStatus($code);
}
$message = nl2br($message);
if (defined('__TYPECHO_EXCEPTION_FILE__')) {
require_once __TYPECHO_EXCEPTION_FILE__;
} else {
echo
<<<EOF
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="{$charset}">
<title>{$code}</title>
<style>
html {
padding: 50px 10px;
font-size: 16px;
line-height: 1.4;
color: #666;
background: #F6F6F3;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
html,
input { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; }
body {
max-width: 500px;
_width: 500px;
padding: 30px 20px;
margin: 0 auto;
background: #FFF;
}
ul {
padding: 0 0 0 40px;
}
.container {
max-width: 380px;
_width: 380px;
margin: 0 auto;
}
</style>
</head>
<body>
<div class="container">
{$message}
</div>
</body>
</html>
EOF;
}
exit;
}
到这里 init 方法已经执行完了,我们回到 install.php 继续往下看,这块就是挡掉跨域攻击,比如 iframe 的嵌套页面,然后接下去就是一大堆函数的定义
// 挡掉可能的跨站请求
if (!empty($_GET) || !empty($_POST)) {
if (empty($_SERVER['HTTP_REFERER'])) {
exit;
}
$parts = parse_url($_SERVER['HTTP_REFERER']);
if (!empty($parts['port'])) {
$parts['host'] = "{$parts['host']}:{$parts['port']}";
}
if (empty($parts['host']) || $_SERVER['HTTP_HOST'] != $parts['host']) {
exit;
}
}
设置版本后,设置语言,顺便把语言写入到 cookie 中。
$options = new stdClass();
$options->generator = 'Typecho ' . Typecho_Common::VERSION;
list($soft, $currentVersion) = explode(' ', $options->generator);
$options->software = $soft;
$options->version = $currentVersion;
list($prefixVersion, $suffixVersion) = explode('/', $currentVersion);
/** 获取语言 */
$lang = _r('lang', Typecho_Cookie::get('__typecho_lang'));
$langs = Widget_Options_General::getLangs();
if (empty($lang) && count($langs) > 1) {
foreach ($langs as $lang) {
if ('zh_CN' != $lang) {
break;
}
}
}
if (empty($lang)) {
$lang = 'zh_CN';
}
if ('zh_CN' != $lang) {
$dir = defined('__TYPECHO_LANG_DIR__') ? __TYPECHO_LANG_DIR__ : __TYPECHO_ROOT_DIR__ . '/usr/langs';
Typecho_I18n::setLang($dir . '/' . $lang . '.mo');
}
Typecho_Cookie::set('__typecho_lang', $lang);
这块就是根据 url 的参数状态决定显示的问题 _e 和 _t 都是 一个是翻译并 echo 另一个是翻译。注意哦,start 这个状态,在正常状态时看不到的哦,只有失败才会看得到。
<div class="typecho-install-patch">
<h1>Typecho</h1>
<ol class="path">
<li<?php if (!isset($_GET['finish']) && !isset($_GET['config'])) : ?> class="current"<?php endif; ?>><span>1</span><?php _e('欢迎使用'); ?></li>
<li<?php if (isset($_GET['config'])) : ?> class="current"<?php endif; ?>><span>2</span><?php _e('初始化配置'); ?></li>
<li<?php if (isset($_GET['start'])) : ?> class="current"<?php endif; ?>><span>3</span><?php _e('开始安装'); ?></li>
<li<?php if (isset($_GET['finish'])) : ?> class="current"<?php endif; ?>><span>4</span><?php _e('安装成功'); ?></li>
</ol>
</div>
安装
显示一些说明文件,如果语言配置有多个,那么就出现语言选择列表框,不过默认就只有一个简体中文。然后点击下一步以后会跳转到当前 url ,增加config 参数。
进入配置
点击下一步以后我们就进入到了配置的步骤,当我们输入完相关数据参数,以及管理员信息以后点击下一步,会 post 方法跳转到当前 ,config 网址。这里有个主要注意的地方是,当我们改变数据库的适配器以后,会跳转到切换相应的数据库适配器配置页面。而且会在页面加载的时候判定支持什么数据。这两段在下面的代码中
<?php elseif (isset($_GET['config'])): ?>
<?php
$adapters = array('Mysql', 'Mysqli', 'Pdo_Mysql', 'SQLite', 'Pdo_SQLite', 'Pgsql', 'Pdo_Pgsql');
foreach ($adapters as $firstAdapter) {
if (_p($firstAdapter)) {
break;
}
}
$adapter = _r('dbAdapter', $firstAdapter);
$parts = explode('_', $adapter);
$type = $adapter == 'Mysqli' ? 'Mysql' : array_pop($parts);
?>
js部分
<script>
var _select = document.config.dbAdapter;
_select.onchange = function() {
setTimeout("window.location.href = 'install.php?config&dbAdapter=" + this.value + "'; ",0);
}
</script>
同的适配器会加载不同的数据库配置页面,我们这边用的是 mysql ,我们进入 install/mysql.php 页面,里面有很多环境的判断 sae、gae、bae 什么的判定。这些我们都跳过,直接看下面,这块就是我们显示配置的位置
<?php else: ?>
<li>
<label class="typecho-label" for="dbHost"><?php _e('数据库地址'); ?></label>
<input type="text" class="text" name="dbHost" id="dbHost" value="<?php _v('dbHost', 'localhost'); ?>"/>
<p class="description"><?php _e('您可能会使用 "%s"', 'localhost'); ?></p>
</li>
<li>
<label class="typecho-label" for="dbPort"><?php _e('数据库端口'); ?></label>
<input type="text" class="text" name="dbPort" id="dbPort" value="<?php _v('dbPort', '3306'); ?>"/>
<p class="description"><?php _e('如果您不知道此选项的意义, 请保留默认设置'); ?></p>
</li>
<li>
<label class="typecho-label" for="dbUser"><?php _e('数据库用户名'); ?></label>
<input type="text" class="text" name="dbUser" id="dbUser" value="<?php _v('dbUser', 'root'); ?>" />
<p class="description"><?php _e('您可能会使用 "%s"', 'root'); ?></p>
</li>
<li>
<label class="typecho-label" for="dbPassword"><?php _e('数据库密码'); ?></label>
<input type="password" class="text" name="dbPassword" id="dbPassword" value="<?php _v('dbPassword'); ?>" />
</li>
<li>
<label class="typecho-label" for="dbDatabase"><?php _e('数据库名'); ?></label>
<input type="text" class="text" name="dbDatabase" id="dbDatabase" value="<?php _v('dbDatabase', 'typecho'); ?>" />
<p class="description"><?php _e('请您指定数据库名称'); ?></p>
</li>
<?php endif; ?>
<input type="hidden" name="dbCharset" value="<?php _e('utf8'); ?>" />
<li>
<label class="typecho-label" for="dbCharset"><?php _e('数据库编码'); ?></label>
<select name="dbCharset" id="dbCharset">
<option value="utf8"<?php if (_r('dbCharset') == 'utf8'): ?> selected<?php endif; ?>>utf8</option>
<option value="utf8mb4"<?php if (_r('dbCharset') == 'utf8mb4'): ?> selected<?php endif; ?>>utf8mb4</option>
</select>
<p class="description"><?php _e('选择 utf8mb4 编码至少需要 MySQL 5.5.3 版本'); ?></p>
</li>
<li>
<label class="typecho-label" for="dbEngine"><?php _e('数据库引擎'); ?></label>
<select name="dbEngine" id="dbEngine">
<option value="MyISAM"<?php if (_r('dbEngine') == 'MyISAM'): ?> selected<?php endif; ?>>MyISAM</option>
<option value="InnoDB"<?php if (_r('dbEngine') == 'InnoDB'): ?> selected<?php endif; ?>>InnoDB</option>
</select>
</li>
我们填写完配置信息以后,post 提交install页面,进入各种判定的部分,不符合规范的会报错
<h2><?php _e('数据库配置'); ?></h2>
<?php
if ('config' == _r('action')) {
$success = true;
if (_r('created') && !file_exists('./config.inc.php')) {
echo '<p class="message error">' . _t('没有检测到您手动创建的配置文件, 请检查后再次创建') . '</p>';
$success = false;
} else {
if (NULL == _r('userUrl')) {
$success = false;
echo '<p class="message error">' . _t('请填写您的网站地址') . '</p>';
} else if (NULL == _r('userName')) {
$success = false;
echo '<p class="message error">' . _t('请填写您的用户名') . '</p>';
} else if (NULL == _r('userMail')) {
$success = false;
echo '<p class="message error">' . _t('请填写您的邮箱地址') . '</p>';
} else if (32 < strlen(_r('userName'))) {
$success = false;
echo '<p class="message error">' . _t('用户名长度超过限制, 请不要超过 32 个字符') . '</p>';
} else if (200 < strlen(_r('userMail'))) {
$success = false;
echo '<p class="message error">' . _t('邮箱长度超过限制, 请不要超过 200 个字符') . '</p>';
}
}
这部分是获取数据库连接配置,然后对数据库进行连接,如果连接失败,会进行报错。
$_dbConfig = _rFrom('dbHost', 'dbUser', 'dbPassword', 'dbCharset', 'dbPort', 'dbDatabase', 'dbFile', 'dbDsn', 'dbEngine');
$_dbConfig = array_filter($_dbConfig);
$dbConfig = array();
foreach ($_dbConfig as $key => $val) {
$dbConfig[strtolower(substr($key, 2))] = $val;
}
// 在特殊服务器上的特殊安装过程处理
if (_r('config')) {
$replace = array_keys($dbConfig);
foreach ($replace as &$key) {
$key = '{' . $key . '}';
}
if (!empty($_dbConfig['dbDsn'])) {
$dbConfig['dsn'] = str_replace($replace, array_values($dbConfig), $dbConfig['dsn']);
}
$config = str_replace($replace, array_values($dbConfig), _r('config'));
}
if (!isset($config) && $success && !_r('created')) {
$installDb = new Typecho_Db($adapter, _r('dbPrefix'));
$installDb->addServer($dbConfig, Typecho_Db::READ | Typecho_Db::WRITE);
/** 检测数据库配置 */
try {
$installDb->query('SELECT 1=1');
} catch (Typecho_Db_Adapter_Exception $e) {
$success = false;
echo '<p class="message error">'
. _t('对不起, 无法连接数据库, 请先检查数据库配置再继续进行安装') . '</p>';
} catch (Typecho_Db_Exception $e) {
$success = false;
echo '<p class="message error">'
. _t('安装程序捕捉到以下错误: " %s ". 程序被终止, 请检查您的配置信息.',$e->getMessage()) . '</p>';
}
}
如果成功了,就重置数据库相关信息,这块应该是应对重复安装的。然后 cookie 写入数据库配置信息
// 重置原有数据库状态
if (isset($installDb)) {
try {
$installDb->query($installDb->update('table.options')
->rows(array('value' => 0))->where('name = ?', 'installed'));
} catch (Exception $e) {
// do nothing
}
}
Typecho_Cookie::set('__typecho_config', base64_encode(serialize(array_merge(array(
'prefix' => _r('dbPrefix'),
'userName' => _r('userName'),
'userPassword' => _r('userPassword'),
'userMail' => _r('userMail'),
'adapter' => $adapter,
'siteUrl' => _r('userUrl')
), $dbConfig))));
这段下面解释
if (_r('created')) {
header('Location: ./install.php?start');
exit;
}
接下来就是写入配置文件
/** 初始化配置文件 */
$lines = array_slice(file(__FILE__), 1, 31);
$lines[] = "
/** 定义数据库参数 */
\$db = new Typecho_Db('{$adapter}', '" . _r('dbPrefix') . "');
\$db->addServer(" . (empty($config) ? var_export($dbConfig, true) : $config) . ", Typecho_Db::READ | Typecho_Db::WRITE);
Typecho_Db::set(\$db);
";
$contents = implode('', $lines);
if (!Typecho_Common::isAppEngine()) {
@file_put_contents('./config.inc.php', $contents);
}
如果写入文件失败了,就会跳转到当前页面了并且携带 created 参数,就应对上一步的判定了。如果写入成功了,就跳转到 start
if (!file_exists('./config.inc.php')) {
?>
<div class="message notice"><p><?php _e('安装程序无法自动创建 <strong>config.inc.php</strong> 文件'); ?><br />
<?php _e('您可以在网站根目录下手动创建 <strong>config.inc.php</strong> 文件, 并复制如下代码至其中'); ?></p>
<p><textarea rows="5" onmouseover="this.select();" class="w-100 mono" readonly><?php echo htmlspecialchars($contents); ?></textarea></p>
<p><button name="created" value="1" type="submit" class="btn primary">创建完毕, 继续安装 »</button></p></div>
<?php
} else {
header('Location: ./install.php?start');
exit;
}
安装失败了,就删除文件。
// 安装不成功删除配置文件
if($success != true && file_exists(__TYPECHO_ROOT_DIR__ . '/config.inc.php')) {
@unlink(__TYPECHO_ROOT_DIR__ . '/config.inc.php');
}
0x03 快速总结
第一次访问
第一次访问是不带任何请求的,只是但存访问其install.php
这里可以看到其多次使用了_e函数
/var/Typecho/Common.php
function _e() {
$args = func_get_args();
echo call_user_func_array('_t', $args);
}
function _t($string) {
if (func_num_args() <= 1) {
return Typecho_I18n::translate($string);
} else {
$args = func_get_args();
array_shift($args);
return vsprintf(Typecho_I18n::translate($string), $args);
}
}
第二次访问
点击我准备好了, 开始下一步进行下一步发现它自动给你带上一个config的post请求现在访问的应该是install.php/?config
首先是_p函数,会寻找对应的函数,如果函数存在则说明扩展存在在数据库配置是会多一个选项
function _p($adapter) {
switch ($adapter) {
case 'Mysql':
return Typecho_Db_Adapter_Mysql::isAvailable();
case 'Mysqli':
return Typecho_Db_Adapter_Mysqli::isAvailable();
case 'Pdo_Mysql':
return Typecho_Db_Adapter_Pdo_Mysql::isAvailable();
case 'SQLite':
return Typecho_Db_Adapter_SQLite::isAvailable();
case 'Pdo_SQLite':
return Typecho_Db_Adapter_Pdo_SQLite::isAvailable();
case 'Pgsql':
return Typecho_Db_Adapter_Pgsql::isAvailable();
case 'Pdo_Pgsql':
return Typecho_Db_Adapter_Pdo_Pgsql::isAvailable();
default:
return false;
}
}
以Pdo_SQLite为例
/var/Typecho/Db/Adapter/Pdo/SQLite.php
public static function isAvailable()
{
return parent::isAvailable() && in_array('sqlite', PDO::getAvailableDrivers());
}
前端输出部分
<select name="dbAdapter" id="dbAdapter">
<?php if (_p('Mysql')): ?><option value="Mysql"<?php if('Mysql' == $adapter): ?> selected="selected"<?php endif; ?>><?php _e('Mysql 原生函数适配器') ?></option><?php endif; ?>
<?php if (_p('SQLite')): ?><option value="SQLite"<?php if('SQLite' == $adapter): ?> selected="selected"<?php endif; ?>><?php _e('SQLite 原生函数适配器 (SQLite 2.x)') ?></option><?php endif; ?>
<?php if (_p('Pgsql')): ?><option value="Pgsql"<?php if('Pgsql' == $adapter): ?> selected="selected"<?php endif; ?>><?php _e('Pgsql 原生函数适配器') ?></option><?php endif; ?>
<?php if (_p('Pdo_Mysql')): ?><option value="Pdo_Mysql"<?php if('Pdo_Mysql' == $adapter): ?> selected="selected"<?php endif; ?>><?php _e('Pdo 驱动 Mysql 适配器') ?></option><?php endif; ?>
<?php if (_p('Pdo_SQLite')): ?><option value="Pdo_SQLite"<?php if('Pdo_SQLite' == $adapter): ?> selected="selected"<?php endif; ?>><?php _e('Pdo 驱动 SQLite 适配器 (SQLite 3.x)') ?></option><?php endif; ?>
<?php if (_p('Pdo_Pgsql')): ?><option value="Pdo_Pgsql"<?php if('Pdo_Pgsql' == $adapter): ?> selected="selected"<?php endif; ?>><?php _e('Pdo 驱动 PostgreSql 适配器') ?></option><?php endif; ?>
</select>
第三次访问
第三次是填好信息后安装,发送了一堆post请求并通过_rForm赋值
function _rFrom() {
$result = array();
$params = func_get_args();
foreach ($params as $param) {
$result[$param] = isset($_REQUEST[$param]) ?
(is_array($_REQUEST[$param]) ? NULL : $_REQUEST[$param]) : NULL;
}
return $result;
}
检测数据库连接
try {
$installDb->query('SELECT 1=1');
} catch (Typecho_Db_Adapter_Exception $e) {
$success = false;
echo '<p class="message error">'. _t('对不起, 无法连接数据库, 请先检查数据库配置再继续进行安装') . '</p>';
} catch (Typecho_Db_Exception $e) {
$success = false;
echo '<p class="message error">'. _t('安装程序捕捉到以下错误: " %s ". 程序被终止, 请检查您的配置信息.',$e->getMessage()) . '</p>';
}
然后cookie写入配置信息
Typecho_Cookie::set('__typecho_config', base64_encode(serialize(array_merge(array(
'prefix' => _r('dbPrefix'),
'userName' => _r('userName'),
'userPassword' => _r('userPassword'),
'userMail' => _r('userMail'),
'adapter' => $adapter,
'siteUrl' => _r('userUrl')
), $dbConfig))));
然后就是写入config.inc.php
$lines = array_slice(file(__FILE__), 1, 31);
$lines[] = "
/** 定义数据库参数 */
\$db = new Typecho_Db('{$adapter}', '" . _r('dbPrefix') . "');
\$db->addServer(" . (empty($config) ? var_export($dbConfig, true) : $config) . ", Typecho_Db::READ | Typecho_Db::WRITE);
Typecho_Db::set(\$db);
";
第四次访问
创建好之后
跳转到/install.php?start
然后就是通过cookie里面的信息反序列化拿到配置信息
$config = unserialize(base64_decode(Typecho_Cookie::get('__typecho_config')));
然后添加一些默认的配置信息,比如文章啥的
/** 初始分类 */
$installDb->query($installDb->insert('table.metas')->rows(array('name' => _t('默认分类'), 'slug' => 'default', 'type' => 'category', 'description' => _t('只是一个默认分类'),
'count' => 1, 'order' => 1)));
/** 初始关系 */
$installDb->query($installDb->insert('table.relationships')->rows(array('cid' => 1, 'mid' => 1)));
......
第五次访问
?finish变回1,代表已完成,之后访问都会是404了
//判断是否已经安装
$db = Typecho_Db::get();
try {
$installed = $db->fetchRow($db->select()->from('table.options')->where('name = ?', 'installed'));
if (empty($installed) || $installed['value'] == 1) {
Typecho_Response::setStatus(404);
exit;
}
} catch (Exception $e) {
// do nothing
}
0x04 参考
https://blog.csdn.net/crayhl/article/details/102943814
https://blog.csdn.net/crayhl/article/details/102984448
https://www.jianshu.com/p/42683139fab9