| 經驗交流 |
|
|---|---|
|
處理含 ASCII 92 字元之文字輸入值 | |
問題說明 | |
| 如果您的 MySQL character set 採用「big5」的話,它本身能辨識雙位元文字,那麼以下的內容是您必須要注意的。 | |
| BIG5 碼系統為兩位元組之內碼系統,共可定義 19782 個字碼。其高、低位元組的範圍如下: | |
| 高位元組:0x81 ∼ 0xFE(ASCII 129 ∼ 254) | |
| 低位元組:0x40 ∼ 0x7E 與 0xA1 ∼ 0xFE(ASCII 64 ∼ 126 與 161 ∼ 254) | |
| 在許多程式語言之中,ASCII 92(\)被當作是跳脫(escape)字元,在程式中需要輸出特定字元時,先加上 \,才能被系統所辨識出來。例如:在指定字串變數值時,$Str = "PHP 是一種程式語言",前後都會用到 " 這個字元,如果希望在字串中使用 " 的話,則可以這麼寫:$Str = "\"PHP\" 是一種程式語言"。如此一來,「PHP」前後的雙引號就可以被視為字串的一部份了。 | |
| 問題就出在這裡!假如使用者在文字框中輸入「成功」二字,我們來看看傳到伺服器端的資料是什麼(以下一併顯示中文與十六進位的 ASCII 碼): | |
| 使用者端傳送:成(A6 A8)功(A5 5C) | |
| 伺服器端接收:成(A6 A8)功(A5 5C 5C) | |
| 有什麼不同?「功」字的後半部是 5C,轉成十進位是 92,剛好就是上述的跳脫字元。為了正確地傳送此一字串,PHP 會自動在該字元之前多加個 \。 | |
| 顯示在畫面上的是「成功\」,只是有點礙眼,沒啥影響。如果要存入資料庫的話,問題就來了: | |
| $SQL = "INSERT INTO mytable VALUES ('$Str');"; | |
| 對資料庫而言,它將收到一個這樣的命令: | |
| INSERT INTO mytable VALUES ('成功\'); | |
| 原本「成功」前後的單引號是用來標示字串的,但後面那個單引號加上 \ 之後,就被視為字串的一部份,而該命令就少了一個單引號了。不正確的命令,當然不能期待它會產生正確的執行結果。 | |
我的做法 | |
| 只要可供使用者輸入文字的元件(如 Text、Textarea...),都需要經過下列函數的過濾,才能組成對資料庫執行存取動作的 SQL 敘述句。 | |
|
01 function Fix_Backslash($org_str) { 02 if ( mysql_client_encoding() != "big5" ) return $org_str; 03 $tmp_length = strlen($org_str); 04 for ( $tmp_i=0; $tmp_i<$tmp_length; $tmp_i++ ) { 05 $ascii_str_a = substr($org_str, $tmp_i , 1); 06 $ascii_str_b = substr($org_str, $tmp_i+1, 1); 07 $ascii_value_a = ord($ascii_str_a); 08 $ascii_value_b = ord($ascii_str_b); 09 if ( $ascii_value_a > 128 ) { 10 if ( $ascii_value_b == 92 ) { 11 $org_str = substr($org_str, 0, $tmp_i+2) . substr($org_str,$tmp_i+3); 12 $tmp_length = strlen($org_str); 13 } 14 $tmp_i++; 15 } 16 } 17 $tmp_length = strlen($org_str); 18 if ( substr($org_str, ($tmp_length-1), 1) == "\\" ) $org_str .= chr(32); 19 $org_str = str_replace("\\0", "\ 0", $org_str); 20 return $org_str; 21 } | |
| 02 如果您的 MySQL character set 是 big5 之類的,它本身能辨識雙位元文字時,才需要本函數來加以處理。否則,就可以直接回傳原字串了。 | |
| 請注意:這裡使用 mysql_client_encoding 函數來判斷您的 MySQL character set,而此函數需在 PHP 4.3.0 版以後才能支援。 | |
| 03 先計算整個字串的總長度,以便後續的迴圈執行「逐字元檢查」的動作。 | |
| 05 - 06 依序挑出每個字元,與它的下一個字元,意即連續擷取兩個字元。 | |
| 07 - 08 將挑出的兩個字元分別轉成 ASCII 碼。 | |
| 若所得的第一個字元大於 ASCII 128 的話(可能是中文字),執行 10 - 13 之間的程式;否則,將迴圈的指標多加 1,這個中文字(兩個字元)算是過關了。 | |
| 所得的第二個字元恰好是 ASCII 92 的話(如:許、功、俞、餐等字),11 可以將被多加上去的 \ 移除。假如被檢查的字串是「成功\了」,請看做法: | |
| 檢查到「功」時 $tmp_i 是 2,substr($org_str, 0, $tmp_i+2) 可以擷取「成功」二字,再用 substr($org_str, $tmp_i+3) 擷取「了」字,並組合起來就行了。 | |
| 12 重算整個字串的總長度,因為字串長度改變了。 | |
| 17 重算整個字串的總長度,供第 18 行使用。 | |
| 18 在上述的處理程序之後,如果在字串末尾還有 \ 的話,在其後加個空白。 | |
| 由於前後文字之間可能組合出 \0 字元(Null),它也會干擾程式的正常運作,所在第 19 行利用 str_replace 函數,將所有的 \0 改成 \ 0(中間加個空白)。 | |
| 20 大功告成了。 | |
| 2005.2.18 修訂 | |
| 經驗交流 |
|


問題說明