【原】discuz! 7.2 超详细代码解析(1)

2010年07月16日 星期五 03:58

由于某度众所周知的举动,让我搬离写了5年的渣度空间,准备把技术性的文章定在CSDN了。这些都是文章备份。勿怪。。

鉴于最近有些抓取机器和抄袭者,把标题的【原】字都复制,我不得不声明:本文为 yukon12345原创,转载请注明出处http://blog.csdn.net/yukon12345

 discuz这名字搞程序的基本都知道,最近有点兴趣,研究了下discuz! 7.2,看完程序感觉它为了兼容,程序风格实在太落伍,不过既然看了写一些代码解析吧。放上研究成果:

首先当然是打开index.PHP:

define('BINDDOMAIN', 'index');
//最开始的引入。打开这个文件继续研究
require_once './include/common.inc.php';

/include/common.inc.php文件:

//不显示错误,不是用magic_quotes.
error_reporting(0);
set_magic_quotes_runtime(0);
//把时间分为秒类型时间戳和万分之一秒类型。
$mtime = explode(' ', microtime());
$discuz_starttime = $mtime[1] + $mtime[0];

//SYS_DEBUG为是否是调试模式,IN_DISCUZ用于判断是否在允许的请求中。DISCUZ_ROOT表示本地绝对路径格式的网站根目录
define('SYS_DEBUG', FALSE);
define('IN_DISCUZ', TRUE);
define('DISCUZ_ROOT', substr(dirname(__FILE__), 0, -7));
define('MAGIC_QUOTES_GPC', get_magic_quotes_gpc());
//!defined('CURSCRIPT') && define('CURSCRIPT', '');

//兼容低版本php
if(PHP_VERSION < '4.1.0') {
$_GET = &$HTTP_GET_VARS;
$_POST = &$HTTP_POST_VARS;
$_COOKIE = &$HTTP_COOKIE_VARS;
$_SERVER = &$HTTP_SERVER_VARS;
$_ENV = &$HTTP_ENV_VARS;
$_FILES = &$HTTP_POST_FILES;
}
//安全代码。当通过Post Get Cookie FILES 方式传递一个键为GLOBALS的变量时会提示数据污染,并关闭
//原因是防止在register_globals=on时通过提交GLOBALS变量就可以绕过紧接下来的一段对特殊字符加反斜杠的代码
//注意:在php 5.3.X中默认的php.ini中request_order为PG,而没有C,就有可能通过cookie来绕过下面的个反斜杠添加。
//解决方法:php.ini中request_order为PGC
if (isset($_REQUEST['GLOBALS']) OR isset($_FILES['GLOBALS'])) {
exit('Request tainting attempted.');
}

require_once DISCUZ_ROOT.'./include/global.func.php';
//getrobot()在global.func.php中,执行后会根据是否为爬虫而设置IS_ROBOT的true,false
getrobot();
//对爬虫禁止。
if(defined('NOROBOT') && IS_ROBOT) {
exit(header("HTTP/1.1 403 Forbidden"));
}
//daddslashes函数在global.func.php中,作用为递归遍历所有$_value,为其加上反斜杠。并将C,P,G的变量统一赋成$key=$value
//注意:之后需严格判定所有的$key=value类型值,防止被get类型的url参数恶意篡改
//这段是为了兼容老版本的$_REQUEST[]而作。
foreach(array('_COOKIE', '_POST', '_GET') as $_request) {
foreach($_request as $_key => $_value) {
$_key{0} != '_' && $_key = daddslashes($_value);
}
}

if (!MAGIC_QUOTES_GPC && $_FILES) {
$_FILES = daddslashes($_FILES);
}

$charset = $dbs = $dbcharset = $forumfounders = $metakeywords = $extrahead = $seodescription = $mnid = '';
$plugins = $admincp = $scriptlang = $forum = $thread = $language = $jsmenu = $actioncode = $modactioncode = $pluginclasses = $hooks = $lang = array();
$_DCOOKIE = $_DSESSION = $_DCACHE = $_DPLUGIN = $advlist = array();

require_once DISCUZ_ROOT.'./config.inc.php';
//开启xss防御,并且有请求时,检测远程链接而来的url。防止通过url的方式被xss
if($urlxssdefend && !empty($_SERVER['REQUEST_URI'])) {
//将url编码解码
$temp = urldecode($_SERVER['REQUEST_URI']);
//当有<或者"时,提示非法链接。其实并不可靠。可以通过html编码绕过。
if(strpos($temp, '<') !== false || strpos($temp, '"') !== false)
exit('Request Bad url');
}
//$cookiepre为config.inc.php中变量。存储cookie的前缀
$prelength = strlen($cookiepre);
foreach($_COOKIE as $key => $val) {
if(substr($key, 0, $prelength) == $cookiepre) {
//将所有_COOKIE信息存入DCOOKIE数组
$_DCOOKIE[(substr($key, $prelength))] = MAGIC_QUOTES_GPC ? $val : daddslashes($val);
}
}
//销毁几个变量
unset($prelength, $_request, $_key, $_value);


//空赋值为false,不空赋值为true
$inajax = !empty($inajax);
$handlekey = !empty($handlekey) ? htmlspecialchars($handlekey) : '';
$timestamp = time();


//启用$attackevasive(config.inc.php中设置)并且CURSCRIPT不等于'seccode',防ddos载入安全设置
if($attackevasive && (!define('CURSCRIPT') || CURSCRIPT != 'seccode')) {
require_once DISCUZ_ROOT.'./include/security.inc.php';
}


//载入mysql操作类
require_once DISCUZ_ROOT.'./include/db_'.$database.'.class.php';

//dhtmlspecialchars()在global.func.php内,作用为:
//将&"<>过滤,同时放过&#+(3到5位10进制数字)或&#x+(4位16进制的数字)格式的编码
//【暂时不清楚为何要如此?完全利用这个对html编码放行的设定而进行xss攻击】
//获得目录地址如include/conmmon.inc.php
$PHP_SELF = dhtmlspecialchars($_SERVER['PHP_SELF'] ? $_SERVER['PHP_SELF'] : $_SERVER['SCRIPT_NAME']);


//获得页面名如common.inc.php
$BASESCRIPT = basename($PHP_SELF);
//获取成common
list($BASEFILENAME) = explode('.', $BASESCRIPT);
//在含wap,archiver,api的url中去除掉这3个词。
$boardurl = htmlspecialchars('http://'.$_SERVER['HTTP_HOST'].preg_replace("/\/+(api|archiver|wap)?\/*$/i", '', substr($PHP_SELF, 0, strrpos($PHP_SELF, '/'))).'/');
//获取访问者的ip
if(getenv('HTTP_CLIENT_IP') && strcasecmp(getenv('HTTP_CLIENT_IP'), 'unknown')) {
$onlineip = getenv('HTTP_CLIENT_IP');
} elseif(getenv('HTTP_X_FORWARDED_FOR') && strcasecmp(getenv('HTTP_X_FORWARDED_FOR'), 'unknown')) {
$onlineip = getenv('HTTP_X_FORWARDED_FOR');
} elseif(getenv('REMOTE_ADDR') && strcasecmp(getenv('REMOTE_ADDR'), 'unknown')) {
$onlineip = getenv('REMOTE_ADDR');
} elseif(isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'], 'unknown')) {
$onlineip = $_SERVER['REMOTE_ADDR'];
}
//匹配出正确格式的ip。
preg_match("/[\d\.]{7,15}/", $onlineip, $onlineipmatches);
//有匹配项,设为ip,没有就设为unknown
$onlineip = $onlineipmatches[0] ? $onlineipmatches[0] : 'unknown';
unset($onlineipmatches);

//引入cache_settings.php,成功$cachelost为空,不成功为settings
$cachelost = (@include DISCUZ_ROOT.'./forumdata/cache/cache_settings.php') ? '' : 'settings';
//将数组拆分。类似list
@extract($_DCACHE['settings']);



//根据后台的绑定设置使版块转向设定的域名。
//下面这段程序非常的绕人。实际上是这样的:discuz可以给一个版块绑定到另一个域名上。当绑定成功后,进入这个特设的域名时,程序会自动转到“老域名/forumdisplay.php”页来处理。
//但浏览器地址栏仍然还是显示 “新域名/index.php?参数”来显示此版帖子列表。而不是“老域名/forumdisplay.php?参数”。
//可能是为了考虑web服务器默认的首页都是index类型,而且在虚拟主机上不好改成forumdisplay.php的原因。
//在wap,archiver等页面都有这种url变通方法。

//===========详细解析:
//如果定义了BINDDOMAIN(index中已经定义为index),且不空,并且cachelost空(空代表成功引入了上面的cache_settings),
//且$binddomains && $forumdomains不空,(forumdata/cache/cache_settings.php引入,默认为空。作用为标记什么版块转向什么域名)
if(defined('BINDDOMAIN') && BINDDOMAIN && !$cachelost && $binddomains && $forumdomains) {
//获取$binddomains数组,如果当前页的域名就是绑定的域名,返回需要转向的版块号fid给$loadforum。否则$loadforum=0
$loadforum = isset($binddomains[$_SERVER['HTTP_HOST']]) ? max(0, intval($binddomains[$_SERVER['HTTP_HOST']])) : 0;
if($loadforum) {//此页面所在网址就是设置绑定的域名的话
//如果当前页面是“老域名forumdisplay.php”,那么就拼装地址,永久重定向到绑定的 “新域名/index.php”上 。
if(BINDDOMAIN == 'forumdisplay' && $loadforum == $fid) {
header("HTTP/1.1 301 Moved Permanently");
//去掉url中的fid=。因为是把此版块绑定了域名不需要fid
$query_string = preg_replace('/\??fid='.$fid.'&?/is', '', $_SERVER['QUERY_STRING']);
//$indexname由缓存页面/forumdata/cache/cache_settings.php引入.默认index.php
dheader("Location: http://$_SERVER[HTTP_HOST]/{$indexname}".($query_string ? "?{$query_string}" : ''));
}
if(BINDDOMAIN == 'index') {
//这时候转跳到了“新域名/index.php”,不通过GR等方式获取fid了。而是通过$loadforum(前面说过了是在cache文件中取得此值的。)
$fid = $_GET['fid'] = $_REQUEST['fid'] = $loadforum;
define('CURSCRIPT', 'forumdisplay');
}
} else {//$loadforum==0时表明$binddomains数组没有当前域名项,
//并且当前页面为某版块帖子列表,isset($forumdomains[$fid])是用来判定此版块是否是需要转跳的版块
//(就是论坛其他页面某些页面通过 forumdisplay.php?fid=xxx 点击进了需要转跳的版块)
//将转跳到绑定的域名。
if(BINDDOMAIN == 'forumdisplay' && isset($forumdomains[$fid])) {
$host = $forumdomains[$fid];
header("HTTP/1.1 301 Moved Permanently");
dheader("Location: http://{$host}/{$indexname}");
}
//别忘了将执行路径改成forumdisplay
define('CURSCRIPT', BINDDOMAIN);
}

}


//定义CURSCRIPT等于BINDDOMAIN的值。CURSCRIPT在其他页面也有定义比如wap目录下的index中定义为wap,用来标示各种不同类型的页面。
if(!defined('CURSCRIPT')) {
define('CURSCRIPT', defined('BINDDOMAIN') ? BINDDOMAIN : '');
}

//没有定义STAT_ID,且$statdisable被定义成假,那么从
if(!defined('STAT_ID') && isset($statdisable) && empty($statdisable)) {
define('STAT_ID', $_DCACHE['settings']['statid']);
define('STAT_KEY', $_DCACHE['settings']['statkey']);
}

//检查$gzipcompress标量,并且判断有ob_gzhandler函数,CURSCRIPT中不包含'attachment', 'wap' ,且$inajax不空,这些都达成的话就开启gzip压缩。否则就不开启
if($gzipcompress && function_exists('ob_gzhandler') && !in_array(CURSCRIPT, array('attachment', 'wap')) && !$inajax) {
ob_start('ob_gzhandler');
} else {
$gzipcompress = 0;
ob_start();
}


//loadctrl由cache_settings.php引入.默认值0即不进行负载检测

//当不为0且不为win系统时
if(!empty($loadctrl) && substr(PHP_OS, 0, 3) != 'WIN') {
// /proc/loadavg为unix类系统上记录均衡负载的文件
//内容可能如 1.41 1.61 1.79 6/149 2331
//前三个值分别对应系统在5分钟、10分钟、15分钟内的平均负载
//第四个值的分子是正在运行的进程数,分母是进程总数,最后一个是最近运行的进程ID号】
//具体请man proc

if($fp = @fopen('/proc/loadavg', 'r')) {
list($loadaverage) = explode(' ', fread($fp, 6));
fclose($fp);
if($loadaverage > $loadctrl) {
//当大于设定的负载值时候,显示
header("HTTP/1.0 503 Service Unavailable");
include DISCUZ_ROOT.'./include/serverbusy.htm';
exit();
}
}
}

//当网址中含有下面这些文件时,尝试是否能引入缓存页面,不能就把相关关键字加入$cachelost中
if(in_array(CURSCRIPT, array('index', 'forumdisplay', 'viewthread', 'post', 'topicadmin', 'register', 'archiver'))) {
$cachelost .= (@include DISCUZ_ROOT.'./forumdata/cache/cache_'.CURSCRIPT.'.php') ? '' : ' '.CURSCRIPT;
}


//实例化之前的db_mysql.class.php操作类。
$db = new dbstuff;
//下面一堆变量都是从config.inc.php获取的变量
$db->connect($dbhost, $dbuser, $dbpw, $dbname, $pconnect, true, $dbcharset);
//变量设置空。
$dbuser = $dbpw = $pconnect = $sdb = NULL;

//注意这个$sid非常重要,是标示session的id,整个论坛认证的关键。同时discuz也把session值存在session表里,做双重存储,防止丢失。
//transsidstatus在forum/cache_settings.php页面中,默认为1,作用为允许传递sid。
//当允许时按GP的优先级获得sid赋给$sid(即session表内的id标示),当不允许时获取cookie中的$sid
$sid = daddslashes(($transsidstatus || CURSCRIPT == 'wap') && (isset($_GET['sid']) || isset($_POST['sid'])) ?
(isset($_GET['sid']) ? $_GET['sid'] : $_POST['sid']) :
(isset($_DCOOKIE['sid']) ? $_DCOOKIE['sid'] : ''));

//当访问方式为attachment时,用GET到的sid解密后赋给$sid
//authcode为/include/global.func.php函数作用是加密/解密接收到的$sid,
CURSCRIPT == 'attachment' && isset($_GET['sid']) && $sid = addslashes(authcode($_GET['sid'], 'DECODE', $_DCACHE['settings']['authkey']));


//设置利用预设的$_DCACHE['settings']['authkey'](forum/cache_settings.php页)拼合访问者的信息,md5加密,赋给$discuz_auth_key
$discuz_auth_key = md5($_DCACHE['settings']['authkey'].$_SERVER['HTTP_USER_AGENT']);

//取cookie中的auth值按\t分割,分别赋给为$discuz_pw(密码), $discuz_secques(安全问题), $discuz_uid(用户id)
list($discuz_pw, $discuz_secques, $discuz_uid) = empty($_DCOOKIE['auth']) ? array('', '', 0) : daddslashes(explode("\t", authcode($_DCOOKIE['auth'], 'DECODE')), 1);


//记得用前初始化。不然有安全问题
$prompt = $sessionexists = $seccode = 0;
//记录一个sql语句。把一堆字段名命名为别名
$membertablefields = 'm.uid AS discuz_uid, m.username AS discuz_user, m.password AS discuz_pw, m.secques AS discuz_secques,
m.adminid, m.groupid, m.groupexpiry, m.extgroupids, m.email, m.timeoffset, m.tpp, m.ppp, m.posts, m.threads, m.digestposts,
m.oltime, m.pageviews, m.credits, m.extcredits1, m.extcredits2, m.extcredits3, m.extcredits4, m.extcredits5,
m.extcredits6, m.extcredits7, m.extcredits8, m.timeformat, m.dateformat, m.pmsound, m.sigstatus, m.invisible,
m.lastvisit, m.lastactivity, m.lastpost, m.prompt, m.accessmasks, m.editormode, m.customshow, m.customaddfeed, m.newbietaskid';
if($sid) {//如果有sid


if($discuz_uid) {//如果cookie中有相关值
//使用数据库操作类进行session表和members表的联合数据查询。"{$tablepre}"防止""中直接使用变量时出错
$query = $db->query("SELECT s.sid, s.styleid, s.groupid='6' AS ipbanned, s.pageviews AS spageviews, s.lastolupdate, s.seccode, $membertablefields
FROM {$tablepre}sessions s, {$tablepre}members m
WHERE m.uid=s.uid AND s.sid='$sid' AND CONCAT_WS('.',s.ip1,s.ip2,s.ip3,s.ip4)='$onlineip' AND m.uid='$discuz_uid'
AND m.password='$discuz_pw' AND m.secques='$discuz_secques'");
}
else {//没有cookie的话,用sid获取session表中的数据
$query = $db->query("SELECT sid, uid AS sessionuid, groupid, groupid='6' AS ipbanned, pageviews AS spageviews, styleid, lastolupdate, seccode
FROM {$tablepre}sessions WHERE sid='$sid' AND CONCAT_WS('.',ip1,ip2,ip3,ip4)='$onlineip'");
}


if($_DSESSION = $db->fetch_array($query)) {//查询成功的话获取结果到$_DSESSION中数据
$sessionexists = 1;//设置已存session
//sessionuid为session表中账号标示--uid字段。如果上面那段程序是在没有cookie的情况下用$sid查询的,那只获得了session表的字段值,
//下面将查询member表中的数据。放入$_DSESSION中
if(!empty($_DSESSION['sessionuid'])) {//不空的话取members一堆值。赋给$_DSESSION数组
$_DSESSION = array_merge($_DSESSION, $db->fetch_first("SELECT $membertablefields
FROM {$tablepre}members m WHERE uid='$_DSESSION[sessionuid]'"));
}
}
else {//第一种情况:有cookie但查询不成功。(就可能是外部伪造的cookie)再用sid查询,成功就清除cookie,设置成在线,还是不成功,就是不在线。
//第二种情况,无cookie,且通过sid也查询不成功,再查询一次,成功就清除cookie,设置成在线,还是不成功,就是不在线。
if($_DSESSION = $db->fetch_first("SELECT sid, groupid, groupid='6' AS ipbanned, pageviews AS spageviews, styleid, lastolupdate, seccode
FROM {$tablepre}sessions WHERE sid='$sid' AND CONCAT_WS('.',ip1,ip2,ip3,ip4)='$onlineip'"))
{
clearcookies();
//设置已存储session
$sessionexists = 1;
}
}
}

if(!$sessionexists) {//当session不存在时
if($discuz_uid) {//获取cookie中的uid值(可能用户新访问网站,并且留有自动登录的cookie。)
if(!($_DSESSION = $db->fetch_first("SELECT $membertablefields, m.styleid
FROM {$tablepre}members m WHERE m.uid='$discuz_uid' AND m.password='$discuz_pw' AND m.secques='$discuz_secques'"))) {
//通过cookie登录不了时,证明为伪造cookie,清空此人cookie
clearcookies();
}
}
//检查此人是否是被禁的ip段,ipbanned在include/global.inc.php中
if(ipbanned($onlineip)) $_DSESSION['ipbanned'] = 1;

$_DSESSION['sid'] = random(6);//设置个错乱的sid。
$_DSESSION['seccode'] = random(6, 1);
}
//获取几个日期格式
$_DSESSION['dateformat'] = empty($_DSESSION['dateformat']) || empty($_DCACHE['settings']['userdateformat'][$_DSESSION['dateformat'] -1])? $_DCACHE['settings']['dateformat'] : $_DCACHE['settings']['userdateformat'][$_DSESSION['dateformat'] -1];
$_DSESSION['timeformat'] = empty($_DSESSION['timeformat']) ? $_DCACHE['settings']['timeformat'] : ($_DSESSION['timeformat'] == 1 ? 'h:i A' : 'H:i');
$_DSESSION['timeoffset'] = isset($_DSESSION['timeoffset']) && $_DSESSION['timeoffset'] != 9999 ? $_DSESSION['timeoffset'] : $_DCACHE['settings']['timeoffset'];

//用完后清空查询语句
$membertablefields = '';
//把$_DSESSION分解成$key=value 格式
@extract($_DSESSION);

$disableprompt = !empty($_DCOOKIE['disableprompt']) ? explode('|', $_DCOOKIE['disableprompt']) : array();
//$prompt为member表中的prompt字段,由$_DSESSION被extract产生的。作用为存储提醒。
if($prompt) {
//当开启任务,并且有消息提示时。taskon在cache_settings中
if($taskon && ($prompt & 8)) {
//$prompts为存在cache_settings.php中的一个数组
$prompts['newbietask'] = 1;
//disallowfloat在cache_setting.php中为newthread|reply。
$disallowfloat = str_replace('task', '', $disallowfloat);
$disallowfloat .= '|newthread|reply';
$editormode = 0;
}
$prompt = 0;
//从prompt表中取出对应uid的提示
$query = $db->query("SELECT typeid, number FROM {$tablepre}prompt WHERE uid='$discuz_uid'");
while($promptrow = $db->fetch_array($query)) {
//$disableprompt出处暂不明。$promptkeys出处cache_settings.php.
//这句是用来跳过不进行提醒的类型的。
if($disableprompt && in_array($promptkeys[$promptrow['typeid']], $disableprompt)) {
continue;
}
$prompt = $promptrow['number'] ? 1 : $prompt;
$prompts[$promptkeys[$promptrow['typeid']]]['new'] = $promptrow['number'];
}
}




 

(字数限制,未完持续)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值