lightless blog - Code Review
type: channel
link: https://lightless.me/category/Code-Review/
description: Code Review
items: zabbix SQL Injection Analysation
items: 删除push到github上的敏感文件
items: CodeIgniter源码阅读笔记5之Hooks.php
items: CodeIgniter源码阅读笔记4之Benchmark.php
items: CodeIgniter源码阅读笔记3之CodeIgniter.php
items: CodeIgniter源码阅读笔记2之Common.php
items: CodeIgniter源码阅读笔记1之index.php
items: 基于SSDT的注册表主防系统
items: 基于PHP Extension的WAF实现
items: Typecho 源码笔记 路由类

zabbix SQL Injection Analysation
type: item
date: 2016-08-22T13:26:17+00:00
description: 曝光过程 2016年8月11日，有人在seclists上公布了这个漏洞的POC，但是针对的是latest.php这个文件的注入，zabbix官方已经在7月22日发布了补丁，在3.0.4版本进行了修复，但是仅修复了latest.php的注入问题 紧接着8月12日，有人在seclists上进行回复，发现另一处jsrpc.php的注入，但两个漏洞的原理是一样的，3.0.4是否修补jsrpc.php的注入不得而知。 Right, it’s the same vuln, just in different places. It was fixed in 3.0.4. 漏洞分析 我们以jsrpc.php的注入为例进行分析，采用zabbix的3.0.3版本，源码编译安装。先看一下POC： http://127.0.0.1:8001/jsrpc.php? type=9& method=screen.get& timestamp=1471403798083& pageFile=history.php& profileIdx=web.item.graph& profileIdx2=1+or+updatexml%281,md5%280x11%29,1%29+or+1=1%29%23& updateProfile=true& period=3600& stime=20160817050632& resourcetype=17 注入点在profileIdx2处，根据POC的结果可以看出来是insert类型的注入。 首先查看jsrpc.php文件，根据method参数，直接来到180行： case 'screen.get': $result = ''; $screenBase = CScreenBuilder::getScreen($data); if ($screenBase !== null) { $screen = $screenBase->get(); if ($data['mode'] == SCREEN_MODE_JS) { $result = $screen; } else { if (is_object($screen)) { $result = $screen->toString(); } } } break; 首先跟进CScreenBuilder::getScreen($data)函数，其中$data参数就是$_REQUEST，在31行被赋值。 在getScreen函数中，根据请求的resourcetype不同，会初实例化不同的类，在POC中看到resourcetype的值为17，从include/defines.inc.php中可以看到17对应的为SCREEN_RESOURCE_HISTORY。 define('SCREEN_RESOURCE_HOSTGROUP_TRIGGERS',14); define('SCREEN_RESOURCE_SYSTEM_STATUS', 15); define('SCREEN_RESOURCE_HOST_TRIGGERS', 16); // used in Monitoring > Latest data > Graph (history.php) define('SCREEN_RESOURCE_HISTORY', 17); define('SCREEN_RESOURCE_CHART', 18); define('SCREEN_RESOURCE_LLD_SIMPLE_GRAPH', 19); define('SCREEN_RESOURCE_LLD_GRAPH', 20); 紧接着我们就可以从getScreen函数中找到对应的处理代码： case SCREEN_RESOURCE_HISTORY: return new CScreenHistory($options); 返回的是CScreenHistory类的实例，继续跟进CScreenHistory类，看到该类是继承自CScreenBase的，传入的参数其实还是$_REQUEST。简单的通读一下这个类的代码加上搜索功能，定位到了下面这段代码： // Calculate timeline. if ($this->required_parameters['timeline'] && $this->timeline === null) { $this->timeline = $this->calculateTime([ 'profileIdx' => $this->profileIdx, 'profileIdx2' => $this->profileIdx2, 'updateProfile' => $this->updateProfile, 'period…

删除push到github上的敏感文件
type: item
date: 2016-07-11T11:38:37+00:00
description: 有时候总是会不小心把存有账号密码之类的敏感文件push到github上。如果这次commit只有这一个文件，并且发现的及时，那还是很好解决的，删除commit然后强推就可以了。 但是不幸的是，总有那么些时候发现的比较晚，并且那次commit中含有其他的文件。 方法1： 试试BFG这个工具吧。 http://rtyley.github.io/bfg-repo-cleaner/ 方法2： 使用git filter-branch命令处理。 step 1 clone项目回来。 step 2 执行以下命令： $ git filter-branch --force --index-filter \ 'git rm --cached --ignore-unmatch PATH-TO-YOUR-FILE-WITH-SENSITIVE-DATA' \ --prune-empty --tag-name-filter cat -- --all 记得把其中的PATH-TO-YOUR-FILE-WITH-SENSITIVE-DATA替换成实际包含敏感信息的文件。 step 3 这个时候该文件其实已经删掉了，但是为了以防万一，现在你可以把它加到.gitignore文件中。或是做一些其他的必要操作。 step 4 强推回去： $ git push origin --force --all 记得把tag也强推一遍，如果没有打过tag，可以不用。 $ git push origin --force --tags step 5 检查一下有没有出现其他的问题，例如少文件，文件内容不对等。没有的话就可以做最后的操作了： $ git for-each-ref --format='delete %(refname)' refs/original | git update-ref --stdin $ git reflog expire --expire=now --all $ git gc --prune=now PS 如果有其他人和你一起维护这个项目，那么应该让他们用rebase来获取最新的内容，千万不要使用merge，不然历史记录中还会出现那些敏感信息的，因为他们的本地log中还存在这些内容，merge的话会重新推上去。 参考资料：https://help.github.com/articles/remove-sensitive-data/

CodeIgniter源码阅读笔记5之Hooks.php
type: item
date: 2015-10-16T05:48:00+00:00
description: CI提供了钩子这种特性，可以在不修改核心文件的情况下用来修改框架内部执行流程。 全局来看，是否开启钩子可以在APPPATH/config/config.php中进行修改，而hooks的配置则在APPPATH/config/hooks.php中进行定义，具体如何实现可以参考官方文档：http://codeigniter.org.cn/user_guide/general/hooks.html?highlight=hooks 这里简单讲一下hooks的定义方式，有助于后面的源码理解。 第一种定义方式，使用数组定义的形式： $hook['pre_controller'] = array( 'class' => 'MyClass', 'function' => 'Myfunction', 'filename' => 'Myclass.php', 'filepath' => 'hooks', 'params' => array('beer', 'wine', 'snacks') ); 另一种出现在PHP5.3以后，可以使用lambda的闭包或是匿名函数作为钩子： $hook['post_controller'] = function() { /* do something here */ }; 如果想在同一个挂钩点添加多个钩子，将其改为二维数组即可。看一下Hooks.php的精简版代码，具体的解析写在了代码的注释中。 <?php defined('BASEPATH') OR exit('No direct script access allowed'); class CI_Hooks { // 钩子是否启用 public $enabled = FALSE; // 所有的hook数组 public $hooks = array(); // 存储hooks的类 protected $_objects = array(); // 运行标志，防止死循环 protected $_in_progress = FALSE; public function __construct() { // 加载配置文件 $CFG =& load_class('Config', 'core'); log_message('info', 'Hooks Class Initialized'); // 检测配置文件中是否存在enable_hooks选项，没有的话直接返回 if ($CFG->item('enable_hooks') === FALSE) { return; } // 查找hooks文件 if (file_exists(APPPATH.'config/hooks.php')) { include(APPPATH.'config/hooks.php'); } if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/hooks.php')) { include(APPPATH.'config/'.ENVIRONMENT.'/hooks.php'); } // 没有hooks，结束。 if ( ! isset($hook) OR ! is_array($hook)) { return; } // 更改相关状态 $this->hooks =& $hook; $this->enabled = TRUE; } public function call_hook($which = '') { // 如果hooks没有启用，或没有传入合法的hooks，返回FALSE if ( ! $this->enabled OR ! isset($this->hooks[$which])) { return FALSE; } // 如果hooks的定义方式为数组，并且没有指定function的时候，依次运行 if (is_array($this->hooks[$which]) && ! isset($this->hooks[$which]['function'])) { foreach ($this->hooks[$which] as $val) { $this->_run_hook($val); } } else // 对于PHP5.3以上，采用匿名函数的时候进入else分支 { $this->_run_hook($this->hooks[$which]); } return TRUE; } protected function _run_hook($data) { // 检测参数是否可调用 if (is_callable($data)) { is_array($data) ? $data[0]->{$data[1]}() : $data(); …

CodeIgniter源码阅读笔记4之Benchmark.php
type: item
date: 2015-09-20T03:50:08+00:00
description: 基准测试类是系统自动加载的，无需用户手动加载。 先贴上代码精简版的Benchmark类 <?php defined('BASEPATH') OR exit('No direct script access allowed'); class CI_Benchmark { public $marker = array(); public function mark($name) { $this->marker[$name] = microtime(TRUE); } public function elapsed_time($point1 = '', $point2 = '', $decimals = 4) { if ($point1 === '') { return '{elapsed_time}'; } if ( ! isset($this->marker[$point1])) { return ''; } if ( ! isset($this->marker[$point2])) { $this->marker[$point2] = microtime(TRUE); } return number_format($this->marker[$point2] - $this->marker[$point1], $decimals); } public function memory_usage() { return '{memory_usage}'; } } 整个类比较简单，有一个$marker变量，三个函数，分别来看一下。 marker变量 存储所有的benchmark点的信息。 mark($name) 这个函数只是简单的调用了microtime函数，并把基准点的名称存入了marker数组中。 elapsed_time($point1 = '', $point2 = '', $decimals = 4) 这个函数是计算两个基准点直接的时间差，接受三个参数。 有个注意点是当$point1为空的时候直接返回了一个字符串，这个字符串其实在后面的输出时被替换成了其他的东西，暂时不管。 memory_usage() 同上，只是返回了一个字符串，也是在后面输出时被替换掉了。

CodeIgniter源码阅读笔记3之CodeIgniter.php
type: item
date: 2015-09-17T07:30:18+00:00
description: 在包含完common.php中的函数后，开始真正的进行一些列的工作。 先是做一些安全方面的处理问题，如果PHP的版本<=5.4，那么会进行防注册全局变量的操作，因为注册全局变量的功能在PHP5.3开始被废弃，并且在PHP5.4以后被移除了。 紧接着设置了错误处理： // 设置错误处理 set_error_handler('_error_handler'); set_exception_handler('_exception_handler'); register_shutdown_function('_shutdown_handler'); 接下来检查了核心类扩展的前缀问题： if ( ! empty($assign_to_config['subclass_prefix'])) { get_config(array('subclass_prefix' => $assign_to_config['subclass_prefix'])); } 通常情况下$assign_to_config变量应当在index.php中设置，subclass_prefix的值在APPPATH/config/config.php文件中设置。 这里通过$assign_to_config['subclass_prefix']指定用户的扩展核心的前缀名。 注意一点，index.php中的subclass_prefix具有更高的优先权。 紧接着检查了是否应当使用Composer autoloader来进行加载。暂时忽略这部分。 后面的部分就是加载了一系列的组件来帮助CI完成更多的功能，并进行一系列的初始化操作。 基准测试类BM（Benchmark），用于计时、性能测试等功能。 扩展组件EXT（Hooks），CI的扩展组件，类似于钩子，可以在不改变CI核心代码的情况下增加功能。然后调用了pre_system这个挂钩点，如果存在的话。 配置文件管理CFG（Config），用于加载配置文件，获取配置项等。加载完成后也处理了一下$assign_to_config的变量。 UNI（Utf-8），负责处理UTF-8相关的工作。 URI和RTR，前者负责解析URI并获取参数等，后者是路由组件 OUT（output），输出组件，所有的输出均是通过该组件输出的。 SEC（Security），负责处理安全相关的问题。 IN(Input），负责处理输入相关的东西。 LANG（Lang），处理语言相关的东西。 这些组件的加载方式都是通过调用load_class函数来处理的，第二个参数指文件所在的目录。

CodeIgniter源码阅读笔记2之Common.php
type: item
date: 2015-09-16T06:59:00+00:00
description: 文件的一开始就有如下代码： defined('BASEPATH') OR exit('No direct script access allowed'); 这个是为了防止直接访问文件而写的，这样可以保证是从index.php访问过来的。 接下来两段代码引入了两个文件： /* * ------------------------------------------------------ * Load the framework constants * ------------------------------------------------------ */ // 引入全局变量文件 // 包括文件权限、open的标志位、是否显示backtrace、退出状态码 if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/constants.php')) { require_once(APPPATH.'config/'.ENVIRONMENT.'/constants.php'); } require_once(APPPATH.'config/constants.php'); /* * ------------------------------------------------------ * Load the global functions * ------------------------------------------------------ */ // 引入全局函数 require_once(BASEPATH.'core/Common.php'); 我在注释中已经写明了这两个文件的作用，constants.php这个文件比较普通，没啥需要注意的，我们看看Common.php这个文件。 这个文件中定义了一些常用的公共函数，而且定义的方式也是比较特别，采用了如下的形式： if (! function_exists('function_name')) { function function_name () { //TODO } } 很明显这是为了防止函数名重复而采取的容错措施，所以用户在自己定义系统级的全局函数的时候也应该采用这种方式，防止函数名重复。 我们依次每个函数看一下。 is_php 很明显这个函数是用于判断版本号的，函数内部有一个static的$_is_php变量，用于缓存version_compare的结果，这样可以每次在使用is_php函数的时候提高效率。 /** * Determines if the current version of PHP is equal to or greater than the supplied value * * @param string * @return bool TRUE if the current version is $version or higher */ function is_php($version) { static $_is_php; $version = (string) $version; if ( ! isset($_is_php[$version])) { $_is_php[$version] = version_compare(PHP_VERSION, $version, '>='); } return $_is_php[$version]; } is_really_writable 这个函数是用于判断某个文件或目录是否可写，本质上该函数是is_writable()的封装，至于为何要封装，有两点原因： 在windows系统中，如果设置了read-only属性，is_writable()会返回true，但是实际上该文件、目录并不可写。 在unix系统中，如果safe_mode的值为on，那么is_writable()的结果也会出现问题。 因此，is_really_writable()需要对这两种情况进行特殊判断，如果在Unix系统上，而且safe_mode的值为off，直接调用is_writable()。否则的话，如果目标是目录，则尝试写入一个文件来判断是否可写，如果目标是文件，则尝试使用fopen来判断是否可写。 /** * Tests for file writability * * is_writable() returns TRUE on Windows servers when you really can't write to * the file, based on the read-only attribute. is_writable() is also unreliable…

CodeIgniter源码阅读笔记1之index.php
type: item
date: 2015-09-15T01:55:00+00:00
description: 目前阅读的版本为稳定版3.0 这个文件作为CI的入口文件，主要做了一些初始化之类的操作，保证后续的正常执行。 运行环境 define('ENVIRONMENT', isset($_SERVER['CI_ENV']) ? $_SERVER['CI_ENV'] : 'development'); 在文件的开始就需要定义一个运行环境，CI默认提供了development、testing、production三种环境，用户可以定义自己的运行环境，后面在很多的文件中都用到了这个变量。很明显后面就根据这个环境设置错误报告的级别，如果用户定义了自己的运行环境，也需要在这里进行相应的修改。 目录设置 接下来就是设置了几个目录，当然允许用户也可以自己更改system、application的名称。 $system_path = 'system'; $application_folder = 'application'; $view_folder = ''; 需要注意最后没有slash。 默认控制器 这里给用户一个定义默认控制器的功能，正常情况下是在路由中设置默认控制器的，但是可以在这里强制硬编码一个控制器。这样做的结果就是你的web程序只能有一个特定的控制器，所以没有特殊需要的话留空即可。 到这里用户可以自定义的配置文件部分就完成了，接下来通常情况下是不需要用户改动的。 配置CLI if (defined('STDIN')) { chdir(dirname(__FILE__)); } if (($_temp = realpath($system_path)) !== FALSE) { $system_path = $_temp.'/'; } else { // Ensure there's a trailing slash $system_path = rtrim($system_path, '/').'/'; } // Is the system path correct? if ( ! is_dir($system_path)) { header('HTTP/1.1 503 Service Unavailable.', TRUE, 503); echo 'Your system folder path does not appear to be set correctly. Please open the following file and correct this: '.pathinfo(__FILE__, PATHINFO_BASENAME); exit(3); // EXIT_CONFIG } 在这里设置一些东西，可以让CLI模式正常运行，比较简单。 定义路径常量 接下来就是定义一些常量了，像什么SELF、BASEPATH等等。在后面的源码阅读中都是比较常用的，建议简单记一部分。 最后引入了 BASEPATH中的core/CodeIgniter.php文件，这个文件可以算是整个CI框架执行流程的核心部分，被称作bootstrap文件。 最后看一下整个CI框架的执行流程

基于SSDT的注册表主防系统
type: item
date: 2015-08-29T13:04:00+00:00
description: 注：这个主防只支持x86系统。。本人技术略渣，x64还没搞出来。。。。 注注：这篇文章是很久以前发在旧博客里的，无意间翻到了觉得还不错，复制过来了，毕竟也是用心做的东西。 这个SSDT的内容我用了之前的一个框架，网上也有很多的SSDT库。关于SSDT还不太清楚的童鞋可以看看我以前的一篇文章。 http://lightlesshome.diandian.com/post/2014-01-29/40060870666 主要就是一个主防的思路问题。我的思路比较简单，很多方面都没有考虑到，用ZwSetKeyValue这个函数来开刀。 首先肯定是要HOOK掉这个函数。之后就要考虑ring0和ring3交互的问题。这里采用的是DeviceIoControl和IOCTL的方法来进行交互的。 首先ring0进行监听，然后得到监听到事件后向ring3发出一个信号（暂且用信号这个词，其实是通知ring3），然后ring3收到通知后，向ring0请求信息，ring0会把一系列的东西传递给ring3，ring3进行处理后弹框并询问用户是否允许修改。 我参考了下这篇文章的方法，在数据量不大的时候并且不要求效率的时候可以考虑采用下面这个模型。 http://www.cnblogs.com/dflower/archive/2009/04/17/1438066.html IOCTL控制码的话主要定义了以下几个。 // 设置事件 #define IOCTL_SETEVENT \ (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN, 0x831, METHOD_BUFFERED, FILE_ANY_ACCESS) // 向ring0 请求数据 #define IOCTL_GETINFO \ (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN, 0x832, METHOD_BUFFERED, FILE_ANY_ACCESS) // 设置等待用户判断的事件 #define IOCTL_SETWAITEVENT \ (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN, 0x833, METHOD_BUFFERED, FILE_ANY_ACCESS) // 传递用户的判断结果 #define IOCTL_PASSUSERRES \ (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN, 0x834, METHOD_BUFFERED, FILE_ANY_ACCESS) 用途稍后会进行解释。 关于这个DeviceIoControl函数的用法可以参考下MSDN，比较简单，主要就是向ring0发送个控制码，然后获取数据之类的啥啥啥的。 在ring0中用这个函数。NTSTATUS DeviceIoControlDispatch(IN PDEVICE_OBJECT DeviceObject, IN PIRP pIrp); 然后设置以下派遣函数 DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DeviceIoControlDispatch; DriverObject->MajorFunction[IRP_MJ_CREATE] = DeviceIoControlDispatch; DriverObject->MajorFunction[IRP_MJ_CLOSE] = DeviceIoControlDispatch; 下面要在这个函数中获取IOCTL功能码 PIO_STACK_LOCATION irpStack; irpStack = IoGetCurrentIrpStackLocation(pIrp); irpStack->Parameters.DeviceIoControl.IoControlCode 那个IoControlCode就是功能码了。 下面就是继续switch case 来进行处理每一个功能码了。 基本上DeviceIoControlDispatch就是这样了。下面来考虑怎么在ring0和ring3中互相通知。 可以在ring3中创建一个事件，然后单独开一个线程来监视这个事件。开线程之前使用DeviceIoControl函数将这个事件的句柄传递到内核中，ring0收到这个事件后，转为句柄并保存起来，在以后需要使用的时候拿出来使用。 在ring3中： 先打开我们的驱动，其中的"\\.\RegDef"是驱动中的符号链接。 m_hDevice = CreateFile("\\\\.\\RegDef", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_SYS…

基于PHP Extension的WAF实现
type: item
date: 2015-07-24T04:18:00+00:00
description: 好久没有写东西，这篇文章也是第一次投给乌云，赚了点外快，乌云那边已经发出来了，贴到自己博客里吧。 0x00 前言 最近上(ri)网(zhan)上(ri)多了，各种狗啊盾啊看的好心烦，好多蜜汁shell都被杀了，搞的我自己也想开发这么一个斩马刀，顺便当作毕设来做了。 未知攻，焉知防。我们先来看看shell们都是怎么躲过查杀的：加密、变形、回调、隐藏关键字……总之就是一句话，让自己变得没有特征，这样就可以躲过狗和盾的查杀。但是万变不离其宗，无论怎么变形，最终都会回到类似这样的格式： $_GET($_POST) 分为执行数据部分($_GET)和传递数据的部分($_POST)，也就是说，无论怎么变形，在执行的过程中都会变成这个样子，接下来还是去执行类似system(), exec(), eval()等等函数，那么我们就直接定位到这里，检测该脚本是否调用危险函数，或是在脚本调用这些函数的时候进行分析，判断该脚本是否为非法用户的shell，会取到很不错的防御效果。 0x01 如何获取先机 既然分析出来了问题的根结，那么下一步就是要控制住这些危险函数的入口点，即system()等函数的底层入口，就像Windows API中hook掉zw系列函数一样，我们也要hook掉system()这些函数。 这里我们需要用到PHP Extension， 即PHP扩展，位于PHP内核zend和PHP应用代码之间，很明显，扩展可以监控应用层代码的执行细节，也可以调用内核提供的ZEND API接口，包括禁用类、禁用函数等等。图为PHP结合其他必要组件的基本结构。 在ZEND中提供了接口供我们进行这样的操作，通过zend_set_user_opcode_handler就可以达到目的，根据某大牛所说，只需要hook三个OPCODE即可。 ZEND_INCLUDE_OR_EVAL 处理eval()、require等 ZEND_DO_FCALL 函数执行system()等 ZEND_DO_FCALL_BY_NAME 变量函数执行 $func = "system";$func();等 以ZEND_DO_FCALL为例，在MINIT中将ZEND_DO_FCALL替换掉： zend_set_user_opcode_handler(ZEND_DO_FCALL, LIGHT_DO_FCALL); 接着定义自己的处理函数，这里是LIGHT_DO_FCALL。 static int LIGHT_DO_FCALL(ZEND_OPCODE_HANDLER_ARGS) { /* 检测是否为合法调用 */ if (/*非法调用*/) { /*拦截掉，不执行。*/ } else { /*合法调用，继续执行*/ return ZEND_USER_OPCODE_DISPATHC; } } 这样一个骨架就完成了，其余的内容可以自行添加。这样每当调用system()等函数时，都会经过你自己的LIGHT_DO_FCALL，于是就可以对上层的代码执行进行检查了。 0x02 上面的方法太粗暴 上面的方法固然是好，但是误判会很多，甚至一个普通的函数调用都会直接被ban掉。这样的WAF放在业务中一定会被喷出翔的。我们需要换个思路。 之前已经说过了，无论如何变形、加密、隐藏关键字等，最终都需要调用到system()，eval()等函数，终究是逃不过这些的。那么我们把刚才的思路的变一下，不要直接hook那几个OPCODE，向上走一层。 eval()函数在底层是要调用zend_compile_string函数的，那么我们是不是可以在底层重载掉zend_compile_string函数，或是用自己的函数替换掉呢？答案肯定是可以的，我们只需要重写自己的zend_compile_string函数即可。 但是对于system来说有些不一样，上面我们hook掉了ZEND_DO_FCALL，但是太粗暴了，我们这里可以在function_table中删除掉system，并用我们自己的函数注册system函数，这样就可以做到了实时检查，如果为合法调用，可以调用旧的system函数并继续执行。 很明显这样做的话，确实减少了误杀，但是也增加了风险，因为除了system()之外还有很多可以用于执行命令的函数。除了执行命令，还有遍历目录、读敏感文件的问题，需要考虑很多的方面，偏僻的函数都要考虑在内，还有PHP SPL、DirectoryIterator等等各种方面。 0x03 说了这么多我们看看实际效果吧 我们来看看hook三个OPCODE的方式会产生什么样子的效果。首先我们在MINIT函数中将这三个OPCODE的处理函数换成我们自己的。 ZEND_MINIT_FUNCTION(lightWAF) { /* * hook掉ZEND_DO_…

Typecho 源码笔记 路由类
type: item
date: 2015-07-02T09:03:00+00:00
description: 这篇文章，我们来分析typecho的路由部分，在我看来路由一直是很NB的东西，现在我们一起来分析一下typecho的路由到底是怎么个东西。 相关文件 typecho中的路由相关的文件不多，只存放于/var/Typecho/Router.php和/var/Typecho/Router/文件夹下面。 /var/Typecho/Router.php 一个很普通的Typecho_Router类，看到TODO里写着以后要增加cache，这样速度应该会提高不少吧。 这个类没有构造函数，所以我们就从头开始看吧，有三个变量，分别是$current存储当前的路由名称，$_routeingTable存储解析完成的路由表配置，最后是$_pathInfo存储全路径。 先来看解析路径的函数，match()，它接受两个参数，分别是全路径以及输入参数。 public static function match($pathInfo, $parameter = NULL) { foreach (self::$_routingTable as $key => $route) { if (preg_match($route['regx'], $pathInfo, $matches)) { // 如果在路由表中匹配到了，就将current赋值为该路由的名称 // 猜测$_routingTable的第一维存储的是每个路由的名称，然后有一个'regx'字段存储的是路由规则 // 即$_routingTable['routeName'] // 其中每一项的内容：$routingTable['routeNameA']['regx'] => '/archive/123.html' self::$current = $key; try { /** 载入参数 */ $params = NULL; if (!empty($route['params'])) { // 如果$route['params']非空 unset($matches[0]); // 撤销变量 // 创建一个新的数组，$route['params']作为新数组的key，$matches作为新数组的value $params = array_combine($route['params'], $matches); } // 这里用到了Typecho_Widget的工厂方法widget，这个我们后面在分析Widget的时候再说 // widget是typecho很重要的组成部分，是基本元素，除了抽象出来的类库，其他的几乎所有功能 // 都会通过widget完成。 $widget = Typecho_Widget::widget($route['widget'], $parameter, $params); // 会返回一个关于路由的widget？ return $widget; } catch (Exception $e) { // 没有匹配到的话就返回404吧大概 if (404 == $e->getCode()) { Typecho_Widget::destory($route['widget']); continue; } throw $e; } } } return false; } 我自己的一些理解写到了注释里，可能有些地方不太对，等后面看完了widget的部分再回来修改。 接下来我们看一看dispatch()路由分发函数，这个和上面的match()函数基本一致，唯一不同的是增加了一段关于widget的代码： if (isset($route['action'])) { $widget->{$route['action']}(); } 应该是调用了这个widget的暂时还不知道啥意思，后面再看。 下面会看到一个路由反解析函数，猜测最后应该是将路由解析成URL吧。但是其中遇到了一个Typecho_Common::url，也只能等分析到Common类的时候再看吧。 最后还剩下两个函数，分别是设置路由默认配置，以及获取路由信息的函数，获取路由信息的函数十分简单，没啥好说的了。而在设置路由默认配置中又遇到了Typecho_Router_Parser这个类，下面我们就去分析这个文件。 /var/Typecho/Router/Parser.php 这个类叫做路由器解析器，先从构造函数开始看吧。 public function __construct(array $routingTable) { $this->_routingTable = $routingTable; $this->_defaultRegx = array( 'string' => '(.%s)', 'char' => '([^/]%s)', 'dig…