#!/usr/bin/env python

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

# 导入所需的标准库和第三方库
import re
import socket

from lib.core.common import filterNone
from lib.core.common import getSafeExString
from lib.core.compat import LooseVersion
from lib.core.compat import xrange
from lib.core.data import conf
from lib.core.data import kb
from lib.core.data import logger
from lib.core.exception import SqlmapConnectionException
from lib.core.settings import PYVERSION
from thirdparty.six.moves import http_client as _http_client
from thirdparty.six.moves import urllib as _urllib

# 尝试导入ssl模块,如果导入失败则ssl为None
ssl = None
try:
    import ssl as _ssl
    ssl = _ssl
except ImportError:
    pass

# 获取所有可用的SSL/TLS协议版本,并存储在_protocols列表中
_protocols = filterNone(getattr(ssl, _, None) for _ in ("PROTOCOL_TLS_CLIENT", "PROTOCOL_TLSv1_2", "PROTOCOL_TLSv1_1", "PROTOCOL_TLSv1", "PROTOCOL_SSLv3", "PROTOCOL_SSLv23", "PROTOCOL_SSLv2"))
# 创建协议名称和值的映射字典
_lut = dict((getattr(ssl, _), _) for _ in dir(ssl) if _.startswith("PROTOCOL_"))
# 存储SSL上下文的字典
_contexts = {}

class HTTPSConnection(_http_client.HTTPSConnection):
    """
    自定义HTTPS连接类,支持使用较新的SSL协议。
    继承自标准库的HTTPSConnection类。

    参考: http://bugs.python.org/msg128686

    注意: 使用 https://check-tls.akamaized.net/ 检查TLS/SNI是否正常工作
    """

    def __init__(self, *args, **kwargs):
        # 修复Python bug: https://bugs.python.org/issue38251
        if hasattr(ssl, "_create_default_https_context"):
            if None not in _contexts:
                _contexts[None] = ssl._create_default_https_context()
            kwargs["context"] = _contexts[None]

        # 重试标志
        self.retrying = False

        # 调用父类初始化方法
        _http_client.HTTPSConnection.__init__(self, *args, **kwargs)

    def connect(self):
        # 创建socket连接的内部函数
        def create_sock():
            sock = socket.create_connection((self.host, self.port), self.timeout)
            if getattr(self, "_tunnel_host", None):
                self.sock = sock
                self._tunnel()
            return sock

        success = False

        # 使用SSLContext方式建立SSL连接(Python 2.7.9及更高版本支持)
        if hasattr(ssl, "SSLContext"):
            for protocol in (_ for _ in _protocols if _ >= ssl.PROTOCOL_TLSv1):
                try:
                    sock = create_sock()
                    if protocol not in _contexts:
                        # 创建SSL上下文
                        _contexts[protocol] = ssl.SSLContext(protocol)

                        # 禁用证书和主机名验证
                        _contexts[protocol].check_hostname = False
                        _contexts[protocol].verify_mode = ssl.CERT_NONE

                        # 如果提供了证书和密钥文件,则加载它们
                        if getattr(self, "cert_file", None) and getattr(self, "key_file", None):
                            _contexts[protocol].load_cert_chain(certfile=self.cert_file, keyfile=self.key_file)
                        try:
                            # 设置加密套件
                            _contexts[protocol].set_ciphers("DEFAULT@SECLEVEL=1")
                        except (ssl.SSLError, AttributeError):
                            pass
                    # 包装socket并进行SSL握手
                    result = _contexts[protocol].wrap_socket(sock, do_handshake_on_connect=True, server_hostname=self.host if re.search(r"\A[\d.]+\Z", self.host or "") is None else None)
                    if result:
                        success = True
                        self.sock = result
                        # 将成功的协议移到列表开头
                        _protocols.remove(protocol)
                        _protocols.insert(0, protocol)
                        break
                    else:
                        sock.close()
                except (ssl.SSLError, socket.error, _http_client.BadStatusLine) as ex:
                    self._tunnel_host = None
                    logger.debug("SSL connection error occurred for '%s' ('%s')" % (_lut[protocol], getSafeExString(ex)))

        # 使用旧式ssl.wrap_socket方式建立SSL连接(用于较老版本的Python)
        elif hasattr(ssl, "wrap_socket"):
            for protocol in _protocols:
                try:
                    sock = create_sock()
                    _ = ssl.wrap_socket(sock, keyfile=getattr(self, "key_file"), certfile=getattr(self, "cert_file"), ssl_version=protocol)
                    if _:
                        success = True
                        self.sock = _
                        _protocols.remove(protocol)
                        _protocols.insert(0, protocol)
                        break
                    else:
                        sock.close()
                except (ssl.SSLError, socket.error, _http_client.BadStatusLine) as ex:
                    self._tunnel_host = None
                    logger.debug("SSL connection error occurred for '%s' ('%s')" % (_lut[protocol], getSafeExString(ex)))

        # 如果所有协议都连接失败
        if not success:
            errMsg = "can't establish SSL connection"
            # 对于Python 2.7.9之前的版本,建议升级
            if LooseVersion(PYVERSION) < LooseVersion("2.7.9"):
                errMsg += " (please retry with Python >= 2.7.9)"

            # 如果之前有成功连接过且未在重试,则进行重试
            if kb.sslSuccess and not self.retrying:
                self.retrying = True

                for _ in xrange(conf.retries):
                    try:
                        self.connect()
                    except SqlmapConnectionException:
                        pass
                    else:
                        return

            raise SqlmapConnectionException(errMsg)
        else:
            kb.sslSuccess = True

class HTTPSHandler(_urllib.request.HTTPSHandler):
    """
    HTTPS处理器类,用于处理HTTPS请求
    """
    def https_open(self, req):
        # 根据是否有ssl模块选择合适的连接类来处理请求
        return self.do_open(HTTPSConnection if ssl else _http_client.HTTPSConnection, req)
