#!/usr/bin/env python

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

# 导入未来版本的除法运算
from __future__ import division

import re

# 导入所需的工具函数
from lib.core.common import extractRegexResult
from lib.core.common import getFilteredPageContent
from lib.core.common import listToStrValue
from lib.core.common import removeDynamicContent
from lib.core.common import getLastRequestHTTPError
from lib.core.common import wasLastResponseDBMSError
from lib.core.common import wasLastResponseHTTPError
from lib.core.convert import getBytes
from lib.core.data import conf
from lib.core.data import kb
from lib.core.data import logger
from lib.core.exception import SqlmapNoneDataException
from lib.core.settings import DEFAULT_PAGE_ENCODING
from lib.core.settings import DIFF_TOLERANCE
from lib.core.settings import HTML_TITLE_REGEX
from lib.core.settings import LOWER_RATIO_BOUND
from lib.core.settings import MAX_DIFFLIB_SEQUENCE_LENGTH
from lib.core.settings import MAX_RATIO
from lib.core.settings import MIN_RATIO
from lib.core.settings import REFLECTED_VALUE_MARKER
from lib.core.settings import UPPER_RATIO_BOUND
from lib.core.settings import URI_HTTP_HEADER
from lib.core.threads import getCurrentThreadData
from thirdparty import six

def comparison(page, headers, code=None, getRatioValue=False, pageLength=None):
    """
    页面比较的主入口函数
    :param page: 要比较的页面内容
    :param headers: HTTP响应头
    :param code: HTTP状态码
    :param getRatioValue: 是否返回相似度值
    :param pageLength: 页面长度
    :return: 比较结果
    """
    _ = _adjust(_comparison(page, headers, code, getRatioValue, pageLength), getRatioValue)
    return _

def _adjust(condition, getRatioValue):
    """
    调整比较结果
    :param condition: 原始比较结果
    :param getRatioValue: 是否返回相似度值
    :return: 调整后的结果
    """
    if not any((conf.string, conf.notString, conf.regexp, conf.code)):
        # 在原始页面比较方案中使用负逻辑方法
        # 与原始PAYLOAD.WHERE.NEGATIVE响应"不同"的内容被认为是True
        retVal = not condition if kb.negativeLogic and condition is not None and not getRatioValue else condition
    else:
        retVal = condition if not getRatioValue else (MAX_RATIO if condition else MIN_RATIO)

    return retVal

def _comparison(page, headers, code, getRatioValue, pageLength):
    """
    核心比较函数
    :param page: 要比较的页面内容
    :param headers: HTTP响应头
    :param code: HTTP状态码
    :param getRatioValue: 是否返回相似度值
    :param pageLength: 页面长度
    :return: 比较结果
    """
    # 获取当前线程数据
    threadData = getCurrentThreadData()

    # 测试模式下保存比较数据
    if kb.testMode:
        threadData.lastComparisonHeaders = listToStrValue(_ for _ in headers.headers if not _.startswith("%s:" % URI_HTTP_HEADER)) if headers else ""
        threadData.lastComparisonPage = page
        threadData.lastComparisonCode = code

    # 页面内容和长度都为空时返回None
    if page is None and pageLength is None:
        return None

    # 处理字符串匹配情况
    if any((conf.string, conf.notString, conf.regexp)):
        rawResponse = "%s%s" % (listToStrValue(_ for _ in headers.headers if not _.startswith("%s:" % URI_HTTP_HEADER)) if headers else "", page)

        # 检查页面是否包含指定的匹配字符串
        if conf.string:
            return conf.string in rawResponse

        # 检查页面是否不包含指定的字符串
        if conf.notString:
            if conf.notString in rawResponse:
                return False
            else:
                if kb.errorIsNone and (wasLastResponseDBMSError() or wasLastResponseHTTPError()):
                    return None
                else:
                    return True

        # 使用正则表达式匹配页面内容
        if conf.regexp:
            return re.search(conf.regexp, rawResponse, re.I | re.M) is not None

    # 检查HTTP状态码是否匹配
    if conf.code:
        return conf.code == code

    # 初始化序列匹配器
    seqMatcher = threadData.seqMatcher
    seqMatcher.set_seq1(kb.pageTemplate)

    if page:
        # 处理数据库错误页面
        if kb.errorIsNone and (wasLastResponseDBMSError() or wasLastResponseHTTPError()) and not kb.negativeLogic:
            if not (wasLastResponseHTTPError() and getLastRequestHTTPError() in (conf.ignoreCode or [])):
                return None

        # 移除动态内容后再比较
        if not kb.nullConnection:
            page = removeDynamicContent(page)
            seqMatcher.set_seq1(removeDynamicContent(kb.pageTemplate))

        if not pageLength:
            pageLength = len(page)

    # 处理空连接情况
    if kb.nullConnection and pageLength:
        if not seqMatcher.a:
            errMsg = "problem occurred while retrieving original page content "
            errMsg += "which prevents sqlmap from continuation. Please rerun, "
            errMsg += "and if the problem persists turn off any optimization switches"
            raise SqlmapNoneDataException(errMsg)

        # 计算页面长度比率
        ratio = 1. * pageLength / len(seqMatcher.a)

        if ratio > 1.:
            ratio = 1. / ratio
    else:
        # 处理编码问题，确保可以正确比较
        if isinstance(seqMatcher.a, six.binary_type) and isinstance(page, six.text_type):
            page = getBytes(page, kb.pageEncoding or DEFAULT_PAGE_ENCODING, "ignore")
        elif isinstance(seqMatcher.a, six.text_type) and isinstance(page, six.binary_type):
            seqMatcher.a = getBytes(seqMatcher.a, kb.pageEncoding or DEFAULT_PAGE_ENCODING, "ignore")

        # 处理各种比较情况
        if any(_ is None for _ in (page, seqMatcher.a)):
            return None
        elif seqMatcher.a and page and seqMatcher.a == page:
            ratio = 1.
        elif kb.skipSeqMatcher or seqMatcher.a and page and any(len(_) > MAX_DIFFLIB_SEQUENCE_LENGTH for _ in (seqMatcher.a, page)):
            if not page or not seqMatcher.a:
                return float(seqMatcher.a == page)
            else:
                ratio = 1. * len(seqMatcher.a) / len(page)
                if ratio > 1:
                    ratio = 1. / ratio
        else:
            seq1, seq2 = None, None

            # 根据配置选择比较内容
            if conf.titles:
                seq1 = extractRegexResult(HTML_TITLE_REGEX, seqMatcher.a)
                seq2 = extractRegexResult(HTML_TITLE_REGEX, page)
            else:
                seq1 = getFilteredPageContent(seqMatcher.a, True) if conf.textOnly else seqMatcher.a
                seq2 = getFilteredPageContent(page, True) if conf.textOnly else page

            if seq1 is None or seq2 is None:
                return None

            # 移除反射值标记
            seq1 = seq1.replace(REFLECTED_VALUE_MARKER, "")
            seq2 = seq2.replace(REFLECTED_VALUE_MARKER, "")

            # 处理高度动态内容
            if kb.heavilyDynamic:
                seq1 = seq1.split("\n")
                seq2 = seq2.split("\n")
                key = None
            else:
                key = (hash(seq1), hash(seq2))

            seqMatcher.set_seq1(seq1)
            seqMatcher.set_seq2(seq2)

            # 使用缓存提高性能
            if key in kb.cache.comparison:
                ratio = kb.cache.comparison[key]
            else:
                ratio = round(seqMatcher.quick_ratio() if not kb.heavilyDynamic else seqMatcher.ratio(), 3)

            if key:
                kb.cache.comparison[key] = ratio

    # 设置匹配比率
    if kb.matchRatio is None:
        if ratio >= LOWER_RATIO_BOUND and ratio <= UPPER_RATIO_BOUND:
            kb.matchRatio = ratio
            logger.debug("setting match ratio for current parameter to %.3f" % kb.matchRatio)

    if kb.testMode:
        threadData.lastComparisonRatio = ratio

    # 根据不同情况返回结果
    if getRatioValue:
        return ratio
    elif ratio > UPPER_RATIO_BOUND:
        return True
    elif ratio < LOWER_RATIO_BOUND:
        return False
    elif kb.matchRatio is None:
        return None
    else:
        return (ratio - kb.matchRatio) > DIFF_TOLERANCE
