#!/usr/bin/env python

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

# 导入正则表达式模块,用于匹配和提取文本
import re

# 导入所需的功能模块
from lib.core.common import readInput  # 用于读取用户输入的函数
from lib.core.data import kb  # 知识库(knowledge base),存储全局变量和配置信息
from lib.core.data import logger  # 日志记录器,用于记录程序运行信息
from lib.core.datatype import OrderedSet  # 有序集合数据类型,可以保持元素插入顺序
from lib.core.exception import SqlmapSyntaxException  # SQL注入工具的语法异常类
from lib.request.connect import Connect as Request  # HTTP请求处理类,用于发送网络请求
from thirdparty.six.moves import http_client as _http_client  # HTTP客户端,用于处理HTTP连接

# 定义全局中止标志,用于控制程序终止
abortedFlag = None

def parseSitemap(url, retVal=None):
    """
    解析网站地图(sitemap)的函数
    
    参数说明:
        url: 网站地图的URL地址,即要解析的sitemap文件地址
        retVal: 存储解析结果的集合,默认为None。如果为None会创建新的集合
    
    返回值:
        OrderedSet类型,包含从sitemap中提取的所有URL地址
    """
    global abortedFlag  # 声明使用全局中止标志变量

    # 如果retVal不为空,说明是递归调用,记录开始解析新sitemap的日志
    if retVal is not None:
        logger.debug("parsing sitemap '%s'" % url)

    try:
        # 如果retVal为空,说明是首次调用,初始化返回值集合和中止标志
        if retVal is None:
            abortedFlag = False  # 重置中止标志为False
            retVal = OrderedSet()  # 创建一个新的有序集合用于存储URL

        try:
            # 发送HTTP请求获取网站地图内容
            # raise404=True表示如果页面不存在(404错误)会抛出异常
            # 如果已经设置中止标志,则返回空字符串
            content = Request.getPage(url=url, raise404=True)[0] if not abortedFlag else ""
        except _http_client.InvalidURL:
            # 如果提供的URL格式无效,抛出语法异常
            errMsg = "invalid URL given for sitemap ('%s')" % url
            raise SqlmapSyntaxException(errMsg)

        # 使用正则表达式查找sitemap中所有<loc>标签内的URL
        # <loc>标签是sitemap格式中用于存放URL的标准标签
        for match in re.finditer(r"<loc>\s*([^<]+)", content or ""):
            if abortedFlag:  # 如果收到中止信号,立即退出循环
                break
            url = match.group(1).strip()  # 提取URL并去除首尾空白字符
            
            # 判断是否为子sitemap文件
            # sitemap文件通常以.xml结尾,且URL中包含"sitemap"字样
            if url.endswith(".xml") and "sitemap" in url.lower():
                # 首次遇到子sitemap时询问用户是否要递归处理
                if kb.followSitemapRecursion is None:
                    message = "sitemap recursion detected. Do you want to follow? [y/N] "
                    kb.followSitemapRecursion = readInput(message, default='N', boolean=True)
                # 如果用户同意递归处理,则解析子sitemap
                if kb.followSitemapRecursion:
                    parseSitemap(url, retVal)  # 递归调用解析函数
            else:
                retVal.add(url)  # 将找到的URL添加到结果集合中

    except KeyboardInterrupt:
        # 捕获键盘中断信号(用户按Ctrl+C)
        abortedFlag = True  # 设置中止标志
        warnMsg = "user aborted during sitemap parsing. sqlmap "
        warnMsg += "will use partial list"
        logger.warning(warnMsg)  # 记录警告信息,提示将使用部分解析结果

    return retVal  # 返回收集到的所有URL集合
