這個文檔描述如何安全顯示的有格式的用戶輸入。我們將討論沒有經過過濾的輸出的危險,給出一個安全的顯示格式化輸出的方法。 沒有過濾輸出的危險如果你僅僅獲得用戶的輸入然後顯示它,你可能會破壞你的輸出頁面,如一些人能惡意地在他們提交的輸入框中嵌入 javascript腳本: This is my comment. <script language="javascript: alert('Do something bad here!')">. 這樣,即使用戶不是惡意的,也會破壞你的一些HTML的語句,如一個表格突然中斷,或是頁面顯示不完整。 只顯示無格式的文本這是一個最簡單的解決方案,你只是將用戶提交的信息顯示為無格式的文本。使用htmlspecialchars()函數,將轉化全部的字符為HTML的編碼。如<b>將轉變為<b>,這可以保證不會有意想不到的HTML標記在不適當的時候輸出。這是一個好的解決方案,如果你的用戶只關注沒有格式的文本內容。但是,如果你給出一些可以格式化的能力,它將更好一些 Formatting with Custom Markup Tags 用戶自己的標記作格式化 你可以提供特殊的標記給用戶使用,例如,你可以允許使用[b]...[/b]加重顯示,[i]...[/i]斜體顯示,這樣做簡單的查找替換操作就可以了: $output = str_replace("[b]", "<b>", $output); $output = str_replace("[i]", "<i>", $output); 再作的好一點,我們可以允許用戶鍵入一些鏈接。例如,用戶將允許輸入[link="url"]...[/link],我們將轉換為<a href="">...</a>語句 這時,我們不能使用一個簡單的查找替換,應該使用正則表達式進行替換: $output = ereg_replace('\[link="([[:graph:]]+)"\]', '<a href="\1">', $output); ereg_replace()的執行就是:查找出現[link="..."]的字符串,使用<a href="..."> 替換它 [[:graph:]]的含義是任何非空字符,有關正則表達式請看相關的文章。 在outputlib.php的format_output()函數提供這些標記的轉換,總體上的原則是:調用htmlspecialchars()將HTML標記轉換成特殊編碼,將不該顯示的HTML標記過濾掉,然後,將一系列我們自定義的標記轉換相應的HTML標記。
以下為引用的內容:
<?php function format_output($output) { /**************************************************************************** * Takes a raw string ($output) and formats it for output using a special * stripped down markup that is similar to HTML ****************************************************************************/ $output = htmlspecialchars(stripslashes($output)); /* new paragraph */ $output = str_replace('[p]', '<p>', $output); /* bold */ $output = str_replace('[b]', '<b>', $output); $output = str_replace('[/b]', '</b>', $output); /* italics */ $output = str_replace('[i]', '<i>', $output); $output = str_replace('[/i]', '</i>', $output); /* preformatted */ $output = str_replace('[pre]', '<pre>', $output); $output = str_replace('[/pre]', '</pre>', $output); /* indented blocks (blockquote) */ $output = str_replace('[indent]', '<blockquote>', $output); $output = str_replace('[/indent]', '</blockquote>', $output); /* anchors */ $output = ereg_replace('\[anchor="([[:graph:]]+)"\]', '<a name="\1"></a>', $output); /* links, note we try to prevent javascript in links */ $output = str_replace('[link="javascript', '[link=" javascript', $output); $output = ereg_replace('\[link="([[:graph:]]+)"\]', '<a href="\1">', $output); $output = str_replace('[/link]', '</a>', $output); return nl2br($output); } ?>
一些注意的地方: 記住替換自定義標記生成HTML標記字符串是在調用htmlspecialchars()函數之後,而不是在這個調用之前,否則你的艱苦的工作在調用htmlspecialchars()後將付之東流。 在經過轉換之後,查找HTML代碼將是替換過的,如雙引號"將成為" nl2br()函數將回車換行符轉換為<br>標記,也要在htmlspecialchars()之後。 當轉換[links=""] 到 <a href="">, 你必須確認提交者不會插入javascript腳本,一個簡單的方法去更改[link="javascript 到 [link=" javascript, 這種方式將不替換,只是將原本的代碼顯示出來。 outputlib.php 在浏覽器中調用test.php,可以看到format_output() 的使用情況 正常的HTML標記不能被使用,用下列的特殊標記替換它: - this is [b]bold[/b] - this is [i]italics[/i] - this is [link="http://www.phpbuilder.com"]a link[/link] - this is [anchor="test"]an anchor, and a [link="#test"]link[/link] to the anchor [p]段落 [pre]預先格式化[/pre] [indent]交錯文本[/indent] 這些只是很少的標記,當然,你可以根據你的需求隨意加入更多的標記 Conclusion 結論 這個討論提供安全顯示用戶輸入的方法,可以使用在下列程序中留言板用戶建議系統公告 BBS系統
v.htm' ) ); $content = "<p>歡迎訪問</p> <img src=\"demo.jpg\"> <p>希望你能夠喜歡本網站</p>"; $tpl->assign('CONTENT', $content); $tpl->parse('HEADER', 'header'); $tpl->parse('LEFTNAV', 'leftnav'); $tpl->parse('MAIN', 'main'); $tpl->FastPrint('MAIN'); ?> 顯然,這種方法有三個問題:我們必須為每一個頁面復制這些復雜的、牽涉到模板的PHP代碼,這與重復公共頁面元素一樣使得頁面難以維護;現在文件又混合了HTML和PHP代碼;為內容變量賦值將變得非常困難,因為我們必須處理好大量的特殊字符。 解決這個問題的關鍵就在於分離PHP代碼和HTML內容,雖然我們不能從文件中刪除所有的HTML內容,但可以移出絕大多數PHP代碼。 靜態網站的模板框架 首先,我們象前面一樣為所有的頁面公用元素以及頁面整體布局編寫模板文件;然後從所有的頁面刪除公共部分,只留下頁面內容;接下來再在每個頁面中加上三行PHP代碼,如下所示: <?php <!-- home.php --> <?php require('prepend.php'); ?> <?php pageStart('Home'); ?> <h1>你好</h1> <p>歡迎訪問</p> <img src="demo.jpg"> <p>希望你能夠喜歡本網站</p> <?php pageFinish(); ?> ?> 這種方法基本上解決了前面提到的各種問題。現在文件裡只有三行PHP代碼,而且沒有任何一行代碼直接涉及到模板,因此要改動這些代碼的可能性極小。此外,由於HTML內容位於PHP標記之外,所以也不存在特殊字符的處理問題。我們可以很容易地將這三行PHP代碼加入到所有靜態HTML頁面中。 require函數引入了一個PHP文件,這個文件包含了所有必需的與模板相關的PHP代碼。其中pageStart函數設置模板對象以及頁面標題,pageFinish函數解析模板然後生成結果發送給浏覽器。 這是如何實現的呢?為什麼在調用pageFinish函數之前文件中的HTML不會發送給浏覽器?答案就在於PHP 4的一個新功能,這個功能允許把輸出到浏覽器的內容截獲到緩沖區之中。讓我們來看看prepend.php的具體代碼:
以下為引用的內容:
<?php require('class.FastTemplate.php'); function pageStart($title = '') { GLOBAL $tpl; $tpl = new FastTemplate('.'); $tpl->define( array( 'main' => 'main.htm', 'header' => 'header.htm', 'leftnav'=> 'leftnav.htm' ) ); $tpl->assign('TITLE', $title); ob_start(); } function pageFinish() { GLOBAL $tpl; $content = ob_get_contents(); ob_end_clean(); $tpl->assign('CONTENT', $content); $tpl->parse('HEADER', 'header'); $tpl->parse('LEFTNAV', 'leftnav'); $tpl->parse('MAIN', 'main'); $tpl->FastPrint('MAIN'); } ?>
pageStart函數首先創建並設置了一個模板實例,然後啟用輸出緩存。此後,所有來自頁面本身的HTML內容都將進入緩存。pageFinish函數取出緩存中的內容,然後在模板對象中指定這些內容,最後解析模板並輸出完成後的頁面。 這就是整個模板框架全部的工作過程了。首先編寫包含了網站各個頁面公共元素的模板,然後從所有頁面中刪除全部公共的頁面布局代碼,代之以三行永遠無需改動的PHP代碼;再把FastTemplate類文件和prepend.php加入到包含路徑,這樣你就得到了一個頁面布局可以集中控制的網站,它有著更好的可靠性和可維護性,而且網站級的大范圍修改也變得相當容易。 本文下載包包含了一個可運行的示例網站,它的代碼注釋要比前面的代碼注釋更詳細一些。FastTemplate類可以在http://www.thewebmasters.net/找到,最新的版本號是1.1.0,那裡還有一個用於保證該類在PHP 4中正確運行的小補丁。本文下載代碼中的類已經經過該補丁的修正。