引用 | 編輯
tailin!
2005-02-23 10:16 |
樓主
▼ |
||
x0
許蓋功何許人也?許蓋功這號人物,只要曾經用過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的問題。 x0
|