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 参数。

07214-idpd6w1fiu.png

进入配置

点击下一步以后我们就进入到了配置的步骤,当我们输入完相关数据参数,以及管理员信息以后点击下一步,会 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">创建完毕, 继续安装 &raquo;</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

84721-q3qnuon07e.png

首先是_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赋值

09251-185t4vugr0c.png

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

89671-nhmmtywa2v.png

然后就是通过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

Last modification:April 23, 2022
  • 本文作者:Juneha
  • 本文链接:https://blog.mo60.cn/index.php/archives/268.html
  • 版权声明:本博客所有文章除特别声明外,均默认采用 CC BY-NC-SA 4.0 许可协议。
  • 法律说明:
  • 文章声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由用户承担全部法律及连带责任,文章作者不承担任何法律及连带责任,本人坚决反对利用文章内容进行恶意攻击行为,推荐大家在了解技术原理的前提下,更好的维护个人信息安全、企业安全、国家安全,本文内容未隐讳任何个人、群体、公司。非文学作品,请勿过度理解,根据《计算机软件保护条例》第十七条,本站所有软件请仅用于学习研究用途。
如果觉得我的文章对你有用,请随意赞赏,可备注留下ID方便感谢