湘里妹子学术网

 找回密码
 注册
查看: 5992|回复: 2

淺談許蓋功(Big5衝碼問題)

[复制链接]
发表于 2005-3-11 11:09:35 | 显示全部楼层 |阅读模式
P.S.這篇文章,是希望大陸的設計者,能夠理解Big5碼衝碼的情況。嚴格說來,
Big5衝碼字是:
A45C么 AE5C娉 B85C稞C25C擺 A55C功
AF5C珮 B95C鈾 C35C黠 A65C吒 B05C豹
BA5C暝 C45C孀 A75C吭 B15C崤 BB5C蓋
C55C髏 A85C沔 B25C淚 BC5C墦 C65C躡
A95C坼 B35C許 BD5C穀 AA5C歿 B45C廄
BE5C閱 AB5C俞 B55C琵 BF5C璞 AC5C枯
B65C跚 C05C餐 AD5C苒 B75C愧 C15C縷
AA7C泜 B47C揉 A87C育 BE7C魯 B27C琍
BC7C慝 C67C鸛 A97C尚 B37C逖 BD7C罵
A77C坑 B17C悴 BB7C誡 C57C疊 A67C帆
B07C院 BA7C漏 C47C辮 AB7C咽 B57C稅
BF7C糕 AC7C洱 B67C閏 C07C嚐 AD7C迢
B77C會 C17C舉 A47C弋 AE7C徑 B87C腮
C27C甕 A57C四 AF7C砝 B97C頌 C37C牘

再加上個像魔鬼的"會"。

在Perl中,這些衝碼都可以解決;但在Java/PHP中,"會"字無法解決。



許蓋功何許人也?

許蓋功這號人物,只要曾經用過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='1234'

假設我有一個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'] == '1')) $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'] == '1')) $where_str .= " or pd.products_deion like '%" . $keyword . "%'";
          $where_str .= ')';
          break;

後記
由於BIG5所造成的問題幾乎無所不在,筆者認為除非BIG5有一個完整的補救計劃,否則許蓋功將會一直困擾著所有架站人。在此,筆者也試圖透過這樣的說明,讓每一位想架站卻又遭受此一問題困擾的人自己找到解決的辦法。最後在此也特別聲明,BIG5的問題可能不只這些,也可能相當棘手,甚至超出筆者所能解決的範圍,但,如果你有任何問題,也歡迎你到 網路甘仔店 社群提出,相信我們有許多熱心的人可以一同來解決BIG5的問題。
发表于 2005-3-11 14:54:16 | 显示全部楼层
可惜没有用过ASP、PHP和Java,以前用的是cgi/ISAPI,用的是C/Delphi。
也没用过mysql,用的是Informax/M$ Sql Server。
不过很多东西应该是共通的。

上面的问题其实主要应该规类为:
1)字符编码及标准;
要解决『字符编码』问题,最终问题就是unicode!
编码方面问题,过渡阶段仍然可以Local处理使用本地编码,例如台湾用户仍然使用big5,而大陆用户使用GBK。
后台数据库,就应该用unicode,处理也一概使用unicode。前端的浏览器,无论使用big5、gbk还是utf-8,大致不会有问题。少量汉字有问题的,估计数量很少,对此使用utf-8会好一些。
曾经做过测试,旧版本的IE、Netscape等等无法正确处理utf-8,但是都能很好处理big5/gbk。所以从big5/gbk过渡到utf-8估计还要等一些日子。
如果同时支持big5/gbk/utf-8,用户根据自己的浏览器进行配置,不过这样程序编写会麻烦一些。

2)程序语言的处理限制;
程序语言的限制,主要是指语言本身提供的功能。例如C/Delphi这些编译执行的语言,都可以避免上述的“欺骗”问题。
C/Delphi处理sql时有两种基本方法:
1)使用字符串函数,生成sql语句,然后扔给数据库,就如上述的方法。这个方法很致命的一点就是程序员新手很容易直接使用输入的字符串串接,造成以上漏洞。手工添加代码判断是否有“1=1”、或引号等等并进行转换,工作量会比较大...
2)使用ADO、DAO、BDE等提供的函数,用户不用判断字符串里面到底是否有“危险”字符,可以交由数据库进行处理。这个才是是安全、高效的正道。

再说说mysql,虽然是免费的,但是以前试了试,感觉功能太弱,例如存储过程、触发器、视图等等,没有支持或支持不完善,这个对写软件会造成一些影响。

至于big5编码也好,gbk也好,两岸的做法都有些问题,这个在其他帖子再讨论吧。

[ 本贴由 getright 于 2005-3-11  14:55 最后编辑 ]
 楼主| 发表于 2005-3-13 11:06:21 | 显示全部楼层
香港的李志成先生開發的Convertz也是以Delphi為核心的,可以參考↓
http://ibtaiwan.com/ipb/index.ph ... 4e&showforum=24
臺灣的老胡也是編碼高手↓
http://www.hokoy.com/
您需要登录后才可以回帖 登录 | 注册

本版积分规则

Archiver|手机版|湘里妹子学术网 ( 粤ICP备2022147245号 )

GMT++8, 2024-4-26 03:30 , Processed in 0.065706 second(s), 18 queries .

Powered by Discuz! X3.4

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表