首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP UBB 代碼解析器:從論壇遺產到現代富文本安全轉換實戰

     編輯:PHP 綜合知識

UBB (Ultimate Bulletin Board) 代碼曾是早期論壇(Discuz!、phpWind、PHPWind)的核心富文本標記語言。雖然現代項目多採用 Markdown 或 HTML 直接輸出,但在老系統遷移、數據清洗、兼容歷史內容場景下,UBB 解析器仍具實戰價值。本文深度解析經典 UBB 轉 HTML 實現,並給出安全加固、擴展性重構、現代替代方案建議。

經典 UBB 解析器代碼解析

<?php
function ubb2html($content) {
    global $article; // 依賴全局變量,耦合度高
    
    // 1. 自動識別 URL(配置開關)
    if ($article['isparseurl'] == "1") {
        $content = parseurl($content); // 外部函數,未定義
    }
    
    // 2. 基礎標籤替換(使用已棄用 eregi_replace)
    $content = eregi_replace(quotemeta("[b]"), "<b>", $content);
    $content = eregi_replace(quotemeta("[/b]"), "</b>", $content);
    $content = eregi_replace(quotemeta("[i]"), "<i>", $content);
    $content = eregi_replace(quotemeta("[/i]"), "</i>", $content);
    $content = eregi_replace(quotemeta("[u]"), "<u>", $content);
    $content = eregi_replace(quotemeta("[/u]"), "</u>", $content);
    $content = eregi_replace(quotemeta("[center]"), "<center>", $content);
    $content = eregi_replace(quotemeta("[/center]"), "</center>", $content);
    
    // 3. 複雜標籤:引用、代碼、圖片、鏈接、郵箱
    $content = eregi_replace(quotemeta("[quote]"), '<table...><b>引用:</b>...', $content);
    $content = eregi_replace(quotemeta("[/quote]"), '</font></td></tr>...', $content);
    
    $content = eregi_replace(quotemeta("[code]"), '<table...><b>代碼:</b>...', $content);
    $content = eregi_replace(quotemeta("[/code]"), '</font></td></tr></table>', $content);
    
    $content = eregi_replace("/\[img\]([^\[]*)\[\/img\]/", '<a href="\\1" target="_blank"><img src="\\1" border=0...></a>', $content);
    $content = eregi_replace("/\[url\]www\.([^\[]*)\[\/url\]/", '<a href="https://www.\\1" target=_blank>www.\\1</a>', $content);
    $content = eregi_replace("/\[url\]([^\[]*)\[\/url\]/", '<a href="\\1" target=_blank>\\1</a>', $content);
    $content = eregi_replace("/\[url=([^\[]*)\]([^\[]*)\[\/url\]/", '<a href="\\1" target=_blank>\\2</a>', $content);
    $content = eregi_replace("/\[email\]([^\[]*)\[\/email\]/", '<a href="mailto:\\1">\\1</a>', $content);
    
    return $content;
}
?>

核心問題與安全隱患

問題風險等級說明
使用 eregi_replace 🔴 嚴重 PHP 5.3 棄用,7.0 移除,代碼無法在現代 PHP 運行
正則缺乏邊界約束 🟠 高 [^\[]* 貪婪匹配導致跨標籤吞噬、嵌套標籤錯亂
無 XSS 過濾 🔴 嚴重 用戶輸入 [url=javascript:alert(1)]點我[/url] 直接輸出可執行 JS
HTML 結構硬編碼 🟡 中 表格佈局、內聯樣式難以適配響應式設計、CSP 策略
依賴全局變量 $article 🟡 中 難以測試、復用,違反單一職責原則
不支持嵌套標籤 🟠 高 [b][i]文字[/b][/i] 會產生無效 HTML

現代化重構:基於 preg_replace_callback 的安全解析器

<?php
class UbbParser {
    private $config = [
        'allow_url'      => true,
        'allow_img'      => true,
        'auto_link'      => true,
        'max_nesting'    => 10,
        'xss_filter'     => true,
    ];
    
    // 標籤定義:正則 => 替換回調
    private $tags = [
        // 簡單標籤
        '/\[b\](.+?)\[\/b\]/is'          => fn($m) => "<strong>{$m[1]}</strong>",
        '/\[i\](.+?)\[\/i\]/is'          => fn($m) => "<em>{$m[1]}</em>",
        '/\[u\](.+?)\[\/u\]/is'          => fn($m) => "<u>{$m[1]}</u>",
        '/\[center\](.+?)\[\/center\]/is' => fn($m) => "<div style='text-align:center'>{$m[1]}</div>",
        
        // 帶屬性標籤
        '/\[url=([^\]]+)\](.+?)\[\/url\]/is' => function($m) {
            $url = $this->sanitize_url($m[1]);
            $text = $this->parse_inline($m[2]); // 遞歸解析內層
            return $url ? "<a href='{$url}' target='_blank' rel='noopener'>{$text}</a>" : $m[0];
        },
        '/\[img\]([^\]]+)\[\/img\]/is'   => function($m) {
            $src = $this->sanitize_url($m[1]);
            return $src ? "<img src='{$src}' alt='' loading='lazy'>" : $m[0];
        },
        '/\[email\]([^\]]+)\[\/email\]/is' => fn($m) => "<a href='mailto:{$m[1]}'>{$m[1]}</a>",
        
        // 區塊標籤
        '/\[quote\](.+?)\[\/quote\]/is'  => function($m) {
            $content = $this->parse_block($m[1]);
            return "<blockquote class='quote'>{$content}</blockquote>";
        },
        '/\[code\](.+?)\[\/code\]/is'    => function($m) {
            $code = htmlspecialchars($m[1], ENT_QUOTES, 'UTF-8');
            return "<pre><code>{$code}</code></pre>";
        },
    ];
    
    public function parse($text) {
        // 1. 預處理:自動識別 URL
        if ($this->config['auto_link']) {
            $text = $this->auto_link($text);
        }
        
        // 2. 多輪迭代解析(處理嵌套)
        for ($depth = 0; $depth < $this->config['max_nesting']; $depth++) {
            $newText = $text;
            foreach ($this->tags as $pattern => $callback) {
                $newText = preg_replace_callback($pattern, $callback, $newText);
            }
            if ($newText === $text) break; // 無變化提前退出
            $text = $newText;
        }
        
        // 3. 兜底:轉義殘留方括號防止輸出破壞
        $text = str_replace(['[', ']'], ['&#91;', '&#93;'], $text);
        
        return $text;
    }
    
    private function sanitize_url($url) {
        $url = trim($url);
        // 僅允許 http/https/mailto 協議,防 XSS
        if (!preg_match('/^(https?|mailto):/i', $url)) {
            // 相對路徑補全或拒絕
            if (str_starts_with($url, '/')) return $url;
            return false;
        }
        // 進一步可集成 HTMLPurifier 深度過濾
        return htmlspecialchars($url, ENT_QUOTES, 'UTF-8');
    }
    
    private function auto_link($text) {
        // 簡易自動鏈接,實際建議使用 league/url-highlighter
        return preg_replace_callback(
            '/(?])(https?:\/\/[^\s<]+)/i',
            fn($m) => "<a href='{$m[1]}' target='_blank' rel='noopener'>{$m[1]}</a>",
            $text
        );
    }
    
    // 遞歸解析內層內容
    private function parse_inline($text) { return $this->parse($text); }
    private function parse_block($text) { return $this->parse($text); }
}

// 使用
$parser = new UbbParser();
$html = $parser->parse("[b]粗體 [url=https://example.com]鏈接[/url] 文字[/b]");
?>

生產環境最佳實踐建議

  1. 棄用 UBB,遷移 Markdown:使用 league/commonmark (CommonMark 標準)、erusev/parsedown,生態成熟、擴展豐富、安全默認。
  2. 若必須兼容歷史 UBB:編寫一次性遷移腳本,將數據庫中 UBB 內容批量轉換為 Markdown 或安全 HTML 存儲,新內容禁用 UBB。
  3. 輸出端統一 XSS 防護:無論輸入何種格式,最終輸出前務必經過 HTMLPurifier 或框架內置 e()/htmlspecialchars()。
  4. 編輯器選型:前端使用 Toast UI Editor、TipTap、Quill 等現代富文本編輯器,輸出 Markdown/JSON,避免用戶手寫標記語言。

總結

經典 UBB 解析器承載了中國早期 Web 社區的記憶,但其正則替換架構、缺乏安全防護、難以處理嵌套的缺陷已不適合現代開發。理解其原理有助於數據遷移與歷史代碼維護,新項目請堅定選擇 Markdown + 成熟解析庫 + HTMLPurifier 的成熟技術棧。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved