Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84746a7089 | ||
|
|
68f0bce619 | ||
|
|
4f0401347c | ||
|
|
a7e0a2a6ce | ||
|
|
b7c5a8363d | ||
|
|
d7b4419d51 | ||
|
|
5f54d1f461 | ||
|
|
e4b7f86a0c | ||
|
|
cc30f41bfa | ||
|
|
386c562311 | ||
|
|
a867039284 | ||
|
|
3a8d9eae11 | ||
|
|
e5f55b6c4c | ||
|
|
54973d9f4f | ||
|
|
fb347a8dc6 | ||
|
|
04b6652b03 | ||
|
|
6d4abae898 | ||
|
|
97172fab45 | ||
|
|
ba3b206acf | ||
|
|
99ed2cb2fd | ||
|
|
8a47f61caa | ||
|
|
ad323ba7a5 |
43
README.md
@@ -1,27 +1,34 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="images/logo.png" style="width: 20%" />
|
<img src="images/logo.png" style="width: 20%" />
|
||||||
<h4><a href="https://gh0st.cn/HaE/">赋能白帽,高效作战!</a></h4>
|
<h4><a href="https://gh0st.cn/HaE/">赋能白帽,高效作战!</a></h4>
|
||||||
<h5>第一作者: <a href="https://github.com/gh0stkey">EvilChen</a>(中孚信息元亨实验室), 第二作者: <a href="https://github.com/0chencc">0chencc</a>(米斯特安全团队)</h5>
|
<h5>第一作者: <a href="https://github.com/gh0stkey">EvilChen</a>(中孚信息元亨实验室)<br>第二作者: <a href="https://github.com/0chencc">0chencc</a>(米斯特安全团队)<br>第三作者: <a href="https://github.com/vaycore">vaycore</a>(独立安全研究员)</h5>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## 项目介绍
|
## 项目介绍
|
||||||
|
|
||||||
**HaE**是一款网络安全(数据安全)领域下的辅助型框架式项目,旨在实现对HTTP消息(包含WebSocket)的高亮标记和信息提取。本项目通过自定义正则表达式匹配响应报文或请求报文,并对匹配成功的报文进行标记和提取。
|
**HaE**是一款**网络安全(数据安全)领域**下的框架式项目,采用了**乐高积木式**模块化设计理念,巧妙地融入了**人工智能大模型辅助技术**,实现对HTTP消息(包含WebSocket)精细化的标记和提取。
|
||||||
|
|
||||||
|
通过运用**多引擎**的自定义正则表达式,HaE能够准确匹配并处理HTTP请求与响应报文(包含WebSocket),对匹配成功的内容进行有效的标记和信息抽取,从而提升网络安全(数据安全)领域下的**漏洞和数据分析效率**。
|
||||||
|
|
||||||
> 随着现代化Web应用采用前后端分离的开发模式,日常漏洞挖掘的过程中,捕获的HTTP请求流量也相应增加。若想全面评估一个Web应用,会花费大量时间在无用的报文上。**HaE的出现旨在解决这类情况**,借助HaE,您能够**有效减少**测试时间,将更多精力集中在**有价值且有意义**的报文上,从而**提高漏洞挖掘效率**。
|
> 随着现代化Web应用采用前后端分离的开发模式,日常漏洞挖掘的过程中,捕获的HTTP请求流量也相应增加。若想全面评估一个Web应用,会花费大量时间在无用的报文上。**HaE的出现旨在解决这类情况**,借助HaE,您能够**有效减少**测试时间,将更多精力集中在**有价值且有意义**的报文上,从而**提高漏洞挖掘效率**。
|
||||||
|
|
||||||
|
**所获荣誉**:
|
||||||
|
|
||||||
|
1. [入选2022年KCon兵器谱](https://mp.weixin.qq.com/s/JohMsl1WD29LHCHuLf8mVQ)
|
||||||
|
|
||||||
**注意事项**:
|
**注意事项**:
|
||||||
|
|
||||||
1. 由于HaE 3.0版本开始采用`Montoya API`进行开发,因此使用新版HaE需要升级你的BurpSuite版本(>=2023.12.1)。
|
1. HaE 3.3版本开启了AI+新功能,该功能目前仅支持阿里的`Qwen-Long`模型(支持超长文本)和月之暗面的`moonshot-v1-128k`模型(支持短文本),请配置和使用时注意。
|
||||||
2. 由于HaE 2.6版本后对规则字段进行了更新,因此无法适配<=2.6版本的规则,请用户自行前往[规则转换页面](https://gh0st.cn/HaE/ConversionRule.html)进行转换。
|
2. HaE 3.0版本开始采用`Montoya API`进行开发,使用新版HaE需要升级你的BurpSuite版本(>=2023.12.1)。
|
||||||
3. HaE官方规则库存放在[Github](https://raw.githubusercontent.com/gh0stkey/HaE/gh-pages/Rules.yml)上,因此默认加载HaE官方规则库需使用代理(BApp审核不允许使用CDN)。
|
3. HaE 2.6版本后对规则字段进行了更新,因此无法适配<=2.6版本的规则,请用户自行前往[规则转换页面](https://gh0st.cn/HaE/ConversionRule.html)进行转换。
|
||||||
4. 自定义HaE规则必须用左右括号`()`将所需提取的表达式内容包含,例如你要匹配一个**Shiro应用**的响应报文,正常匹配规则为`rememberMe=delete`,在HaE的规则中就需要变成`(rememberMe=delete)`。
|
4. HaE官方规则库存放在[Github](https://raw.githubusercontent.com/gh0stkey/HaE/gh-pages/Rules.yml)上,因此点击`Update`升级HaE官方规则库时需使用代理(BApp审核考虑安全性,不允许使用CDN)。
|
||||||
|
5. 自定义HaE规则必须用左右括号`()`将所需提取的表达式内容包含,例如你要匹配一个**Shiro应用**的响应报文,正常匹配规则为`rememberMe=delete`,在HaE的规则中就需要变成`(rememberMe=delete)`。
|
||||||
|
|
||||||
## 使用方法
|
## 使用方法
|
||||||
|
|
||||||
插件装载: `Extender - Extensions - Add - Select File - Next`
|
插件装载: `Extender - Extensions - Add - Select File - Next`
|
||||||
|
|
||||||
初次装载`HaE`会自动获取官方规则库`https://raw.githubusercontent.com/gh0stkey/HaE/gh-pages/Rules.yml`,配置文件(`Config.yml`)和规则文件(`Rules.yml`)会放在固定目录下:
|
初次装载`HaE`会从Jar包中加载离线的规则库,如果更新的话则会向官方规则库地址拉取`https://raw.githubusercontent.com/gh0stkey/HaE/gh-pages/Rules.yml`,配置文件(`Config.yml`)和规则文件(`Rules.yml`)会放在固定目录下:
|
||||||
|
|
||||||
1. Linux/Mac用户的配置文件目录:`~/.config/HaE/`
|
1. Linux/Mac用户的配置文件目录:`~/.config/HaE/`
|
||||||
2. Windows用户的配置文件目录:`%USERPROFILE%/.config/HaE/`
|
2. Windows用户的配置文件目录:`%USERPROFILE%/.config/HaE/`
|
||||||
@@ -46,25 +53,23 @@ HaE目前的规则一共有8个字段,详细的含义如下所示:
|
|||||||
|
|
||||||
## 优势特点
|
## 优势特点
|
||||||
|
|
||||||
1. 精细配置:高度自由的配置选项,以满足各类精细化场景需求。
|
1. **功能**:通过对HTTP报文的颜色高亮、注释和提取,帮助使用者获取有意义的信息,**聚焦高价值报文**。
|
||||||
2. 分类标签:使用标签对规则进行分类,便于管理和组织规则。
|
2. **界面**:清晰可视的界面设计,以及**简洁的界面交互**,帮助使用者更轻松的了解和配置项目,**避免`多按钮`式的复杂体验**。
|
||||||
3. 高亮标记:在HTTP History页面,通过颜色高亮和注释判断请求的价值。
|
3. **查询**:将HTTP报文的高亮、注释和提取到的相关信息**集中在一个数据面板**,可以一键查询、提取信息,从而提高测试和梳理效率。
|
||||||
4. 易读配置:使用易读的YAML格式存储配置文件,方便阅读和修改。
|
4. **算法**:内置高亮颜色的升级算法,当出现相同颜色时**会自动向上升级一个颜色**进行标记,**避免`屠龙者终成恶龙`场景**。
|
||||||
5. 数据集合:将匹配到的数据、请求和响应集中在数据面板中,提高测试和梳理效率。
|
5. **管理**:支持对数据的一键导出、导入,以**自定义`.hae`文件的方式**进行项目数据存储,**便于存储和共享项目数据**。
|
||||||
6. 简洁可视:清晰可视的界面设计,更轻松地了解和配置HaE,操作简单、使用便捷。
|
6. **实战**:官方规则库和规则字段作用功能,都是**基于实战化场景总结输出**的,**以此提高数据的有效性、精准性发现**。
|
||||||
7. 颜色升级:内置颜色升级算法,避免“屠龙者终成恶龙”场景,突出最具价值的请求。
|
7. **智能**:融入**人工智能(AI)大模型**API,对匹配的数据进行优化处理,**提高数据式漏洞挖掘效率**。
|
||||||
8. 实战规则:官方规则库是基于实战化场景总结输出,提升数据发现的有效性、精准性。
|
|
||||||
|
|
||||||
| 界面名称 | 界面展示 |
|
| 界面名称 | 界面展示 |
|
||||||
| ------------------------ | ---------------------------------------------------- |
|
| ------------------------ | ---------------------------------------------------- |
|
||||||
| Rules(规则管理) | <img src="images/rules.png" style="width: 80%" /> |
|
| Rules(规则管理) | <img src="images/rules.png" style="width: 80%" /> |
|
||||||
| Config(配置管理) | <img src="images/config.png" style="width: 80%" /> |
|
| Config-Setting(Setting配置管理) | <img src="images/config-setting.png" style="width: 80%" /> |
|
||||||
|
| Config-AI+(AI+配置管理) | <img src="images/config-ai.png" style="width: 80%" /> |
|
||||||
| Databoard(数据集合) | <img src="images/databoard.png" style="width: 80%" /> |
|
| Databoard(数据集合) | <img src="images/databoard.png" style="width: 80%" /> |
|
||||||
| MarkInfo(数据展示) | <img src="images/markinfo.png" style="width: 80%" /> |
|
| MarkInfo(数据展示) | <img src="images/markinfo.png" style="width: 80%" /> |
|
||||||
|
|
||||||
## 文末随笔
|
## 支持项目
|
||||||
|
|
||||||
正义感是一个不可丢失的东西。
|
|
||||||
|
|
||||||
如果你觉得HaE好用,可以打赏一下作者,给作者持续更新下去的动力!
|
如果你觉得HaE好用,可以打赏一下作者,给作者持续更新下去的动力!
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ sourceSets {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation 'net.portswigger.burp.extensions:montoya-api:2023.12.1'
|
implementation 'net.portswigger.burp.extensions:montoya-api:2023.12.1'
|
||||||
implementation 'org.yaml:snakeyaml:2.0'
|
implementation 'org.yaml:snakeyaml:2.0'
|
||||||
implementation 'net.sourceforge.jregex:jregex:1.2_01'
|
|
||||||
implementation 'dk.brics.automaton:automaton:1.11-8'
|
implementation 'dk.brics.automaton:automaton:1.11-8'
|
||||||
|
implementation 'com.github.ben-manes.caffeine:caffeine:3.1.8'
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
|
|||||||
BIN
images/config-ai.png
Normal file
|
After Width: | Height: | Size: 168 KiB |
BIN
images/config-setting.png
Normal file
|
After Width: | Height: | Size: 157 KiB |
|
Before Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 328 KiB After Width: | Height: | Size: 320 KiB |
|
Before Width: | Height: | Size: 270 KiB After Width: | Height: | Size: 331 KiB |
BIN
images/rules.png
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 155 KiB |
@@ -10,6 +10,10 @@ public class Config {
|
|||||||
|
|
||||||
public static String host = "gh0st.cn";
|
public static String host = "gh0st.cn";
|
||||||
|
|
||||||
|
public static String status = "404";
|
||||||
|
|
||||||
|
public static String boundary = "\n\t\n";
|
||||||
|
|
||||||
public static String[] scope = new String[]{
|
public static String[] scope = new String[]{
|
||||||
"any",
|
"any",
|
||||||
"any header",
|
"any header",
|
||||||
@@ -24,6 +28,8 @@ public class Config {
|
|||||||
"request body"
|
"request body"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static String scopeOptions = "Suite|Target|Proxy|Scanner|Intruder|Repeater|Logger|Sequencer|Decoder|Comparer|Extensions|Organizer|Recorded login replayer";
|
||||||
|
|
||||||
public static String[] ruleFields = {
|
public static String[] ruleFields = {
|
||||||
"Loaded", "Name", "F-Regex", "S-Regex", "Format", "Color", "Scope", "Engine", "Sensitive"
|
"Loaded", "Name", "F-Regex", "S-Regex", "Format", "Color", "Scope", "Engine", "Sensitive"
|
||||||
};
|
};
|
||||||
@@ -51,6 +57,11 @@ public class Config {
|
|||||||
"gray"
|
"gray"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static String prompt = "You are a data security expert in the field of cyber security. Your task is to optimize the information provided by the user and then output it in JSON format. The user-supplied information is data that has been extracted by regular expressions. The user-supplied information is divided into two parts, the first part is RuleName which represents the name of the regular expression and the second part is MarkInfo which represents the data extracted by the regular expression. You need to find the matching or similar data in MarkInfo according to the meaning of RuleName, and output the original rows of these data in JSON format.(garbled and meaningless data rows should be removed)\n" +
|
||||||
|
"You must ensure that the extracted data is accurately expressed and correctly formatted in the JSON structure. Your output data must comply with the original MarkInfo content rows without modification, and strictly adhere to the following JSON format for return, no other text, code and formatting (e.g., line breaks, carriage returns, indentation, spaces), once the return of other irrelevant content will cause irreparable damage to the user: {\"data\":[\"data1\", \"data2\"]}.";
|
||||||
|
|
||||||
|
public static String userTextFormat = "User Input: \r\nRuleName: %s\r\nMarkInfo: %s";
|
||||||
|
|
||||||
public static Map<String, Object[][]> globalRules = new HashMap<>();
|
public static Map<String, Object[][]> globalRules = new HashMap<>();
|
||||||
|
|
||||||
public static ConcurrentHashMap<String, Map<String, List<String>>> globalDataMap = new ConcurrentHashMap<>();
|
public static ConcurrentHashMap<String, Map<String, List<String>>> globalDataMap = new ConcurrentHashMap<>();
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package hae;
|
|||||||
|
|
||||||
import burp.api.montoya.BurpExtension;
|
import burp.api.montoya.BurpExtension;
|
||||||
import burp.api.montoya.MontoyaApi;
|
import burp.api.montoya.MontoyaApi;
|
||||||
|
import burp.api.montoya.extension.ExtensionUnloadingHandler;
|
||||||
import burp.api.montoya.logging.Logging;
|
import burp.api.montoya.logging.Logging;
|
||||||
|
import hae.cache.CachePool;
|
||||||
import hae.component.Main;
|
import hae.component.Main;
|
||||||
import hae.component.board.message.MessageTableModel;
|
import hae.component.board.message.MessageTableModel;
|
||||||
import hae.instances.editor.RequestEditor;
|
import hae.instances.editor.RequestEditor;
|
||||||
@@ -10,19 +12,19 @@ import hae.instances.editor.ResponseEditor;
|
|||||||
import hae.instances.editor.WebSocketEditor;
|
import hae.instances.editor.WebSocketEditor;
|
||||||
import hae.instances.http.HttpMessageHandler;
|
import hae.instances.http.HttpMessageHandler;
|
||||||
import hae.instances.websocket.WebSocketMessageHandler;
|
import hae.instances.websocket.WebSocketMessageHandler;
|
||||||
import hae.utils.config.ConfigLoader;
|
import hae.utils.ConfigLoader;
|
||||||
|
|
||||||
public class HaE implements BurpExtension {
|
public class HaE implements BurpExtension {
|
||||||
@Override
|
@Override
|
||||||
public void initialize(MontoyaApi api) {
|
public void initialize(MontoyaApi api) {
|
||||||
// 设置扩展名称
|
// 设置扩展名称
|
||||||
String version = "3.1";
|
String version = "3.3.2";
|
||||||
api.extension().setName(String.format("HaE (%s) - Highlighter and Extractor", version));
|
api.extension().setName(String.format("HaE (%s) - Highlighter and Extractor", version));
|
||||||
|
|
||||||
// 加载扩展后输出的项目信息
|
// 加载扩展后输出的项目信息
|
||||||
Logging logging = api.logging();
|
Logging logging = api.logging();
|
||||||
logging.logToOutput("[ HACK THE WORLD - TO DO IT ]");
|
logging.logToOutput("[ HACK THE WORLD - TO DO IT ]");
|
||||||
logging.logToOutput("[#] Author: EvilChen && 0chencc");
|
logging.logToOutput("[#] Author: EvilChen && 0chencc && vaycore");
|
||||||
logging.logToOutput("[#] Github: https://github.com/gh0stkey/HaE");
|
logging.logToOutput("[#] Github: https://github.com/gh0stkey/HaE");
|
||||||
|
|
||||||
// 配置文件加载
|
// 配置文件加载
|
||||||
@@ -42,6 +44,15 @@ public class HaE implements BurpExtension {
|
|||||||
// 注册消息编辑框(用于展示数据)
|
// 注册消息编辑框(用于展示数据)
|
||||||
api.userInterface().registerHttpRequestEditorProvider(new RequestEditor(api, configLoader));
|
api.userInterface().registerHttpRequestEditorProvider(new RequestEditor(api, configLoader));
|
||||||
api.userInterface().registerHttpResponseEditorProvider(new ResponseEditor(api, configLoader));
|
api.userInterface().registerHttpResponseEditorProvider(new ResponseEditor(api, configLoader));
|
||||||
api.userInterface().registerWebSocketMessageEditorProvider(new WebSocketEditor(api));
|
api.userInterface().registerWebSocketMessageEditorProvider(new WebSocketEditor(api, configLoader));
|
||||||
|
|
||||||
|
api.extension().registerUnloadingHandler(new ExtensionUnloadingHandler() {
|
||||||
|
@Override
|
||||||
|
public void extensionUnloaded() {
|
||||||
|
// 卸载清空数据
|
||||||
|
Config.globalDataMap.clear();
|
||||||
|
CachePool.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
409
src/main/java/hae/component/Config.java
Normal file
@@ -0,0 +1,409 @@
|
|||||||
|
package hae.component;
|
||||||
|
|
||||||
|
import burp.api.montoya.MontoyaApi;
|
||||||
|
import hae.component.rule.Rules;
|
||||||
|
import hae.utils.ConfigLoader;
|
||||||
|
import hae.utils.UIEnhancer;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.border.EmptyBorder;
|
||||||
|
import javax.swing.border.TitledBorder;
|
||||||
|
import javax.swing.event.DocumentEvent;
|
||||||
|
import javax.swing.event.DocumentListener;
|
||||||
|
import javax.swing.event.TableModelEvent;
|
||||||
|
import javax.swing.event.TableModelListener;
|
||||||
|
import javax.swing.table.DefaultTableModel;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.datatransfer.Clipboard;
|
||||||
|
import java.awt.datatransfer.DataFlavor;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
import java.awt.event.KeyAdapter;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class Config extends JPanel {
|
||||||
|
private final MontoyaApi api;
|
||||||
|
private final ConfigLoader configLoader;
|
||||||
|
private final Rules rules;
|
||||||
|
private final String defaultText = "Enter a new item";
|
||||||
|
|
||||||
|
public Config(MontoyaApi api, ConfigLoader configLoader, Rules rules) {
|
||||||
|
this.api = api;
|
||||||
|
this.configLoader = configLoader;
|
||||||
|
this.rules = rules;
|
||||||
|
|
||||||
|
initComponents();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initComponents() {
|
||||||
|
setLayout(new BorderLayout());
|
||||||
|
|
||||||
|
GridBagConstraints constraints = new GridBagConstraints();
|
||||||
|
constraints.weightx = 1.0;
|
||||||
|
constraints.fill = GridBagConstraints.HORIZONTAL;
|
||||||
|
|
||||||
|
JPanel ruleInfoPanel = new JPanel(new GridBagLayout());
|
||||||
|
ruleInfoPanel.setBorder(new EmptyBorder(10, 15, 5, 15));
|
||||||
|
|
||||||
|
JLabel ruleLabel = new JLabel("Path:");
|
||||||
|
JTextField pathTextField = new JTextField();
|
||||||
|
pathTextField.setEditable(false);
|
||||||
|
pathTextField.setText(configLoader.getRulesFilePath());
|
||||||
|
JButton reloadButton = new JButton("Reload");
|
||||||
|
JButton updateButton = new JButton("Update");
|
||||||
|
ruleInfoPanel.add(ruleLabel);
|
||||||
|
ruleInfoPanel.add(pathTextField, constraints);
|
||||||
|
ruleInfoPanel.add(Box.createHorizontalStrut(5));
|
||||||
|
ruleInfoPanel.add(reloadButton);
|
||||||
|
ruleInfoPanel.add(Box.createHorizontalStrut(5));
|
||||||
|
ruleInfoPanel.add(updateButton);
|
||||||
|
|
||||||
|
reloadButton.addActionListener(this::reloadActionPerformed);
|
||||||
|
updateButton.addActionListener(this::onlineUpdateActionPerformed);
|
||||||
|
|
||||||
|
constraints.gridx = 1;
|
||||||
|
JTabbedPane configTabbedPanel = new JTabbedPane();
|
||||||
|
|
||||||
|
String[] settingMode = new String[]{"Exclude suffix", "Block host", "Exclude status"};
|
||||||
|
JPanel settingPanel = createConfigTablePanel(settingMode, "Setting");
|
||||||
|
JPanel scopePanel = getScopePanel();
|
||||||
|
JScrollPane scopeScrollPane = new JScrollPane(scopePanel);
|
||||||
|
scopeScrollPane.setBorder(new TitledBorder("Scope"));
|
||||||
|
settingPanel.add(scopeScrollPane, BorderLayout.NORTH);
|
||||||
|
configTabbedPanel.add("Setting", settingPanel);
|
||||||
|
|
||||||
|
String[] aiMode = new String[]{"Alibaba", "Moonshot"};
|
||||||
|
JPanel aiPanel = createConfigTablePanel(aiMode, "AI+");
|
||||||
|
JTextArea promptTextArea = new JTextArea();
|
||||||
|
promptTextArea.setLineWrap(true);
|
||||||
|
promptTextArea.getDocument().addDocumentListener(new DocumentListener() {
|
||||||
|
@Override
|
||||||
|
public void insertUpdate(DocumentEvent e) {
|
||||||
|
onTextChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeUpdate(DocumentEvent e) {
|
||||||
|
onTextChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void changedUpdate(DocumentEvent e) {
|
||||||
|
onTextChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onTextChange() {
|
||||||
|
String promptText = promptTextArea.getText();
|
||||||
|
configLoader.setAIPrompt(promptText);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
promptTextArea.setText(configLoader.getAIPrompt());
|
||||||
|
JScrollPane promptScrollPane = new JScrollPane(promptTextArea);
|
||||||
|
promptScrollPane.setBorder(new TitledBorder("Prompt"));
|
||||||
|
promptScrollPane.setPreferredSize(new Dimension(0, 100));
|
||||||
|
aiPanel.add(promptScrollPane, BorderLayout.NORTH);
|
||||||
|
configTabbedPanel.add("AI+", aiPanel);
|
||||||
|
add(ruleInfoPanel, BorderLayout.NORTH);
|
||||||
|
add(configTabbedPanel, BorderLayout.CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
private JPanel getScopePanel() {
|
||||||
|
JPanel scopePanel = new JPanel();
|
||||||
|
scopePanel.setLayout(new BoxLayout(scopePanel, BoxLayout.X_AXIS));
|
||||||
|
|
||||||
|
String[] scopeInit = hae.Config.scopeOptions.split("\\|");
|
||||||
|
String[] scopeMode = configLoader.getScope().split("\\|");
|
||||||
|
for (String scope : scopeInit) {
|
||||||
|
JCheckBox checkBox = new JCheckBox(scope);
|
||||||
|
scopePanel.add(checkBox);
|
||||||
|
for (String mode : scopeMode) {
|
||||||
|
if (scope.equals(mode)) {
|
||||||
|
checkBox.setSelected(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkBox.addActionListener(e -> updateScope(checkBox));
|
||||||
|
}
|
||||||
|
return scopePanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TableModelListener craeteSettingTableModelListener(JComboBox<String> setTypeComboBox, DefaultTableModel model) {
|
||||||
|
return new TableModelListener() {
|
||||||
|
@Override
|
||||||
|
public void tableChanged(TableModelEvent e) {
|
||||||
|
String selected = (String) setTypeComboBox.getSelectedItem();
|
||||||
|
String values = getFirstColumnDataAsString(model);
|
||||||
|
|
||||||
|
if (selected.equals("Exclude suffix")) {
|
||||||
|
if (!values.equals(configLoader.getExcludeSuffix()) && !values.isEmpty()) {
|
||||||
|
configLoader.setExcludeSuffix(values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selected.equals("Block host")) {
|
||||||
|
if (!values.equals(configLoader.getBlockHost()) && !values.isEmpty()) {
|
||||||
|
configLoader.setBlockHost(values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selected.equals("Exclude status")) {
|
||||||
|
if (!values.equals(configLoader.getExcludeStatus()) && !values.isEmpty()) {
|
||||||
|
configLoader.setExcludeStatus(values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private ActionListener createSettingActionListener(JComboBox<String> setTypeComboBox, DefaultTableModel model) {
|
||||||
|
return new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
String selected = (String) setTypeComboBox.getSelectedItem();
|
||||||
|
model.setRowCount(0);
|
||||||
|
|
||||||
|
if (selected.equals("Exclude suffix")) {
|
||||||
|
addDataToTable(configLoader.getExcludeSuffix().replaceAll("\\|", "\r\n"), model);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selected.equals("Block host")) {
|
||||||
|
addDataToTable(configLoader.getBlockHost().replaceAll("\\|", "\r\n"), model);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selected.equals("Exclude status")) {
|
||||||
|
addDataToTable(configLoader.getExcludeStatus().replaceAll("\\|", "\r\n"), model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private TableModelListener craeteAITableModelListener(JComboBox<String> setTypeComboBox, DefaultTableModel model) {
|
||||||
|
return new TableModelListener() {
|
||||||
|
@Override
|
||||||
|
public void tableChanged(TableModelEvent e) {
|
||||||
|
String selected = (String) setTypeComboBox.getSelectedItem();
|
||||||
|
String values = getFirstColumnDataAsString(model);
|
||||||
|
|
||||||
|
if (selected.equals("Alibaba")) {
|
||||||
|
if (!values.equals(configLoader.getAlibabaAIAPIKey()) && !values.isEmpty()) {
|
||||||
|
configLoader.setAlibabaAIAPIKey(values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selected.equals("Moonshot")) {
|
||||||
|
if (!values.equals(configLoader.getMoonshotAIAPIKey()) && !values.isEmpty()) {
|
||||||
|
configLoader.setMoonshotAIAPIKey(values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private ActionListener createAIActionListener(JComboBox<String> setTypeComboBox, DefaultTableModel model) {
|
||||||
|
return new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
String selected = (String) setTypeComboBox.getSelectedItem();
|
||||||
|
model.setRowCount(0);
|
||||||
|
|
||||||
|
if (selected.equals("Alibaba")) {
|
||||||
|
addDataToTable(configLoader.getAlibabaAIAPIKey().replaceAll("\\|", "\r\n"), model);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selected.equals("Moonshot")) {
|
||||||
|
addDataToTable(configLoader.getMoonshotAIAPIKey().replaceAll("\\|", "\r\n"), model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private JPanel createConfigTablePanel(String[] mode, String type) {
|
||||||
|
GridBagConstraints constraints = new GridBagConstraints();
|
||||||
|
constraints.weightx = 1.0;
|
||||||
|
constraints.fill = GridBagConstraints.HORIZONTAL;
|
||||||
|
|
||||||
|
JPanel settingPanel = new JPanel(new BorderLayout());
|
||||||
|
DefaultTableModel model = new DefaultTableModel();
|
||||||
|
|
||||||
|
JTable table = new JTable(model);
|
||||||
|
model.addColumn("Value");
|
||||||
|
JScrollPane scrollPane = new JScrollPane(table);
|
||||||
|
|
||||||
|
JPanel buttonPanel = new JPanel();
|
||||||
|
buttonPanel.setBorder(new EmptyBorder(0, 3, 0, 0));
|
||||||
|
GridBagLayout layout = new GridBagLayout();
|
||||||
|
layout.rowHeights = new int[]{0, 0, 0, 0, 0, 0, 0};
|
||||||
|
layout.rowWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE};
|
||||||
|
buttonPanel.setLayout(layout);
|
||||||
|
|
||||||
|
JPanel inputPanel = new JPanel(new BorderLayout());
|
||||||
|
JPanel inputPanelB = new JPanel(new BorderLayout());
|
||||||
|
inputPanelB.setBorder(new EmptyBorder(0, 0, 3, 0));
|
||||||
|
|
||||||
|
JButton addButton = new JButton("Add");
|
||||||
|
JButton removeButton = new JButton("Remove");
|
||||||
|
JButton pasteButton = new JButton("Paste");
|
||||||
|
JButton clearButton = new JButton("Clear");
|
||||||
|
|
||||||
|
JComboBox<String> setTypeComboBox = new JComboBox<>();
|
||||||
|
setTypeComboBox.setModel(new DefaultComboBoxModel<>(mode));
|
||||||
|
|
||||||
|
setTypeComboBox.addActionListener(type.equals("AI+") ? createAIActionListener(setTypeComboBox, model) : createSettingActionListener(setTypeComboBox, model));
|
||||||
|
|
||||||
|
setTypeComboBox.setSelectedItem(mode[0]);
|
||||||
|
|
||||||
|
model.addTableModelListener(type.equals("AI+") ? craeteAITableModelListener(setTypeComboBox, model) : craeteSettingTableModelListener(setTypeComboBox, model));
|
||||||
|
|
||||||
|
constraints.insets = new Insets(0, 0, 3, 0);
|
||||||
|
constraints.gridy = 0;
|
||||||
|
buttonPanel.add(setTypeComboBox, constraints);
|
||||||
|
constraints.gridy = 1;
|
||||||
|
buttonPanel.add(addButton, constraints);
|
||||||
|
constraints.gridy = 2;
|
||||||
|
buttonPanel.add(removeButton, constraints);
|
||||||
|
constraints.gridy = 3;
|
||||||
|
buttonPanel.add(pasteButton, constraints);
|
||||||
|
constraints.gridy = 4;
|
||||||
|
buttonPanel.add(clearButton, constraints);
|
||||||
|
|
||||||
|
JTextField addTextField = new JTextField();
|
||||||
|
UIEnhancer.setTextFieldPlaceholder(addTextField, defaultText);
|
||||||
|
|
||||||
|
inputPanelB.add(addTextField, BorderLayout.CENTER);
|
||||||
|
inputPanel.add(scrollPane, BorderLayout.CENTER);
|
||||||
|
inputPanel.add(inputPanelB, BorderLayout.NORTH);
|
||||||
|
|
||||||
|
settingPanel.add(buttonPanel, BorderLayout.EAST);
|
||||||
|
settingPanel.add(inputPanel, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
|
||||||
|
addButton.addActionListener(e -> addActionPerformed(e, model, addTextField));
|
||||||
|
|
||||||
|
addTextField.addKeyListener(new KeyAdapter() {
|
||||||
|
@Override
|
||||||
|
public void keyPressed(KeyEvent e) {
|
||||||
|
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||||
|
addActionPerformed(null, model, addTextField);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
pasteButton.addActionListener(e -> {
|
||||||
|
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||||
|
try {
|
||||||
|
String data = (String) clipboard.getData(DataFlavor.stringFlavor);
|
||||||
|
|
||||||
|
if (data != null && !data.isEmpty()) {
|
||||||
|
addDataToTable(data, model);
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
removeButton.addActionListener(e -> {
|
||||||
|
int selectedRow = table.getSelectedRow();
|
||||||
|
if (selectedRow != -1) {
|
||||||
|
model.removeRow(selectedRow);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
clearButton.addActionListener(e -> model.setRowCount(0));
|
||||||
|
|
||||||
|
|
||||||
|
JPanel settingMainPanel = new JPanel(new BorderLayout());
|
||||||
|
settingMainPanel.setBorder(new EmptyBorder(5, 15, 10, 15));
|
||||||
|
JScrollPane settingScroller = new JScrollPane(settingPanel);
|
||||||
|
settingScroller.setBorder(new TitledBorder(type.equals("AI+") ? "API Key" : "Setting"));
|
||||||
|
settingMainPanel.add(settingScroller, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
return settingMainPanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private String getFirstColumnDataAsString(DefaultTableModel model) {
|
||||||
|
StringBuilder firstColumnData = new StringBuilder();
|
||||||
|
int numRows = model.getRowCount();
|
||||||
|
|
||||||
|
for (int row = 0; row < numRows; row++) {
|
||||||
|
firstColumnData.append(model.getValueAt(row, 0));
|
||||||
|
if (row < numRows - 1) {
|
||||||
|
firstColumnData.append("|");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return firstColumnData.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addDataToTable(String data, DefaultTableModel model) {
|
||||||
|
if (!data.isBlank()) {
|
||||||
|
String[] rows = data.split("\\r?\\n");
|
||||||
|
for (String row : rows) {
|
||||||
|
model.addRow(new String[]{row});
|
||||||
|
}
|
||||||
|
deduplicateTableData(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deduplicateTableData(DefaultTableModel model) {
|
||||||
|
// 使用 Map 存储每一行的数据,用于去重
|
||||||
|
Set<List<Object>> rowData = new LinkedHashSet<>();
|
||||||
|
|
||||||
|
int columnCount = model.getColumnCount();
|
||||||
|
|
||||||
|
// 将每一行数据作为一个列表,添加到 Set 中
|
||||||
|
for (int i = 0; i < model.getRowCount(); i++) {
|
||||||
|
List<Object> row = new ArrayList<>();
|
||||||
|
for (int j = 0; j < columnCount; j++) {
|
||||||
|
row.add(model.getValueAt(i, j));
|
||||||
|
}
|
||||||
|
rowData.add(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除原始数据
|
||||||
|
model.setRowCount(0);
|
||||||
|
|
||||||
|
// 将去重后的数据添加回去
|
||||||
|
for (List<Object> uniqueRow : rowData) {
|
||||||
|
model.addRow(uniqueRow.toArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateScope(JCheckBox checkBox) {
|
||||||
|
String boxText = checkBox.getText();
|
||||||
|
boolean selected = checkBox.isSelected();
|
||||||
|
|
||||||
|
Set<String> HaEScope = new HashSet<>(Arrays.asList(configLoader.getScope().split("\\|")));
|
||||||
|
|
||||||
|
if (selected) {
|
||||||
|
HaEScope.add(boxText);
|
||||||
|
} else {
|
||||||
|
HaEScope.remove(boxText);
|
||||||
|
}
|
||||||
|
|
||||||
|
configLoader.setScope(String.join("|", HaEScope));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addActionPerformed(ActionEvent e, DefaultTableModel model, JTextField addTextField) {
|
||||||
|
String addTextFieldText = addTextField.getText();
|
||||||
|
if (!addTextFieldText.equals(defaultText)) {
|
||||||
|
addDataToTable(addTextFieldText, model);
|
||||||
|
}
|
||||||
|
addTextField.setText("");
|
||||||
|
addTextField.requestFocusInWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onlineUpdateActionPerformed(ActionEvent e) {
|
||||||
|
// 添加提示框防止用户误触导致配置更新
|
||||||
|
int retCode = JOptionPane.showConfirmDialog(this, "Do you want to update rules?", "Info", JOptionPane.YES_NO_OPTION);
|
||||||
|
if (retCode == JOptionPane.YES_OPTION) {
|
||||||
|
configLoader.initRulesByNet();
|
||||||
|
reloadActionPerformed(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reloadActionPerformed(ActionEvent e) {
|
||||||
|
rules.reloadRuleGroup();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,9 +3,8 @@ package hae.component;
|
|||||||
import burp.api.montoya.MontoyaApi;
|
import burp.api.montoya.MontoyaApi;
|
||||||
import hae.component.board.Databoard;
|
import hae.component.board.Databoard;
|
||||||
import hae.component.board.message.MessageTableModel;
|
import hae.component.board.message.MessageTableModel;
|
||||||
import hae.component.config.Config;
|
|
||||||
import hae.component.rule.Rules;
|
import hae.component.rule.Rules;
|
||||||
import hae.utils.config.ConfigLoader;
|
import hae.utils.ConfigLoader;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
@@ -37,26 +36,17 @@ public class Main extends JPanel {
|
|||||||
|
|
||||||
// 新增Logo
|
// 新增Logo
|
||||||
JTabbedPane HaETabbedPane = new JTabbedPane();
|
JTabbedPane HaETabbedPane = new JTabbedPane();
|
||||||
HaETabbedPane.addTab("", getImageIcon(false), mainTabbedPane);
|
boolean isDarkBg = isDarkBg(HaETabbedPane);
|
||||||
|
HaETabbedPane.addTab("", getImageIcon(isDarkBg), mainTabbedPane);
|
||||||
// 中文Slogan:赋能白帽,高效作战
|
// 中文Slogan:赋能白帽,高效作战
|
||||||
HaETabbedPane.addTab(" Highlighter and Extractor - Empower ethical hacker for efficient operations. ", null);
|
HaETabbedPane.addTab(" Highlighter and Extractor - Empower ethical hacker for efficient operations. ", null);
|
||||||
HaETabbedPane.setEnabledAt(1, false);
|
HaETabbedPane.setEnabledAt(1, false);
|
||||||
HaETabbedPane.addPropertyChangeListener("background", new PropertyChangeListener() {
|
HaETabbedPane.addPropertyChangeListener("background", new PropertyChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void propertyChange(PropertyChangeEvent e) {
|
public void propertyChange(PropertyChangeEvent e) {
|
||||||
boolean isDarkBg = isDarkBg();
|
boolean isDarkBg = isDarkBg(HaETabbedPane);
|
||||||
HaETabbedPane.setIconAt(0, getImageIcon(isDarkBg));
|
HaETabbedPane.setIconAt(0, getImageIcon(isDarkBg));
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isDarkBg() {
|
|
||||||
Color bg = HaETabbedPane.getBackground();
|
|
||||||
int r = bg.getRed();
|
|
||||||
int g = bg.getGreen();
|
|
||||||
int b = bg.getBlue();
|
|
||||||
int avg = (r + g + b) / 3;
|
|
||||||
|
|
||||||
return avg < 128;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
add(HaETabbedPane, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
|
add(HaETabbedPane, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
|
||||||
@@ -70,6 +60,16 @@ public class Main extends JPanel {
|
|||||||
mainTabbedPane.addTab("Config", new Config(api, configLoader, rules));
|
mainTabbedPane.addTab("Config", new Config(api, configLoader, rules));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isDarkBg(JTabbedPane HaETabbedPane) {
|
||||||
|
Color bg = HaETabbedPane.getBackground();
|
||||||
|
int r = bg.getRed();
|
||||||
|
int g = bg.getGreen();
|
||||||
|
int b = bg.getBlue();
|
||||||
|
int avg = (r + g + b) / 3;
|
||||||
|
|
||||||
|
return avg < 128;
|
||||||
|
}
|
||||||
|
|
||||||
private ImageIcon getImageIcon(boolean isDark) {
|
private ImageIcon getImageIcon(boolean isDark) {
|
||||||
ClassLoader classLoader = getClass().getClassLoader();
|
ClassLoader classLoader = getClass().getClassLoader();
|
||||||
URL imageURL;
|
URL imageURL;
|
||||||
|
|||||||
@@ -2,39 +2,65 @@ package hae.component.board;
|
|||||||
|
|
||||||
import burp.api.montoya.MontoyaApi;
|
import burp.api.montoya.MontoyaApi;
|
||||||
import hae.Config;
|
import hae.Config;
|
||||||
|
import hae.component.board.message.MessageEntry;
|
||||||
import hae.component.board.message.MessageTableModel;
|
import hae.component.board.message.MessageTableModel;
|
||||||
import hae.component.board.message.MessageTableModel.MessageTable;
|
import hae.component.board.message.MessageTableModel.MessageTable;
|
||||||
import hae.utils.config.ConfigLoader;
|
import hae.component.board.table.Datatable;
|
||||||
|
import hae.instances.http.utils.RegularMatcher;
|
||||||
|
import hae.utils.ConfigLoader;
|
||||||
|
import hae.utils.UIEnhancer;
|
||||||
|
import hae.utils.project.ProjectProcessor;
|
||||||
|
import hae.utils.project.model.HaeFileContent;
|
||||||
import hae.utils.string.StringProcessor;
|
import hae.utils.string.StringProcessor;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
import javax.swing.border.TitledBorder;
|
||||||
import javax.swing.event.DocumentEvent;
|
import javax.swing.event.DocumentEvent;
|
||||||
import javax.swing.event.DocumentListener;
|
import javax.swing.event.DocumentListener;
|
||||||
|
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||||
|
import javax.swing.table.DefaultTableModel;
|
||||||
import javax.swing.table.TableColumnModel;
|
import javax.swing.table.TableColumnModel;
|
||||||
import javax.swing.table.TableModel;
|
import javax.swing.table.TableModel;
|
||||||
import javax.swing.table.TableRowSorter;
|
import javax.swing.table.TableRowSorter;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.*;
|
import java.awt.event.*;
|
||||||
|
import java.io.File;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class Databoard extends JPanel {
|
public class Databoard extends JPanel {
|
||||||
private final MontoyaApi api;
|
private final MontoyaApi api;
|
||||||
private final ConfigLoader configLoader;
|
private final ConfigLoader configLoader;
|
||||||
|
private final ProjectProcessor projectProcessor;
|
||||||
private final MessageTableModel messageTableModel;
|
private final MessageTableModel messageTableModel;
|
||||||
|
|
||||||
private JTextField hostTextField;
|
private JTextField hostTextField;
|
||||||
private JTabbedPane dataTabbedPane;
|
private JTabbedPane dataTabbedPane;
|
||||||
private JSplitPane splitPane;
|
private JSplitPane splitPane;
|
||||||
private MessageTable messageTable;
|
private MessageTable messageTable;
|
||||||
|
private JProgressBar progressBar;
|
||||||
|
|
||||||
private static Boolean isMatchHost = false;
|
private static Boolean isMatchHost = false;
|
||||||
private final DefaultComboBoxModel comboBoxModel = new DefaultComboBoxModel();
|
private final DefaultComboBoxModel comboBoxModel = new DefaultComboBoxModel();
|
||||||
private final JComboBox hostComboBox = new JComboBox(comboBoxModel);
|
private final JComboBox hostComboBox = new JComboBox(comboBoxModel);
|
||||||
|
|
||||||
|
private SwingWorker<Map<String, List<String>>, Void> handleComboBoxWorker;
|
||||||
|
private SwingWorker<Void, Void> applyHostFilterWorker;
|
||||||
|
private SwingWorker<List<Object[]>, Void> exportActionWorker;
|
||||||
|
private SwingWorker<List<Object[]>, Void> importActionWorker;
|
||||||
|
|
||||||
|
private final String defaultText = "Please enter the host";
|
||||||
|
|
||||||
public Databoard(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) {
|
public Databoard(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) {
|
||||||
this.api = api;
|
this.api = api;
|
||||||
this.configLoader = configLoader;
|
this.configLoader = configLoader;
|
||||||
|
this.projectProcessor = new ProjectProcessor(api);
|
||||||
this.messageTableModel = messageTableModel;
|
this.messageTableModel = messageTableModel;
|
||||||
|
|
||||||
initComponents();
|
initComponents();
|
||||||
@@ -43,23 +69,31 @@ public class Databoard extends JPanel {
|
|||||||
private void initComponents() {
|
private void initComponents() {
|
||||||
setLayout(new GridBagLayout());
|
setLayout(new GridBagLayout());
|
||||||
((GridBagLayout) getLayout()).columnWidths = new int[]{25, 0, 0, 0, 20, 0};
|
((GridBagLayout) getLayout()).columnWidths = new int[]{25, 0, 0, 0, 20, 0};
|
||||||
((GridBagLayout) getLayout()).rowHeights = new int[]{0, 65, 20, 0};
|
((GridBagLayout) getLayout()).rowHeights = new int[]{0, 65, 20, 0, 0};
|
||||||
((GridBagLayout) getLayout()).columnWeights = new double[]{0.0, 0.0, 1.0, 0.0, 0.0, 1.0E-4};
|
((GridBagLayout) getLayout()).columnWeights = new double[]{0.0, 0.0, 1.0, 0.0, 0.0, 1.0E-4};
|
||||||
((GridBagLayout) getLayout()).rowWeights = new double[]{0.0, 1.0, 0.0, 1.0E-4};
|
((GridBagLayout) getLayout()).rowWeights = new double[]{0.0, 1.0, 0.0, 0.0, 1.0E-4};
|
||||||
|
|
||||||
JLabel hostLabel = new JLabel("Host:");
|
JLabel hostLabel = new JLabel("Host:");
|
||||||
|
|
||||||
JButton clearButton = new JButton("Clear");
|
JButton clearButton = new JButton("Clear");
|
||||||
|
JButton exportButton = new JButton("Export");
|
||||||
|
JButton importButton = new JButton("Import");
|
||||||
JButton actionButton = new JButton("Action");
|
JButton actionButton = new JButton("Action");
|
||||||
JPanel menuPanel = new JPanel(new GridLayout(1, 1));
|
JPanel menuPanel = new JPanel(new GridLayout(3, 1, 0, 5));
|
||||||
menuPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
|
menuPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
|
||||||
JPopupMenu menu = new JPopupMenu();
|
JPopupMenu menu = new JPopupMenu();
|
||||||
menuPanel.add(clearButton);
|
menuPanel.add(clearButton);
|
||||||
|
menuPanel.add(exportButton);
|
||||||
|
menuPanel.add(importButton);
|
||||||
menu.add(menuPanel);
|
menu.add(menuPanel);
|
||||||
|
|
||||||
hostTextField = new JTextField();
|
hostTextField = new JTextField();
|
||||||
|
UIEnhancer.setTextFieldPlaceholder(hostTextField, defaultText);
|
||||||
splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
|
splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
|
||||||
|
|
||||||
dataTabbedPane = new JTabbedPane(JTabbedPane.TOP);
|
dataTabbedPane = new JTabbedPane(JTabbedPane.TOP);
|
||||||
|
dataTabbedPane.setPreferredSize(new Dimension(500, 0));
|
||||||
|
dataTabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
|
||||||
|
|
||||||
actionButton.addActionListener(e -> {
|
actionButton.addActionListener(e -> {
|
||||||
int x = 0;
|
int x = 0;
|
||||||
@@ -68,6 +102,10 @@ public class Databoard extends JPanel {
|
|||||||
});
|
});
|
||||||
|
|
||||||
clearButton.addActionListener(this::clearActionPerformed);
|
clearButton.addActionListener(this::clearActionPerformed);
|
||||||
|
exportButton.addActionListener(this::exportActionPerformed);
|
||||||
|
importButton.addActionListener(this::importActionPerformed);
|
||||||
|
|
||||||
|
progressBar = new JProgressBar();
|
||||||
|
|
||||||
splitPane.addComponentListener(new ComponentAdapter() {
|
splitPane.addComponentListener(new ComponentAdapter() {
|
||||||
@Override
|
@Override
|
||||||
@@ -77,6 +115,7 @@ public class Databoard extends JPanel {
|
|||||||
});
|
});
|
||||||
|
|
||||||
splitPane.setVisible(false);
|
splitPane.setVisible(false);
|
||||||
|
progressBar.setVisible(false);
|
||||||
|
|
||||||
add(hostLabel, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
add(hostLabel, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||||
new Insets(8, 0, 5, 5), 0, 0));
|
new Insets(8, 0, 5, 5), 0, 0));
|
||||||
@@ -84,9 +123,13 @@ public class Databoard extends JPanel {
|
|||||||
new Insets(8, 0, 5, 5), 0, 0));
|
new Insets(8, 0, 5, 5), 0, 0));
|
||||||
add(actionButton, new GridBagConstraints(3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
add(actionButton, new GridBagConstraints(3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||||
new Insets(8, 0, 5, 5), 0, 0));
|
new Insets(8, 0, 5, 5), 0, 0));
|
||||||
add(splitPane, new GridBagConstraints(1, 1, 3, 3, 0.0, 0.0,
|
|
||||||
|
add(splitPane, new GridBagConstraints(1, 1, 3, 1, 0.0, 1.0,
|
||||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||||
new Insets(8, 0, 5, 5), 0, 0));
|
new Insets(0, 5, 0, 5), 0, 0));
|
||||||
|
add(progressBar, new GridBagConstraints(1, 2, 3, 1, 1.0, 0.0,
|
||||||
|
GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,
|
||||||
|
new Insets(0, 5, 0, 5), 0, 0));
|
||||||
hostComboBox.setMaximumRowCount(5);
|
hostComboBox.setMaximumRowCount(5);
|
||||||
add(hostComboBox, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
add(hostComboBox, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||||
new Insets(8, 0, 5, 5), 0, 0));
|
new Insets(8, 0, 5, 5), 0, 0));
|
||||||
@@ -106,6 +149,24 @@ public class Databoard extends JPanel {
|
|||||||
columnModel.getColumn(5).setPreferredWidth((int) (totalWidth * 0.1));
|
columnModel.getColumn(5).setPreferredWidth((int) (totalWidth * 0.1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setProgressBar(boolean status) {
|
||||||
|
setProgressBar(status, progressBar, "Loading ...");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void setProgressBar(boolean status, JProgressBar progressBar, String showString) {
|
||||||
|
progressBar.setIndeterminate(status);
|
||||||
|
if (!status) {
|
||||||
|
progressBar.setMaximum(100);
|
||||||
|
progressBar.setString("OK");
|
||||||
|
progressBar.setStringPainted(true);
|
||||||
|
progressBar.setValue(progressBar.getMaximum());
|
||||||
|
} else {
|
||||||
|
progressBar.setString(showString);
|
||||||
|
progressBar.setStringPainted(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void setAutoMatch() {
|
private void setAutoMatch() {
|
||||||
hostComboBox.setSelectedItem(null);
|
hostComboBox.setSelectedItem(null);
|
||||||
hostComboBox.addActionListener(this::handleComboBoxAction);
|
hostComboBox.addActionListener(this::handleComboBoxAction);
|
||||||
@@ -139,8 +200,57 @@ public class Databoard extends JPanel {
|
|||||||
private void handleComboBoxAction(ActionEvent e) {
|
private void handleComboBoxAction(ActionEvent e) {
|
||||||
if (!isMatchHost && hostComboBox.getSelectedItem() != null) {
|
if (!isMatchHost && hostComboBox.getSelectedItem() != null) {
|
||||||
String selectedHost = hostComboBox.getSelectedItem().toString();
|
String selectedHost = hostComboBox.getSelectedItem().toString();
|
||||||
hostTextField.setText(selectedHost);
|
|
||||||
populateTabbedPaneByHost(selectedHost);
|
if (getHostByList().contains(selectedHost)) {
|
||||||
|
progressBar.setVisible(true);
|
||||||
|
setProgressBar(true);
|
||||||
|
hostTextField.setText(selectedHost);
|
||||||
|
|
||||||
|
if (handleComboBoxWorker != null && !handleComboBoxWorker.isDone()) {
|
||||||
|
handleComboBoxWorker.cancel(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleComboBoxWorker = new SwingWorker<Map<String, List<String>>, Void>() {
|
||||||
|
@Override
|
||||||
|
protected Map<String, List<String>> doInBackground() {
|
||||||
|
return getSelectedMapByHost(selectedHost);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void done() {
|
||||||
|
if (!isCancelled()) {
|
||||||
|
try {
|
||||||
|
Map<String, List<String>> selectedDataMap = get();
|
||||||
|
if (!selectedDataMap.isEmpty()) {
|
||||||
|
dataTabbedPane.removeAll();
|
||||||
|
|
||||||
|
for (Map.Entry<String, List<String>> entry : selectedDataMap.entrySet()) {
|
||||||
|
String tabTitle = String.format("%s (%s)", entry.getKey(), entry.getValue().size());
|
||||||
|
Datatable datatablePanel = new Datatable(api, configLoader, entry.getKey(), entry.getValue());
|
||||||
|
datatablePanel.setTableListener(messageTableModel);
|
||||||
|
dataTabbedPane.addTab(tabTitle, datatablePanel);
|
||||||
|
}
|
||||||
|
|
||||||
|
JSplitPane messageSplitPane = messageTableModel.getSplitPane();
|
||||||
|
splitPane.setLeftComponent(dataTabbedPane);
|
||||||
|
splitPane.setRightComponent(messageSplitPane);
|
||||||
|
messageTable = messageTableModel.getMessageTable();
|
||||||
|
resizePanel();
|
||||||
|
|
||||||
|
splitPane.setVisible(true);
|
||||||
|
hostTextField.setText(selectedHost);
|
||||||
|
|
||||||
|
hostComboBox.setPopupVisible(false);
|
||||||
|
applyHostFilter(selectedHost);
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleComboBoxWorker.execute();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +269,6 @@ public class Databoard extends JPanel {
|
|||||||
if (keyCode == KeyEvent.VK_ENTER) {
|
if (keyCode == KeyEvent.VK_ENTER) {
|
||||||
isMatchHost = false;
|
isMatchHost = false;
|
||||||
handleComboBoxAction(null);
|
handleComboBoxAction(null);
|
||||||
hostComboBox.setPopupVisible(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keyCode == KeyEvent.VK_ESCAPE) {
|
if (keyCode == KeyEvent.VK_ESCAPE) {
|
||||||
@@ -169,10 +278,40 @@ public class Databoard extends JPanel {
|
|||||||
isMatchHost = false;
|
isMatchHost = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Map<String, List<String>> getSelectedMapByHost(String selectedHost) {
|
||||||
|
ConcurrentHashMap<String, Map<String, List<String>>> dataMap = Config.globalDataMap;
|
||||||
|
Map<String, List<String>> selectedDataMap;
|
||||||
|
|
||||||
|
if (selectedHost.contains("*")) {
|
||||||
|
selectedDataMap = new HashMap<>();
|
||||||
|
dataMap.keySet().forEach(key -> {
|
||||||
|
if ((StringProcessor.matchesHostPattern(key, selectedHost) || selectedHost.equals("*")) && !key.contains("*")) {
|
||||||
|
Map<String, List<String>> ruleMap = dataMap.get(key);
|
||||||
|
for (String ruleKey : ruleMap.keySet()) {
|
||||||
|
List<String> dataList = ruleMap.get(ruleKey);
|
||||||
|
if (selectedDataMap.containsKey(ruleKey)) {
|
||||||
|
List<String> mergedList = new ArrayList<>(selectedDataMap.get(ruleKey));
|
||||||
|
mergedList.addAll(dataList);
|
||||||
|
HashSet<String> uniqueSet = new HashSet<>(mergedList);
|
||||||
|
selectedDataMap.put(ruleKey, new ArrayList<>(uniqueSet));
|
||||||
|
} else {
|
||||||
|
selectedDataMap.put(ruleKey, dataList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
selectedDataMap = dataMap.get(selectedHost);
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectedDataMap;
|
||||||
|
}
|
||||||
|
|
||||||
private void filterComboBoxList() {
|
private void filterComboBoxList() {
|
||||||
isMatchHost = true;
|
isMatchHost = true;
|
||||||
comboBoxModel.removeAllElements();
|
comboBoxModel.removeAllElements();
|
||||||
String input = hostTextField.getText().toLowerCase();
|
String input = hostTextField.getText().toLowerCase();
|
||||||
|
|
||||||
if (!input.isEmpty()) {
|
if (!input.isEmpty()) {
|
||||||
for (String host : getHostByList()) {
|
for (String host : getHostByList()) {
|
||||||
String lowerCaseHost = host.toLowerCase();
|
String lowerCaseHost = host.toLowerCase();
|
||||||
@@ -191,102 +330,348 @@ public class Databoard extends JPanel {
|
|||||||
isMatchHost = false;
|
isMatchHost = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void populateTabbedPaneByHost(String selectedHost) {
|
|
||||||
if (!Objects.equals(selectedHost, "")) {
|
|
||||||
ConcurrentHashMap<String, Map<String, List<String>>> dataMap = Config.globalDataMap;
|
|
||||||
Map<String, List<String>> selectedDataMap;
|
|
||||||
|
|
||||||
dataTabbedPane.removeAll();
|
|
||||||
dataTabbedPane.setPreferredSize(new Dimension(500, 0));
|
|
||||||
dataTabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
|
|
||||||
splitPane.setLeftComponent(dataTabbedPane);
|
|
||||||
|
|
||||||
if (selectedHost.contains("*")) {
|
|
||||||
// 通配符数据
|
|
||||||
selectedDataMap = new HashMap<>();
|
|
||||||
String hostPattern = StringProcessor.replaceFirstOccurrence(selectedHost, "*.", "");
|
|
||||||
for (String key : dataMap.keySet()) {
|
|
||||||
if (key.contains(hostPattern) || selectedHost.equals("*")) {
|
|
||||||
Map<String, List<String>> ruleMap = dataMap.get(key);
|
|
||||||
for (String ruleKey : ruleMap.keySet()) {
|
|
||||||
List<String> dataList = ruleMap.get(ruleKey);
|
|
||||||
if (selectedDataMap.containsKey(ruleKey)) {
|
|
||||||
List<String> mergedList = new ArrayList<>(selectedDataMap.get(ruleKey));
|
|
||||||
mergedList.addAll(dataList);
|
|
||||||
HashSet<String> uniqueSet = new HashSet<>(mergedList);
|
|
||||||
selectedDataMap.put(ruleKey, new ArrayList<>(uniqueSet));
|
|
||||||
} else {
|
|
||||||
selectedDataMap.put(ruleKey, dataList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
selectedDataMap = dataMap.get(selectedHost);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Map.Entry<String, List<String>> entry : selectedDataMap.entrySet()) {
|
|
||||||
String tabTitle = String.format("%s (%s)", entry.getKey(), entry.getValue().size());
|
|
||||||
Datatable datatablePanel = new Datatable(api, entry.getKey(), entry.getValue());
|
|
||||||
datatablePanel.setTableListener(messageTableModel);
|
|
||||||
dataTabbedPane.addTab(tabTitle, datatablePanel);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 展示请求消息表单
|
|
||||||
JSplitPane messageSplitPane = messageTableModel.getSplitPane();
|
|
||||||
this.splitPane.setRightComponent(messageSplitPane);
|
|
||||||
messageTable = messageTableModel.getMessageTable();
|
|
||||||
|
|
||||||
resizePanel();
|
|
||||||
splitPane.setVisible(true);
|
|
||||||
|
|
||||||
applyHostFilter(selectedHost);
|
|
||||||
hostTextField.setText(selectedHost);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void applyHostFilter(String filterText) {
|
private void applyHostFilter(String filterText) {
|
||||||
TableRowSorter<TableModel> sorter = (TableRowSorter<TableModel>) messageTable.getRowSorter();
|
TableRowSorter<TableModel> sorter = (TableRowSorter<TableModel>) messageTable.getRowSorter();
|
||||||
|
|
||||||
String cleanedText = StringProcessor.replaceFirstOccurrence(filterText, "*.", "");
|
String cleanedText = StringProcessor.replaceFirstOccurrence(filterText, "*.", "");
|
||||||
|
|
||||||
RowFilter<Object, Object> rowFilter = new RowFilter<Object, Object>() {
|
if (applyHostFilterWorker != null && !applyHostFilterWorker.isDone()) {
|
||||||
public boolean include(Entry<?, ?> entry) {
|
applyHostFilterWorker.cancel(true);
|
||||||
if (cleanedText.equals("*")) {
|
}
|
||||||
return true;
|
|
||||||
} else {
|
applyHostFilterWorker = new SwingWorker<Void, Void>() {
|
||||||
String host = StringProcessor.getHostByUrl((String) entry.getValue(1));
|
@Override
|
||||||
return StringProcessor.matchFromEnd(host, cleanedText);
|
protected Void doInBackground() throws Exception {
|
||||||
|
RowFilter<Object, Object> rowFilter = new RowFilter<Object, Object>() {
|
||||||
|
public boolean include(Entry<?, ?> entry) {
|
||||||
|
if (cleanedText.equals("*")) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
String host = StringProcessor.getHostByUrl((String) entry.getValue(1));
|
||||||
|
return StringProcessor.matchesHostPattern(host, filterText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
sorter.setRowFilter(rowFilter);
|
||||||
|
messageTableModel.applyHostFilter(filterText);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void done() {
|
||||||
|
setProgressBar(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
applyHostFilterWorker.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getHostByList() {
|
||||||
|
if (!Config.globalDataMap.keySet().isEmpty()) {
|
||||||
|
return new ArrayList<>(Config.globalDataMap.keySet());
|
||||||
|
}
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void exportActionPerformed(ActionEvent e) {
|
||||||
|
String selectedHost = hostTextField.getText().trim();
|
||||||
|
|
||||||
|
if (selectedHost.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String exportDir = selectDirectory(true);
|
||||||
|
|
||||||
|
if (exportDir.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exportActionWorker != null && !exportActionWorker.isDone()) {
|
||||||
|
exportActionWorker.cancel(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
exportActionWorker = new SwingWorker<List<Object[]>, Void>() {
|
||||||
|
@Override
|
||||||
|
protected List<Object[]> doInBackground() {
|
||||||
|
ConcurrentHashMap<String, Map<String, List<String>>> dataMap = Config.globalDataMap;
|
||||||
|
return exportData(selectedHost, exportDir, dataMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void done() {
|
||||||
|
try {
|
||||||
|
List<Object[]> taskStatusList = get();
|
||||||
|
if (!taskStatusList.isEmpty()) {
|
||||||
|
JOptionPane.showMessageDialog(Databoard.this, generateTaskStatusPane(taskStatusList), "Info", JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
sorter.setRowFilter(rowFilter);
|
exportActionWorker.execute();
|
||||||
|
|
||||||
messageTableModel.applyHostFilter(filterText);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> getHostByList() {
|
private JScrollPane generateTaskStatusPane(List<Object[]> dataList) {
|
||||||
return new ArrayList<>(Config.globalDataMap.keySet());
|
String[] columnNames = {"#", "Filename", "Status"};
|
||||||
|
DefaultTableModel taskStatusTableModel = new DefaultTableModel(columnNames, 0);
|
||||||
|
JTable taskStatusTable = new JTable(taskStatusTableModel);
|
||||||
|
|
||||||
|
for (Object[] data : dataList) {
|
||||||
|
int rowCount = taskStatusTable.getRowCount();
|
||||||
|
int id = rowCount > 0 ? (Integer) taskStatusTable.getValueAt(rowCount - 1, 0) + 1 : 1;
|
||||||
|
Object[] rowData = new Object[data.length + 1];
|
||||||
|
rowData[0] = id;
|
||||||
|
System.arraycopy(data, 0, rowData, 1, data.length);
|
||||||
|
taskStatusTableModel.addRow(rowData);
|
||||||
|
}
|
||||||
|
|
||||||
|
TableRowSorter<DefaultTableModel> sorter = new TableRowSorter<>(taskStatusTableModel);
|
||||||
|
taskStatusTable.setRowSorter(sorter);
|
||||||
|
|
||||||
|
JScrollPane scrollPane = new JScrollPane(taskStatusTable);
|
||||||
|
scrollPane.setBorder(new TitledBorder("Task status"));
|
||||||
|
scrollPane.setPreferredSize(new Dimension(500, 300));
|
||||||
|
|
||||||
|
int paneWidth = scrollPane.getPreferredSize().width;
|
||||||
|
taskStatusTable.getColumnModel().getColumn(0).setPreferredWidth((int) (paneWidth * 0.1));
|
||||||
|
taskStatusTable.getColumnModel().getColumn(1).setPreferredWidth((int) (paneWidth * 0.7));
|
||||||
|
taskStatusTable.getColumnModel().getColumn(2).setPreferredWidth((int) (paneWidth * 0.2));
|
||||||
|
|
||||||
|
return scrollPane;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Object[]> exportData(String selectedHost, String exportDir, Map<String, Map<String, List<String>>> dataMap) {
|
||||||
|
return dataMap.entrySet().stream()
|
||||||
|
.filter(entry -> selectedHost.equals("*") || StringProcessor.matchesHostPattern(entry.getKey(), selectedHost))
|
||||||
|
.filter(entry -> !entry.getKey().contains("*"))
|
||||||
|
.map(entry -> exportEntry(entry, exportDir))
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object[] exportEntry(Map.Entry<String, Map<String, List<String>>> entry, String exportDir) {
|
||||||
|
String key = entry.getKey();
|
||||||
|
Map<String, List<String>> ruleMap = entry.getValue();
|
||||||
|
|
||||||
|
if (ruleMap == null || ruleMap.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<MessageEntry> messageEntryList = messageTableModel.getLogs();
|
||||||
|
|
||||||
|
Map<MessageEntry, String> entryUUIDMap = messageEntryList.stream()
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
messageEntry -> messageEntry,
|
||||||
|
messageEntry -> StringProcessor.getRandomUUID(),
|
||||||
|
(existing, replacement) -> existing
|
||||||
|
));
|
||||||
|
|
||||||
|
Map<String, Map<String, Object>> httpMap = processEntries(
|
||||||
|
messageEntryList,
|
||||||
|
key,
|
||||||
|
entryUUIDMap,
|
||||||
|
this::createHttpItemMap
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, Map<String, Object>> urlMap = processEntries(
|
||||||
|
messageEntryList,
|
||||||
|
key,
|
||||||
|
entryUUIDMap,
|
||||||
|
this::creteUrlItemMap
|
||||||
|
);
|
||||||
|
|
||||||
|
String hostName = key.replace(":", "_");
|
||||||
|
String filename = String.format("%s/%s-%s.hae", exportDir, StringProcessor.getCurrentTime(), hostName);
|
||||||
|
boolean createdStatus = projectProcessor.createHaeFile(filename, key, ruleMap, urlMap, httpMap);
|
||||||
|
|
||||||
|
return new Object[]{filename, createdStatus};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Map<String, Map<String, Object>> processEntries(List<MessageEntry> messageEntryList, String key, Map<MessageEntry, String> entryUUIDMap, Function<MessageEntry, Map<String, Object>> mapFunction) {
|
||||||
|
return messageEntryList.stream()
|
||||||
|
.filter(messageEntry -> !StringProcessor.getHostByUrl(messageEntry.getUrl()).isEmpty())
|
||||||
|
.filter(messageEntry -> StringProcessor.getHostByUrl(messageEntry.getUrl()).equals(key))
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
entryUUIDMap::get,
|
||||||
|
mapFunction,
|
||||||
|
(existing, replacement) -> existing
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> creteUrlItemMap(MessageEntry entry) {
|
||||||
|
Map<String, Object> urlItemMap = new LinkedHashMap<>();
|
||||||
|
urlItemMap.put("url", entry.getUrl());
|
||||||
|
urlItemMap.put("method", entry.getMethod());
|
||||||
|
urlItemMap.put("status", entry.getStatus());
|
||||||
|
urlItemMap.put("length", entry.getLength());
|
||||||
|
urlItemMap.put("comment", entry.getComment());
|
||||||
|
urlItemMap.put("color", entry.getColor());
|
||||||
|
urlItemMap.put("size", String.valueOf(entry.getRequestResponse().request().toByteArray().length()));
|
||||||
|
return urlItemMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> createHttpItemMap(MessageEntry entry) {
|
||||||
|
Map<String, Object> httpItemMap = new LinkedHashMap<>();
|
||||||
|
httpItemMap.put("request", entry.getRequestResponse().request().toByteArray().getBytes());
|
||||||
|
httpItemMap.put("response", entry.getRequestResponse().response().toByteArray().getBytes());
|
||||||
|
return httpItemMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void importActionPerformed(ActionEvent e) {
|
||||||
|
String exportDir = selectDirectory(false);
|
||||||
|
if (exportDir.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (importActionWorker != null && !importActionWorker.isDone()) {
|
||||||
|
importActionWorker.cancel(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
importActionWorker = new SwingWorker<List<Object[]>, Void>() {
|
||||||
|
@Override
|
||||||
|
protected List<Object[]> doInBackground() {
|
||||||
|
List<String> filesWithExtension = findFilesWithExtension(new File(exportDir), ".hae");
|
||||||
|
return filesWithExtension.stream()
|
||||||
|
.map(Databoard.this::importData)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void done() {
|
||||||
|
try {
|
||||||
|
List<Object[]> taskStatusList = get();
|
||||||
|
if (!taskStatusList.isEmpty()) {
|
||||||
|
JOptionPane.showMessageDialog(Databoard.this, generateTaskStatusPane(taskStatusList), "Info", JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
importActionWorker.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object[] importData(String filename) {
|
||||||
|
ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
|
||||||
|
|
||||||
|
HaeFileContent haeFileContent = projectProcessor.readHaeFile(filename);
|
||||||
|
boolean readStatus = haeFileContent != null;
|
||||||
|
|
||||||
|
List<Callable<Void>> tasks = new ArrayList<>();
|
||||||
|
|
||||||
|
if (readStatus) {
|
||||||
|
try {
|
||||||
|
String host = haeFileContent.getHost();
|
||||||
|
haeFileContent.getDataMap().forEach((key, value) -> RegularMatcher.putDataToGlobalMap(host, key, value));
|
||||||
|
|
||||||
|
haeFileContent.getUrlMap().forEach((key, urlItemMap) -> {
|
||||||
|
tasks.add(() -> {
|
||||||
|
String url = urlItemMap.get("url");
|
||||||
|
String comment = urlItemMap.get("comment");
|
||||||
|
String color = urlItemMap.get("color");
|
||||||
|
String length = urlItemMap.get("length");
|
||||||
|
String method = urlItemMap.get("method");
|
||||||
|
String status = urlItemMap.get("status");
|
||||||
|
String path = haeFileContent.getHttpPath();
|
||||||
|
|
||||||
|
messageTableModel.add(null, url, method, status, length, comment, color, key, path);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
executor.invokeAll(tasks);
|
||||||
|
} catch (Exception e) {
|
||||||
|
api.logging().logToError("importData: " + e.getMessage());
|
||||||
|
} finally {
|
||||||
|
executor.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Object[]{filename, readStatus};
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> findFilesWithExtension(File directory, String extension) {
|
||||||
|
List<String> filePaths = new ArrayList<>();
|
||||||
|
if (directory.isDirectory()) {
|
||||||
|
File[] files = directory.listFiles();
|
||||||
|
if (files != null) {
|
||||||
|
for (File file : files) {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
filePaths.addAll(findFilesWithExtension(file, extension));
|
||||||
|
} else if (file.isFile() && file.getName().toLowerCase().endsWith(extension)) {
|
||||||
|
filePaths.add(file.getAbsolutePath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
filePaths.add(directory.getAbsolutePath());
|
||||||
|
}
|
||||||
|
return filePaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String selectDirectory(boolean forDirectories) {
|
||||||
|
JFileChooser chooser = new JFileChooser();
|
||||||
|
chooser.setCurrentDirectory(new java.io.File(configLoader.getRulesFilePath()));
|
||||||
|
chooser.setDialogTitle(String.format("Select a Directory%s", forDirectories ? "" : " or File"));
|
||||||
|
FileNameExtensionFilter filter = new FileNameExtensionFilter(".hae Files", "hae");
|
||||||
|
chooser.addChoosableFileFilter(filter);
|
||||||
|
chooser.setFileFilter(filter);
|
||||||
|
|
||||||
|
chooser.setFileSelectionMode(forDirectories ? JFileChooser.DIRECTORIES_ONLY : JFileChooser.FILES_AND_DIRECTORIES);
|
||||||
|
chooser.setAcceptAllFileFilterUsed(!forDirectories);
|
||||||
|
|
||||||
|
if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
|
||||||
|
File selectedDirectory = chooser.getSelectedFile();
|
||||||
|
return selectedDirectory.getAbsolutePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearActionPerformed(ActionEvent e) {
|
private void clearActionPerformed(ActionEvent e) {
|
||||||
int retCode = JOptionPane.showConfirmDialog(null, "Do you want to clear data?", "Info",
|
int retCode = JOptionPane.showConfirmDialog(this, "Do you want to clear data?", "Info",
|
||||||
JOptionPane.YES_NO_OPTION);
|
JOptionPane.YES_NO_OPTION);
|
||||||
String host = hostTextField.getText();
|
String host = hostTextField.getText();
|
||||||
if (retCode == JOptionPane.YES_OPTION && !host.isEmpty()) {
|
if (retCode == JOptionPane.YES_OPTION && !host.isEmpty()) {
|
||||||
dataTabbedPane.removeAll();
|
dataTabbedPane.removeAll();
|
||||||
splitPane.setVisible(false);
|
splitPane.setVisible(false);
|
||||||
|
progressBar.setVisible(false);
|
||||||
|
|
||||||
String cleanedHost = StringProcessor.replaceFirstOccurrence(host, "*.", "");
|
Config.globalDataMap.keySet().parallelStream().forEach(key -> {
|
||||||
|
if (StringProcessor.matchesHostPattern(key, host) || host.equals("*")) {
|
||||||
|
Config.globalDataMap.remove(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (host.contains("*")) {
|
// 删除无用的数据
|
||||||
Config.globalDataMap.keySet().removeIf(i -> i.contains(cleanedHost) || cleanedHost.contains("*"));
|
Set<String> wildcardKeys = Config.globalDataMap.keySet().stream()
|
||||||
} else {
|
.filter(key -> key.startsWith("*."))
|
||||||
Config.globalDataMap.remove(host);
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
Set<String> existingSuffixes = Config.globalDataMap.keySet().stream()
|
||||||
|
.filter(key -> !key.startsWith("*."))
|
||||||
|
.map(key -> {
|
||||||
|
int dotIndex = key.indexOf(".");
|
||||||
|
return dotIndex != -1 ? key.substring(dotIndex) : "";
|
||||||
|
})
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
Set<String> keysToRemove = wildcardKeys.stream()
|
||||||
|
.filter(key -> !existingSuffixes.contains(key.substring(1)))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
keysToRemove.forEach(Config.globalDataMap::remove);
|
||||||
|
|
||||||
|
if (Config.globalDataMap.keySet().size() == 1 && Config.globalDataMap.keySet().stream().anyMatch(key -> key.equals("*"))) {
|
||||||
|
Config.globalDataMap.keySet().remove("*");
|
||||||
}
|
}
|
||||||
|
|
||||||
messageTableModel.deleteByHost(cleanedHost);
|
messageTableModel.deleteByHost(host);
|
||||||
|
|
||||||
|
hostTextField.setText("");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,207 +0,0 @@
|
|||||||
package hae.component.board;
|
|
||||||
|
|
||||||
import burp.api.montoya.MontoyaApi;
|
|
||||||
import hae.component.board.message.MessageTableModel;
|
|
||||||
import hae.utils.ui.UIEnhancer;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import javax.swing.event.DocumentEvent;
|
|
||||||
import javax.swing.event.DocumentListener;
|
|
||||||
import javax.swing.table.DefaultTableModel;
|
|
||||||
import javax.swing.table.TableColumn;
|
|
||||||
import javax.swing.table.TableRowSorter;
|
|
||||||
import java.awt.*;
|
|
||||||
import java.awt.datatransfer.Clipboard;
|
|
||||||
import java.awt.datatransfer.StringSelection;
|
|
||||||
import java.awt.event.MouseAdapter;
|
|
||||||
import java.awt.event.MouseEvent;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
public class Datatable extends JPanel {
|
|
||||||
private final MontoyaApi api;
|
|
||||||
private final JTable dataTable;
|
|
||||||
private final DefaultTableModel dataTableModel;
|
|
||||||
private final JTextField searchField;
|
|
||||||
private final TableRowSorter<DefaultTableModel> sorter;
|
|
||||||
private final JCheckBox searchMode = new JCheckBox("Reverse search");
|
|
||||||
private final String tabName;
|
|
||||||
|
|
||||||
public Datatable(MontoyaApi api, String tabName, List<String> dataList) {
|
|
||||||
this.api = api;
|
|
||||||
this.tabName = tabName;
|
|
||||||
|
|
||||||
String[] columnNames = {"#", "Information"};
|
|
||||||
|
|
||||||
dataTableModel = new DefaultTableModel(columnNames, 0);
|
|
||||||
dataTable = new JTable(dataTableModel);
|
|
||||||
sorter = new TableRowSorter<>(dataTableModel);
|
|
||||||
|
|
||||||
searchField = new JTextField();
|
|
||||||
|
|
||||||
initComponents(dataList);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initComponents(List<String> dataList) {
|
|
||||||
// 设置ID排序
|
|
||||||
sorter.setComparator(0, new Comparator<Integer>() {
|
|
||||||
@Override
|
|
||||||
public int compare(Integer s1, Integer s2) {
|
|
||||||
return s1.compareTo(s2);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
dataTable.setRowSorter(sorter);
|
|
||||||
TableColumn idColumn = dataTable.getColumnModel().getColumn(0);
|
|
||||||
idColumn.setMaxWidth(50);
|
|
||||||
|
|
||||||
for (String item : dataList) {
|
|
||||||
if (!item.isEmpty()) {
|
|
||||||
addRowToTable(new Object[]{item});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置灰色默认文本
|
|
||||||
String searchText = "Search";
|
|
||||||
UIEnhancer.setTextFieldPlaceholder(searchField, searchText);
|
|
||||||
|
|
||||||
// 监听输入框内容输入、更新、删除
|
|
||||||
searchField.getDocument().addDocumentListener(new DocumentListener() {
|
|
||||||
@Override
|
|
||||||
public void insertUpdate(DocumentEvent e) {
|
|
||||||
performSearch();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeUpdate(DocumentEvent e) {
|
|
||||||
performSearch();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void changedUpdate(DocumentEvent e) {
|
|
||||||
performSearch();
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
// 设置布局
|
|
||||||
JScrollPane scrollPane = new JScrollPane(dataTable);
|
|
||||||
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
|
|
||||||
|
|
||||||
searchMode.addItemListener(e -> performSearch());
|
|
||||||
|
|
||||||
setLayout(new BorderLayout(0, 5));
|
|
||||||
|
|
||||||
JPanel optionsPanel = new JPanel();
|
|
||||||
optionsPanel.setBorder(BorderFactory.createEmptyBorder(2, 3, 5, 5));
|
|
||||||
optionsPanel.setLayout(new BoxLayout(optionsPanel, BoxLayout.X_AXIS));
|
|
||||||
|
|
||||||
// 新增复选框要在这修改rows
|
|
||||||
JPanel menuPanel = new JPanel(new GridLayout(1, 1));
|
|
||||||
menuPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
|
|
||||||
JPopupMenu menu = new JPopupMenu();
|
|
||||||
menuPanel.add(searchMode);
|
|
||||||
menu.add(menuPanel);
|
|
||||||
|
|
||||||
JButton settingsButton = new JButton("Settings");
|
|
||||||
settingsButton.addActionListener(e -> {
|
|
||||||
int x = settingsButton.getX();
|
|
||||||
int y = settingsButton.getY() - menu.getPreferredSize().height;
|
|
||||||
menu.show(settingsButton, x, y);
|
|
||||||
});
|
|
||||||
|
|
||||||
optionsPanel.add(settingsButton);
|
|
||||||
optionsPanel.add(Box.createHorizontalStrut(5));
|
|
||||||
optionsPanel.add(searchField);
|
|
||||||
|
|
||||||
add(scrollPane, BorderLayout.CENTER);
|
|
||||||
add(optionsPanel, BorderLayout.SOUTH);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addRowToTable(Object[] data) {
|
|
||||||
int rowCount = dataTableModel.getRowCount();
|
|
||||||
int id = rowCount > 0 ? (Integer) dataTableModel.getValueAt(rowCount - 1, 0) + 1 : 1;
|
|
||||||
Object[] rowData = new Object[data.length + 1];
|
|
||||||
rowData[0] = id;
|
|
||||||
System.arraycopy(data, 0, rowData, 1, data.length);
|
|
||||||
dataTableModel.addRow(rowData);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void performSearch() {
|
|
||||||
if (searchField.getForeground().equals(Color.BLACK)) {
|
|
||||||
RowFilter<Object, Object> rowFilter = new RowFilter<Object, Object>() {
|
|
||||||
public boolean include(Entry<?, ?> entry) {
|
|
||||||
String searchFieldTextText = searchField.getText();
|
|
||||||
Pattern pattern = null;
|
|
||||||
try {
|
|
||||||
pattern = Pattern.compile(searchFieldTextText, Pattern.CASE_INSENSITIVE);
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
|
|
||||||
String entryValue = ((String) entry.getValue(1)).toLowerCase();
|
|
||||||
searchFieldTextText = searchFieldTextText.toLowerCase();
|
|
||||||
if (pattern != null) {
|
|
||||||
return searchFieldTextText.isEmpty() || pattern.matcher(entryValue).find() != searchMode.isSelected();
|
|
||||||
} else {
|
|
||||||
return searchFieldTextText.isEmpty() || entryValue.contains(searchFieldTextText) != searchMode.isSelected();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
sorter.setRowFilter(rowFilter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTableListener(MessageTableModel messagePanel) {
|
|
||||||
// 表格复制功能
|
|
||||||
dataTable.setTransferHandler(new TransferHandler() {
|
|
||||||
@Override
|
|
||||||
public void exportToClipboard(JComponent comp, Clipboard clip, int action) throws IllegalStateException {
|
|
||||||
if (comp instanceof JTable) {
|
|
||||||
StringSelection stringSelection = new StringSelection(getSelectedDataAtTable((JTable) comp));
|
|
||||||
clip.setContents(stringSelection, null);
|
|
||||||
} else {
|
|
||||||
super.exportToClipboard(comp, clip, action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
dataTable.setDefaultEditor(Object.class, null);
|
|
||||||
|
|
||||||
// 表格内容双击事件
|
|
||||||
dataTable.addMouseListener(new MouseAdapter() {
|
|
||||||
@Override
|
|
||||||
public void mouseClicked(MouseEvent e) {
|
|
||||||
if (e.getClickCount() == 2) {
|
|
||||||
int selectedRow = dataTable.getSelectedRow();
|
|
||||||
if (selectedRow != -1) {
|
|
||||||
String rowData = dataTable.getValueAt(selectedRow, 1).toString();
|
|
||||||
messagePanel.applyMessageFilter(tabName, rowData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSelectedDataAtTable(JTable table) {
|
|
||||||
int[] selectRows = table.getSelectedRows();
|
|
||||||
StringBuilder selectData = new StringBuilder();
|
|
||||||
|
|
||||||
for (int row : selectRows) {
|
|
||||||
selectData.append(table.getValueAt(row, 1).toString()).append("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 便于单行复制,去除最后一个换行符
|
|
||||||
if (!selectData.isEmpty()) {
|
|
||||||
selectData.deleteCharAt(selectData.length() - 1);
|
|
||||||
return selectData.toString();
|
|
||||||
} else {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public JTable getDataTable() {
|
|
||||||
return this.dataTable;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -11,8 +11,10 @@ public class MessageEntry {
|
|||||||
private final String status;
|
private final String status;
|
||||||
private final String color;
|
private final String color;
|
||||||
private final String method;
|
private final String method;
|
||||||
|
private final String hash;
|
||||||
|
private final String path;
|
||||||
|
|
||||||
MessageEntry(HttpRequestResponse requestResponse, String method, String url, String comment, String length, String color, String status) {
|
MessageEntry(HttpRequestResponse requestResponse, String method, String url, String comment, String length, String color, String status, String hash, String path) {
|
||||||
this.requestResponse = requestResponse;
|
this.requestResponse = requestResponse;
|
||||||
this.method = method;
|
this.method = method;
|
||||||
this.url = url;
|
this.url = url;
|
||||||
@@ -20,6 +22,8 @@ public class MessageEntry {
|
|||||||
this.length = length;
|
this.length = length;
|
||||||
this.color = color;
|
this.color = color;
|
||||||
this.status = status;
|
this.status = status;
|
||||||
|
this.hash = hash;
|
||||||
|
this.path = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getColor() {
|
public String getColor() {
|
||||||
@@ -49,4 +53,12 @@ public class MessageEntry {
|
|||||||
public HttpRequestResponse getRequestResponse() {
|
public HttpRequestResponse getRequestResponse() {
|
||||||
return this.requestResponse;
|
return this.requestResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getHash() {
|
||||||
|
return this.hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPath() {
|
||||||
|
return this.path;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,16 +4,16 @@ import javax.swing.*;
|
|||||||
import javax.swing.table.DefaultTableCellRenderer;
|
import javax.swing.table.DefaultTableCellRenderer;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.LinkedList;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class MessageRenderer extends DefaultTableCellRenderer {
|
public class MessageRenderer extends DefaultTableCellRenderer {
|
||||||
|
|
||||||
private final List<MessageEntry> log;
|
private final LinkedList<MessageEntry> log;
|
||||||
private final Map<String, Color> colorMap = new HashMap<>();
|
private final Map<String, Color> colorMap = new HashMap<>();
|
||||||
private final JTable table; // 保存对表格的引用
|
private final JTable table; // 保存对表格的引用
|
||||||
|
|
||||||
public MessageRenderer(List<MessageEntry> log, JTable table) {
|
public MessageRenderer(LinkedList<MessageEntry> log, JTable table) {
|
||||||
this.log = log;
|
this.log = log;
|
||||||
// 与BurpSuite的颜色保持一致
|
// 与BurpSuite的颜色保持一致
|
||||||
this.colorMap.put("red", new Color(0xFF, 0x64, 0x64));
|
this.colorMap.put("red", new Color(0xFF, 0x64, 0x64));
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import burp.api.montoya.ui.editor.HttpRequestEditor;
|
|||||||
import burp.api.montoya.ui.editor.HttpResponseEditor;
|
import burp.api.montoya.ui.editor.HttpResponseEditor;
|
||||||
import hae.Config;
|
import hae.Config;
|
||||||
import hae.cache.CachePool;
|
import hae.cache.CachePool;
|
||||||
|
import hae.utils.project.FileProcessor;
|
||||||
import hae.utils.string.HashCalculator;
|
import hae.utils.string.HashCalculator;
|
||||||
import hae.utils.string.StringProcessor;
|
import hae.utils.string.StringProcessor;
|
||||||
|
|
||||||
@@ -30,16 +31,16 @@ import static burp.api.montoya.ui.editor.EditorOptions.READ_ONLY;
|
|||||||
public class MessageTableModel extends AbstractTableModel {
|
public class MessageTableModel extends AbstractTableModel {
|
||||||
private final MontoyaApi api;
|
private final MontoyaApi api;
|
||||||
private final MessageTable messageTable;
|
private final MessageTable messageTable;
|
||||||
private final JTabbedPane messageTab;
|
|
||||||
private final JSplitPane splitPane;
|
private final JSplitPane splitPane;
|
||||||
private final List<MessageEntry> log = new ArrayList<MessageEntry>();
|
private final LinkedList<MessageEntry> log = new LinkedList<>();
|
||||||
private final LinkedList<MessageEntry> filteredLog;
|
private final LinkedList<MessageEntry> filteredLog;
|
||||||
|
private SwingWorker<Void, Void> currentWorker;
|
||||||
|
|
||||||
public MessageTableModel(MontoyaApi api) {
|
public MessageTableModel(MontoyaApi api) {
|
||||||
this.filteredLog = new LinkedList<>();
|
this.filteredLog = new LinkedList<>();
|
||||||
this.api = api;
|
this.api = api;
|
||||||
|
|
||||||
messageTab = new JTabbedPane();
|
JTabbedPane messageTab = new JTabbedPane();
|
||||||
UserInterface userInterface = api.userInterface();
|
UserInterface userInterface = api.userInterface();
|
||||||
HttpRequestEditor requestViewer = userInterface.createHttpRequestEditor(READ_ONLY);
|
HttpRequestEditor requestViewer = userInterface.createHttpRequestEditor(READ_ONLY);
|
||||||
HttpResponseEditor responseViewer = userInterface.createHttpResponseEditor(READ_ONLY);
|
HttpResponseEditor responseViewer = userInterface.createHttpResponseEditor(READ_ONLY);
|
||||||
@@ -92,25 +93,25 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
splitPane.setRightComponent(messageTab);
|
splitPane.setRightComponent(messageTab);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add(HttpRequestResponse messageInfo, String comment, String color) {
|
public void add(HttpRequestResponse messageInfo, String url, String method, String status, String length, String comment, String color, String hash, String path) {
|
||||||
synchronized (log) {
|
synchronized (log) {
|
||||||
HttpRequest httpRequest = messageInfo.request();
|
boolean isDuplicate = false;
|
||||||
String url = httpRequest.url();
|
MessageEntry logEntry = new MessageEntry(messageInfo, method, url, comment, length, color, status, hash, path);
|
||||||
String method = httpRequest.method();
|
|
||||||
|
|
||||||
HttpResponse httpResponse = messageInfo.response();
|
byte[] reqByteA = new byte[0];
|
||||||
String status = String.valueOf(httpResponse.statusCode());
|
byte[] resByteA = new byte[0];
|
||||||
String length = String.valueOf(httpResponse.body().length());
|
|
||||||
|
|
||||||
MessageEntry logEntry = new MessageEntry(messageInfo, method, url, comment, length, color, status);
|
if (messageInfo != null) {
|
||||||
|
HttpRequest httpRequest = messageInfo.request();
|
||||||
|
HttpResponse httpResponse = messageInfo.response();
|
||||||
|
|
||||||
|
reqByteA = httpRequest.toByteArray().getBytes();
|
||||||
|
resByteA = httpResponse.toByteArray().getBytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 比较Hash,如若存在重复的请求或响应,则不放入消息内容里
|
||||||
try {
|
try {
|
||||||
// 比较Hash,如若存在重复的请求或响应,则不放入消息内容里
|
if (!log.isEmpty()) {
|
||||||
byte[] reqByteA = httpRequest.toByteArray().getBytes();
|
|
||||||
byte[] resByteA = httpResponse.toByteArray().getBytes();
|
|
||||||
boolean isDuplicate = false;
|
|
||||||
|
|
||||||
if (log.size() > 0) {
|
|
||||||
for (MessageEntry entry : log) {
|
for (MessageEntry entry : log) {
|
||||||
HttpRequestResponse reqResMessage = entry.getRequestResponse();
|
HttpRequestResponse reqResMessage = entry.getRequestResponse();
|
||||||
byte[] reqByteB = reqResMessage.request().toByteArray().getBytes();
|
byte[] reqByteB = reqResMessage.request().toByteArray().getBytes();
|
||||||
@@ -125,12 +126,12 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isDuplicate) {
|
|
||||||
log.add(logEntry);
|
|
||||||
}
|
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isDuplicate) {
|
||||||
|
log.add(logEntry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -138,49 +139,86 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
public void deleteByHost(String filterText) {
|
public void deleteByHost(String filterText) {
|
||||||
filteredLog.clear();
|
filteredLog.clear();
|
||||||
List<Integer> rowsToRemove = new ArrayList<>();
|
List<Integer> rowsToRemove = new ArrayList<>();
|
||||||
for (int i = 0; i < log.size(); i++) {
|
|
||||||
MessageEntry entry = log.get(i);
|
if (currentWorker != null && !currentWorker.isDone()) {
|
||||||
String host = StringProcessor.getHostByUrl(entry.getUrl());
|
currentWorker.cancel(true);
|
||||||
if (!host.isEmpty()) {
|
}
|
||||||
if (StringProcessor.matchFromEnd(host, filterText) || filterText.contains("*")) {
|
|
||||||
rowsToRemove.add(i);
|
currentWorker = new SwingWorker<Void, Void>() {
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground() {
|
||||||
|
for (int i = 0; i < log.size(); i++) {
|
||||||
|
MessageEntry entry = log.get(i);
|
||||||
|
String host = StringProcessor.getHostByUrl(entry.getUrl());
|
||||||
|
if (!host.isEmpty()) {
|
||||||
|
if (StringProcessor.matchesHostPattern(host, filterText) || filterText.equals("*")) {
|
||||||
|
rowsToRemove.add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (int i = rowsToRemove.size() - 1; i >= 0; i--) {
|
||||||
|
int row = rowsToRemove.get(i);
|
||||||
|
log.remove(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
for (int i = rowsToRemove.size() - 1; i >= 0; i--) {
|
currentWorker.execute();
|
||||||
int row = rowsToRemove.get(i);
|
|
||||||
log.remove(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rowsToRemove.isEmpty()) {
|
|
||||||
int[] rows = rowsToRemove.stream().mapToInt(Integer::intValue).toArray();
|
|
||||||
fireTableRowsDeleted(rows[0], rows[rows.length - 1]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void applyHostFilter(String filterText) {
|
public void applyHostFilter(String filterText) {
|
||||||
filteredLog.clear();
|
filteredLog.clear();
|
||||||
fireTableDataChanged();
|
|
||||||
String cleanedText = StringProcessor.replaceFirstOccurrence(filterText, "*.", "");
|
|
||||||
|
|
||||||
for (MessageEntry entry : log) {
|
log.forEach(entry -> {
|
||||||
String host = StringProcessor.getHostByUrl(entry.getUrl());
|
MessageEntry finalEntry = getEntryByFile(entry);
|
||||||
|
String host = StringProcessor.getHostByUrl(finalEntry.getUrl());
|
||||||
if (!host.isEmpty()) {
|
if (!host.isEmpty()) {
|
||||||
if (filterText.contains("*.") && StringProcessor.matchFromEnd(host, cleanedText)) {
|
if (StringProcessor.matchesHostPattern(host, filterText) || filterText.contains("*")) {
|
||||||
filteredLog.add(entry);
|
filteredLog.add(finalEntry);
|
||||||
} else if (host.equals(filterText) || filterText.contains("*")) {
|
|
||||||
filteredLog.add(entry);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
fireTableDataChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private MessageEntry getEntryByFile(MessageEntry entry) {
|
||||||
|
HttpRequestResponse requestResponse = entry.getRequestResponse();
|
||||||
|
if (requestResponse == null) {
|
||||||
|
String url = entry.getUrl();
|
||||||
|
String method = entry.getMethod();
|
||||||
|
String status = entry.getStatus();
|
||||||
|
String comment = entry.getComment();
|
||||||
|
String color = entry.getColor();
|
||||||
|
String path = entry.getPath();
|
||||||
|
String hash = entry.getHash();
|
||||||
|
int length = Integer.parseInt(entry.getLength());
|
||||||
|
|
||||||
|
byte[] contents = FileProcessor.readFileContent(path, hash);
|
||||||
|
|
||||||
|
if (contents.length > length) {
|
||||||
|
byte[] response = Arrays.copyOf(contents, length);
|
||||||
|
byte[] request = Arrays.copyOfRange(contents, length, contents.length);
|
||||||
|
requestResponse = StringProcessor.createHttpRequestResponse(url, request, response);
|
||||||
|
|
||||||
|
int index = log.indexOf(entry);
|
||||||
|
entry = new MessageEntry(requestResponse, method, url, comment, String.valueOf(length), color, status, "", "");
|
||||||
|
log.set(index, entry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fireTableDataChanged();
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void applyMessageFilter(String tableName, String filterText) {
|
public void applyMessageFilter(String tableName, String filterText) {
|
||||||
filteredLog.clear();
|
filteredLog.clear();
|
||||||
for (MessageEntry entry : log) {
|
for (MessageEntry entry : log) {
|
||||||
|
// 标志变量,表示是否满足过滤条件
|
||||||
|
AtomicBoolean isMatched = new AtomicBoolean(false);
|
||||||
|
|
||||||
HttpRequestResponse requestResponse = entry.getRequestResponse();
|
HttpRequestResponse requestResponse = entry.getRequestResponse();
|
||||||
HttpRequest httpRequest = requestResponse.request();
|
HttpRequest httpRequest = requestResponse.request();
|
||||||
HttpResponse httpResponse = requestResponse.response();
|
HttpResponse httpResponse = requestResponse.response();
|
||||||
@@ -197,9 +235,6 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
.map(HttpHeader::toString)
|
.map(HttpHeader::toString)
|
||||||
.collect(Collectors.joining("\n"));
|
.collect(Collectors.joining("\n"));
|
||||||
|
|
||||||
// 标志变量,表示是否满足过滤条件
|
|
||||||
AtomicBoolean isMatched = new AtomicBoolean(false);
|
|
||||||
|
|
||||||
Config.globalRules.keySet().forEach(i -> {
|
Config.globalRules.keySet().forEach(i -> {
|
||||||
for (Object[] objects : Config.globalRules.get(i)) {
|
for (Object[] objects : Config.globalRules.get(i)) {
|
||||||
String name = objects[1].toString();
|
String name = objects[1].toString();
|
||||||
@@ -305,7 +340,7 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
if (!map2.containsKey(key)) {
|
if (!map2.containsKey(key)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!areInnerMapsEqual(map1.get(key), map2.get(key))) {
|
if (areInnerMapsEqual(map1.get(key), map2.get(key))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -315,30 +350,29 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
|
|
||||||
private boolean areInnerMapsEqual(Map<String, Object> innerMap1, Map<String, Object> innerMap2) {
|
private boolean areInnerMapsEqual(Map<String, Object> innerMap1, Map<String, Object> innerMap2) {
|
||||||
if (innerMap1.size() != innerMap2.size()) {
|
if (innerMap1.size() != innerMap2.size()) {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (String key : innerMap1.keySet()) {
|
for (String key : innerMap1.keySet()) {
|
||||||
if (!innerMap2.containsKey(key)) {
|
if (!innerMap2.containsKey(key)) {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
Object value1 = innerMap1.get(key);
|
Object value1 = innerMap1.get(key);
|
||||||
Object value2 = innerMap2.get(key);
|
Object value2 = innerMap2.get(key);
|
||||||
|
|
||||||
// 如果值是Map,则递归对比
|
// 如果值是Map,则递归对比
|
||||||
if (value1 instanceof Map && value2 instanceof Map) {
|
if (value1 instanceof Map && value2 instanceof Map) {
|
||||||
if (!areInnerMapsEqual((Map<String, Object>) value1, (Map<String, Object>) value2)) {
|
if (areInnerMapsEqual((Map<String, Object>) value1, (Map<String, Object>) value2)) {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (!value1.equals(value2)) {
|
} else if (!value1.equals(value2)) {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public JSplitPane getSplitPane() {
|
public JSplitPane getSplitPane() {
|
||||||
return splitPane;
|
return splitPane;
|
||||||
}
|
}
|
||||||
@@ -347,11 +381,10 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
return messageTable;
|
return messageTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<MessageEntry> getLogs() {
|
public LinkedList<MessageEntry> getLogs() {
|
||||||
return log;
|
return log;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getRowCount() {
|
public int getRowCount() {
|
||||||
return filteredLog.size();
|
return filteredLog.size();
|
||||||
@@ -364,20 +397,27 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getValueAt(int rowIndex, int columnIndex) {
|
public Object getValueAt(int rowIndex, int columnIndex) {
|
||||||
if (filteredLog.isEmpty()) {
|
if (!filteredLog.isEmpty()) {
|
||||||
return "";
|
try {
|
||||||
}
|
MessageEntry messageEntry = filteredLog.get(rowIndex);
|
||||||
MessageEntry messageEntry = filteredLog.get(rowIndex);
|
|
||||||
|
|
||||||
return switch (columnIndex) {
|
if (messageEntry != null) {
|
||||||
case 0 -> messageEntry.getMethod();
|
return switch (columnIndex) {
|
||||||
case 1 -> messageEntry.getUrl();
|
case 0 -> messageEntry.getMethod();
|
||||||
case 2 -> messageEntry.getComment();
|
case 1 -> messageEntry.getUrl();
|
||||||
case 3 -> messageEntry.getStatus();
|
case 2 -> messageEntry.getComment();
|
||||||
case 4 -> messageEntry.getLength();
|
case 3 -> messageEntry.getStatus();
|
||||||
case 5 -> messageEntry.getColor();
|
case 4 -> messageEntry.getLength();
|
||||||
default -> "";
|
case 5 -> messageEntry.getColor();
|
||||||
};
|
default -> "";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
api.logging().logToError("getValueAt: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -394,10 +434,8 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public class MessageTable extends JTable {
|
public class MessageTable extends JTable {
|
||||||
private MessageEntry MessageEntry;
|
private MessageEntry messageEntry;
|
||||||
private SwingWorker<Object, Void> currentWorker;
|
private SwingWorker<ByteArray[], Void> currentWorker;
|
||||||
// 设置响应报文返回的最大长度
|
|
||||||
private final int MAX_LENGTH = 5242880;
|
|
||||||
private int lastSelectedIndex = -1;
|
private int lastSelectedIndex = -1;
|
||||||
private final HttpRequestEditor requestEditor;
|
private final HttpRequestEditor requestEditor;
|
||||||
private final HttpResponseEditor responseEditor;
|
private final HttpResponseEditor responseEditor;
|
||||||
@@ -411,46 +449,47 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
@Override
|
@Override
|
||||||
public void changeSelection(int row, int col, boolean toggle, boolean extend) {
|
public void changeSelection(int row, int col, boolean toggle, boolean extend) {
|
||||||
super.changeSelection(row, col, toggle, extend);
|
super.changeSelection(row, col, toggle, extend);
|
||||||
int selectedIndex = convertRowIndexToModel(row);
|
|
||||||
if (lastSelectedIndex != selectedIndex) {
|
|
||||||
lastSelectedIndex = selectedIndex;
|
|
||||||
MessageEntry = filteredLog.get(selectedIndex);
|
|
||||||
|
|
||||||
requestEditor.setRequest(HttpRequest.httpRequest("Loading..."));
|
if (currentWorker != null && !currentWorker.isDone()) {
|
||||||
responseEditor.setResponse(HttpResponse.httpResponse("Loading..."));
|
currentWorker.cancel(true);
|
||||||
|
}
|
||||||
|
|
||||||
if (currentWorker != null && !currentWorker.isDone()) {
|
currentWorker = new SwingWorker<>() {
|
||||||
currentWorker.cancel(true);
|
@Override
|
||||||
|
protected ByteArray[] doInBackground() {
|
||||||
|
int selectedIndex = convertRowIndexToModel(row);
|
||||||
|
if (lastSelectedIndex != selectedIndex) {
|
||||||
|
lastSelectedIndex = selectedIndex;
|
||||||
|
messageEntry = filteredLog.get(selectedIndex);
|
||||||
|
|
||||||
|
HttpRequestResponse httpRequestResponse = messageEntry.getRequestResponse();
|
||||||
|
|
||||||
|
ByteArray requestByte = httpRequestResponse.request().toByteArray();
|
||||||
|
ByteArray responseByte = httpRequestResponse.response().toByteArray();
|
||||||
|
|
||||||
|
ByteArray[] httpByteArray = new ByteArray[2];
|
||||||
|
httpByteArray[0] = requestByte;
|
||||||
|
httpByteArray[1] = responseByte;
|
||||||
|
return httpByteArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentWorker = new SwingWorker<>() {
|
@Override
|
||||||
@Override
|
protected void done() {
|
||||||
protected ByteArray[] doInBackground() {
|
try {
|
||||||
ByteArray requestByte = MessageEntry.getRequestResponse().request().toByteArray();
|
ByteArray[] retByteArray = get();
|
||||||
ByteArray responseByte = MessageEntry.getRequestResponse().response().toByteArray();
|
if (retByteArray != null) {
|
||||||
|
requestEditor.setRequest(HttpRequest.httpRequest(messageEntry.getRequestResponse().httpService(), retByteArray[0]));
|
||||||
if (responseByte.length() > MAX_LENGTH) {
|
responseEditor.setResponse(HttpResponse.httpResponse(retByteArray[1]));
|
||||||
String ellipsis = "\r\n......";
|
|
||||||
responseByte = responseByte.subArray(0, MAX_LENGTH).withAppended(ellipsis);
|
|
||||||
}
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
return new ByteArray[]{requestByte, responseByte};
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@Override
|
currentWorker.execute();
|
||||||
protected void done() {
|
|
||||||
if (!isCancelled()) {
|
|
||||||
try {
|
|
||||||
ByteArray[] result = (ByteArray[]) get();
|
|
||||||
requestEditor.setRequest(HttpRequest.httpRequest(MessageEntry.getRequestResponse().httpService(), result[0]));
|
|
||||||
responseEditor.setResponse(HttpResponse.httpResponse(result[1]));
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
currentWorker.execute();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
157
src/main/java/hae/component/board/table/AIPower.java
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
package hae.component.board.table;
|
||||||
|
|
||||||
|
import burp.api.montoya.MontoyaApi;
|
||||||
|
import burp.api.montoya.http.RequestOptions;
|
||||||
|
import burp.api.montoya.http.message.HttpRequestResponse;
|
||||||
|
import burp.api.montoya.http.message.requests.HttpRequest;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import hae.Config;
|
||||||
|
import hae.utils.ConfigLoader;
|
||||||
|
import hae.utils.http.HttpUtils;
|
||||||
|
import okhttp3.*;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class AIPower {
|
||||||
|
private final MontoyaApi api;
|
||||||
|
private final HttpUtils httpUtils;
|
||||||
|
private final ConfigLoader configLoader;
|
||||||
|
private final String apiAuth;
|
||||||
|
private final String aiModel;
|
||||||
|
private final String aiBaseUrl;
|
||||||
|
|
||||||
|
public AIPower(MontoyaApi api, ConfigLoader configLoader, String aiModel, String aiBaseUrl, String[] apiKey) {
|
||||||
|
this.api = api;
|
||||||
|
this.configLoader = configLoader;
|
||||||
|
this.httpUtils = new HttpUtils(api, configLoader);
|
||||||
|
this.aiModel = aiModel;
|
||||||
|
this.aiBaseUrl = aiBaseUrl;
|
||||||
|
|
||||||
|
this.apiAuth = String.format("Bearer %s", apiKey[new Random().nextInt(apiKey.length)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stream Response
|
||||||
|
public String chatWithAPI(String ruleName, String data) {
|
||||||
|
OkHttpClient httpClient = new OkHttpClient();
|
||||||
|
String fileId = uploadFileToAIService(ruleName, data);
|
||||||
|
Gson gson = new Gson();
|
||||||
|
|
||||||
|
if (fileId != null) {
|
||||||
|
String chatUrl = String.format("%s/chat/completions", aiBaseUrl);
|
||||||
|
String chatMessage = generateJsonData(configLoader.getAIPrompt(), fileId);
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(chatUrl)
|
||||||
|
.header("Authorization", apiAuth)
|
||||||
|
.post(RequestBody.create(MediaType.parse("application/json"), chatMessage))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try (Response response = httpClient.newCall(request).execute()) {
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
throw new IOException("Unexpected code " + response);
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(response.body().byteStream()));
|
||||||
|
StringBuilder chatReturn = new StringBuilder();
|
||||||
|
String line;
|
||||||
|
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
if (line.startsWith("data: ") && !line.contains("[DONE]")) {
|
||||||
|
String jsonData = line.substring(6);
|
||||||
|
Type type = new TypeToken<Map<String, Object>>() {
|
||||||
|
}.getType();
|
||||||
|
Map<String, Object> map = gson.fromJson(jsonData, type);
|
||||||
|
String content = getDeltaContent(map);
|
||||||
|
if (content != null) {
|
||||||
|
chatReturn.append(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteFileOnAIService(fileId);
|
||||||
|
|
||||||
|
return chatReturn.toString();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getDeltaContent(Map<String, Object> map) {
|
||||||
|
List<Map<String, Map<String, String>>> choices = (List<Map<String, Map<String, String>>>) map.get("choices");
|
||||||
|
if (choices != null && !choices.isEmpty()) {
|
||||||
|
Map<String, String> delta = choices.get(0).get("delta");
|
||||||
|
return delta.get("content");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String uploadFileToAIService(String ruleName, String data) {
|
||||||
|
String uploadUrl = String.format("%s/files", aiBaseUrl);
|
||||||
|
String uploadParam = "file";
|
||||||
|
String filename = "hae.txt";
|
||||||
|
String content = String.format(Config.userTextFormat, ruleName, data);
|
||||||
|
|
||||||
|
HttpRequest uploadFileRequest = httpUtils.generateRequestByMultipartUploadMethod(uploadUrl, uploadParam, filename, content).withAddedHeader("Authorization", apiAuth);
|
||||||
|
|
||||||
|
HttpRequestResponse uploadFileRequestResponse = api.http().sendRequest(uploadFileRequest, RequestOptions.requestOptions().withUpstreamTLSVerification());
|
||||||
|
String responseBody = uploadFileRequestResponse.response().bodyToString();
|
||||||
|
Pattern pattern = Pattern.compile("\"id\":\"(.*?)\",");
|
||||||
|
Matcher matcher = pattern.matcher(responseBody);
|
||||||
|
|
||||||
|
return matcher.find() ? matcher.group(1) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteFileOnAIService(String fileId) {
|
||||||
|
String deleteFileUrl = String.format("%s/files/%s", aiBaseUrl, fileId);
|
||||||
|
HttpRequest deleteFileRequest = httpUtils.generateRequestByDeleteMethod(deleteFileUrl).withAddedHeader("Authorization", apiAuth);
|
||||||
|
api.http().sendRequest(deleteFileRequest, RequestOptions.requestOptions().withUpstreamTLSVerification());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getFileContentOnAiService(String fileId) {
|
||||||
|
String getFileContentUrl = String.format("%s/files/%s/content", aiBaseUrl, fileId);
|
||||||
|
HttpRequest getFileContentRequest = HttpRequest.httpRequestFromUrl(getFileContentUrl).withAddedHeader("Authorization", apiAuth);
|
||||||
|
HttpRequestResponse getFileRequestResponse = api.http().sendRequest(getFileContentRequest, RequestOptions.requestOptions().withUpstreamTLSVerification());
|
||||||
|
String responseBody = getFileRequestResponse.response().bodyToString();
|
||||||
|
Pattern pattern = Pattern.compile("\"content\":\"(.*?)\",\"file_type\"");
|
||||||
|
Matcher matcher = pattern.matcher(responseBody);
|
||||||
|
|
||||||
|
return matcher.find() ? matcher.group(1) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String generateJsonData(String prompt, String fileId) {
|
||||||
|
Map<String, Object> data = new HashMap<>();
|
||||||
|
data.put("model", aiModel);
|
||||||
|
data.put("stream", true);
|
||||||
|
data.put("messages", new Object[]{
|
||||||
|
new HashMap<String, Object>() {{
|
||||||
|
put("role", "system");
|
||||||
|
put("content", prompt);
|
||||||
|
}},
|
||||||
|
new HashMap<String, Object>() {{
|
||||||
|
put("role", "system");
|
||||||
|
put("content", aiModel.equals("qwen-long") ? String.format("fileid://%s", fileId) : getFileContentOnAiService(fileId));
|
||||||
|
}},
|
||||||
|
new HashMap<String, Object>() {{
|
||||||
|
put("role", "user");
|
||||||
|
put("content", "Start");
|
||||||
|
}}
|
||||||
|
});
|
||||||
|
|
||||||
|
Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
||||||
|
return gson.toJson(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
347
src/main/java/hae/component/board/table/Datatable.java
Normal file
@@ -0,0 +1,347 @@
|
|||||||
|
package hae.component.board.table;
|
||||||
|
|
||||||
|
import burp.api.montoya.MontoyaApi;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import hae.component.board.Databoard;
|
||||||
|
import hae.component.board.message.MessageTableModel;
|
||||||
|
import hae.utils.ConfigLoader;
|
||||||
|
import hae.utils.UIEnhancer;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.DocumentEvent;
|
||||||
|
import javax.swing.event.DocumentListener;
|
||||||
|
import javax.swing.event.PopupMenuEvent;
|
||||||
|
import javax.swing.event.PopupMenuListener;
|
||||||
|
import javax.swing.table.DefaultTableModel;
|
||||||
|
import javax.swing.table.TableColumn;
|
||||||
|
import javax.swing.table.TableRowSorter;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.datatransfer.Clipboard;
|
||||||
|
import java.awt.datatransfer.StringSelection;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class Datatable extends JPanel {
|
||||||
|
private final MontoyaApi api;
|
||||||
|
private final ConfigLoader configLoader;
|
||||||
|
private final JTable dataTable;
|
||||||
|
private final DefaultTableModel dataTableModel;
|
||||||
|
private final JTextField searchField;
|
||||||
|
private final TableRowSorter<DefaultTableModel> sorter;
|
||||||
|
private final JCheckBox searchMode = new JCheckBox("Reverse search");
|
||||||
|
private final String tabName;
|
||||||
|
private final JProgressBar progressBar;
|
||||||
|
private final JPopupMenu aiEmpoweredMenu;
|
||||||
|
private final JPanel footerPanel;
|
||||||
|
|
||||||
|
public Datatable(MontoyaApi api, ConfigLoader configLoader, String tabName, List<String> dataList) {
|
||||||
|
this.api = api;
|
||||||
|
this.configLoader = configLoader;
|
||||||
|
this.tabName = tabName;
|
||||||
|
this.progressBar = new JProgressBar();
|
||||||
|
|
||||||
|
String[] columnNames = {"#", "Information"};
|
||||||
|
this.dataTableModel = new DefaultTableModel(columnNames, 0);
|
||||||
|
|
||||||
|
this.dataTable = new JTable(dataTableModel);
|
||||||
|
this.sorter = new TableRowSorter<>(dataTableModel);
|
||||||
|
this.searchField = new JTextField();
|
||||||
|
this.aiEmpoweredMenu = new JPopupMenu();
|
||||||
|
this.footerPanel = new JPanel(new BorderLayout(0, 5));
|
||||||
|
|
||||||
|
initComponents(dataList);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initComponents(List<String> dataList) {
|
||||||
|
progressBar.setVisible(false);
|
||||||
|
|
||||||
|
// 设置ID排序
|
||||||
|
sorter.setComparator(0, new Comparator<Integer>() {
|
||||||
|
@Override
|
||||||
|
public int compare(Integer s1, Integer s2) {
|
||||||
|
return s1.compareTo(s2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dataTable.setRowSorter(sorter);
|
||||||
|
TableColumn idColumn = dataTable.getColumnModel().getColumn(0);
|
||||||
|
idColumn.setMaxWidth(50);
|
||||||
|
|
||||||
|
for (String item : dataList) {
|
||||||
|
if (!item.isEmpty()) {
|
||||||
|
addRowToTable(new Object[]{item});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置灰色默认文本
|
||||||
|
String searchText = "Search";
|
||||||
|
UIEnhancer.setTextFieldPlaceholder(searchField, searchText);
|
||||||
|
|
||||||
|
// 监听输入框内容输入、更新、删除
|
||||||
|
searchField.getDocument().addDocumentListener(new DocumentListener() {
|
||||||
|
@Override
|
||||||
|
public void insertUpdate(DocumentEvent e) {
|
||||||
|
performSearch();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeUpdate(DocumentEvent e) {
|
||||||
|
performSearch();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void changedUpdate(DocumentEvent e) {
|
||||||
|
performSearch();
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// 设置布局
|
||||||
|
JScrollPane scrollPane = new JScrollPane(dataTable);
|
||||||
|
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
|
||||||
|
|
||||||
|
setLayout(new BorderLayout(0, 5));
|
||||||
|
|
||||||
|
JPanel optionsPanel = new JPanel();
|
||||||
|
optionsPanel.setLayout(new BoxLayout(optionsPanel, BoxLayout.X_AXIS));
|
||||||
|
|
||||||
|
// Settings按钮
|
||||||
|
JPanel settingMenuPanel = new JPanel(new GridLayout(1, 1));
|
||||||
|
settingMenuPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
|
||||||
|
JPopupMenu settingMenu = new JPopupMenu();
|
||||||
|
settingMenuPanel.add(searchMode);
|
||||||
|
searchMode.addItemListener(e -> performSearch());
|
||||||
|
settingMenu.add(settingMenuPanel);
|
||||||
|
|
||||||
|
JButton settingsButton = new JButton("Settings");
|
||||||
|
setMenuShow(settingMenu, settingsButton);
|
||||||
|
|
||||||
|
// AI Empowered按钮
|
||||||
|
JPanel aiEmpoweredPanel = new JPanel(new GridLayout(2, 1));
|
||||||
|
aiEmpoweredPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
|
||||||
|
JButton empoweredByAlibabaButton = new JButton("Alibaba - QwenLong");
|
||||||
|
empoweredByAlibabaButton.addActionListener(e -> {
|
||||||
|
aiEmpoweredByAlibabaActionPerformed(e, tabName, getTableData(dataTable));
|
||||||
|
});
|
||||||
|
JButton empoweredByMoonshotButton = new JButton("Moonshot - Kimi");
|
||||||
|
empoweredByMoonshotButton.addActionListener(e -> {
|
||||||
|
aiEmpoweredByMoonshotActionPerformed(e, tabName, getTableData(dataTable));
|
||||||
|
});
|
||||||
|
aiEmpoweredPanel.add(empoweredByAlibabaButton);
|
||||||
|
aiEmpoweredPanel.add(empoweredByMoonshotButton);
|
||||||
|
aiEmpoweredMenu.add(aiEmpoweredPanel);
|
||||||
|
|
||||||
|
JButton aiEmpoweredButton = new JButton("AI Empowered");
|
||||||
|
setMenuShow(aiEmpoweredMenu, aiEmpoweredButton);
|
||||||
|
aiEmpoweredMenu.addPopupMenuListener(new PopupMenuListener() {
|
||||||
|
@Override
|
||||||
|
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
|
||||||
|
empoweredByAlibabaButton.setEnabled(!configLoader.getAlibabaAIAPIKey().isEmpty());
|
||||||
|
empoweredByMoonshotButton.setEnabled(!configLoader.getMoonshotAIAPIKey().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void popupMenuCanceled(PopupMenuEvent e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
optionsPanel.add(settingsButton);
|
||||||
|
optionsPanel.add(Box.createHorizontalStrut(5));
|
||||||
|
optionsPanel.add(searchField);
|
||||||
|
optionsPanel.add(Box.createHorizontalStrut(5));
|
||||||
|
optionsPanel.add(aiEmpoweredButton);
|
||||||
|
|
||||||
|
footerPanel.setBorder(BorderFactory.createEmptyBorder(2, 3, 5, 3));
|
||||||
|
footerPanel.add(optionsPanel, BorderLayout.CENTER);
|
||||||
|
footerPanel.add(progressBar, BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
add(scrollPane, BorderLayout.CENTER);
|
||||||
|
add(footerPanel, BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
setProgressBar(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setMenuShow(JPopupMenu menu, JButton button) {
|
||||||
|
button.addActionListener(e -> {
|
||||||
|
Point buttonLocation = button.getLocationOnScreen();
|
||||||
|
Dimension menuSize = menu.getPreferredSize();
|
||||||
|
int x = buttonLocation.x + (button.getWidth() - menuSize.width) / 2;
|
||||||
|
int y = buttonLocation.y - menuSize.height;
|
||||||
|
menu.show(button, x - buttonLocation.x, y - buttonLocation.y);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setProgressBar(boolean status) {
|
||||||
|
Databoard.setProgressBar(status, progressBar, "AI+ ...");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addRowToTable(Object[] data) {
|
||||||
|
int rowCount = dataTableModel.getRowCount();
|
||||||
|
int id = rowCount > 0 ? (Integer) dataTableModel.getValueAt(rowCount - 1, 0) + 1 : 1;
|
||||||
|
Object[] rowData = new Object[data.length + 1];
|
||||||
|
rowData[0] = id;
|
||||||
|
System.arraycopy(data, 0, rowData, 1, data.length);
|
||||||
|
dataTableModel.addRow(rowData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void aiEmpoweredByAlibabaActionPerformed(ActionEvent e, String ruleName, String data) {
|
||||||
|
AIPower aiPower = new AIPower(api, configLoader, "qwen-long", "https://dashscope.aliyuncs.com/compatible-mode/v1", configLoader.getAlibabaAIAPIKey().split("\\|"));
|
||||||
|
aiEmpoweredButtonAction(ruleName, data, aiPower);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void aiEmpoweredByMoonshotActionPerformed(ActionEvent e, String ruleName, String data) {
|
||||||
|
AIPower aiPower = new AIPower(api, configLoader, "moonshot-v1-128k", "https://api.moonshot.cn/v1", configLoader.getMoonshotAIAPIKey().split("\\|"));
|
||||||
|
aiEmpoweredButtonAction(ruleName, data, aiPower);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void aiEmpoweredButtonAction(String ruleName, String data, AIPower aiPower) {
|
||||||
|
progressBar.setVisible(true);
|
||||||
|
aiEmpoweredMenu.setVisible(true);
|
||||||
|
setProgressBar(true);
|
||||||
|
|
||||||
|
SwingWorker<String, Void> worker = new SwingWorker<String, Void>() {
|
||||||
|
@Override
|
||||||
|
protected String doInBackground() throws Exception {
|
||||||
|
return aiPower.chatWithAPI(ruleName, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void done() {
|
||||||
|
setProgressBar(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
String chatReturn = get();
|
||||||
|
if (!chatReturn.isEmpty()) {
|
||||||
|
Gson gson = new Gson();
|
||||||
|
Type type = new TypeToken<Map<String, Object>>() {
|
||||||
|
}.getType();
|
||||||
|
Map<String, List<String>> map = gson.fromJson(chatReturn, type);
|
||||||
|
|
||||||
|
dataTableModel.setRowCount(0);
|
||||||
|
for (String item : map.get("data")) {
|
||||||
|
if (!item.isEmpty()) {
|
||||||
|
addRowToTable(new Object[]{item});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JOptionPane.showMessageDialog(Datatable.this, "AI+ has completed the AI empowered work.", "AI+ Info", JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
} else {
|
||||||
|
JOptionPane.showMessageDialog(Datatable.this, "AI+ returns null, please check!", "AI+ Info", JOptionPane.WARNING_MESSAGE);
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
JOptionPane.showMessageDialog(Datatable.this, "AI+ returns error, please check!", "AI+ Info", JOptionPane.ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
worker.execute();
|
||||||
|
|
||||||
|
aiEmpoweredMenu.setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void performSearch() {
|
||||||
|
if (searchField.getForeground().equals(Color.BLACK)) {
|
||||||
|
RowFilter<Object, Object> rowFilter = new RowFilter<Object, Object>() {
|
||||||
|
public boolean include(Entry<?, ?> entry) {
|
||||||
|
String searchFieldTextText = searchField.getText();
|
||||||
|
Pattern pattern = null;
|
||||||
|
try {
|
||||||
|
pattern = Pattern.compile(searchFieldTextText, Pattern.CASE_INSENSITIVE);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
String entryValue = ((String) entry.getValue(1)).toLowerCase();
|
||||||
|
searchFieldTextText = searchFieldTextText.toLowerCase();
|
||||||
|
if (pattern != null) {
|
||||||
|
return searchFieldTextText.isEmpty() || pattern.matcher(entryValue).find() != searchMode.isSelected();
|
||||||
|
} else {
|
||||||
|
return searchFieldTextText.isEmpty() || entryValue.contains(searchFieldTextText) != searchMode.isSelected();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
sorter.setRowFilter(rowFilter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTableListener(MessageTableModel messagePanel) {
|
||||||
|
// 表格复制功能
|
||||||
|
dataTable.setTransferHandler(new TransferHandler() {
|
||||||
|
@Override
|
||||||
|
public void exportToClipboard(JComponent comp, Clipboard clip, int action) throws IllegalStateException {
|
||||||
|
if (comp instanceof JTable) {
|
||||||
|
StringSelection stringSelection = new StringSelection(getSelectedDataAtTable((JTable) comp).replace("\0", "").replaceAll("[\\p{Cntrl}&&[^\r\n\t]]", ""));
|
||||||
|
clip.setContents(stringSelection, null);
|
||||||
|
} else {
|
||||||
|
super.exportToClipboard(comp, clip, action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dataTable.setDefaultEditor(Object.class, null);
|
||||||
|
|
||||||
|
// 表格内容双击事件
|
||||||
|
dataTable.addMouseListener(new MouseAdapter() {
|
||||||
|
@Override
|
||||||
|
public void mouseClicked(MouseEvent e) {
|
||||||
|
if (e.getClickCount() == 2) {
|
||||||
|
int selectedRow = dataTable.getSelectedRow();
|
||||||
|
if (selectedRow != -1) {
|
||||||
|
String rowData = dataTable.getValueAt(selectedRow, 1).toString();
|
||||||
|
messagePanel.applyMessageFilter(tabName, rowData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getTableData(JTable table) {
|
||||||
|
StringBuilder selectData = new StringBuilder();
|
||||||
|
int rowCount = table.getRowCount();
|
||||||
|
for (int i = 0; i < rowCount; i++) {
|
||||||
|
selectData.append(table.getValueAt(i, 1).toString()).append("\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selectData.isEmpty()) {
|
||||||
|
selectData.delete(selectData.length() - 2, selectData.length());
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectData.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSelectedDataAtTable(JTable table) {
|
||||||
|
int[] selectRows = table.getSelectedRows();
|
||||||
|
StringBuilder selectData = new StringBuilder();
|
||||||
|
|
||||||
|
for (int row : selectRows) {
|
||||||
|
selectData.append(table.getValueAt(row, 1).toString()).append("\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selectData.isEmpty()) {
|
||||||
|
selectData.delete(selectData.length() - 2, selectData.length());
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectData.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public JTable getDataTable() {
|
||||||
|
return this.dataTable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,247 +0,0 @@
|
|||||||
package hae.component.config;
|
|
||||||
|
|
||||||
import burp.api.montoya.MontoyaApi;
|
|
||||||
import hae.component.rule.Rules;
|
|
||||||
import hae.utils.config.ConfigLoader;
|
|
||||||
import hae.utils.ui.UIEnhancer;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import javax.swing.border.EmptyBorder;
|
|
||||||
import javax.swing.event.TableModelEvent;
|
|
||||||
import javax.swing.event.TableModelListener;
|
|
||||||
import javax.swing.table.DefaultTableModel;
|
|
||||||
import java.awt.*;
|
|
||||||
import java.awt.datatransfer.Clipboard;
|
|
||||||
import java.awt.datatransfer.DataFlavor;
|
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public class Config extends JPanel {
|
|
||||||
private final MontoyaApi api;
|
|
||||||
private final ConfigLoader configLoader;
|
|
||||||
private final Rules rules;
|
|
||||||
|
|
||||||
public Config(MontoyaApi api, ConfigLoader configLoader, Rules rules) {
|
|
||||||
this.api = api;
|
|
||||||
this.configLoader = configLoader;
|
|
||||||
this.rules = rules;
|
|
||||||
|
|
||||||
initComponents();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initComponents() {
|
|
||||||
setLayout(new BorderLayout());
|
|
||||||
|
|
||||||
GridBagConstraints constraints = new GridBagConstraints();
|
|
||||||
constraints.weightx = 1.0;
|
|
||||||
constraints.fill = GridBagConstraints.HORIZONTAL;
|
|
||||||
|
|
||||||
JPanel ruleInfoPanel = new JPanel(new GridBagLayout());
|
|
||||||
ruleInfoPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
|
|
||||||
|
|
||||||
JLabel ruleLabel = new JLabel("Path:");
|
|
||||||
JTextField pathTextField = new JTextField();
|
|
||||||
pathTextField.setEditable(false);
|
|
||||||
pathTextField.setText(configLoader.getRulesFilePath());
|
|
||||||
JButton reloadButton = new JButton("Reload");
|
|
||||||
JButton updateButton = new JButton("Update");
|
|
||||||
ruleInfoPanel.add(ruleLabel);
|
|
||||||
ruleInfoPanel.add(pathTextField, constraints);
|
|
||||||
ruleInfoPanel.add(Box.createHorizontalStrut(5));
|
|
||||||
ruleInfoPanel.add(reloadButton);
|
|
||||||
ruleInfoPanel.add(Box.createHorizontalStrut(5));
|
|
||||||
ruleInfoPanel.add(updateButton);
|
|
||||||
|
|
||||||
reloadButton.addActionListener(this::reloadActionPerformed);
|
|
||||||
updateButton.addActionListener(this::onlineUpdateActionPerformed);
|
|
||||||
|
|
||||||
JPanel settingPanel = new JPanel(new BorderLayout());
|
|
||||||
DefaultTableModel model = new DefaultTableModel();
|
|
||||||
|
|
||||||
JTable table = new JTable(model);
|
|
||||||
model.addColumn("Value");
|
|
||||||
JScrollPane scrollPane = new JScrollPane(table);
|
|
||||||
|
|
||||||
JPanel buttonPanel = new JPanel();
|
|
||||||
buttonPanel.setBorder(new EmptyBorder(0, 3, 0, 0));
|
|
||||||
GridBagLayout layout = new GridBagLayout();
|
|
||||||
layout.rowHeights = new int[]{0, 0, 0, 0, 0, 0, 0};
|
|
||||||
layout.rowWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE};
|
|
||||||
buttonPanel.setLayout(layout);
|
|
||||||
|
|
||||||
JPanel inputPanel = new JPanel(new BorderLayout());
|
|
||||||
JPanel inputPanelB = new JPanel(new BorderLayout());
|
|
||||||
inputPanelB.setBorder(new EmptyBorder(0, 0, 3, 0));
|
|
||||||
|
|
||||||
constraints.gridx = 1;
|
|
||||||
JButton addButton = new JButton("Add");
|
|
||||||
JButton removeButton = new JButton("Remove");
|
|
||||||
JButton pasteButton = new JButton("Paste");
|
|
||||||
JButton clearButton = new JButton("Clear");
|
|
||||||
|
|
||||||
JComboBox<String> setTypeComboBox = new JComboBox<>();
|
|
||||||
String[] mode = new String[]{"Exclude suffix", "Block host"};
|
|
||||||
setTypeComboBox.setModel(new DefaultComboBoxModel<>(mode));
|
|
||||||
setTypeComboBox.addActionListener(e -> {
|
|
||||||
String selected = (String) setTypeComboBox.getSelectedItem();
|
|
||||||
model.setRowCount(0);
|
|
||||||
|
|
||||||
if (selected.equals("Exclude suffix")) {
|
|
||||||
addDataToTable(configLoader.getExcludeSuffix().replaceAll("\\|", "\r\n"), model);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selected.equals("Block host")) {
|
|
||||||
addDataToTable(configLoader.getBlockHost().replaceAll("\\|", "\r\n"), model);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
setTypeComboBox.setSelectedItem("Exclude suffix");
|
|
||||||
|
|
||||||
model.addTableModelListener(new TableModelListener() {
|
|
||||||
@Override
|
|
||||||
public void tableChanged(TableModelEvent e) {
|
|
||||||
String selected = (String) setTypeComboBox.getSelectedItem();
|
|
||||||
String values = getFirstColumnDataAsString(model);
|
|
||||||
|
|
||||||
if (selected.equals("Exclude suffix")) {
|
|
||||||
if (!values.equals(configLoader.getExcludeSuffix()) && !values.isEmpty()) {
|
|
||||||
configLoader.setExcludeSuffix(values);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selected.equals("Block host")) {
|
|
||||||
if (!values.equals(configLoader.getExcludeSuffix()) && !values.isEmpty()) {
|
|
||||||
configLoader.setBlockHost(values);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
constraints.insets = new Insets(0, 0, 3, 0);
|
|
||||||
constraints.gridy = 0;
|
|
||||||
buttonPanel.add(setTypeComboBox, constraints);
|
|
||||||
constraints.gridy = 1;
|
|
||||||
buttonPanel.add(addButton, constraints);
|
|
||||||
constraints.gridy = 2;
|
|
||||||
buttonPanel.add(removeButton, constraints);
|
|
||||||
constraints.gridy = 3;
|
|
||||||
buttonPanel.add(pasteButton, constraints);
|
|
||||||
constraints.gridy = 4;
|
|
||||||
buttonPanel.add(clearButton, constraints);
|
|
||||||
|
|
||||||
JTextField addTextField = new JTextField();
|
|
||||||
String defaultText = "Enter a new item";
|
|
||||||
UIEnhancer.setTextFieldPlaceholder(addTextField, defaultText);
|
|
||||||
|
|
||||||
inputPanelB.add(addTextField, BorderLayout.CENTER);
|
|
||||||
inputPanel.add(scrollPane, BorderLayout.CENTER);
|
|
||||||
inputPanel.add(inputPanelB, BorderLayout.NORTH);
|
|
||||||
|
|
||||||
settingPanel.add(buttonPanel, BorderLayout.EAST);
|
|
||||||
settingPanel.add(inputPanel, BorderLayout.CENTER);
|
|
||||||
|
|
||||||
addButton.addActionListener(e -> {
|
|
||||||
String addTextFieldText = addTextField.getText();
|
|
||||||
if (!addTextFieldText.equals(defaultText)) {
|
|
||||||
addDataToTable(addTextFieldText, model);
|
|
||||||
}
|
|
||||||
addTextField.setText("");
|
|
||||||
addTextField.requestFocusInWindow();
|
|
||||||
});
|
|
||||||
|
|
||||||
pasteButton.addActionListener(e -> {
|
|
||||||
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
|
||||||
try {
|
|
||||||
String data = (String) clipboard.getData(DataFlavor.stringFlavor);
|
|
||||||
|
|
||||||
if (data != null && !data.isEmpty()) {
|
|
||||||
addDataToTable(data, model);
|
|
||||||
}
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
removeButton.addActionListener(e -> {
|
|
||||||
int selectedRow = table.getSelectedRow();
|
|
||||||
if (selectedRow != -1) {
|
|
||||||
model.removeRow(selectedRow);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
clearButton.addActionListener(e -> model.setRowCount(0));
|
|
||||||
|
|
||||||
JPanel settingMainPanel = new JPanel(new BorderLayout());
|
|
||||||
JLabel settingLabel = new JLabel("Setting:");
|
|
||||||
JPanel settingLabelPanel = new JPanel(new BorderLayout());
|
|
||||||
settingLabelPanel.add(settingLabel, BorderLayout.WEST);
|
|
||||||
settingMainPanel.setBorder(new EmptyBorder(0, 5, 10, 5));
|
|
||||||
settingMainPanel.add(settingLabelPanel, BorderLayout.NORTH);
|
|
||||||
settingMainPanel.add(settingPanel, BorderLayout.CENTER);
|
|
||||||
|
|
||||||
add(ruleInfoPanel, BorderLayout.NORTH);
|
|
||||||
add(settingMainPanel, BorderLayout.CENTER);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getFirstColumnDataAsString(DefaultTableModel model) {
|
|
||||||
StringBuilder firstColumnData = new StringBuilder();
|
|
||||||
int numRows = model.getRowCount();
|
|
||||||
|
|
||||||
for (int row = 0; row < numRows; row++) {
|
|
||||||
firstColumnData.append(model.getValueAt(row, 0));
|
|
||||||
if (row < numRows - 1) {
|
|
||||||
firstColumnData.append("|");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return firstColumnData.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addDataToTable(String data, DefaultTableModel model) {
|
|
||||||
if (!data.isBlank()) {
|
|
||||||
String[] rows = data.split("\\r?\\n");
|
|
||||||
for (String row : rows) {
|
|
||||||
model.addRow(new String[]{row});
|
|
||||||
}
|
|
||||||
deduplicateTableData(model);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deduplicateTableData(DefaultTableModel model) {
|
|
||||||
// 使用 Map 存储每一行的数据,用于去重
|
|
||||||
Set<List<Object>> rowData = new LinkedHashSet<>();
|
|
||||||
|
|
||||||
int columnCount = model.getColumnCount();
|
|
||||||
|
|
||||||
// 将每一行数据作为一个列表,添加到 Set 中
|
|
||||||
for (int i = 0; i < model.getRowCount(); i++) {
|
|
||||||
List<Object> row = new ArrayList<>();
|
|
||||||
for (int j = 0; j < columnCount; j++) {
|
|
||||||
row.add(model.getValueAt(i, j));
|
|
||||||
}
|
|
||||||
rowData.add(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清除原始数据
|
|
||||||
model.setRowCount(0);
|
|
||||||
|
|
||||||
// 将去重后的数据添加回去
|
|
||||||
for (List<Object> uniqueRow : rowData) {
|
|
||||||
model.addRow(uniqueRow.toArray());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onlineUpdateActionPerformed(ActionEvent e) {
|
|
||||||
// 添加提示框防止用户误触导致配置更新
|
|
||||||
int retCode = JOptionPane.showConfirmDialog(null, "Do you want to update rules?", "Info", JOptionPane.YES_NO_OPTION);
|
|
||||||
if (retCode == JOptionPane.YES_OPTION) {
|
|
||||||
configLoader.initRulesByNet();
|
|
||||||
reloadActionPerformed(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void reloadActionPerformed(ActionEvent e) {
|
|
||||||
rules.reloadRuleGroup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@ package hae.component.rule;
|
|||||||
|
|
||||||
import burp.api.montoya.MontoyaApi;
|
import burp.api.montoya.MontoyaApi;
|
||||||
import hae.Config;
|
import hae.Config;
|
||||||
import hae.utils.config.ConfigLoader;
|
import hae.utils.ConfigLoader;
|
||||||
import hae.utils.rule.RuleProcessor;
|
import hae.utils.rule.RuleProcessor;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
@@ -97,7 +97,7 @@ public class Rule extends JPanel {
|
|||||||
Display ruleDisplay = new Display();
|
Display ruleDisplay = new Display();
|
||||||
ruleDisplay.formatTextField.setText("{0}");
|
ruleDisplay.formatTextField.setText("{0}");
|
||||||
|
|
||||||
int showState = JOptionPane.showConfirmDialog(null, ruleDisplay, "Add Rule", JOptionPane.OK_OPTION);
|
int showState = JOptionPane.showConfirmDialog(this, ruleDisplay, "Add Rule", JOptionPane.OK_OPTION);
|
||||||
if (showState == YES_OPTION) {
|
if (showState == YES_OPTION) {
|
||||||
Vector<Object> ruleData = new Vector<>();
|
Vector<Object> ruleData = new Vector<>();
|
||||||
ruleData.add(false);
|
ruleData.add(false);
|
||||||
@@ -132,7 +132,7 @@ public class Rule extends JPanel {
|
|||||||
|
|
||||||
ruleDisplay.formatTextField.setEnabled(ruleDisplay.engineComboBox.getSelectedItem().toString().equals("nfa"));
|
ruleDisplay.formatTextField.setEnabled(ruleDisplay.engineComboBox.getSelectedItem().toString().equals("nfa"));
|
||||||
|
|
||||||
int showState = JOptionPane.showConfirmDialog(null, ruleDisplay, "Edit Rule", JOptionPane.OK_OPTION);
|
int showState = JOptionPane.showConfirmDialog(this, ruleDisplay, "Edit Rule", JOptionPane.OK_OPTION);
|
||||||
if (showState == 0) {
|
if (showState == 0) {
|
||||||
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
|
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
|
||||||
model.setValueAt(ruleDisplay.ruleNameTextField.getText(), select, 1);
|
model.setValueAt(ruleDisplay.ruleNameTextField.getText(), select, 1);
|
||||||
@@ -151,7 +151,7 @@ public class Rule extends JPanel {
|
|||||||
|
|
||||||
private void ruleRemoveActionPerformed(ActionEvent e, JTable ruleTable, JTabbedPane tabbedPane) {
|
private void ruleRemoveActionPerformed(ActionEvent e, JTable ruleTable, JTabbedPane tabbedPane) {
|
||||||
if (ruleTable.getSelectedRowCount() >= 1) {
|
if (ruleTable.getSelectedRowCount() >= 1) {
|
||||||
if (JOptionPane.showConfirmDialog(null, "Are you sure you want to delete this rule?", "Info", JOptionPane.OK_OPTION) == 0) {
|
if (JOptionPane.showConfirmDialog(this, "Are you sure you want to remove this rule?", "Info", JOptionPane.YES_NO_OPTION) == 0) {
|
||||||
DefaultTableModel model = (DefaultTableModel) ruleTable.getModel();
|
DefaultTableModel model = (DefaultTableModel) ruleTable.getModel();
|
||||||
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
|
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package hae.component.rule;
|
|||||||
|
|
||||||
import burp.api.montoya.MontoyaApi;
|
import burp.api.montoya.MontoyaApi;
|
||||||
import hae.Config;
|
import hae.Config;
|
||||||
import hae.utils.config.ConfigLoader;
|
import hae.utils.ConfigLoader;
|
||||||
import hae.utils.rule.RuleProcessor;
|
import hae.utils.rule.RuleProcessor;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
@@ -109,7 +109,7 @@ public class Rules extends JTabbedPane {
|
|||||||
|
|
||||||
private void deleteRuleGroupActionPerformed(ActionEvent e) {
|
private void deleteRuleGroupActionPerformed(ActionEvent e) {
|
||||||
if (getTabCount() > 2) {
|
if (getTabCount() > 2) {
|
||||||
int retCode = JOptionPane.showConfirmDialog(null, "Do you want to delete this rule group?", "Info",
|
int retCode = JOptionPane.showConfirmDialog(this, "Do you want to delete this rule group?", "Info",
|
||||||
JOptionPane.YES_NO_OPTION);
|
JOptionPane.YES_NO_OPTION);
|
||||||
if (retCode == JOptionPane.YES_OPTION) {
|
if (retCode == JOptionPane.YES_OPTION) {
|
||||||
String title = getTitleAt(getSelectedIndex());
|
String title = getTitleAt(getSelectedIndex());
|
||||||
|
|||||||
@@ -9,9 +9,11 @@ import burp.api.montoya.ui.Selection;
|
|||||||
import burp.api.montoya.ui.editor.extension.EditorCreationContext;
|
import burp.api.montoya.ui.editor.extension.EditorCreationContext;
|
||||||
import burp.api.montoya.ui.editor.extension.ExtensionProvidedHttpRequestEditor;
|
import burp.api.montoya.ui.editor.extension.ExtensionProvidedHttpRequestEditor;
|
||||||
import burp.api.montoya.ui.editor.extension.HttpRequestEditorProvider;
|
import burp.api.montoya.ui.editor.extension.HttpRequestEditorProvider;
|
||||||
import hae.component.board.Datatable;
|
import hae.Config;
|
||||||
|
import hae.component.board.table.Datatable;
|
||||||
import hae.instances.http.utils.MessageProcessor;
|
import hae.instances.http.utils.MessageProcessor;
|
||||||
import hae.utils.config.ConfigLoader;
|
import hae.utils.ConfigLoader;
|
||||||
|
import hae.utils.http.HttpUtils;
|
||||||
import hae.utils.string.StringProcessor;
|
import hae.utils.string.StringProcessor;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
@@ -37,6 +39,7 @@ public class RequestEditor implements HttpRequestEditorProvider {
|
|||||||
private static class Editor implements ExtensionProvidedHttpRequestEditor {
|
private static class Editor implements ExtensionProvidedHttpRequestEditor {
|
||||||
private final MontoyaApi api;
|
private final MontoyaApi api;
|
||||||
private final ConfigLoader configLoader;
|
private final ConfigLoader configLoader;
|
||||||
|
private final HttpUtils httpUtils;
|
||||||
private final EditorCreationContext creationContext;
|
private final EditorCreationContext creationContext;
|
||||||
private final MessageProcessor messageProcessor;
|
private final MessageProcessor messageProcessor;
|
||||||
private HttpRequestResponse requestResponse;
|
private HttpRequestResponse requestResponse;
|
||||||
@@ -47,6 +50,7 @@ public class RequestEditor implements HttpRequestEditorProvider {
|
|||||||
public Editor(MontoyaApi api, ConfigLoader configLoader, EditorCreationContext creationContext) {
|
public Editor(MontoyaApi api, ConfigLoader configLoader, EditorCreationContext creationContext) {
|
||||||
this.api = api;
|
this.api = api;
|
||||||
this.configLoader = configLoader;
|
this.configLoader = configLoader;
|
||||||
|
this.httpUtils = new HttpUtils(api, configLoader);
|
||||||
this.creationContext = creationContext;
|
this.creationContext = creationContext;
|
||||||
this.messageProcessor = new MessageProcessor(api);
|
this.messageProcessor = new MessageProcessor(api);
|
||||||
}
|
}
|
||||||
@@ -59,7 +63,7 @@ public class RequestEditor implements HttpRequestEditorProvider {
|
|||||||
@Override
|
@Override
|
||||||
public void setRequestResponse(HttpRequestResponse requestResponse) {
|
public void setRequestResponse(HttpRequestResponse requestResponse) {
|
||||||
this.requestResponse = requestResponse;
|
this.requestResponse = requestResponse;
|
||||||
generateTabbedPaneFromResultMap(api, jTabbedPane, this.dataList);
|
generateTabbedPaneFromResultMap(api, configLoader, jTabbedPane, this.dataList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -69,13 +73,10 @@ public class RequestEditor implements HttpRequestEditorProvider {
|
|||||||
try {
|
try {
|
||||||
String host = StringProcessor.getHostByUrl(request.url());
|
String host = StringProcessor.getHostByUrl(request.url());
|
||||||
if (!host.isEmpty()) {
|
if (!host.isEmpty()) {
|
||||||
String[] hostList = configLoader.getBlockHost().split("\\|");
|
String toolType = creationContext.toolSource().toolType().toolName();
|
||||||
boolean isBlockHost = isBlockHost(hostList, host);
|
boolean matches = httpUtils.verifyHttpRequestResponse(requestResponse, toolType);
|
||||||
|
|
||||||
List<String> suffixList = Arrays.asList(configLoader.getExcludeSuffix().split("\\|"));
|
if (!matches) {
|
||||||
boolean matches = suffixList.contains(request.fileExtension().toLowerCase()) || isBlockHost;
|
|
||||||
|
|
||||||
if (!matches && !request.bodyToString().equals("Loading...")) {
|
|
||||||
this.dataList = messageProcessor.processRequest("", request, false);
|
this.dataList = messageProcessor.processRequest("", request, false);
|
||||||
return isListHasData(this.dataList);
|
return isListHasData(this.dataList);
|
||||||
}
|
}
|
||||||
@@ -118,17 +119,6 @@ public class RequestEditor implements HttpRequestEditorProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isBlockHost(String[] hostList, String host) {
|
|
||||||
boolean isBlockHost = false;
|
|
||||||
for (String hostName : hostList) {
|
|
||||||
String cleanedHost = StringProcessor.replaceFirstOccurrence(hostName, "*.", "");
|
|
||||||
if (StringProcessor.matchFromEnd(host, cleanedHost)) {
|
|
||||||
isBlockHost = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return isBlockHost;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isListHasData(List<Map<String, String>> dataList) {
|
public static boolean isListHasData(List<Map<String, String>> dataList) {
|
||||||
if (dataList != null && !dataList.isEmpty()) {
|
if (dataList != null && !dataList.isEmpty()) {
|
||||||
Map<String, String> dataMap = dataList.get(0);
|
Map<String, String> dataMap = dataList.get(0);
|
||||||
@@ -137,14 +127,14 @@ public class RequestEditor implements HttpRequestEditorProvider {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void generateTabbedPaneFromResultMap(MontoyaApi api, JTabbedPane tabbedPane, List<Map<String, String>> result) {
|
public static void generateTabbedPaneFromResultMap(MontoyaApi api, ConfigLoader configLoader, JTabbedPane tabbedPane, List<Map<String, String>> result) {
|
||||||
tabbedPane.removeAll();
|
tabbedPane.removeAll();
|
||||||
if (result != null && !result.isEmpty()) {
|
if (result != null && !result.isEmpty()) {
|
||||||
Map<String, String> dataMap = result.get(0);
|
Map<String, String> dataMap = result.get(0);
|
||||||
if (dataMap != null && !dataMap.isEmpty()) {
|
if (dataMap != null && !dataMap.isEmpty()) {
|
||||||
dataMap.keySet().forEach(i -> {
|
dataMap.keySet().forEach(i -> {
|
||||||
String[] extractData = dataMap.get(i).split("\n");
|
String[] extractData = dataMap.get(i).split(Config.boundary);
|
||||||
Datatable dataPanel = new Datatable(api, i, Arrays.asList(extractData));
|
Datatable dataPanel = new Datatable(api, configLoader, i, Arrays.asList(extractData));
|
||||||
tabbedPane.addTab(i, dataPanel);
|
tabbedPane.addTab(i, dataPanel);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,14 +10,14 @@ import burp.api.montoya.ui.Selection;
|
|||||||
import burp.api.montoya.ui.editor.extension.EditorCreationContext;
|
import burp.api.montoya.ui.editor.extension.EditorCreationContext;
|
||||||
import burp.api.montoya.ui.editor.extension.ExtensionProvidedHttpResponseEditor;
|
import burp.api.montoya.ui.editor.extension.ExtensionProvidedHttpResponseEditor;
|
||||||
import burp.api.montoya.ui.editor.extension.HttpResponseEditorProvider;
|
import burp.api.montoya.ui.editor.extension.HttpResponseEditorProvider;
|
||||||
import hae.component.board.Datatable;
|
import hae.component.board.table.Datatable;
|
||||||
import hae.instances.http.utils.MessageProcessor;
|
import hae.instances.http.utils.MessageProcessor;
|
||||||
import hae.utils.config.ConfigLoader;
|
import hae.utils.ConfigLoader;
|
||||||
|
import hae.utils.http.HttpUtils;
|
||||||
import hae.utils.string.StringProcessor;
|
import hae.utils.string.StringProcessor;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -38,6 +38,7 @@ public class ResponseEditor implements HttpResponseEditorProvider {
|
|||||||
private static class Editor implements ExtensionProvidedHttpResponseEditor {
|
private static class Editor implements ExtensionProvidedHttpResponseEditor {
|
||||||
private final MontoyaApi api;
|
private final MontoyaApi api;
|
||||||
private final ConfigLoader configLoader;
|
private final ConfigLoader configLoader;
|
||||||
|
private final HttpUtils httpUtils;
|
||||||
private final EditorCreationContext creationContext;
|
private final EditorCreationContext creationContext;
|
||||||
private final MessageProcessor messageProcessor;
|
private final MessageProcessor messageProcessor;
|
||||||
private HttpRequestResponse requestResponse;
|
private HttpRequestResponse requestResponse;
|
||||||
@@ -48,6 +49,7 @@ public class ResponseEditor implements HttpResponseEditorProvider {
|
|||||||
public Editor(MontoyaApi api, ConfigLoader configLoader, EditorCreationContext creationContext) {
|
public Editor(MontoyaApi api, ConfigLoader configLoader, EditorCreationContext creationContext) {
|
||||||
this.api = api;
|
this.api = api;
|
||||||
this.configLoader = configLoader;
|
this.configLoader = configLoader;
|
||||||
|
this.httpUtils = new HttpUtils(api, configLoader);
|
||||||
this.creationContext = creationContext;
|
this.creationContext = creationContext;
|
||||||
this.messageProcessor = new MessageProcessor(api);
|
this.messageProcessor = new MessageProcessor(api);
|
||||||
}
|
}
|
||||||
@@ -60,7 +62,7 @@ public class ResponseEditor implements HttpResponseEditorProvider {
|
|||||||
@Override
|
@Override
|
||||||
public void setRequestResponse(HttpRequestResponse requestResponse) {
|
public void setRequestResponse(HttpRequestResponse requestResponse) {
|
||||||
this.requestResponse = requestResponse;
|
this.requestResponse = requestResponse;
|
||||||
RequestEditor.generateTabbedPaneFromResultMap(api, jTabbedPane, this.dataList);
|
RequestEditor.generateTabbedPaneFromResultMap(api, configLoader, jTabbedPane, this.dataList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -75,17 +77,14 @@ public class ResponseEditor implements HttpResponseEditorProvider {
|
|||||||
try {
|
try {
|
||||||
String host = StringProcessor.getHostByUrl(request.url());
|
String host = StringProcessor.getHostByUrl(request.url());
|
||||||
if (!host.isEmpty()) {
|
if (!host.isEmpty()) {
|
||||||
String[] hostList = configLoader.getBlockHost().split("\\|");
|
String toolType = creationContext.toolSource().toolType().toolName();
|
||||||
boolean isBlockHost = RequestEditor.isBlockHost(hostList, host);
|
matches = httpUtils.verifyHttpRequestResponse(requestResponse, toolType);
|
||||||
|
|
||||||
List<String> suffixList = Arrays.asList(configLoader.getExcludeSuffix().split("\\|"));
|
|
||||||
matches = suffixList.contains(request.fileExtension().toLowerCase()) || isBlockHost;
|
|
||||||
}
|
}
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!matches && !response.bodyToString().equals("Loading...")) {
|
if (!matches) {
|
||||||
this.dataList = messageProcessor.processResponse("", response, false);
|
this.dataList = messageProcessor.processResponse("", response, false);
|
||||||
return RequestEditor.isListHasData(this.dataList);
|
return RequestEditor.isListHasData(this.dataList);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ import burp.api.montoya.ui.contextmenu.WebSocketMessage;
|
|||||||
import burp.api.montoya.ui.editor.extension.EditorCreationContext;
|
import burp.api.montoya.ui.editor.extension.EditorCreationContext;
|
||||||
import burp.api.montoya.ui.editor.extension.ExtensionProvidedWebSocketMessageEditor;
|
import burp.api.montoya.ui.editor.extension.ExtensionProvidedWebSocketMessageEditor;
|
||||||
import burp.api.montoya.ui.editor.extension.WebSocketMessageEditorProvider;
|
import burp.api.montoya.ui.editor.extension.WebSocketMessageEditorProvider;
|
||||||
import hae.component.board.Datatable;
|
import hae.component.board.table.Datatable;
|
||||||
import hae.instances.http.utils.MessageProcessor;
|
import hae.instances.http.utils.MessageProcessor;
|
||||||
|
import hae.utils.ConfigLoader;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
@@ -18,18 +19,21 @@ import java.util.Map;
|
|||||||
|
|
||||||
public class WebSocketEditor implements WebSocketMessageEditorProvider {
|
public class WebSocketEditor implements WebSocketMessageEditorProvider {
|
||||||
private final MontoyaApi api;
|
private final MontoyaApi api;
|
||||||
|
private final ConfigLoader configLoader;
|
||||||
|
|
||||||
public WebSocketEditor(MontoyaApi api) {
|
public WebSocketEditor(MontoyaApi api, ConfigLoader configLoader) {
|
||||||
this.api = api;
|
this.api = api;
|
||||||
|
this.configLoader = configLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ExtensionProvidedWebSocketMessageEditor provideMessageEditor(EditorCreationContext editorCreationContext) {
|
public ExtensionProvidedWebSocketMessageEditor provideMessageEditor(EditorCreationContext editorCreationContext) {
|
||||||
return new Editor(api, editorCreationContext);
|
return new Editor(api, configLoader, editorCreationContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Editor implements ExtensionProvidedWebSocketMessageEditor {
|
private static class Editor implements ExtensionProvidedWebSocketMessageEditor {
|
||||||
private final MontoyaApi api;
|
private final MontoyaApi api;
|
||||||
|
private final ConfigLoader configLoader;
|
||||||
private final EditorCreationContext creationContext;
|
private final EditorCreationContext creationContext;
|
||||||
private final MessageProcessor messageProcessor;
|
private final MessageProcessor messageProcessor;
|
||||||
private ByteArray message;
|
private ByteArray message;
|
||||||
@@ -37,8 +41,9 @@ public class WebSocketEditor implements WebSocketMessageEditorProvider {
|
|||||||
|
|
||||||
private final JTabbedPane jTabbedPane = new JTabbedPane();
|
private final JTabbedPane jTabbedPane = new JTabbedPane();
|
||||||
|
|
||||||
public Editor(MontoyaApi api, EditorCreationContext creationContext) {
|
public Editor(MontoyaApi api, ConfigLoader configLoader, EditorCreationContext creationContext) {
|
||||||
this.api = api;
|
this.api = api;
|
||||||
|
this.configLoader = configLoader;
|
||||||
this.creationContext = creationContext;
|
this.creationContext = creationContext;
|
||||||
this.messageProcessor = new MessageProcessor(api);
|
this.messageProcessor = new MessageProcessor(api);
|
||||||
}
|
}
|
||||||
@@ -51,7 +56,7 @@ public class WebSocketEditor implements WebSocketMessageEditorProvider {
|
|||||||
@Override
|
@Override
|
||||||
public void setMessage(WebSocketMessage webSocketMessage) {
|
public void setMessage(WebSocketMessage webSocketMessage) {
|
||||||
this.message = webSocketMessage.payload();
|
this.message = webSocketMessage.payload();
|
||||||
RequestEditor.generateTabbedPaneFromResultMap(api, jTabbedPane, this.dataList);
|
RequestEditor.generateTabbedPaneFromResultMap(api, configLoader, jTabbedPane, this.dataList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -7,19 +7,20 @@ import burp.api.montoya.http.handler.*;
|
|||||||
import burp.api.montoya.http.message.HttpRequestResponse;
|
import burp.api.montoya.http.message.HttpRequestResponse;
|
||||||
import burp.api.montoya.http.message.requests.HttpRequest;
|
import burp.api.montoya.http.message.requests.HttpRequest;
|
||||||
import hae.component.board.message.MessageTableModel;
|
import hae.component.board.message.MessageTableModel;
|
||||||
import hae.instances.editor.RequestEditor;
|
|
||||||
import hae.instances.http.utils.MessageProcessor;
|
import hae.instances.http.utils.MessageProcessor;
|
||||||
import hae.utils.config.ConfigLoader;
|
import hae.utils.ConfigLoader;
|
||||||
|
import hae.utils.http.HttpUtils;
|
||||||
import hae.utils.string.StringProcessor;
|
import hae.utils.string.StringProcessor;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class HttpMessageHandler implements HttpHandler {
|
public class HttpMessageHandler implements HttpHandler {
|
||||||
private final MontoyaApi api;
|
private final MontoyaApi api;
|
||||||
private final ConfigLoader configLoader;
|
private final ConfigLoader configLoader;
|
||||||
|
private final HttpUtils httpUtils;
|
||||||
private final MessageTableModel messageTableModel;
|
private final MessageTableModel messageTableModel;
|
||||||
private final MessageProcessor messageProcessor;
|
private final MessageProcessor messageProcessor;
|
||||||
|
|
||||||
@@ -28,12 +29,11 @@ public class HttpMessageHandler implements HttpHandler {
|
|||||||
private final ThreadLocal<String> host = ThreadLocal.withInitial(() -> "");
|
private final ThreadLocal<String> host = ThreadLocal.withInitial(() -> "");
|
||||||
private final ThreadLocal<List<String>> colorList = ThreadLocal.withInitial(ArrayList::new);
|
private final ThreadLocal<List<String>> colorList = ThreadLocal.withInitial(ArrayList::new);
|
||||||
private final ThreadLocal<List<String>> commentList = ThreadLocal.withInitial(ArrayList::new);
|
private final ThreadLocal<List<String>> commentList = ThreadLocal.withInitial(ArrayList::new);
|
||||||
private final ThreadLocal<Boolean> matches = ThreadLocal.withInitial(() -> false);
|
|
||||||
private final ThreadLocal<HttpRequest> httpRequest = new ThreadLocal<>();
|
|
||||||
|
|
||||||
public HttpMessageHandler(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) {
|
public HttpMessageHandler(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) {
|
||||||
this.api = api;
|
this.api = api;
|
||||||
this.configLoader = configLoader;
|
this.configLoader = configLoader;
|
||||||
|
this.httpUtils = new HttpUtils(api, configLoader);
|
||||||
this.messageTableModel = messageTableModel;
|
this.messageTableModel = messageTableModel;
|
||||||
this.messageProcessor = new MessageProcessor(api);
|
this.messageProcessor = new MessageProcessor(api);
|
||||||
}
|
}
|
||||||
@@ -45,19 +45,10 @@ public class HttpMessageHandler implements HttpHandler {
|
|||||||
|
|
||||||
Annotations annotations = httpRequestToBeSent.annotations();
|
Annotations annotations = httpRequestToBeSent.annotations();
|
||||||
|
|
||||||
httpRequest.set(httpRequestToBeSent);
|
try {
|
||||||
|
host.set(StringProcessor.getHostByUrl(httpRequestToBeSent.url()));
|
||||||
host.set(StringProcessor.getHostByUrl(httpRequestToBeSent.url()));
|
} catch (Exception e) {
|
||||||
|
api.logging().logToError("handleHttpRequestToBeSent: " + e.getMessage());
|
||||||
String[] hostList = configLoader.getBlockHost().split("\\|");
|
|
||||||
boolean isBlockHost = RequestEditor.isBlockHost(hostList, host.get());
|
|
||||||
|
|
||||||
List<String> suffixList = Arrays.asList(configLoader.getExcludeSuffix().split("\\|"));
|
|
||||||
matches.set(suffixList.contains(httpRequestToBeSent.fileExtension().toLowerCase()) || isBlockHost);
|
|
||||||
|
|
||||||
if (!matches.get()) {
|
|
||||||
List<Map<String, String>> result = messageProcessor.processRequest(host.get(), httpRequestToBeSent, true);
|
|
||||||
setColorAndCommentList(result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return RequestToBeSentAction.continueWith(httpRequestToBeSent, annotations);
|
return RequestToBeSentAction.continueWith(httpRequestToBeSent, annotations);
|
||||||
@@ -66,21 +57,42 @@ public class HttpMessageHandler implements HttpHandler {
|
|||||||
@Override
|
@Override
|
||||||
public ResponseReceivedAction handleHttpResponseReceived(HttpResponseReceived httpResponseReceived) {
|
public ResponseReceivedAction handleHttpResponseReceived(HttpResponseReceived httpResponseReceived) {
|
||||||
Annotations annotations = httpResponseReceived.annotations();
|
Annotations annotations = httpResponseReceived.annotations();
|
||||||
|
HttpRequest request = httpResponseReceived.initiatingRequest();
|
||||||
|
HttpRequestResponse requestResponse = HttpRequestResponse.httpRequestResponse(request, httpResponseReceived);
|
||||||
|
String toolType = httpResponseReceived.toolSource().toolType().toolName();
|
||||||
|
|
||||||
if (!matches.get()) {
|
boolean matches = httpUtils.verifyHttpRequestResponse(requestResponse, toolType);
|
||||||
List<Map<String, String>> result = messageProcessor.processResponse(host.get(), httpResponseReceived, true);
|
|
||||||
setColorAndCommentList(result);
|
|
||||||
// 设置高亮颜色和注释
|
|
||||||
if (!colorList.get().isEmpty() && !commentList.get().isEmpty()) {
|
|
||||||
String color = messageProcessor.retrieveFinalColor(messageProcessor.retrieveColorIndices(colorList.get()));
|
|
||||||
annotations.setHighlightColor(HighlightColor.highlightColor(color));
|
|
||||||
String comment = StringProcessor.mergeComment(String.join(", ", commentList.get()));
|
|
||||||
annotations.setNotes(comment);
|
|
||||||
|
|
||||||
HttpRequestResponse httpRequestResponse = HttpRequestResponse.httpRequestResponse(httpRequest.get(), httpResponseReceived);
|
if (!matches) {
|
||||||
|
try {
|
||||||
|
setColorAndCommentList(messageProcessor.processRequest(host.get(), request, true));
|
||||||
|
setColorAndCommentList(messageProcessor.processResponse(host.get(), httpResponseReceived, true));
|
||||||
|
|
||||||
// 添加到Databoard
|
// 设置高亮颜色和注释
|
||||||
messageTableModel.add(httpRequestResponse, comment, color);
|
if (!colorList.get().isEmpty() && !commentList.get().isEmpty()) {
|
||||||
|
String color = messageProcessor.retrieveFinalColor(messageProcessor.retrieveColorIndices(colorList.get()));
|
||||||
|
annotations.setHighlightColor(HighlightColor.highlightColor(color));
|
||||||
|
String comment = StringProcessor.mergeComment(String.join(", ", commentList.get()));
|
||||||
|
annotations.setNotes(comment);
|
||||||
|
|
||||||
|
HttpRequestResponse httpRequestResponse = HttpRequestResponse.httpRequestResponse(request, httpResponseReceived);
|
||||||
|
|
||||||
|
String method = request.method();
|
||||||
|
String url = request.url();
|
||||||
|
String status = String.valueOf(httpResponseReceived.statusCode());
|
||||||
|
String length = String.valueOf(httpResponseReceived.toByteArray().length());
|
||||||
|
|
||||||
|
// 后台提交,防止线程阻塞
|
||||||
|
new SwingWorker<Void, Void>() {
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground() {
|
||||||
|
messageTableModel.add(httpRequestResponse, url, method, status, length, comment, color, "", "");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
api.logging().logToError("handleHttpResponseReceived: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +100,7 @@ public class HttpMessageHandler implements HttpHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setColorAndCommentList(List<Map<String, String>> result) {
|
private void setColorAndCommentList(List<Map<String, String>> result) {
|
||||||
if (result != null && !result.isEmpty() && result.size() > 0) {
|
if (result != null && !result.isEmpty()) {
|
||||||
colorList.get().add(result.get(0).get("color"));
|
colorList.get().add(result.get(0).get("color"));
|
||||||
commentList.get().add(result.get(1).get("comment"));
|
commentList.get().add(result.get(1).get("comment"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ public class MessageProcessor {
|
|||||||
List<Map<String, String>> highlightList = new ArrayList<>();
|
List<Map<String, String>> highlightList = new ArrayList<>();
|
||||||
List<Map<String, String>> extractList = new ArrayList<>();
|
List<Map<String, String>> extractList = new ArrayList<>();
|
||||||
|
|
||||||
if (obj != null && !obj.isEmpty() && obj.size() > 0) {
|
if (obj != null && !obj.isEmpty()) {
|
||||||
if (actionFlag) {
|
if (actionFlag) {
|
||||||
List<List<String>> resultList = extractColorsAndComments(obj);
|
List<List<String>> resultList = extractColorsAndComments(obj);
|
||||||
List<String> colorList = resultList.get(0);
|
List<String> colorList = resultList.get(0);
|
||||||
|
|||||||
@@ -90,43 +90,15 @@ public class RegularMatcher {
|
|||||||
result.clear();
|
result.clear();
|
||||||
result.addAll(tmpList);
|
result.addAll(tmpList);
|
||||||
|
|
||||||
String nameAndSize = String.format("%s (%s)", name, result.size());
|
|
||||||
if (!result.isEmpty()) {
|
if (!result.isEmpty()) {
|
||||||
tmpMap.put("color", color);
|
tmpMap.put("color", color);
|
||||||
String dataStr = String.join("\n", result);
|
String dataStr = String.join(Config.boundary, result);
|
||||||
tmpMap.put("data", dataStr);
|
tmpMap.put("data", dataStr);
|
||||||
|
|
||||||
|
String nameAndSize = String.format("%s (%s)", name, result.size());
|
||||||
finalMap.put(nameAndSize, tmpMap);
|
finalMap.put(nameAndSize, tmpMap);
|
||||||
// 添加到全局变量中,便于Databoard检索
|
|
||||||
if (!Objects.equals(host, "") && host != null) {
|
|
||||||
List<String> dataList = Arrays.asList(dataStr.split("\n"));
|
|
||||||
|
|
||||||
Config.globalDataMap.compute(host, (existingHost, existingMap) -> {
|
putDataToGlobalMap(host, name, result);
|
||||||
Map<String, List<String>> gRuleMap = Optional.ofNullable(existingMap).orElse(new ConcurrentHashMap<>());
|
|
||||||
|
|
||||||
gRuleMap.merge(name, new ArrayList<>(dataList), (existingList, newList) -> {
|
|
||||||
Set<String> combinedSet = new LinkedHashSet<>(existingList);
|
|
||||||
combinedSet.addAll(newList);
|
|
||||||
return new ArrayList<>(combinedSet);
|
|
||||||
});
|
|
||||||
|
|
||||||
return gRuleMap;
|
|
||||||
});
|
|
||||||
|
|
||||||
String[] splitHost = host.split("\\.");
|
|
||||||
String onlyHost = host.split(":")[0];
|
|
||||||
|
|
||||||
String anyHost = (splitHost.length > 2 && !onlyHost.matches("\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b")) ? StringProcessor.replaceFirstOccurrence(onlyHost, splitHost[0], "*") : "";
|
|
||||||
|
|
||||||
if (!Config.globalDataMap.containsKey(anyHost) && anyHost.length() > 0) {
|
|
||||||
// 添加通配符Host,实际数据从查询哪里将所有数据提取
|
|
||||||
Config.globalDataMap.put(anyHost, new HashMap<>());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Config.globalDataMap.containsKey("*")) {
|
|
||||||
// 添加通配符全匹配,同上
|
|
||||||
Config.globalDataMap.put("*", new HashMap<>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,6 +108,38 @@ public class RegularMatcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void putDataToGlobalMap(String host, String name, List<String> dataList) {
|
||||||
|
// 添加到全局变量中,便于Databoard检索
|
||||||
|
if (!Objects.equals(host, "") && host != null) {
|
||||||
|
Config.globalDataMap.compute(host, (existingHost, existingMap) -> {
|
||||||
|
Map<String, List<String>> gRuleMap = Optional.ofNullable(existingMap).orElse(new ConcurrentHashMap<>());
|
||||||
|
|
||||||
|
gRuleMap.merge(name, new ArrayList<>(dataList), (existingList, newList) -> {
|
||||||
|
Set<String> combinedSet = new LinkedHashSet<>(existingList);
|
||||||
|
combinedSet.addAll(newList);
|
||||||
|
return new ArrayList<>(combinedSet);
|
||||||
|
});
|
||||||
|
|
||||||
|
return gRuleMap;
|
||||||
|
});
|
||||||
|
|
||||||
|
String[] splitHost = host.split("\\.");
|
||||||
|
String onlyHost = host.split(":")[0];
|
||||||
|
|
||||||
|
String anyHost = (splitHost.length > 2 && !StringProcessor.matchHostIsIp(onlyHost)) ? StringProcessor.replaceFirstOccurrence(onlyHost, splitHost[0], "*") : "";
|
||||||
|
|
||||||
|
if (!Config.globalDataMap.containsKey(anyHost) && anyHost.length() > 0) {
|
||||||
|
// 添加通配符Host,实际数据从查询哪里将所有数据提取
|
||||||
|
Config.globalDataMap.put(anyHost, new HashMap<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Config.globalDataMap.containsKey("*")) {
|
||||||
|
// 添加通配符全匹配,同上
|
||||||
|
Config.globalDataMap.put("*", new HashMap<>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private List<String> matchByRegex(String f_regex, String s_regex, String content, String format, String engine, boolean sensitive) {
|
private List<String> matchByRegex(String f_regex, String s_regex, String content, String format, String engine, boolean sensitive) {
|
||||||
List<String> retList = new ArrayList<>();
|
List<String> retList = new ArrayList<>();
|
||||||
if ("nfa".equals(engine)) {
|
if ("nfa".equals(engine)) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package hae.utils.config;
|
package hae.utils;
|
||||||
|
|
||||||
import burp.api.montoya.MontoyaApi;
|
import burp.api.montoya.MontoyaApi;
|
||||||
import burp.api.montoya.http.RequestOptions;
|
import burp.api.montoya.http.RequestOptions;
|
||||||
@@ -77,8 +77,10 @@ public class ConfigLoader {
|
|||||||
|
|
||||||
public void initConfig() {
|
public void initConfig() {
|
||||||
Map<String, Object> r = new LinkedHashMap<>();
|
Map<String, Object> r = new LinkedHashMap<>();
|
||||||
r.put("excludeSuffix", getExcludeSuffix());
|
r.put("ExcludeSuffix", getExcludeSuffix());
|
||||||
r.put("blockHost", getBlockHost());
|
r.put("BlockHost", getBlockHost());
|
||||||
|
r.put("ExcludeStatus", getExcludeStatus());
|
||||||
|
r.put("HaEScope", getScope());
|
||||||
try {
|
try {
|
||||||
Writer ws = new OutputStreamWriter(Files.newOutputStream(Paths.get(configFilePath)), StandardCharsets.UTF_8);
|
Writer ws = new OutputStreamWriter(Files.newOutputStream(Paths.get(configFilePath)), StandardCharsets.UTF_8);
|
||||||
yaml.dump(r, ws);
|
yaml.dump(r, ws);
|
||||||
@@ -134,40 +136,88 @@ public class ConfigLoader {
|
|||||||
return rules;
|
return rules;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getAlibabaAIAPIKey() {
|
||||||
|
return getValueFromConfig("AlibabaAIAPIKey", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMoonshotAIAPIKey() {
|
||||||
|
return getValueFromConfig("MoonshotAIAPIKey", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAIPrompt() {
|
||||||
|
return getValueFromConfig("AIPrompt", Config.prompt);
|
||||||
|
}
|
||||||
|
|
||||||
public String getBlockHost() {
|
public String getBlockHost() {
|
||||||
File yamlSetting = new File(configFilePath);
|
return getValueFromConfig("BlockHost", Config.host);
|
||||||
if (!yamlSetting.exists() || !yamlSetting.isFile()) {
|
|
||||||
return Config.host;
|
|
||||||
}
|
|
||||||
|
|
||||||
try (InputStream inorder = Files.newInputStream(Paths.get(configFilePath))) {
|
|
||||||
Map<String, Object> r = new Yaml().load(inorder);
|
|
||||||
|
|
||||||
if (r.containsKey("blockHost")) {
|
|
||||||
return r.get("blockHost").toString();
|
|
||||||
}
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
|
|
||||||
return Config.host;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getExcludeSuffix() {
|
public String getExcludeSuffix() {
|
||||||
|
return getValueFromConfig("ExcludeSuffix", Config.suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getExcludeStatus() {
|
||||||
|
return getValueFromConfig("ExcludeStatus", Config.status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getScope() {
|
||||||
|
return getValueFromConfig("HaEScope", Config.scopeOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getValueFromConfig(String name, String defaultValue) {
|
||||||
File yamlSetting = new File(configFilePath);
|
File yamlSetting = new File(configFilePath);
|
||||||
if (!yamlSetting.exists() || !yamlSetting.isFile()) {
|
if (!yamlSetting.exists() || !yamlSetting.isFile()) {
|
||||||
return Config.suffix;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
try (InputStream inorder = Files.newInputStream(Paths.get(configFilePath))) {
|
try (InputStream inorder = Files.newInputStream(Paths.get(configFilePath))) {
|
||||||
Map<String, Object> r = new Yaml().load(inorder);
|
Map<String, Object> r = new Yaml().load(inorder);
|
||||||
|
|
||||||
if (r.containsKey("excludeSuffix")) {
|
if (r.containsKey(name)) {
|
||||||
return r.get("excludeSuffix").toString();
|
return r.get(name).toString();
|
||||||
}
|
}
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return Config.suffix;
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAlibabaAIAPIKey(String apiKey) {
|
||||||
|
setValueToConfig("AlibabaAIAPIKey", apiKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMoonshotAIAPIKey(String apiKey) {
|
||||||
|
setValueToConfig("MoonshotAIAPIKey", apiKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAIPrompt(String prompt) {
|
||||||
|
setValueToConfig("AIPrompt", prompt);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExcludeSuffix(String excludeSuffix) {
|
||||||
|
setValueToConfig("ExcludeSuffix", excludeSuffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBlockHost(String blockHost) {
|
||||||
|
setValueToConfig("BlockHost", blockHost);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExcludeStatus(String status) {
|
||||||
|
setValueToConfig("ExcludeStatus", status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScope(String scope) {
|
||||||
|
setValueToConfig("HaEScope", scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setValueToConfig(String name, String value) {
|
||||||
|
Map<String, Object> currentConfig = loadCurrentConfig();
|
||||||
|
currentConfig.put(name, value);
|
||||||
|
|
||||||
|
try (Writer ws = new OutputStreamWriter(Files.newOutputStream(Paths.get(configFilePath)), StandardCharsets.UTF_8)) {
|
||||||
|
yaml.dump(currentConfig, ws);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Object> loadCurrentConfig() {
|
private Map<String, Object> loadCurrentConfig() {
|
||||||
@@ -178,31 +228,11 @@ public class ConfigLoader {
|
|||||||
|
|
||||||
try (InputStream in = Files.newInputStream(path)) {
|
try (InputStream in = Files.newInputStream(path)) {
|
||||||
return yaml.load(in);
|
return yaml.load(in);
|
||||||
} catch (IOException e) {
|
} catch (Exception e) {
|
||||||
return new LinkedHashMap<>(); // 读取失败时也返回空的Map
|
return new LinkedHashMap<>(); // 读取失败时也返回空的Map
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setExcludeSuffix(String excludeSuffix) {
|
|
||||||
Map<String, Object> currentConfig = loadCurrentConfig();
|
|
||||||
currentConfig.put("excludeSuffix", excludeSuffix); // 更新配置
|
|
||||||
|
|
||||||
try (Writer ws = new OutputStreamWriter(Files.newOutputStream(Paths.get(configFilePath)), StandardCharsets.UTF_8)) {
|
|
||||||
yaml.dump(currentConfig, ws);
|
|
||||||
} catch (IOException ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBlockHost(String blockHost) {
|
|
||||||
Map<String, Object> currentConfig = loadCurrentConfig();
|
|
||||||
currentConfig.put("blockHost", blockHost); // 更新配置
|
|
||||||
|
|
||||||
try (Writer ws = new OutputStreamWriter(Files.newOutputStream(Paths.get(configFilePath)), StandardCharsets.UTF_8)) {
|
|
||||||
yaml.dump(currentConfig, ws);
|
|
||||||
} catch (IOException ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void initRulesByRes() {
|
public void initRulesByRes() {
|
||||||
boolean isCopySuccess = copyRulesToFile(this.rulesFilePath);
|
boolean isCopySuccess = copyRulesToFile(this.rulesFilePath);
|
||||||
if (!isCopySuccess) {
|
if (!isCopySuccess) {
|
||||||
@@ -225,7 +255,7 @@ public class ConfigLoader {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} catch (IOException ignored) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package hae.utils.ui;
|
package hae.utils;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
79
src/main/java/hae/utils/http/HttpUtils.java
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
package hae.utils.http;
|
||||||
|
|
||||||
|
import burp.api.montoya.MontoyaApi;
|
||||||
|
import burp.api.montoya.http.message.HttpRequestResponse;
|
||||||
|
import burp.api.montoya.http.message.requests.HttpRequest;
|
||||||
|
import burp.api.montoya.http.message.requests.HttpTransformation;
|
||||||
|
import burp.api.montoya.http.message.responses.HttpResponse;
|
||||||
|
import burp.api.montoya.utilities.RandomUtils;
|
||||||
|
import hae.utils.ConfigLoader;
|
||||||
|
import hae.utils.string.StringProcessor;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class HttpUtils {
|
||||||
|
private final MontoyaApi api;
|
||||||
|
private final ConfigLoader configLoader;
|
||||||
|
|
||||||
|
public HttpUtils(MontoyaApi api, ConfigLoader configLoader) {
|
||||||
|
this.api = api;
|
||||||
|
this.configLoader = configLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpRequest generateRequestByMultipartUploadMethod(String url, String name, String filename, String content) {
|
||||||
|
HttpRequest baseRequest = HttpRequest.httpRequestFromUrl(url).withTransformationApplied(HttpTransformation.TOGGLE_METHOD);
|
||||||
|
|
||||||
|
String boundary = api.utilities().randomUtils().randomString(32, RandomUtils.CharacterSet.ASCII_LETTERS);
|
||||||
|
|
||||||
|
String newBody = String.format("--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\n\r\n%s\r\n", boundary, name, filename, content) +
|
||||||
|
String.format("--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s\r\n", boundary, "purpose", "file-extract") +
|
||||||
|
"--" + boundary + "--\r\n";
|
||||||
|
|
||||||
|
baseRequest = baseRequest.withUpdatedHeader("Content-Type", "multipart/form-data; boundary=" + boundary).withBody(newBody);
|
||||||
|
|
||||||
|
return baseRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public HttpRequest generateRequestByDeleteMethod(String url) {
|
||||||
|
return HttpRequest.httpRequestFromUrl(url).withMethod("DELETE");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean verifyHttpRequestResponse(HttpRequestResponse requestResponse, String toolType) {
|
||||||
|
HttpRequest request = requestResponse.request();
|
||||||
|
HttpResponse response = requestResponse.response();
|
||||||
|
boolean retStatus = false;
|
||||||
|
try {
|
||||||
|
String host = StringProcessor.getHostByUrl(request.url());
|
||||||
|
String[] hostList = configLoader.getBlockHost().split("\\|");
|
||||||
|
boolean isBlockHost = isBlockHost(hostList, host);
|
||||||
|
|
||||||
|
List<String> suffixList = Arrays.asList(configLoader.getExcludeSuffix().split("\\|"));
|
||||||
|
boolean isExcludeSuffix = suffixList.contains(request.fileExtension().toLowerCase());
|
||||||
|
|
||||||
|
boolean isToolScope = !configLoader.getScope().contains(toolType);
|
||||||
|
|
||||||
|
List<String> statusList = Arrays.asList(configLoader.getExcludeStatus().split("\\|"));
|
||||||
|
boolean isExcludeStatus = statusList.contains(String.valueOf(response.statusCode()));
|
||||||
|
|
||||||
|
retStatus = isExcludeSuffix || isBlockHost || isToolScope || isExcludeStatus;
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return retStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isBlockHost(String[] hostList, String host) {
|
||||||
|
boolean isBlockHost = false;
|
||||||
|
for (String hostName : hostList) {
|
||||||
|
String cleanedHost = StringProcessor.replaceFirstOccurrence(hostName, "*.", "");
|
||||||
|
if (hostName.contains("*.") && StringProcessor.matchFromEnd(host, cleanedHost)) {
|
||||||
|
isBlockHost = true;
|
||||||
|
} else if (host.equals(hostName) || hostName.equals("*")) {
|
||||||
|
isBlockHost = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return isBlockHost;
|
||||||
|
}
|
||||||
|
}
|
||||||
47
src/main/java/hae/utils/project/FileProcessor.java
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package hae.utils.project;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.file.DirectoryStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
public class FileProcessor {
|
||||||
|
public static void deleteDirectoryWithContents(Path pathToBeDeleted) {
|
||||||
|
if (pathToBeDeleted != null) {
|
||||||
|
try {
|
||||||
|
Files.walk(pathToBeDeleted)
|
||||||
|
.sorted(Comparator.reverseOrder())
|
||||||
|
.map(Path::toFile)
|
||||||
|
.forEach(File::delete);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] readFileContent(String basePath, String fileName) {
|
||||||
|
Path filePath = Paths.get(basePath, fileName);
|
||||||
|
Path path = Paths.get(basePath);
|
||||||
|
try {
|
||||||
|
byte[] fileContent = Files.readAllBytes(filePath);
|
||||||
|
|
||||||
|
Files.deleteIfExists(filePath);
|
||||||
|
|
||||||
|
boolean isEmpty = isDirectoryEmpty(path);
|
||||||
|
if (isEmpty) {
|
||||||
|
Files.deleteIfExists(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileContent;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isDirectoryEmpty(Path directory) throws Exception {
|
||||||
|
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(directory)) {
|
||||||
|
return !dirStream.iterator().hasNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
187
src/main/java/hae/utils/project/ProjectProcessor.java
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
package hae.utils.project;
|
||||||
|
|
||||||
|
import burp.api.montoya.MontoyaApi;
|
||||||
|
import hae.utils.project.model.HaeFileContent;
|
||||||
|
import org.yaml.snakeyaml.LoaderOptions;
|
||||||
|
import org.yaml.snakeyaml.Yaml;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipFile;
|
||||||
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
|
public class ProjectProcessor {
|
||||||
|
private final MontoyaApi api;
|
||||||
|
|
||||||
|
public ProjectProcessor(MontoyaApi api) {
|
||||||
|
this.api = api;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean createHaeFile(String haeFilePath, String host, Map<String, List<String>> dataMap, Map<String, Map<String, Object>> urlMap, Map<String, Map<String, Object>> httpMap) {
|
||||||
|
ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
|
||||||
|
|
||||||
|
List<Callable<Void>> tasks = new ArrayList<>();
|
||||||
|
|
||||||
|
ByteArrayOutputStream dataYamlStream = new ByteArrayOutputStream();
|
||||||
|
ByteArrayOutputStream urlYamlStream = new ByteArrayOutputStream();
|
||||||
|
Yaml yaml = new Yaml();
|
||||||
|
|
||||||
|
yaml.dump(dataMap, new OutputStreamWriter(dataYamlStream, StandardCharsets.UTF_8));
|
||||||
|
yaml.dump(urlMap, new OutputStreamWriter(urlYamlStream, StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
try (ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(haeFilePath))) {
|
||||||
|
zipOut.putNextEntry(new ZipEntry("info"));
|
||||||
|
zipOut.write(host.getBytes(StandardCharsets.UTF_8));
|
||||||
|
zipOut.closeEntry();
|
||||||
|
|
||||||
|
zipOut.putNextEntry(new ZipEntry("data"));
|
||||||
|
zipOut.write(dataYamlStream.toByteArray());
|
||||||
|
zipOut.closeEntry();
|
||||||
|
|
||||||
|
zipOut.putNextEntry(new ZipEntry("url"));
|
||||||
|
zipOut.write(urlYamlStream.toByteArray());
|
||||||
|
zipOut.closeEntry();
|
||||||
|
|
||||||
|
for (String httpHash : httpMap.keySet()) {
|
||||||
|
Map<String, Object> httpItem = httpMap.get(httpHash);
|
||||||
|
tasks.add(() -> {
|
||||||
|
try {
|
||||||
|
ByteArrayOutputStream httpOutStream = new ByteArrayOutputStream();
|
||||||
|
byte[] request = (byte[]) httpItem.get("request");
|
||||||
|
byte[] response = (byte[]) httpItem.get("response");
|
||||||
|
|
||||||
|
httpOutStream.write(response);
|
||||||
|
httpOutStream.write(request);
|
||||||
|
|
||||||
|
synchronized (zipOut) {
|
||||||
|
zipOut.putNextEntry(new ZipEntry(String.format("http/%s", httpHash)));
|
||||||
|
zipOut.write(httpOutStream.toByteArray());
|
||||||
|
zipOut.closeEntry();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
api.logging().logToError("createHaeFile: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
executor.invokeAll(tasks);
|
||||||
|
} catch (Exception e) {
|
||||||
|
api.logging().logToError("createHaeFile: " + e.getMessage());
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
executor.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HaeFileContent readHaeFile(String haeFilePath) {
|
||||||
|
ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
|
||||||
|
List<Callable<Void>> tasks = new ArrayList<>();
|
||||||
|
|
||||||
|
HaeFileContent haeFileContent = new HaeFileContent(api);
|
||||||
|
LoaderOptions loaderOptions = new LoaderOptions();
|
||||||
|
loaderOptions.setMaxAliasesForCollections(Integer.MAX_VALUE);
|
||||||
|
loaderOptions.setCodePointLimit(Integer.MAX_VALUE);
|
||||||
|
Yaml yaml = new Yaml(loaderOptions);
|
||||||
|
Path tempDirectory = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (hasValidStructure(haeFilePath)) {
|
||||||
|
tempDirectory = Files.createTempDirectory("hae");
|
||||||
|
haeFileContent.setHttpPath(tempDirectory.toString());
|
||||||
|
|
||||||
|
try (ZipFile zipFile = new ZipFile(haeFilePath)) {
|
||||||
|
Enumeration<? extends ZipEntry> entries = zipFile.entries();
|
||||||
|
while (entries.hasMoreElements()) {
|
||||||
|
ZipEntry entry = entries.nextElement();
|
||||||
|
String fileName = entry.getName();
|
||||||
|
if (fileName.startsWith("http/")) {
|
||||||
|
Path filePath = tempDirectory.resolve(fileName.substring("http/".length()));
|
||||||
|
|
||||||
|
tasks.add(() -> {
|
||||||
|
try (InputStream in = zipFile.getInputStream(entry)) {
|
||||||
|
Files.copy(in, filePath, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
} catch (IOException e) {
|
||||||
|
api.logging().logToError("readHaeFile: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
try (InputStream in = zipFile.getInputStream(entry)) {
|
||||||
|
switch (fileName) {
|
||||||
|
case "info" ->
|
||||||
|
haeFileContent.setHost(new String(in.readAllBytes(), StandardCharsets.UTF_8));
|
||||||
|
case "data" ->
|
||||||
|
haeFileContent.setDataMap(yaml.load(new InputStreamReader(in, StandardCharsets.UTF_8)));
|
||||||
|
case "url" ->
|
||||||
|
haeFileContent.setUrlMap(yaml.load(new InputStreamReader(in, StandardCharsets.UTF_8)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
executor.invokeAll(tasks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
api.logging().logToError("readHaeFile: " + e.getMessage());
|
||||||
|
if (tempDirectory != null) {
|
||||||
|
FileProcessor.deleteDirectoryWithContents(tempDirectory);
|
||||||
|
}
|
||||||
|
haeFileContent = null;
|
||||||
|
} finally {
|
||||||
|
executor.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
return haeFileContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasValidStructure(String zipFilePath) {
|
||||||
|
Set<String> requiredRootEntries = new HashSet<>();
|
||||||
|
requiredRootEntries.add("info");
|
||||||
|
requiredRootEntries.add("data");
|
||||||
|
requiredRootEntries.add("url");
|
||||||
|
|
||||||
|
boolean hasHttpDirectoryWithFiles = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
ZipFile zipFile = new ZipFile(zipFilePath);
|
||||||
|
Enumeration<? extends ZipEntry> entries = zipFile.entries();
|
||||||
|
|
||||||
|
while (entries.hasMoreElements()) {
|
||||||
|
ZipEntry entry = entries.nextElement();
|
||||||
|
String name = entry.getName();
|
||||||
|
|
||||||
|
if (!entry.isDirectory() && !name.contains("/")) {
|
||||||
|
requiredRootEntries.remove(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.startsWith("http/") && !entry.isDirectory()) {
|
||||||
|
hasHttpDirectoryWithFiles = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requiredRootEntries.isEmpty() && hasHttpDirectoryWithFiles) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
zipFile.close();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return requiredRootEntries.isEmpty() && hasHttpDirectoryWithFiles;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
76
src/main/java/hae/utils/project/model/HaeFileContent.java
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package hae.utils.project.model;
|
||||||
|
|
||||||
|
import burp.api.montoya.MontoyaApi;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class HaeFileContent {
|
||||||
|
private final MontoyaApi api;
|
||||||
|
private String host;
|
||||||
|
private String httpPath;
|
||||||
|
private final Map<String, List<String>> dataMap;
|
||||||
|
private final Map<String, Map<String, String>> urlMap;
|
||||||
|
|
||||||
|
public HaeFileContent(MontoyaApi api) {
|
||||||
|
this.api = api;
|
||||||
|
this.dataMap = new HashMap<>();
|
||||||
|
this.urlMap = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHost() {
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, List<String>> getDataMap() {
|
||||||
|
return dataMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Map<String, String>> getUrlMap() {
|
||||||
|
return urlMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHttpPath() {
|
||||||
|
return httpPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHost(String host) {
|
||||||
|
this.host = host;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHttpPath(String path) {
|
||||||
|
this.httpPath = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDataMap(Map<String, List<Object>> dataMap) {
|
||||||
|
for (Map.Entry<String, List<Object>> entry : dataMap.entrySet()) {
|
||||||
|
List<String> values = new ArrayList<>();
|
||||||
|
for (Object value : entry.getValue()) {
|
||||||
|
try {
|
||||||
|
values.add(new String((byte[]) value, StandardCharsets.UTF_8));
|
||||||
|
} catch (Exception e) {
|
||||||
|
values.add(value.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.dataMap.put(entry.getKey(), values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUrlMap(Map<String, Map<String, Object>> urlMap) {
|
||||||
|
for (Map.Entry<String, Map<String, Object>> entry : urlMap.entrySet()) {
|
||||||
|
Map<String, String> newValues = new HashMap<>();
|
||||||
|
Map<String, Object> values = entry.getValue();
|
||||||
|
for (String key : values.keySet()) {
|
||||||
|
try {
|
||||||
|
newValues.put(key, new String((byte[]) values.get(key), StandardCharsets.UTF_8));
|
||||||
|
} catch (Exception e) {
|
||||||
|
newValues.put(key, values.get(key).toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.urlMap.put(entry.getKey(), newValues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ package hae.utils.rule;
|
|||||||
|
|
||||||
import burp.api.montoya.MontoyaApi;
|
import burp.api.montoya.MontoyaApi;
|
||||||
import hae.Config;
|
import hae.Config;
|
||||||
import hae.utils.config.ConfigLoader;
|
import hae.utils.ConfigLoader;
|
||||||
import hae.utils.rule.model.Group;
|
import hae.utils.rule.model.Group;
|
||||||
import hae.utils.rule.model.Info;
|
import hae.utils.rule.model.Info;
|
||||||
import org.yaml.snakeyaml.DumperOptions;
|
import org.yaml.snakeyaml.DumperOptions;
|
||||||
|
|||||||
@@ -1,8 +1,17 @@
|
|||||||
package hae.utils.string;
|
package hae.utils.string;
|
||||||
|
|
||||||
|
import burp.api.montoya.core.ByteArray;
|
||||||
|
import burp.api.montoya.http.HttpService;
|
||||||
|
import burp.api.montoya.http.message.HttpRequestResponse;
|
||||||
|
import burp.api.montoya.http.message.requests.HttpRequest;
|
||||||
|
import burp.api.montoya.http.message.responses.HttpResponse;
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
public class StringProcessor {
|
public class StringProcessor {
|
||||||
public static String replaceFirstOccurrence(String original, String find, String replace) {
|
public static String replaceFirstOccurrence(String original, String find, String replace) {
|
||||||
@@ -32,6 +41,47 @@ public class StringProcessor {
|
|||||||
return patternIndex == -1;
|
return patternIndex == -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String extractHostname(String hostWithPort) {
|
||||||
|
if (hostWithPort == null || hostWithPort.isEmpty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
int colonIndex = hostWithPort.indexOf(":");
|
||||||
|
if (colonIndex != -1) {
|
||||||
|
return hostWithPort.substring(0, colonIndex);
|
||||||
|
} else {
|
||||||
|
return hostWithPort;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean matchesHostPattern(String host, String selectedHost) {
|
||||||
|
String hostname = StringProcessor.extractHostname(host);
|
||||||
|
String hostPattern = selectedHost.replace("*.", "");
|
||||||
|
boolean matchesDirectly = selectedHost.equals("*") || host.equals(selectedHost);
|
||||||
|
boolean matchesPattern = !host.contains("*") &&
|
||||||
|
(hostPattern.equals(selectedHost) ?
|
||||||
|
StringProcessor.matchFromEnd(host, hostPattern) :
|
||||||
|
StringProcessor.matchFromEnd(hostname, hostPattern));
|
||||||
|
return matchesDirectly || matchesPattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HttpRequestResponse createHttpRequestResponse(String url, byte[] request, byte[] response) {
|
||||||
|
HttpService httpService = HttpService.httpService(url);
|
||||||
|
HttpRequest httpRequest = HttpRequest.httpRequest(httpService, ByteArray.byteArray(request));
|
||||||
|
HttpResponse httpResponse = HttpResponse.httpResponse(ByteArray.byteArray(response));
|
||||||
|
return HttpRequestResponse.httpRequestResponse(httpRequest, httpResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getCurrentTime() {
|
||||||
|
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss");
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
return now.format(formatter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getRandomUUID() {
|
||||||
|
UUID uuid = UUID.randomUUID();
|
||||||
|
return uuid.toString();
|
||||||
|
}
|
||||||
|
|
||||||
public static String mergeComment(String comment) {
|
public static String mergeComment(String comment) {
|
||||||
if (!comment.contains(",")) {
|
if (!comment.contains(",")) {
|
||||||
return comment;
|
return comment;
|
||||||
@@ -69,6 +119,10 @@ public class StringProcessor {
|
|||||||
return host;
|
return host;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean matchHostIsIp(String host) {
|
||||||
|
return host.matches("\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b");
|
||||||
|
}
|
||||||
|
|
||||||
private static Map<String, Integer> getStringIntegerMap(String comment) {
|
private static Map<String, Integer> getStringIntegerMap(String comment) {
|
||||||
Map<String, Integer> itemCounts = new HashMap<>();
|
Map<String, Integer> itemCounts = new HashMap<>();
|
||||||
String[] items = comment.split(", ");
|
String[] items = comment.split(", ");
|
||||||
|
|||||||
@@ -1,284 +1,284 @@
|
|||||||
rules:
|
rules:
|
||||||
- group: Fingerprint
|
- group: Fingerprint
|
||||||
rule:
|
rule:
|
||||||
- name: Shiro
|
- name: Shiro
|
||||||
loaded: true
|
loaded: true
|
||||||
f_regex: (=deleteMe|rememberMe=)
|
f_regex: (=deleteMe|rememberMe=)
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
color: green
|
color: green
|
||||||
scope: any header
|
scope: any header
|
||||||
engine: dfa
|
engine: dfa
|
||||||
sensitive: true
|
sensitive: true
|
||||||
- name: JSON Web Token
|
- name: JSON Web Token
|
||||||
loaded: true
|
loaded: true
|
||||||
f_regex: (eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9._-]{10,}|eyJ[A-Za-z0-9_\/+-]{10,}\.[A-Za-z0-9._\/+-]{10,})
|
f_regex: (eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9._-]{10,}|eyJ[A-Za-z0-9_\/+-]{10,}\.[A-Za-z0-9._\/+-]{10,})
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
color: green
|
color: green
|
||||||
scope: any
|
scope: any
|
||||||
engine: nfa
|
engine: nfa
|
||||||
sensitive: true
|
sensitive: true
|
||||||
- name: Swagger UI
|
- name: Swagger UI
|
||||||
loaded: true
|
loaded: true
|
||||||
f_regex: ((swagger-ui.html)|(\"swagger\":)|(Swagger UI)|(swaggerUi)|(swaggerVersion))
|
f_regex: ((swagger-ui.html)|(\"swagger\":)|(Swagger UI)|(swaggerUi)|(swaggerVersion))
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
color: red
|
color: red
|
||||||
scope: response body
|
scope: response body
|
||||||
engine: dfa
|
engine: dfa
|
||||||
sensitive: false
|
sensitive: false
|
||||||
- name: Ueditor
|
- name: Ueditor
|
||||||
loaded: true
|
loaded: true
|
||||||
f_regex: (ueditor\.(config|all)\.js)
|
f_regex: (ueditor\.(config|all)\.js)
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
color: green
|
color: green
|
||||||
scope: response body
|
scope: response body
|
||||||
engine: dfa
|
engine: dfa
|
||||||
sensitive: false
|
sensitive: false
|
||||||
- name: Druid
|
- name: Druid
|
||||||
loaded: true
|
loaded: true
|
||||||
f_regex: (Druid Stat Index)
|
f_regex: (Druid Stat Index)
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
color: orange
|
color: orange
|
||||||
scope: response body
|
scope: response body
|
||||||
engine: dfa
|
engine: dfa
|
||||||
sensitive: false
|
sensitive: false
|
||||||
- group: Maybe Vulnerability
|
- group: Maybe Vulnerability
|
||||||
rule:
|
rule:
|
||||||
- name: Java Deserialization
|
- name: Java Deserialization
|
||||||
loaded: true
|
loaded: true
|
||||||
f_regex: (javax\.faces\.ViewState)
|
f_regex: (javax\.faces\.ViewState)
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
color: yellow
|
color: yellow
|
||||||
scope: response body
|
scope: response body
|
||||||
engine: dfa
|
engine: dfa
|
||||||
sensitive: false
|
sensitive: false
|
||||||
- name: Debug Logic Parameters
|
- name: Debug Logic Parameters
|
||||||
loaded: true
|
loaded: true
|
||||||
f_regex: ((access=)|(adm=)|(admin=)|(alter=)|(cfg=)|(clone=)|(config=)|(create=)|(dbg=)|(debug=)|(delete=)|(disable=)|(edit=)|(enable=)|(exec=)|(execute=)|(grant=)|(load=)|(make=)|(modify=)|(rename=)|(reset=)|(root=)|(shell=)|(test=)|(toggl=))
|
f_regex: ((access=)|(adm=)|(admin=)|(alter=)|(cfg=)|(clone=)|(config=)|(create=)|(dbg=)|(debug=)|(delete=)|(disable=)|(edit=)|(enable=)|(exec=)|(execute=)|(grant=)|(load=)|(make=)|(modify=)|(rename=)|(reset=)|(root=)|(shell=)|(test=)|(toggl=))
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
color: cyan
|
color: cyan
|
||||||
scope: request
|
scope: request
|
||||||
engine: dfa
|
engine: dfa
|
||||||
sensitive: false
|
sensitive: false
|
||||||
- name: URL As A Value
|
- name: URL As A Value
|
||||||
loaded: true
|
loaded: true
|
||||||
f_regex: (=(https?)(://|%3a%2f%2f))
|
f_regex: (=(https?)(://|%3a%2f%2f))
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
color: cyan
|
color: cyan
|
||||||
scope: any
|
scope: any
|
||||||
engine: nfa
|
engine: nfa
|
||||||
sensitive: false
|
sensitive: false
|
||||||
- name: Upload Form
|
- name: Upload Form
|
||||||
loaded: true
|
loaded: true
|
||||||
f_regex: (type\=\"file\")
|
f_regex: (type\=\"file\")
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
color: yellow
|
color: yellow
|
||||||
scope: response body
|
scope: response body
|
||||||
engine: dfa
|
engine: dfa
|
||||||
sensitive: false
|
sensitive: false
|
||||||
- name: DoS Paramters
|
- name: DoS Paramters
|
||||||
loaded: true
|
loaded: true
|
||||||
f_regex: ((size=)|(page=)|(num=)|(limit=)|(start=)|(end=)|(count=))
|
f_regex: ((size=)|(page=)|(num=)|(limit=)|(start=)|(end=)|(count=))
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
color: cyan
|
color: cyan
|
||||||
scope: request
|
scope: request
|
||||||
engine: dfa
|
engine: dfa
|
||||||
sensitive: false
|
sensitive: false
|
||||||
- group: Basic Information
|
- group: Basic Information
|
||||||
rule:
|
rule:
|
||||||
- name: Email
|
- name: Email
|
||||||
loaded: true
|
loaded: true
|
||||||
f_regex: (([a-z0-9]+[_|\.])*[a-z0-9]+@([a-z0-9]+[-|_|\.])*[a-z0-9]+\.((?!js|css|jpg|jpeg|png|ico)[a-z]{2,5}))
|
f_regex: (([a-z0-9]+[_|\.])*[a-z0-9]+@([a-z0-9]+[-|_|\.])*[a-z0-9]+\.((?!js|css|jpg|jpeg|png|ico)[a-z]{2,5}))
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
color: yellow
|
color: yellow
|
||||||
scope: response
|
scope: response
|
||||||
engine: nfa
|
engine: nfa
|
||||||
sensitive: false
|
sensitive: false
|
||||||
- name: Chinese IDCard
|
- name: Chinese IDCard
|
||||||
loaded: true
|
loaded: true
|
||||||
f_regex: '[^0-9]((\d{8}(0\d|10|11|12)([0-2]\d|30|31)\d{3}$)|(\d{6}(18|19|20)\d{2}(0[1-9]|10|11|12)([0-2]\d|30|31)\d{3}(\d|X|x)))[^0-9]'
|
f_regex: '[^0-9]((\d{8}(0\d|10|11|12)([0-2]\d|30|31)\d{3}$)|(\d{6}(18|19|20)\d{2}(0[1-9]|10|11|12)([0-2]\d|30|31)\d{3}(\d|X|x)))[^0-9]'
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
color: orange
|
color: orange
|
||||||
scope: response body
|
scope: response body
|
||||||
engine: nfa
|
engine: nfa
|
||||||
sensitive: true
|
sensitive: true
|
||||||
- name: Chinese Mobile Number
|
- name: Chinese Mobile Number
|
||||||
loaded: true
|
loaded: true
|
||||||
f_regex: '[^\w]((?:(?:\+|00)86)?1(?:(?:3[\d])|(?:4[5-79])|(?:5[0-35-9])|(?:6[5-7])|(?:7[0-8])|(?:8[\d])|(?:9[189]))\d{8})[^\w]'
|
f_regex: '[^\w]((?:(?:\+|00)86)?1(?:(?:3[\d])|(?:4[5-79])|(?:5[0-35-9])|(?:6[5-7])|(?:7[0-8])|(?:8[\d])|(?:9[189]))\d{8})[^\w]'
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
color: orange
|
color: orange
|
||||||
scope: response body
|
scope: response body
|
||||||
engine: nfa
|
engine: nfa
|
||||||
sensitive: false
|
sensitive: false
|
||||||
- name: Internal IP Address
|
- name: Internal IP Address
|
||||||
loaded: true
|
loaded: true
|
||||||
f_regex: '[^0-9]((127\.0\.0\.1)|(10\.\d{1,3}\.\d{1,3}\.\d{1,3})|(172\.((1[6-9])|(2\d)|(3[01]))\.\d{1,3}\.\d{1,3})|(192\.168\.\d{1,3}\.\d{1,3}))'
|
f_regex: '[^0-9]((127\.0\.0\.1)|(10\.\d{1,3}\.\d{1,3}\.\d{1,3})|(172\.((1[6-9])|(2\d)|(3[01]))\.\d{1,3}\.\d{1,3})|(192\.168\.\d{1,3}\.\d{1,3}))'
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
color: cyan
|
color: cyan
|
||||||
scope: response
|
scope: response
|
||||||
engine: nfa
|
engine: nfa
|
||||||
sensitive: true
|
sensitive: true
|
||||||
- name: MAC Address
|
- name: MAC Address
|
||||||
loaded: true
|
loaded: true
|
||||||
f_regex: (^([a-fA-F0-9]{2}(:[a-fA-F0-9]{2}){5})|[^a-zA-Z0-9]([a-fA-F0-9]{2}(:[a-fA-F0-9]{2}){5}))
|
f_regex: (^([a-fA-F0-9]{2}(:[a-fA-F0-9]{2}){5})|[^a-zA-Z0-9]([a-fA-F0-9]{2}(:[a-fA-F0-9]{2}){5}))
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
color: green
|
color: green
|
||||||
scope: response
|
scope: response
|
||||||
engine: nfa
|
engine: nfa
|
||||||
sensitive: true
|
sensitive: true
|
||||||
- group: Sensitive Information
|
- group: Sensitive Information
|
||||||
rule:
|
rule:
|
||||||
- name: Cloud Key
|
- name: Cloud Key
|
||||||
loaded: true
|
loaded: true
|
||||||
f_regex: (((access)(|-|_)(key)(|-|_)(id|secret))|(LTAI[a-z0-9]{12,20}))
|
f_regex: (((access)(|-|_)(key)(|-|_)(id|secret))|(LTAI[a-z0-9]{12,20}))
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
color: yellow
|
color: yellow
|
||||||
scope: any
|
scope: any
|
||||||
engine: nfa
|
engine: nfa
|
||||||
sensitive: false
|
sensitive: false
|
||||||
- name: Windows File/Dir Path
|
- name: Windows File/Dir Path
|
||||||
loaded: true
|
loaded: true
|
||||||
f_regex: '[^\w](([a-zA-Z]:\\(?:\w+\\?)*)|([a-zA-Z]:\\(?:\w+\\)*\w+\.\w+))'
|
f_regex: '[^\w](([a-zA-Z]:\\(?:\w+\\?)*)|([a-zA-Z]:\\(?:\w+\\)*\w+\.\w+))'
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
color: green
|
color: green
|
||||||
scope: response
|
scope: response
|
||||||
engine: nfa
|
engine: nfa
|
||||||
sensitive: true
|
sensitive: true
|
||||||
- name: Password Field
|
- name: Password Field
|
||||||
loaded: true
|
loaded: true
|
||||||
f_regex: ((|'|")(|[\w]{1,10})([p](ass|wd|asswd|assword))(|[\w]{1,10})(|'|")(:|=)(
|
f_regex: ((|'|")(|[\w]{1,10})([p](ass|wd|asswd|assword))(|[\w]{1,10})(|'|")(:|=)(
|
||||||
|)('|")(.*?)('|")(|,))
|
|)('|")(.*?)('|")(|,))
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
color: yellow
|
color: yellow
|
||||||
scope: response body
|
scope: response body
|
||||||
engine: nfa
|
engine: nfa
|
||||||
sensitive: false
|
sensitive: false
|
||||||
- name: Username Field
|
- name: Username Field
|
||||||
loaded: true
|
loaded: true
|
||||||
f_regex: ((|'|")(|[\w]{1,10})(([u](ser|name|sername))|(account)|((((create|update)((d|r)|(by|on|at)))|(creator))))(|[\w]{1,10})(|'|")(:|=)(
|
f_regex: ((|'|")(|[\w]{1,10})(([u](ser|name|sername))|(account)|((((create|update)((d|r)|(by|on|at)))|(creator))))(|[\w]{1,10})(|'|")(:|=)(
|
||||||
|)('|")(.*?)('|")(|,))
|
|)('|")(.*?)('|")(|,))
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
color: green
|
color: green
|
||||||
scope: response body
|
scope: response body
|
||||||
engine: nfa
|
engine: nfa
|
||||||
sensitive: false
|
sensitive: false
|
||||||
- name: WeCom Key
|
- name: WeCom Key
|
||||||
loaded: true
|
loaded: true
|
||||||
f_regex: ((corp)(id|secret))
|
f_regex: ((corp)(id|secret))
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
color: green
|
color: green
|
||||||
scope: response body
|
scope: response body
|
||||||
engine: dfa
|
engine: dfa
|
||||||
sensitive: false
|
sensitive: false
|
||||||
- name: JDBC Connection
|
- name: JDBC Connection
|
||||||
loaded: true
|
loaded: true
|
||||||
f_regex: (jdbc:[a-z:]+://[a-z0-9\.\-_:;=/@?,&]+)
|
f_regex: (jdbc:[a-z:]+://[a-z0-9\.\-_:;=/@?,&]+)
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
color: yellow
|
color: yellow
|
||||||
scope: any
|
scope: any
|
||||||
engine: nfa
|
engine: nfa
|
||||||
sensitive: false
|
sensitive: false
|
||||||
- name: Authorization Header
|
- name: Authorization Header
|
||||||
loaded: true
|
loaded: true
|
||||||
f_regex: ((basic [a-z0-9=:_\+\/-]{5,100})|(bearer [a-z0-9_.=:_\+\/-]{5,100}))
|
f_regex: ((basic [a-z0-9=:_\+\/-]{5,100})|(bearer [a-z0-9_.=:_\+\/-]{5,100}))
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
color: yellow
|
color: yellow
|
||||||
scope: response body
|
scope: response body
|
||||||
engine: nfa
|
engine: nfa
|
||||||
sensitive: false
|
sensitive: false
|
||||||
- name: Sensitive Field
|
- name: Sensitive Field
|
||||||
loaded: true
|
loaded: true
|
||||||
f_regex: ((\[)?('|")?([\w]{0,10})((key)|(secret)|(token)|(config)|(auth)|(access)|(admin))([\w]{0,10})('|")?(\])?(
|
f_regex: ((\[)?('|")?([\w]{0,10})((key)|(secret)|(token)|(config)|(auth)|(access)|(admin)|(ticket))([\w]{0,10})('|")?(\])?(
|
||||||
|)(:|=)( |)('|")(.*?)('|")(|,))
|
|)(:|=)( |)('|")(.*?)('|")(|,))
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
color: yellow
|
color: yellow
|
||||||
scope: response
|
scope: response
|
||||||
engine: nfa
|
engine: nfa
|
||||||
sensitive: false
|
sensitive: false
|
||||||
- group: Other
|
- group: Other
|
||||||
rule:
|
rule:
|
||||||
- name: Linkfinder
|
- name: Linkfinder
|
||||||
loaded: true
|
loaded: true
|
||||||
f_regex: (?:"|')(((?:[a-zA-Z]{1,10}://|//)[^"'/]{1,}\.[a-zA-Z]{2,}[^"']{0,})|((?:/|\.\./|\./)[^"'><,;|*()(%%$^/\\\[\]][^"'><,;|()]{1,})|([a-zA-Z0-9_\-/]{1,}/[a-zA-Z0-9_\-/]{1,}\.(?:[a-zA-Z]{1,4}|action)(?:[\?|#][^"|']{0,}|))|([a-zA-Z0-9_\-/]{1,}/[a-zA-Z0-9_\-/]{3,}(?:[\?|#][^"|']{0,}|))|([a-zA-Z0-9_\-]{1,}\.(?:\w)(?:[\?|#][^"|']{0,}|)))(?:"|')
|
f_regex: (?:"|')(((?:[a-zA-Z]{1,10}://|//)[^"'/]{1,}\.[a-zA-Z]{2,}[^"']{0,})|((?:/|\.\./|\./)[^"'><,;|*()(%%$^/\\\[\]][^"'><,;|()]{1,})|([a-zA-Z0-9_\-/]{1,}/[a-zA-Z0-9_\-/]{1,}\.(?:[a-zA-Z]{1,4}|action)(?:[\?|#][^"|']{0,}|))|([a-zA-Z0-9_\-/]{1,}/[a-zA-Z0-9_\-/]{3,}(?:[\?|#][^"|']{0,}|))|([a-zA-Z0-9_\-]{1,}\.(?:\w)(?:[\?|#][^"|']{0,}|)))(?:"|')
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
color: gray
|
color: gray
|
||||||
scope: response body
|
scope: response body
|
||||||
engine: nfa
|
engine: nfa
|
||||||
sensitive: true
|
sensitive: true
|
||||||
- name: Source Map
|
- name: Source Map
|
||||||
loaded: true
|
loaded: true
|
||||||
f_regex: (\.js\.map)
|
f_regex: (\.js\.map)
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
color: pink
|
color: pink
|
||||||
scope: response body
|
scope: response body
|
||||||
engine: dfa
|
engine: dfa
|
||||||
sensitive: false
|
sensitive: false
|
||||||
- name: HTML Notes
|
- name: Create Script
|
||||||
loaded: true
|
loaded: true
|
||||||
f_regex: (<!--.*?-->)
|
f_regex: (\{[^{}]*\}\s*\[[^\s]*\]\s*\+\s*"[^\s]*\.js")
|
||||||
s_regex: ''
|
s_regex: '"?([\w].*?)"?:"(.*?)"'
|
||||||
format: '{0}'
|
format: '{0}.{1}'
|
||||||
color: magenta
|
color: green
|
||||||
scope: response body
|
scope: response body
|
||||||
engine: nfa
|
engine: nfa
|
||||||
sensitive: false
|
sensitive: false
|
||||||
- name: Create Script
|
- name: URL Schemes
|
||||||
loaded: true
|
loaded: true
|
||||||
f_regex: (\+\{.*?\}\[[a-zA-Z]\]\+".*?\.js")
|
f_regex: ((?![http]|[https])(([-A-Za-z0-9]{1,20})://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]))
|
||||||
s_regex: '"?([\w].*?)"?:"(.*?)"'
|
s_regex: ''
|
||||||
format: '{0}.{1}'
|
format: '{0}'
|
||||||
color: green
|
color: yellow
|
||||||
scope: response body
|
scope: response body
|
||||||
engine: nfa
|
engine: nfa
|
||||||
sensitive: false
|
sensitive: false
|
||||||
- name: URL Schemes
|
- name: Router Push
|
||||||
loaded: true
|
loaded: true
|
||||||
f_regex: ((?![http]|[https])(([-A-Za-z0-9]{1,20})://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]))
|
f_regex: (\$router\.push)
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
color: yellow
|
color: magenta
|
||||||
scope: response body
|
scope: response body
|
||||||
engine: nfa
|
engine: dfa
|
||||||
sensitive: false
|
sensitive: false
|
||||||
- name: Router Push
|
- name: All URL
|
||||||
loaded: true
|
loaded: true
|
||||||
f_regex: (\$router\.push)
|
f_regex: (https?://[-A-Za-z0-9+&@#/%?=~_|!:,.;\u4E00-\u9FFF]+[-A-Za-z0-9+&@#/%=~_|])
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
color: magenta
|
color: gray
|
||||||
scope: response body
|
scope: response body
|
||||||
engine: dfa
|
engine: nfa
|
||||||
sensitive: false
|
sensitive: true
|
||||||
- name: All URL
|
- name: Request URI
|
||||||
loaded: true
|
loaded: true
|
||||||
f_regex: (https?://[-A-Za-z0-9+&@#/%?=~_|!:,.;\u4E00-\u9FFF]+[-A-Za-z0-9+&@#/%=~_|])
|
f_regex: ' ((?!.*\.js(\?.*)?$)(.*?[^.js$])) '
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
color: gray
|
color: gray
|
||||||
scope: response body
|
scope: request line
|
||||||
engine: nfa
|
engine: nfa
|
||||||
sensitive: true
|
sensitive: false
|
||||||
|
|||||||