许盖功何许人也?
许盖功这号人物,只要曾经用过php+mysql架站的人,无人不知无人不晓,且绝对是这些架站人心中永远的痛....
(摘自osCommerce购物网站架设实战)
仔细探究原因,你会发现,错的人其实不该是许盖功,而是通称大五码的BIG5码,关于大五码的历史传说众说纷纭,无一定论。我们并不评论当初编码的对错与是非,对这历史有兴趣的读者不妨到google搜寻"BIG5",相信可以找到一堆资料,或是到图书馆翻翻下列两本书:
书名:中文字码:万码奔腾,一码当先
作者:黄大一
出版单位:永麒科技
书名:国字整理小组十年
作者:谢清俊、黄克东
出版单位:资讯应用国字整理小组
既然大五码听来似乎有些问题,为何目前几乎所有的繁体中文却又都采用此一编码呢?在西元1983-1984年间,个人电脑正在台湾逐渐推广,电脑上的套装软体也开始盛行,为了解决电脑处理中文的问题,因而制定一套中文内码,也就是我们通称的"BIG5码",又称大五码。而经历过这段时间的人,一定不会忘记当初倚天中文的行销手法,在国外软体厂商开始引进原版软体观念的当时,倚天中文却反其道而行,允许校园甚至一般使用者无条件且免费复制其中文系统,因此,让倚天中文在当时几乎成为中文的标准,也由于倚天中文正是采用BIG5编码,严格说来,也就是BIG5码为何一直沿用到现今的主要原因了。
大五码错在哪里?
错在编码时没有把美国标准资讯交换码ASCII(American Standard Code for Information Interchange)的控制码排除在外,凡是念过计算机概论的人都知道ASCII是以byte为单位,又1 byte=8 bits,所以ASCII最多可以编2^8=256个字元,对于只有26个字母的英文语系国家来说已绰绰有余,但对于有几万字的中文绝对不够,因此必须用两个byte来代表一个中文字,如"中"字的编码即是"A4A4"。然而,BIG5码设计时为了避免与ASCII冲突,每个中文字的第一个byte仅使用ASCII里的高字元(129-255),但在第二个byte却用到了部分低字元(1-128),这正是BIG5码在日后应用上造成极大不便的最大帮凶了。
为何BIG5码专找php+mysql麻烦?
原因有三:
一、sql隐码问题:
我们知道,如果要在mysql资料库中撷取得资料时的语法为:
select * from administrator where id='ABC' and passwd='
假设我有一个login.php的网页,内容是用来输入id和passwd值的表单(from),若有人直接于网址列输入:
login.php?id=ABC&passwd='%20or%201=1%20or%201='
由于%20会被浏览器解译为空白,因此最后丢到mysql的sql语法是:
select * from administrator where id='ABC' and passwd='' or 1=1 or 1=''
这是一个恒为真的式子,骗过了验证而取得administrator的权限。
因此,单引号变成了头号隐形杀手。
二:php 的跳脱字元
5C在php里面是被拿来当跳脱字元,也就是说当变数里面的文字带有 、单引号或双引号时,为了要可以正确显示这些特殊字元,通常需要多加一个 \,常见的例子如:
<?php
echo "<table border=\"0\">";
?>
如果没加,立刻会出现错误讯息:
Parse error: parse error, unexpected T_LNUMBER, expecting ',' or '' in c:appservwwwcode.php on line 2
这样,问题就来了,当我们要插入一笔资料到资料库如:
INSERT INTO mytable VALUES ('许盖功\');
由于功的第二个byte是 5C ,加上后面接的是单引号,因此经过解译之后,最后面的单引号却认定为文字,因而导致sql语法少了最后那个单引号,当然就写不进资料库而发生错误了。
三、addslashes与stripslashes函数:
为了解决单引号可能被用来当成攻击资料库的工具,一般在写php程式时会利用addslashes函数将变数里的单引号前加入一个跳脱字元,如上述原本在passwd输入:
' or 1=1 or 1='
可以骗过验证,在经过addslashes函数处理后变成:
\' or 1=1 or 1=\'
这样便可以避免单引号被用来当成攻击资料库的工具了。但是,如此一来,跳脱字元会被当成输入文字直接写入资料库内,因此,当我们写入资料库时若用了addslashes函数,从资料库取出该笔资料时就必须使用stripslashes将删去,否则显示出来的资料就会多一个的跳脱字元了。
BIG5不只找php麻烦,连unix都幸免于难
7C 是 ASCII 里的 pipe '|' 用过 unix 的应该知道它是作什么的,举一个简单的例子,如果你用 ftp 上传一个 "四.doc" 的档名到 unix ,传完后立刻变成 "北.doc',我想太多人有过这种经验,原因无他,中文字 "四" 的 BIG5 码是"A57C",当 unix 看到 7C 时会觉得莫名其妙,上传一个 "|" 给我做什么?于是就自己处理掉了...
因此,你可以想像只要是中文字第二个byte是 "7C" 的,保证也都难逃BIG5的魔掌。
许盖功的解决之道
一、去除程式里出现问题那段程式码里的stripslashes函数,如此,除了显示"许盖功\"时可能变成"许盖功\"之外,似乎没有太大的问题,但是,mysql server的隐码及跳脱字元问题还是存在的。
二、使用big5_func字串处理函数集
如果你曾经仔细研究笔者在OSC里处理许盖功的方法,你应该就会发现[webroot]/catalog/includes/languages/tchinese 目录下有一个叫big5_func的资料夹,其实就是网路上的高人为了解决BIG5的问题而写的函数集,我们称之为"big5 字串处理函数集"。
为了尊重原作者版权,特别将相关讯息摘录于后:
/*
程式 : big5 字串处理函数集
档名 : big5_func.inc
作者 : Pigo Chu<
pigo@ms5.url.com.tw>
说明 :
这些函数是以 PHP4 来处理 big5 字元
任何人都可以自由散布本程式
写这些程式是看见 LinuxFab 上讨论区上很多人有中文问题才写的
我不能保证会发生什么问题 , 若有 bug 请来信讨论不要谩骂
时间 : 2002/4/21
版本 : 0.10
版本介绍 :
0.01 版(2001/5/27) 提供的函数
string big5_addslashes(string str) : 与 PHP addslashes 一样的功能 , 可以处理中文
string big5_stripslashes(string str) : 与 stripslashes 一样
int big5_strlen(string str) : 与 strlen 功能相同
string big5_substr(string str,int start , int length) : 与 substr 一样
string big5_strtolower(string str) : 与 strtolower 一样
string big5_strtoupper(string str) : 与 strtoupper 一样
0.02 版(2001/5/28) 提供的函数
string big5_chunk_split(string $str, [int $chunklen=76] , [string $end=" "]) : 与 chunk_split 相同
0.03 版(2001/6/16) 提供的函数
string big5_strpos(string haystack ,string needle , int [offset]) : 传回第一个找到 $str 的位置
0.04 版(2001/11/12) 修改 bug
把一些定义与判断式的写错修正 , 感谢网友小蓝 ...
0.05 版(2002/2/13) 修正 big5_stripslashes()
此函数会把所有 "" 去掉的问题 , 谢谢网友Neil指正
0.06 版(2002/2/22) 新增 big5_str_replace()
此函数用法与 str_replace() 一样
0.07 版(2002/4/12) 新增 int big5_stroke($string)
此函数可计算单一中文字的笔划 , 若输入的不是中文则return false
0.08 版(2002/4/19) 新增 big5_unicode($string) , big5_utf8_encode(),big5_utf8_decode(), 修改 big5_stroke($string)
big5_unicode() 可以将中文转成多国语言给网页用的码
big5_utf8_encode() 可以将中文转成 UTF8 码
big5_utf8_decode() 可以将 UTF8 转成 BIG5 码
big5_stroke() 改成开档方式 , 这样不用到此函数时比较省记忆体
0.09 呕心版 (2002/4/20) 修正许多函数写法 , 提升效能
据测试 : big5 转 utf 与 big5 转 unicode 提升效能 0.08 版效能 10 倍以上
测试 1 万 个中文字转 utf8 大约需要 2.2 秒 , 比前一版(居然超过2分钟快上非常多)
虽然还不是挺满意 , 不过已经可以接受
另外 big5_substr , big5_strlen 改了一些写法所有快了一点点 ...
0.10 版 (2002/4/21) 提升 bi5 转 utf8 , unicode , 效能再提升加快 2 倍
据我自己的电脑测试 , 测试 1 万中文字转 utf8 已经可以低于 1 秒了 ...
big5_substr() 重写也加快了一点点速度
*/
这就是目前笔者使用于OSC处理中文字串的函数集,有兴趣的读者不妨自行参考big5_func.inc一档。事实上如果要处理许盖功等\"5C"的问题,在big5_func里只用到两个函数,也就是big5_addslashes和big5_stripslashes,而这两个函数的功能除了拥有原来addslashes跟stripslashes的功能之外,最重要的就是可以分辨出哪些是中文字,哪些才是真正的跳脱字元。举例来说:
使用php时的程式码:
echo addslashes('许盖功\');
echo stripslashes('许盖功\');
?>
结果是:
许\盖\功\
顶?
使用big5_func字串函数集的程式码:
echo big5_addslashes('许盖功\');
echo big5_stripslashes('许盖功\');
?>
结果是:
许盖功
许盖功
这样的确可以解决php处理盖功等相关中文字的问题,但是,同样的当你写入mysql资料库时仍然无法解决跳脱字元的问题而出现稍早提过的错误,因为" 许盖功\"内还是含有"5C"的字元。因此,当你决定使用big5_func来处理时,就必须将mysql的charset也一并改为BIG5且不可让许盖功等字放在要插入资料字串的最后面。请参考上述php的跳脱字元一节。此时,还有一个比较严重的问题是,变更charset是必须重新编译mysql的,也就是说如果你是已经运作正常的主机,必须重新安装mysql server并加入charset=big5的参数,若如果你是租用的网页主机,那问题就会变得更为复杂,因为主机供应商通常不会特别因为你要使用big5_func的函数而重新编译他的mysql server。
因此,笔者于光碟附的繁体中文OSC版本,仅针对mysql server的charset为latin1做修正,所以,如果你的mysql server的charset设定为big5,则参考本章所提的观念,应该可以很轻松修正许盖功的问题了。
笔者于目前OSC版本的修改方式为(mysql server charset=latin1)
1.开启 [webroot]/catalog/includes/functions/database.php
找到
function tep_db_input($string) {
return addslashes($string);
}
function tep_db_prepare_input($string) {
if (is_string($string)) {
return trim(tep_sanitize_string(stripslashes($string)));
改为
function tep_db_input($string) {
return addslashes(big5_stripslashes(($string)));
}
function tep_db_prepare_input($string) {
if (is_string($string)) {
return trim(tep_sanitize_string(big5_stripslashes(big5_addslashes($string))));
2.开启[webroot]/catalog/admin/includes/functions/database.php
找到
function tep_db_input($string) {
return addslashes($string);
}
function tep_db_prepare_input($string) {
if (is_string($string)) {
return trim(stripslashes($string));
改成
function tep_db_input($string) {
return addslashes($string);
}
function tep_db_prepare_input($string) {
if (is_string($string)) {
return trim(big5_stripslashes($string));
这样就可以解决大部分因许盖功造成的问题。
OSC前台无法搜寻许盖功等产品问题
这个问题还是跳脱字元"5C"搞的鬼,当我们在前台想要搜寻跟"许盖功\"有关的商品时,所产生的sql语法会像:
select * from tablename where products_name like '%许盖功\%'
这样的sql语法放到mysql里面,因为功的第二个byte就是"5C"跳脱字元,实际却被误判成:
select * from tablename where products_name like '%许盖?\%'
因此,就造成了,明明资料库里有许盖功相关的商品,就是怎样也搜寻不到相关的产品资料。
那么,要如何才可以修正这个错误呢?答案就是想办法让你的sql语法变成这样:
select * from tablename where products_name like '%许盖功\\\%'
所以你会看到笔者的做法:
开启[webroot]/catalog/advanced_search_result.php
约在256行:
找到
default:
$keyword = tep_db_prepare_input($search_keywords[$i]);
$where_str .= "(pd.products_name like '%" . tep_db_input($keyword) . "%' or p.products_model like '%" . tep_db_input($keyword) . "%' or m.manufacturers_name like '%" . tep_db_input($keyword) . "%'";
if (isset($HTTP_GET_VARS['search_in_deion']) && ($HTTP_GET_VARS['search_in_deion'] == Ƈ')) $where_str .= " or pd.products_deion like '%" . tep_db_input($keyword) . "%'";
$where_str .= ')'
break;
改成
default:
$keyword = big5_addslashes(stripslashes(big5_addslashes(tep_db_prepare_input($search_keywords[$i]))));
$keyword1 = big5_addslashes($search_keywords[$i]); //output -> 功\\
$keyword = str_replace( chr(92).chr(92) ,chr(92).chr(92).chr(92),$keyword1); // 将 功\\ 换成 功\\\
$where_str .= "(pd.products_name like '%" . $keyword . "%' or p.products_model like '%" . $keyword . "%' or m.manufacturers_name like '%" . $keyword . "%'";
if (isset($HTTP_GET_VARS['search_in_deion']) && ($HTTP_GET_VARS['search_in_deion'] == Ƈ')) $where_str .= " or pd.products_deion like '%" . $keyword . "%'";
$where_str .= ')'
break;
后记
由于BIG5所造成的问题几乎无所不在,笔者认为除非BIG5有一个完整的补救计划,否则许盖功将会一直困扰着所有架站人。在此,笔者也试图透过这样的说明,让每一位想架站却又遭受此一问题困扰的人自己找到解决的办法。最后在此也特别声明,BIG5的问题可能不只这些,也可能相当棘手,甚至超出笔者所能解决的范围,但,如果你有任何问题,也欢迎你到 网路甘仔店 社群提出,相信我们有许多热心的人可以一同来解决BIG5的问题。