輸入和輸出
輸入和輸出應該說是很多網站的基本功能。使用者輸入數據,網站輸出數據供其他人瀏覽。
拿目前流行的Blog為例,這裡的輸入輸出就是作者編輯文章後產生部落格文章頁面供他人閱讀。
這裡有一個問題,即使用者輸入通常是不受控制的,它可能包含不正確的格式亦或者含有有安全隱患的代碼;而最終網站輸出的內容卻必須是正確的HTML代碼。這就需要對用戶輸入的內容進行糾錯過濾。
永遠不要相信使用者的輸入
你可能會說:現在到處都是所見即所得的編輯器(WYSIWYG),FCKeditor、TinyMCE...你可能會舉出一大堆。是的,它們都可以自動產生標準的XHTML程式碼,但是身為web開發人員,你肯定聽過"永遠不要相信使用者遞交的資料"。
因此對使用者輸入資料進行糾錯和過濾是必需的。
需要更好的糾錯和過濾
目前為止我還沒見過有讓我滿意的相關實現,能接觸到的通常都是效率低下、效果不太理想,有這樣那樣的明顯缺陷。舉個比較知名的例子:WordPress是一種使用非常廣泛的blog系統,操作簡單功能強大且有豐富的插件支持,但是它集成的TinyMCE和後台一堆有些自作聰明的糾錯過濾代碼卻令人相當頭痛,對半角字符的強制替換,過於保守的替換規則等等.....導致像貼一段代碼讓它正確顯示這種需求都很難做到。
這裡順便抱怨一下,這個blog是用WordPress架的,為了讓這幾篇文章能正確顯示程式碼,網路上搜了很多也試用了一些插件,最後還是翻了它的程式碼把一些過濾規則註解掉才勉強可以顯示得體面一點 -.-b
當然,我不想過多的指責它(wordpress),只是想說明它還可以做的更好。
Tidy是什麼,它如何運作?
摘自Tidy ManPage的說明說明:
Tidy reads HTML, XHTML and XML files and writes cleaned up markup. strives to produce visually equivalent markup that is both W3C compliant and works on most browsers. A common use of Tidy is to convert plain HTML to XHTML. ors and pretty printing.
簡單說Tidy是清理HTML程式碼的,產生乾淨的符合W3C標準的HTML程式碼,支援HTML,XHTML,XML。 Tidy提供一個庫TidyLib,以方便在其他應用中利用Tidy的強大功能。非常幸運,PHP有對應的tidy模組可以使用。
老兄,為什麼又是PHP?
呃,這個問題... 慚愧,因為我只會那麼點PHP而已 -.-v
不過還好,我這裡講的都不是純粹的程式碼,好歹也有些分析的過程,分享這些東西比貼程式碼有用多了。
PHP中使用Tidy
要在PHP中使用Tidy需要安裝Tidy模組,也就是加載tidy.so這個PHP extension,具體過程就略了,純粹是體力活。最後能在phpinfo()看到"Tidy support enabled" 就OK。
在這個模組的支援下,PHP中就可以使用Tidy提供的幾乎所有的功能。常用的HTML清理是異常輕鬆的事情,甚至可以產生文件的解析樹,像在客戶端操作DOM那樣的操作HTML的各個Node。以下將會有具體的程式碼說明,也可以看看PHP官方的相關手冊。
糾錯過濾的PHP+Tidy實作
上面說了這麼多背景素材,似乎太羅唆了,具體的解決問題的程式碼才最直接。
1. 簡單的錯誤實作
function HtmlFix($html)
{
if(!function_exists('tidy_repair_string')) to repair html code
//repair
$str = tidy_repair_string($html,
'utf8');
//parse
$str = tidy_parse_string($str,
'utf8');
$s = '';
$nodes = @ tidy_get_body($str)->child;
if(!is_array($nodes)){
$returnVal = 0;
n){
$s .= $n->value;
}
return $s;
}
上面的程式碼就是對可能不規範的XHTML程式碼清理糾錯,上面的程式碼就是對可能不規範的XHTML輸出標準的XHTML程式碼(輸入輸出都是UTF-8編碼)。實作程式碼不是最精簡的,因為為了配合下面的過濾功能,我寫的盡可能細緻了一些。
2. 高階實作: 糾錯+過濾
功能:
XHTML的糾錯,輸出標準的XHTML程式碼。
過濾不安全的程式碼但是不影響內容展示,只是對style/javascript中不安全程式碼進行清除。
將超長字串插入
function HtmlFixSafe($html)
{
if(!function_exists('tidy_repair_string'))
return / tidy的參數設定
$conf = array(
'output- FALSE
,'join-classes'=>TRUE
,'show-body-only'=>TRUE
repair_string($html,$conf,'utf8');
//產生解析樹
$str = tidy_parse_string($str,$conf,'utf8');
$s ='';
//得到身體節點
='';
//得到身體節點
='';
//
//函數 _dumpnode,檢查每個節點,過濾後輸出
function _dumpnode($node,&$s){
//查看節點名,如果是<script> 和<style>直接清除<BR> switch($node->name){ <br> case 'script': <BR> case 'style': < default: <BR> } <BR> if($node->type == TIDY_NODETYPE_TEXT){ <BR> /* <BR> 超連結的自動辨識(未實作) <BR> */ <BR> // insert <wbr> <br> $s"$).=nococ)'c/Cqm'o'f/Fm''m'm'm''h.<nohm''&H); // auto links ??? *** TODO *** <BR> return; <BR> } <BR> //不是文字節點,那麼處理標籤和它的屬性<$ $node->name; <BR> //檢查每個屬性<BR> if($node->attribute){ <BR> for * <BR> 清理一些DOM事件,通常是on開頭的, <br> 例如onclick onmouseover 例如href="javascript:"的也清除. <BR> */ <BR> if(strpos($name,'on') === 0 < ) { <br> continue; <BR> } <br> tmlEscape($value).'"'; <BR> } <BR> } <br> //遞歸檢查節點下的子節點<BR> if($node->child){ <BR> forfor> child as $child){ <BR> _dumpnode($child,$s); <BR> } .$node-> name.'>'; <BR> }else{ <br> /* <BR> ($node->type == TIDY_NODETYPE_START) <BR> $s .= '></'.$node->name.'>'; <BR> 對非配對標籤,如<hr/> < br/> <img / alt="xhtml PHP+Tidy-完美的XHTML糾錯+過濾" >等<BR> 直接使用 />閉合中<BR> } <BR> } <BR> //函數定義end <BR> //透過上面的函數 對 body節點開始過濾。 <BR> if($body->child){ <BR> foreach($body->child as $child) <BR> _ <BR> return $s; <BR>} <BR>上面程式碼註解應該比較詳細,運作原理就配合程式碼看吧。 <br>更嚴格的過濾也很容易擴展,例如實現文中的連結自動識別。 <BR>一點補充<br>如果你看過我之前寫的網頁中超長文字的斷行問題,你可能發現上面程式碼中處理自動換行的函數有所不同: <BR>之前介紹的是HtmlEscapeInsertWbrs(),而上面使用的是HtmlInsertWbrs()。 <BR>這裡要做解釋: <BR>HtmlEscapeInsertWbrs()要求輸入的字串未作特殊字元轉義的,也就是沒有經過htmlspecialchars()對<>&等作<>&處理的。因為函數內部有專門的處理。 <br>而在處理經Tidy處理過後的文字節點的時候,因為Tidy的關係,已經自動把<>&等字符作相應的<>&轉義,因此需要用一個專門的函數避免重複的轉義,這個函數就是HtmlInsertWbrs(),從名字上就知道它只插入<wbr>標記,不做額外工作。 <BR>那麼你可能有個問題: <BR>如果<wbr>被插入到HTML標籤中間,例如在<div>或>的中間插入了<wbr>,變成<d<wbr>iv>和& <wbr>gt;,那就會影響到原始訊息的展示。 <BR>沒錯,的確是個新問題,不過使用一些技巧就可以有效解決: <br>因為我們處理的是Tidy得到的文字節點,意味著不可能碰到HTML標籤,因此不會碰到在標籤中間插入<wbr>的情況。 <br>對於第二種情況,轉義後的字元都是&xxxxx;這樣的形式,那麼只要在1所有&符號前面都插入<wbr>標記就可以了(注意看調用時的第四個參數) ,因為下一個<wbr>標記將會插在30(以上面程式碼中實際呼叫的第二個參數為例)個字元之後,這個已經2遠遠大於xxxxx的長度。這樣由上面1、2兩點可以保證不會插到轉義字符的中間。 <BR>給下面HtmlInsertWbrs()的PHP實作: <br>function HtmlInsertWbrs($str, $n=10, <BR> $輸出=''; <BR> $strpos = 0; <BR> $spc = 0; <BR> $len = mb_strlen($str,'UTF-8'); <BR> for ($i = 1; $i < $len; ++$i) { <BR> $prev_char = mb_substr($strstr,$1,1,),$ <BR> $next_char = mb_substr($str,$i,1,'UTF-8'); <BR> if (_u_IsSpace($next_char)) { <BR> $spc = $i; <BR> } else { <BR> if ($i - $spc == $n <> $prev_char,0,'UTF-8' ) <BR> 🎜> mb_strpos($chars_to_break_before, <BR> !== FALSE <br> ) { <BR> > $i-$strpos,'UTF-8') <BR> <br> $strpos = $i; <BR> $spc = $i; <BR> } <BR> } <BR> } <BR> $ <BR> 返回$out;<BR>} <BR>... <BR>好了,先寫這麼多,有關的資料在文中都有連結。
<BR>
以上就介紹了xhtml PHP+Tidy-完美的XHTML糾錯+過濾,包含了xhtml方面的內容,希望對PHP教學有興趣的朋友有幫助。
<BR>
<BR></script>