#!/usr/bin/env python

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

# 导入所需的模块
import re  # 正则表达式模块

# 导入sqlmap自定义模块
from lib.core.agent import agent  # SQL语句处理代理
from lib.core.bigarray import BigArray  # 大数组数据结构
from lib.core.common import Backend  # 数据库后端
from lib.core.common import filterNone  # 过滤None值
from lib.core.common import getSafeExString  # 安全获取异常字符串
from lib.core.common import isNoneValue  # 判断是否为None值
from lib.core.common import isNumPosStrValue  # 判断是否为正数字符串
from lib.core.common import singleTimeWarnMessage  # 单次警告消息
from lib.core.common import unArrayizeValue  # 数组值转换
from lib.core.common import unsafeSQLIdentificatorNaming  # SQL标识符命名
from lib.core.compat import xrange  # 兼容Python2/3的range
from lib.core.convert import getUnicode  # 转换为Unicode
from lib.core.data import conf  # 配置数据
from lib.core.data import kb  # 知识库数据
from lib.core.data import logger  # 日志记录器
from lib.core.data import queries  # SQL查询语句
from lib.core.dicts import DUMP_REPLACEMENTS  # 转储替换字典
from lib.core.enums import CHARSET_TYPE  # 字符集类型
from lib.core.enums import EXPECTED  # 期望值类型
from lib.core.exception import SqlmapConnectionException  # 连接异常
from lib.core.exception import SqlmapNoneDataException  # 空数据异常
from lib.core.settings import MAX_INT  # 最大整数值
from lib.core.settings import NULL  # NULL值
from lib.core.settings import SINGLE_QUOTE_MARKER  # 单引号标记
from lib.core.unescaper import unescaper  # SQL转义处理
from lib.request import inject  # SQL注入
from lib.utils.safe2bin import safechardecode  # 安全字符解码
from thirdparty.six import unichr as _unichr  # Unicode字符处理

def pivotDumpTable(table, colList, count=None, blind=True, alias=None):
    """
    数据透视表转储函数
    
    参数:
    table - 要转储的表名
    colList - 列名列表
    count - 记录数(可选)
    blind - 是否使用盲注(默认True)
    alias - 表别名(可选)
    """
    
    # 初始化存储结构
    lengths = {}  # 存储每列的最大长度
    entries = {}  # 存储每列的数据

    # 获取当前数据库对应的转储查询语句
    dumpNode = queries[Backend.getIdentifiedDbms()].dump_table.blind

    # 初始化验证标志
    validColumnList = False  # 列表是否有效
    validPivotValue = False  # 透视值是否有效

    # 如果没有提供记录数,则查询获取
    if count is None:
        query = dumpNode.count % table
        query = agent.whereQuery(query)  # 添加WHERE子句
        # 根据注入方式获取记录数
        count = inject.getValue(query, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) if blind else inject.getValue(query, blind=False, time=False, expected=EXPECTED.INT)

    # 验证记录数的有效性
    if hasattr(count, "isdigit") and count.isdigit():
        count = int(count)

    # 处理空表情况
    if count == 0:
        infoMsg = "table '%s' appears to be empty" % unsafeSQLIdentificatorNaming(table)
        logger.info(infoMsg)

        # 初始化空表的返回结构
        for column in colList:
            lengths[column] = len(column)
            entries[column] = []

        return entries, lengths

    # 检查记录数是否有效
    elif not isNumPosStrValue(count):
        return None

    # 初始化每列的数据结构
    for column in colList:
        lengths[column] = 0
        entries[column] = BigArray()

    # 按列名长度排序列表
    colList = filterNone(sorted(colList, key=lambda x: len(x) if x else MAX_INT))

    # 处理指定的透视列
    if conf.pivotColumn:
        for _ in colList:
            if re.search(r"(.+\.)?%s" % re.escape(conf.pivotColumn), _, re.I):
                infoMsg = "using column '%s' as a pivot " % conf.pivotColumn
                infoMsg += "for retrieving row data"
                logger.info(infoMsg)

                # 将透视列移到列表首位
                colList.remove(_)
                colList.insert(0, _)

                validPivotValue = True
                break

        # 透视列未找到时发出警告
        if not validPivotValue:
            warnMsg = "column '%s' not " % conf.pivotColumn
            warnMsg += "found in table '%s'" % table
            logger.warning(warnMsg)

    # 如果没有有效的透视值,则自动选择合适的透视列
    if not validPivotValue:
        for column in colList:
            infoMsg = "fetching number of distinct "
            infoMsg += "values for column '%s'" % column.replace(("%s." % alias) if alias else "", "")
            logger.info(infoMsg)

            # 查询列的唯一值数量
            query = dumpNode.count2 % (column, table)
            query = agent.whereQuery(query)
            value = inject.getValue(query, blind=blind, union=not blind, error=not blind, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS)

            if isNumPosStrValue(value):
                validColumnList = True

                # 如果唯一值数量等于记录数,则选为透视列
                if value == count:
                    infoMsg = "using column '%s' as a pivot " % column.replace(("%s." % alias) if alias else "", "")
                    infoMsg += "for retrieving row data"
                    logger.info(infoMsg)

                    validPivotValue = True
                    colList.remove(column)
                    colList.insert(0, column)
                    break

        # 处理无效列名情况
        if not validColumnList:
            errMsg = "all provided column name(s) are non-existent"
            raise SqlmapNoneDataException(errMsg)

        # 没有合适的透视列时发出警告
        if not validPivotValue:
            warnMsg = "no proper pivot column provided (with unique values)."
            warnMsg += " It won't be possible to retrieve all rows"
            logger.warning(warnMsg)

    pivotValue = " "  # 初始透视值
    breakRetrieval = False  # 中断检索标志

    # 内部查询函数
    def _(column, pivotValue):
        """
        执行单个值的查询
        
        参数:
        column - 列名
        pivotValue - 透视值
        """
        if column == colList[0]:
            # 透视列的查询
            query = dumpNode.query.replace("'%s'" if unescaper.escape(pivotValue, False) != pivotValue else "%s", "%s") % (agent.preprocessField(table, column), table, agent.preprocessField(table, column), unescaper.escape(pivotValue, False))
        else:
            # 非透视列的查询
            query = dumpNode.query2.replace("'%s'" if unescaper.escape(pivotValue, False) != pivotValue else "%s", "%s") % (agent.preprocessField(table, column), table, agent.preprocessField(table, colList[0]), unescaper.escape(pivotValue, False) if SINGLE_QUOTE_MARKER not in dumpNode.query2 else pivotValue)

        query = agent.whereQuery(query)
        return unArrayizeValue(inject.getValue(query, blind=blind, time=blind, union=not blind, error=not blind))

    try:
        # 主循环:遍历所有记录
        for i in xrange(count):
            if breakRetrieval:
                break

            # 遍历每一列
            for column in colList:
                value = _(column, pivotValue)
                if column == colList[0]:
                    # 处理透视列的特殊情况
                    if isNoneValue(value):
                        try:
                            # 尝试不同的透视值
                            for pivotValue in filterNone(("  " if pivotValue == " " else None, "%s%s" % (pivotValue[0], _unichr(ord(pivotValue[1]) + 1)) if len(pivotValue) > 1 else None, _unichr(ord(pivotValue[0]) + 1))):
                                value = _(column, pivotValue)
                                if not isNoneValue(value):
                                    break
                        except ValueError:
                            pass

                    # 检查是否需要中断检索
                    if isNoneValue(value) or value == NULL:
                        breakRetrieval = True
                        break

                    pivotValue = safechardecode(value)

                # 处理LIMIT语句
                if conf.limitStart or conf.limitStop:
                    if conf.limitStart and (i + 1) < conf.limitStart:
                        warnMsg = "skipping first %d pivot " % conf.limitStart
                        warnMsg += "point values"
                        singleTimeWarnMessage(warnMsg)
                        break
                    elif conf.limitStop and (i + 1) > conf.limitStop:
                        breakRetrieval = True
                        break

                # 处理获取到的值
                value = "" if isNoneValue(value) else unArrayizeValue(value)

                # 更新列的最大长度
                lengths[column] = max(lengths[column], len(DUMP_REPLACEMENTS.get(getUnicode(value), getUnicode(value))))
                # 添加值到结果集
                entries[column].append(value)

    except KeyboardInterrupt:
        # 处理用户中断
        kb.dumpKeyboardInterrupt = True

        warnMsg = "user aborted during enumeration. sqlmap "
        warnMsg += "will display partial output"
        logger.warning(warnMsg)

    except SqlmapConnectionException as ex:
        # 处理连接异常
        errMsg = "connection exception detected ('%s'). sqlmap " % getSafeExString(ex)
        errMsg += "will display partial output"

        logger.critical(errMsg)

    # 返回结果
    return entries, lengths
