#!/usr/bin/env python

"""
Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""

# 导入所需的模块
import re  # 用于正则表达式处理

from xml.sax.handler import ContentHandler  # XML内容处理器

from lib.core.common import urldecode  # URL解码函数
from lib.core.common import parseXmlFile  # XML文件解析函数
from lib.core.data import kb  # 知识库,存储全局变量
from lib.core.data import paths  # 路径相关配置
from lib.core.settings import HEURISTIC_PAGE_SIZE_THRESHOLD  # 页面大小阈值设置
from lib.core.threads import getCurrentThreadData  # 获取当前线程数据

class HTMLHandler(ContentHandler):
    """
    这个类定义了解析HTML页面的方法,用于识别后端数据库管理系统的指纹
    """

    def __init__(self, page):
        # 初始化父类
        ContentHandler.__init__(self)

        self._dbms = None  # 存储数据库类型
        self._page = (page or "")  # 存储页面内容
        try:
            self._lower_page = self._page.lower()  # 将页面转换为小写
        except SystemError:  # 处理Python bug: https://bugs.python.org/issue18183
            self._lower_page = None
        self._urldecoded_page = urldecode(self._page)  # URL解码后的页面内容

        self.dbms = None  # 最终识别出的数据库类型

    def _markAsErrorPage(self):
        """标记当前页面为错误页面"""
        threadData = getCurrentThreadData()
        threadData.lastErrorPage = (threadData.lastRequestUID, self._page)

    def startElement(self, name, attrs):
        """
        处理XML元素开始标签
        name: 标签名
        attrs: 标签属性
        """
        if self.dbms:  # 如果已经识别出数据库类型,直接返回
            return

        if name == "dbms":  # 如果是数据库标签
            self._dbms = attrs.get("value")

        elif name == "error":  # 如果是错误标签
            regexp = attrs.get("regexp")
            if regexp not in kb.cache.regex:
                # 提取正则表达式中的关键词
                keywords = re.findall(r"\w+", re.sub(r"\\.", " ", regexp))
                keywords = sorted(keywords, key=len)
                kb.cache.regex[regexp] = keywords[-1].lower()

            # 检查页面是否匹配错误模式
            if ('|' in regexp or kb.cache.regex[regexp] in (self._lower_page or kb.cache.regex[regexp])) and re.search(regexp, self._urldecoded_page, re.I):
                self.dbms = self._dbms  # 设置识别出的数据库类型
                self._markAsErrorPage()  # 标记为错误页面
                kb.forkNote = kb.forkNote or attrs.get("fork")  # 设置fork注释

def htmlParser(page):
    """
    解析HTML页面以识别后端数据库类型的主函数
    page: 要解析的HTML页面内容
    
    示例:
    >>> from lib.core.enums import DBMS
    >>> htmlParser("Warning: mysql_fetch_array() expects parameter 1 to be resource") == DBMS.MYSQL
    True
    >>> threadData = getCurrentThreadData()
    >>> threadData.lastErrorPage = None
    """

    page = page[:HEURISTIC_PAGE_SIZE_THRESHOLD]  # 截取页面内容到阈值大小

    xmlfile = paths.ERRORS_XML  # 错误模式的XML配置文件
    handler = HTMLHandler(page)  # 创建处理器实例
    key = hash(page)  # 计算页面内容的哈希值

    # 检查通用SQL警告/错误信息
    if re.search(r"SQL (warning|error|syntax)", page, re.I):
        handler._markAsErrorPage()

    # 如果页面已经解析过,直接返回缓存的结果
    if key in kb.cache.parsedDbms:
        retVal = kb.cache.parsedDbms[key]
        if retVal:
            handler._markAsErrorPage()
        return retVal

    parseXmlFile(xmlfile, handler)  # 解析XML配置文件

    # 更新识别状态
    if handler.dbms and handler.dbms not in kb.htmlFp:
        kb.lastParserStatus = handler.dbms
        kb.htmlFp.append(handler.dbms)
    else:
        kb.lastParserStatus = None

    kb.cache.parsedDbms[key] = handler.dbms  # 缓存解析结果

    return handler.dbms  # 返回识别出的数据库类型
