Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
980999a2f0 | ||
|
|
1236b9579e | ||
|
|
d561b6815d | ||
|
|
97d20bb7f5 | ||
|
|
74fa78db5e | ||
|
|
230cad7f91 | ||
|
|
1957599b07 | ||
|
|
5f26db2a9e | ||
|
|
692e26044c | ||
|
|
b597a9e6d9 | ||
|
|
fca804cb7c | ||
|
|
3cbf0591c6 | ||
|
|
186430bb35 | ||
|
|
56c7973261 | ||
|
|
d71965ce10 | ||
|
|
1ffe94e78d | ||
|
|
3859f81b2a | ||
|
|
20ae5bc811 | ||
|
|
b7734ca710 | ||
|
|
d75991043e | ||
|
|
95e1cb4dc1 | ||
|
|
baa7270f46 | ||
|
|
0b1d502f79 | ||
|
|
8c7ac8f47d | ||
|
|
ec4a10753f | ||
|
|
ed698b9861 |
35
README.md
35
README.md
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
<img src="images/logo.png" style="width: 20%" />
|
||||
<h4><a href="https://gh0st.cn/HaE/">Empower ethical hacker for efficient operations.</a></h4>
|
||||
<h5>First Author:: <a href="https://github.com/gh0stkey">EvilChen</a>(Zhongfu Information Yuanheng Laboratory)<br>Second Author: <a href="https://github.com/0chencc">0chencc</a>(Mystery Security Team)<br>Third Author: <a href="https://github.com/vaycore">vaycore</a>(Independent Security Researcher)</h5>
|
||||
<h4><a href="https://github.com/gh0stkey/HaE">Empower ethical hacker for efficient operations.</a></h4>
|
||||
<h5>First Author: <a href="https://github.com/gh0stkey">EvilChen</a><br>Second Author: <a href="https://github.com/0chencc">0chencc</a>(Mystery Security Team)<br>Third Author: <a href="https://github.com/vaycore">vaycore</a>(Independent Security Researcher)</h5>
|
||||
</div>
|
||||
|
||||
README Version: \[[English](README.md) | [简体中文](README_CN.md)\]
|
||||
@@ -28,6 +28,20 @@ GitCode project address: https://gitcode.com/gh0stkey/HaE
|
||||
1. Starting with HaE version 3.0, development is done using the `Montoya API`. To use the new version of HaE, you need to upgrade your BurpSuite version (>=2023.12.1).
|
||||
2. Custom HaE rules must enclose the expressions to be extracted within parentheses `()`. For example, if you want to match a response message from a **Shiro application**, the normal matching rule would be `rememberMe=delete`, but in HaE's rule format, it needs to be written as `(rememberMe=delete)`.
|
||||
|
||||
## Usage
|
||||
|
||||
**Plugin Installation**: `Extender - Extensions - Add - Select File - Next`
|
||||
|
||||
When you load `HaE` for the first time, it will load the offline rule database from the Jar package. If you need to update the rules, click `Reinit` to reinitialize. The address of the built-in rule database can be found on GitHub:
|
||||
`https://github.com/gh0stkey/HaE/blob/master/src/main/resources/rules/Rules.yml`
|
||||
|
||||
The configuration file (`Config.yml`) and rule file (`Rules.yml`) are stored in a fixed directory:
|
||||
|
||||
1. For Linux/Mac users: `~/.config/HaE/`
|
||||
2. For Windows users: `%USERPROFILE%/.config/HaE/`
|
||||
|
||||
Alternatively, you can also place the configuration files in the `/.config/HaE/` directory under the same folder as the `HaE Jar package`, **for easier offline portability**.
|
||||
|
||||
### Rule Definitions
|
||||
|
||||
Currently, HaE rules consist of 8 fields, with detailed meanings as follows:
|
||||
@@ -74,15 +88,28 @@ We appreciate everyone's support for the project. The following list is sorted b
|
||||
| Kite | 48.00 CNY |
|
||||
| 红色键盘 | 99.99 CNY |
|
||||
| 曾哥 | 188.88 CNY |
|
||||
| 祝祝 | 488.00 CNY |
|
||||
| NOP Team | 200.00 CNY |
|
||||
| vaycore | 188.88 CNY |
|
||||
| xccc | 168.00 CNY |
|
||||
| 柯林斯-民间新秀 | 1000.00 CNY |
|
||||
| 柯林斯-民间新秀 | 3288.8 CNY |
|
||||
| Cuber | 100.00 CNY |
|
||||
| 时光难逆 | 50.00 CNY |
|
||||
| Celvin | 66.00 CNY |
|
||||
| Celvin | 150.88 CNY |
|
||||
| 呱呱 | 18.80 CNY |
|
||||
| 红炉点雪 | 50.00 CNY |
|
||||
| 王傑 | 100.00 CNY |
|
||||
| 联系不到我请拨打我手机号码 | 200.00 CNY |
|
||||
| Shu2e | 59.90 CNY |
|
||||
| 亦 | 50.00 CNY |
|
||||
| 是果实菌啊 | 38.88 CNY |
|
||||
| caytez | 77.77 CNY |
|
||||
| Sn0w33 | 18.88 CNY |
|
||||
| Edwater | 18.88 CNY |
|
||||
| 云中鹤 | 18.88 CNY |
|
||||
| Twit | 18.88 CNY |
|
||||
| cshu | 18.88 CNY |
|
||||
| Fzz2 | 50.00 CNY |
|
||||
|
||||
## Support the Project
|
||||
|
||||
|
||||
56
README_CN.md
56
README_CN.md
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
<img src="images/logo.png" style="width: 20%" />
|
||||
<h4><a href="https://gh0st.cn/HaE/">赋能白帽,高效作战!</a></h4>
|
||||
<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>
|
||||
<h4><a href="https://github.com/gh0stkey/HaE">赋能白帽,高效作战!</a></h4>
|
||||
<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>
|
||||
|
||||
README 版本: \[[English](README.md) | [简体中文](README_CN.md)\]
|
||||
@@ -76,26 +76,40 @@ HaE目前的规则一共有8个字段,详细的含义如下所示:
|
||||
|
||||
感谢各位对项目的赞赏,以下名单基于赞赏时间进行排序,不分先后,如有遗留可联系项目作者进行补充。
|
||||
|
||||
| ID | 金额 |
|
||||
| ID | Amount |
|
||||
| -------- | -------- |
|
||||
| 毁三观大人 | 200.00元 |
|
||||
| ttt | 50.00元 |
|
||||
| C_soon5 | 66.66元 |
|
||||
| 1wtbb | 25.00元 |
|
||||
| Deep | 66.66元 |
|
||||
| NaTsUk0 | 50.00元 |
|
||||
| Kite | 48.00元 |
|
||||
| 红色键盘 | 99.99元 |
|
||||
| 曾哥 | 188.88元 |
|
||||
| NOP Team | 200.00元 |
|
||||
| vaycore | 188.88元 |
|
||||
| xccc | 168.00元 |
|
||||
| 柯林斯-民间新秀 | 1000.00元 |
|
||||
| Cuber | 100.00元 |
|
||||
| 时光难逆 | 50.00元 |
|
||||
| Celvin | 66.00元 |
|
||||
| 呱呱 | 18.80元 |
|
||||
| 红炉点雪 | 50.00元 |
|
||||
| 毁三观大人 | 200.00 元 |
|
||||
| ttt | 50.00 元 |
|
||||
| C_soon5 | 66.66 元 |
|
||||
| 1wtbb | 25.00 元 |
|
||||
| Deep | 66.66 元 |
|
||||
| NaTsUk0 | 50.00 元 |
|
||||
| Kite | 48.00 元 |
|
||||
| 红色键盘 | 99.99 元 |
|
||||
| 曾哥 | 188.88 元 |
|
||||
| 祝祝 | 488.00 元 |
|
||||
| NOP Team | 200.00 元 |
|
||||
| vaycore | 188.88 元 |
|
||||
| xccc | 168.00 元 |
|
||||
| 柯林斯-民间新秀 | 3288.8 元 |
|
||||
| Cuber | 100.00 元 |
|
||||
| 时光难逆 | 50.00 元 |
|
||||
| Celvin | 150.88 元 |
|
||||
| 呱呱 | 18.80 元 |
|
||||
| 红炉点雪 | 50.00 元 |
|
||||
| 王傑 | 100.00 元 |
|
||||
| 联系不到我请拨打我手机号码 | 200.00 元 |
|
||||
| Shu2e | 59.90 元 |
|
||||
| 亦 | 50.00 元 |
|
||||
| 是果实菌啊 | 38.88 元 |
|
||||
| caytez | 77.77 元 |
|
||||
| Sn0w33 | 18.88 元 |
|
||||
| Edwater | 18.88 元 |
|
||||
| 云中鹤 | 18.88 元 |
|
||||
| Twit | 18.88 元 |
|
||||
| cshu | 18.88 元 |
|
||||
| Fzz2 | 50.00 元 |
|
||||
|
||||
|
||||
## 支持项目
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ public class Config {
|
||||
|
||||
public static String status = "404";
|
||||
|
||||
public static String header = "Last-Modified|Date|Connection|ETag";
|
||||
|
||||
public static String size = "0";
|
||||
|
||||
public static String boundary = "\n\t\n";
|
||||
@@ -58,7 +60,8 @@ public class Config {
|
||||
"blue",
|
||||
"pink",
|
||||
"magenta",
|
||||
"gray"
|
||||
"gray",
|
||||
"none"
|
||||
};
|
||||
|
||||
public static Boolean proVersionStatus = true;
|
||||
|
||||
@@ -2,14 +2,14 @@ package hae;
|
||||
|
||||
import burp.api.montoya.BurpExtension;
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import burp.api.montoya.core.BurpSuiteEdition;
|
||||
import burp.api.montoya.logging.Logging;
|
||||
import hae.cache.MessageCache;
|
||||
import hae.cache.DataCache;
|
||||
import hae.component.Main;
|
||||
import hae.component.board.message.MessageTableModel;
|
||||
import hae.instances.editor.RequestEditor;
|
||||
import hae.instances.editor.ResponseEditor;
|
||||
import hae.instances.editor.WebSocketEditor;
|
||||
import hae.instances.http.HttpMessagePassiveHandler;
|
||||
import hae.instances.websocket.WebSocketMessageHandler;
|
||||
import hae.utils.ConfigLoader;
|
||||
import hae.utils.DataManager;
|
||||
@@ -19,7 +19,7 @@ public class HaE implements BurpExtension {
|
||||
public void initialize(MontoyaApi api) {
|
||||
// 设置扩展名称
|
||||
api.extension().setName("HaE - Highlighter and Extractor");
|
||||
String version = "4.1.2";
|
||||
String version = "4.3.1";
|
||||
|
||||
// 加载扩展后输出的项目信息
|
||||
Logging logging = api.logging();
|
||||
@@ -34,13 +34,13 @@ public class HaE implements BurpExtension {
|
||||
MessageTableModel messageTableModel = new MessageTableModel(api, configLoader);
|
||||
|
||||
// 设置BurpSuite专业版状态
|
||||
Config.proVersionStatus = getBurpSuiteProStatus(api, configLoader, messageTableModel);
|
||||
Config.proVersionStatus = getBurpSuiteProStatus(api);
|
||||
|
||||
// 注册Tab页(用于查询数据)
|
||||
api.userInterface().registerSuiteTab("HaE", new Main(api, configLoader, messageTableModel));
|
||||
|
||||
// 注册WebSocket处理器
|
||||
api.proxy().registerWebSocketCreationHandler(proxyWebSocketCreation -> proxyWebSocketCreation.proxyWebSocket().registerProxyMessageHandler(new WebSocketMessageHandler(api)));
|
||||
api.proxy().registerWebSocketCreationHandler(proxyWebSocketCreation -> proxyWebSocketCreation.proxyWebSocket().registerProxyMessageHandler(new WebSocketMessageHandler(api, configLoader)));
|
||||
|
||||
// 注册消息编辑框(用于展示数据)
|
||||
api.userInterface().registerHttpRequestEditorProvider(new RequestEditor(api, configLoader));
|
||||
@@ -51,24 +51,19 @@ public class HaE implements BurpExtension {
|
||||
DataManager dataManager = new DataManager(api);
|
||||
dataManager.loadData(messageTableModel);
|
||||
|
||||
|
||||
api.extension().registerUnloadingHandler(() -> {
|
||||
// 卸载清空数据
|
||||
Config.globalDataMap.clear();
|
||||
MessageCache.clear();
|
||||
DataCache.clear();
|
||||
});
|
||||
}
|
||||
|
||||
private Boolean getBurpSuiteProStatus(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) {
|
||||
private Boolean getBurpSuiteProStatus(MontoyaApi api) {
|
||||
boolean burpSuiteProStatus = false;
|
||||
|
||||
try {
|
||||
burpSuiteProStatus = api.burpSuite().version().name().contains("Professional");
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
api.scanner().registerScanCheck(new HttpMessagePassiveHandler(api, configLoader, messageTableModel)).deregister();
|
||||
burpSuiteProStatus = true;
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
burpSuiteProStatus = api.burpSuite().version().edition() == BurpSuiteEdition.PROFESSIONAL;
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
return burpSuiteProStatus;
|
||||
|
||||
@@ -6,7 +6,7 @@ import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class MessageCache {
|
||||
public class DataCache {
|
||||
private static final int MAX_SIZE = 100000;
|
||||
private static final int EXPIRE_DURATION = 4;
|
||||
|
||||
@@ -32,6 +32,8 @@ public class Config extends JPanel {
|
||||
private Registration activeHandler;
|
||||
private Registration passiveHandler;
|
||||
|
||||
private boolean isLoadingData = false;
|
||||
|
||||
public Config(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel, Rules rules) {
|
||||
this.api = api;
|
||||
this.configLoader = configLoader;
|
||||
@@ -73,7 +75,7 @@ public class Config extends JPanel {
|
||||
constraints.gridx = 1;
|
||||
JTabbedPane configTabbedPanel = new JTabbedPane();
|
||||
|
||||
String[] settingMode = new String[]{"Exclude suffix", "Block host", "Exclude status"};
|
||||
String[] settingMode = new String[]{"Exclude suffix", "Block host", "Exclude status", "Dynamic Header"};
|
||||
JPanel settingPanel = createConfigTablePanel(settingMode);
|
||||
|
||||
JPanel northPanel = new JPanel(new BorderLayout());
|
||||
@@ -174,26 +176,38 @@ public class Config extends JPanel {
|
||||
|
||||
private TableModelListener craeteSettingTableModelListener(JComboBox<String> setTypeComboBox, DefaultTableModel model) {
|
||||
return e -> {
|
||||
// 如果是程序正在加载数据,不处理事件
|
||||
if (isLoadingData) {
|
||||
return;
|
||||
}
|
||||
|
||||
String selected = (String) setTypeComboBox.getSelectedItem();
|
||||
String values = getFirstColumnDataAsString(model);
|
||||
|
||||
if (selected != null) {
|
||||
if (selected.equals("Exclude suffix")) {
|
||||
if (!values.equals(configLoader.getExcludeSuffix()) && !values.isEmpty()) {
|
||||
if (!values.equals(configLoader.getExcludeSuffix())) {
|
||||
configLoader.setExcludeSuffix(values);
|
||||
}
|
||||
}
|
||||
|
||||
if (selected.equals("Block host")) {
|
||||
if (!values.equals(configLoader.getBlockHost()) && !values.isEmpty()) {
|
||||
if (!values.equals(configLoader.getBlockHost())) {
|
||||
configLoader.setBlockHost(values);
|
||||
}
|
||||
}
|
||||
|
||||
if (selected.equals("Exclude status")) {
|
||||
if (!values.equals(configLoader.getExcludeStatus()) && !values.isEmpty()) {
|
||||
if (!values.equals(configLoader.getExcludeStatus())) {
|
||||
configLoader.setExcludeStatus(values);
|
||||
}
|
||||
}
|
||||
|
||||
if (selected.equals("Dynamic Header")) {
|
||||
if (!values.equals(configLoader.getExcludeStatus())) {
|
||||
configLoader.setDynamicHeader(values);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -201,7 +215,11 @@ public class Config extends JPanel {
|
||||
private ActionListener createSettingActionListener(JComboBox<String> setTypeComboBox, DefaultTableModel model) {
|
||||
return e -> {
|
||||
String selected = (String) setTypeComboBox.getSelectedItem();
|
||||
|
||||
// 设置标志,表示正在加载数据
|
||||
isLoadingData = true;
|
||||
model.setRowCount(0);
|
||||
|
||||
if (selected != null) {
|
||||
if (selected.equals("Exclude suffix")) {
|
||||
addDataToTable(configLoader.getExcludeSuffix().replaceAll("\\|", "\r\n"), model);
|
||||
@@ -214,7 +232,14 @@ public class Config extends JPanel {
|
||||
if (selected.equals("Exclude status")) {
|
||||
addDataToTable(configLoader.getExcludeStatus().replaceAll("\\|", "\r\n"), model);
|
||||
}
|
||||
|
||||
if (selected.equals("Dynamic Header")) {
|
||||
addDataToTable(configLoader.getDynamicHeader().replaceAll("\\|", "\r\n"), model);
|
||||
}
|
||||
}
|
||||
|
||||
// 重置标志
|
||||
isLoadingData = false;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import hae.component.board.Databoard;
|
||||
import hae.component.board.message.MessageTableModel;
|
||||
import hae.component.rule.Rules;
|
||||
import hae.utils.ConfigLoader;
|
||||
import hae.utils.UIEnhancer;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
@@ -36,7 +37,7 @@ public class Main extends JPanel {
|
||||
|
||||
// 新增Logo
|
||||
JTabbedPane HaETabbedPane = new JTabbedPane();
|
||||
boolean isDarkBg = isDarkBg(HaETabbedPane);
|
||||
boolean isDarkBg = UIEnhancer.isDarkColor(HaETabbedPane.getBackground());
|
||||
HaETabbedPane.addTab("", getImageIcon(isDarkBg), mainTabbedPane);
|
||||
// 中文Slogan:赋能白帽,高效作战
|
||||
HaETabbedPane.addTab(" Highlighter and Extractor - Empower ethical hacker for efficient operations. ", null);
|
||||
@@ -44,7 +45,7 @@ public class Main extends JPanel {
|
||||
HaETabbedPane.addPropertyChangeListener("background", new PropertyChangeListener() {
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent e) {
|
||||
boolean isDarkBg = isDarkBg(HaETabbedPane);
|
||||
boolean isDarkBg = UIEnhancer.isDarkColor(HaETabbedPane.getBackground());
|
||||
HaETabbedPane.setIconAt(0, getImageIcon(isDarkBg));
|
||||
}
|
||||
});
|
||||
@@ -60,16 +61,6 @@ public class Main extends JPanel {
|
||||
mainTabbedPane.addTab("Config", new Config(api, configLoader, messageTableModel, 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) {
|
||||
ClassLoader classLoader = getClass().getClassLoader();
|
||||
URL imageURL;
|
||||
|
||||
@@ -2,7 +2,7 @@ package hae.component.board;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import hae.Config;
|
||||
import hae.cache.MessageCache;
|
||||
import hae.cache.DataCache;
|
||||
import hae.component.board.message.MessageTableModel;
|
||||
import hae.component.board.message.MessageTableModel.MessageTable;
|
||||
import hae.component.board.table.Datatable;
|
||||
@@ -18,8 +18,9 @@ import javax.swing.table.TableModel;
|
||||
import javax.swing.table.TableRowSorter;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.util.List;
|
||||
import java.text.Collator;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -35,7 +36,7 @@ public class Databoard extends JPanel {
|
||||
private JSplitPane splitPane;
|
||||
private MessageTable messageTable;
|
||||
private JProgressBar progressBar;
|
||||
private SwingWorker<Map<String, List<String>>, Void> handleComboBoxWorker;
|
||||
private SwingWorker<Map<String, List<String>>, Integer> handleComboBoxWorker;
|
||||
private SwingWorker<Void, Void> applyHostFilterWorker;
|
||||
|
||||
public Databoard(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) {
|
||||
@@ -125,16 +126,16 @@ public class Databoard extends JPanel {
|
||||
columnModel.getColumn(5).setPreferredWidth((int) (totalWidth * 0.1));
|
||||
}
|
||||
|
||||
private void setProgressBar(boolean status) {
|
||||
progressBar.setIndeterminate(status);
|
||||
if (!status) {
|
||||
progressBar.setMaximum(100);
|
||||
progressBar.setString("OK");
|
||||
progressBar.setStringPainted(true);
|
||||
private void setProgressBar(boolean status, String message, int progress) {
|
||||
progressBar.setIndeterminate(status && progress <= 0);
|
||||
progressBar.setString(message);
|
||||
progressBar.setStringPainted(true);
|
||||
progressBar.setMaximum(100);
|
||||
|
||||
if (progress > 0) {
|
||||
progressBar.setValue(progress);
|
||||
} else if (!status) {
|
||||
progressBar.setValue(progressBar.getMaximum());
|
||||
} else {
|
||||
progressBar.setString("Loading...");
|
||||
progressBar.setStringPainted(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,54 +174,15 @@ public class Databoard extends JPanel {
|
||||
String selectedHost = hostComboBox.getSelectedItem().toString();
|
||||
|
||||
if (getHostByList().contains(selectedHost)) {
|
||||
progressBar.setVisible(true);
|
||||
setProgressBar(true);
|
||||
hostTextField.setText(selectedHost);
|
||||
hostComboBox.setPopupVisible(false);
|
||||
|
||||
if (handleComboBoxWorker != null && !handleComboBoxWorker.isDone()) {
|
||||
progressBar.setVisible(false);
|
||||
handleComboBoxWorker.cancel(true);
|
||||
}
|
||||
|
||||
handleComboBoxWorker = new SwingWorker<>() {
|
||||
@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);
|
||||
|
||||
setProgressBar(false);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
handleComboBoxWorker = new DataLoadingWorker(selectedHost);
|
||||
|
||||
handleComboBoxWorker.execute();
|
||||
}
|
||||
@@ -251,33 +213,57 @@ public class Databoard extends JPanel {
|
||||
isMatchHost = false;
|
||||
}
|
||||
|
||||
private Map<String, List<String>> getSelectedMapByHost(String selectedHost) {
|
||||
private Map<String, List<String>> getSelectedMapByHost(String selectedHost, DataLoadingWorker worker) {
|
||||
ConcurrentHashMap<String, Map<String, List<String>>> dataMap = Config.globalDataMap;
|
||||
Map<String, List<String>> selectedDataMap;
|
||||
|
||||
if (selectedHost.contains("*")) {
|
||||
selectedDataMap = new HashMap<>();
|
||||
dataMap.keySet().forEach(key -> {
|
||||
List<String> matchingKeys = new ArrayList<>();
|
||||
|
||||
// 第一步:找出所有匹配的键(预处理)
|
||||
for (String key : dataMap.keySet()) {
|
||||
if ((StringProcessor.matchesHostPattern(key, selectedHost) || selectedHost.equals("*")) && !key.contains("*")) {
|
||||
Map<String, List<String>> ruleMap = dataMap.get(key);
|
||||
matchingKeys.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
// 第二步:分批处理数据
|
||||
int totalKeys = matchingKeys.size();
|
||||
for (int i = 0; i < totalKeys; i++) {
|
||||
String key = matchingKeys.get(i);
|
||||
Map<String, List<String>> ruleMap = dataMap.get(key);
|
||||
|
||||
if (ruleMap != null) {
|
||||
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去重
|
||||
HashSet<String> uniqueSet = new HashSet<>(mergedList);
|
||||
selectedDataMap.put(ruleKey, new ArrayList<>(uniqueSet));
|
||||
} else {
|
||||
selectedDataMap.put(ruleKey, dataList);
|
||||
selectedDataMap.put(ruleKey, new ArrayList<>(dataList));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 报告进度
|
||||
if (worker != null && i % 5 == 0) {
|
||||
int progress = (int) ((i + 1) * 90.0 / totalKeys);
|
||||
worker.publishProgress(progress);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
selectedDataMap = dataMap.get(selectedHost);
|
||||
// 对于非通配符匹配,直接返回结果
|
||||
if (worker != null) {
|
||||
worker.publishProgress(90);
|
||||
}
|
||||
}
|
||||
|
||||
return selectedDataMap;
|
||||
return selectedDataMap != null ? selectedDataMap : new HashMap<>();
|
||||
}
|
||||
|
||||
private void filterComboBoxList() {
|
||||
@@ -345,7 +331,11 @@ public class Databoard extends JPanel {
|
||||
}
|
||||
|
||||
private void clearCacheActionPerformed(ActionEvent e) {
|
||||
MessageCache.clear();
|
||||
int retCode = JOptionPane.showConfirmDialog(this, "Do you want to clear cache?", "Info",
|
||||
JOptionPane.YES_NO_OPTION);
|
||||
if (retCode == JOptionPane.YES_OPTION) {
|
||||
DataCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void clearDataActionPerformed(ActionEvent e) {
|
||||
@@ -391,4 +381,87 @@ public class Databoard extends JPanel {
|
||||
hostTextField.setText("");
|
||||
}
|
||||
}
|
||||
|
||||
// 定义为内部类
|
||||
private class DataLoadingWorker extends SwingWorker<Map<String, List<String>>, Integer> {
|
||||
private final String selectedHost;
|
||||
|
||||
public DataLoadingWorker(String selectedHost) {
|
||||
this.selectedHost = selectedHost;
|
||||
progressBar.setVisible(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, List<String>> doInBackground() throws Exception {
|
||||
return getSelectedMapByHost(selectedHost, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void process(List<Integer> chunks) {
|
||||
if (!chunks.isEmpty()) {
|
||||
int progress = chunks.get(chunks.size() - 1);
|
||||
setProgressBar(true, "Loading... " + progress + "%", progress);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
if (!isCancelled()) {
|
||||
try {
|
||||
Map<String, List<String>> selectedDataMap = get();
|
||||
if (selectedDataMap != null && !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);
|
||||
insertTabSorted(dataTabbedPane, tabTitle, datatablePanel);
|
||||
}
|
||||
|
||||
JSplitPane messageSplitPane = messageTableModel.getSplitPane();
|
||||
splitPane.setLeftComponent(dataTabbedPane);
|
||||
splitPane.setRightComponent(messageSplitPane);
|
||||
messageTable = messageTableModel.getMessageTable();
|
||||
resizePanel();
|
||||
|
||||
splitPane.setVisible(true);
|
||||
|
||||
applyHostFilter(selectedHost);
|
||||
setProgressBar(false, "OK", 100);
|
||||
} else {
|
||||
setProgressBar(false, "Error", 0);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
api.logging().logToOutput("DataLoadingWorker: " + e.getMessage());
|
||||
setProgressBar(false, "Error", 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void insertTabSorted(JTabbedPane tabbedPane, String title, Component component) {
|
||||
int insertIndex = 0;
|
||||
int tabCount = tabbedPane.getTabCount();
|
||||
|
||||
// 使用 Collator 实现更友好的语言排序(支持中文、特殊字符等)
|
||||
Collator collator = Collator.getInstance(Locale.getDefault());
|
||||
collator.setStrength(Collator.PRIMARY); // 忽略大小写和重音
|
||||
|
||||
for (int i = 0; i < tabCount; i++) {
|
||||
String existingTitle = tabbedPane.getTitleAt(i);
|
||||
if (collator.compare(existingTitle, title) > 0) {
|
||||
insertIndex = i;
|
||||
break;
|
||||
}
|
||||
insertIndex = i + 1;
|
||||
}
|
||||
|
||||
tabbedPane.insertTab(title, null, component, null, insertIndex);
|
||||
}
|
||||
|
||||
// 提供一个公共方法来发布进度
|
||||
public void publishProgress(int progress) {
|
||||
publish(progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ public class MessageRenderer extends DefaultTableCellRenderer {
|
||||
this.colorMap.put("pink", new Color(0xFF, 0xC8, 0xC8));
|
||||
this.colorMap.put("magenta", new Color(0xFF, 0x64, 0xFF));
|
||||
this.colorMap.put("gray", new Color(0xB4, 0xB4, 0xB4));
|
||||
this.colorMap.put("none", new Color(0, 0, 0, 0));
|
||||
this.table = table;
|
||||
}
|
||||
|
||||
@@ -33,17 +34,29 @@ public class MessageRenderer extends DefaultTableCellRenderer {
|
||||
boolean hasFocus, int row, int column) {
|
||||
Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
|
||||
MessageEntry messageEntry = log.get(table.convertRowIndexToModel(row)); // 使用convertRowIndexToModel方法转换行索引
|
||||
// 添加边界检查以防止IndexOutOfBoundsException
|
||||
int modelRow = table.convertRowIndexToModel(row);
|
||||
if (modelRow < 0 || modelRow >= log.size()) {
|
||||
// 如果索引无效,返回默认渲染组件(使用默认背景色)
|
||||
component.setBackground(Color.WHITE);
|
||||
component.setForeground(Color.BLACK);
|
||||
return component;
|
||||
}
|
||||
|
||||
MessageEntry messageEntry = log.get(modelRow);
|
||||
|
||||
// 设置颜色
|
||||
String colorByLog = messageEntry.getColor();
|
||||
Color color = colorMap.get(colorByLog);
|
||||
|
||||
// 如果颜色映射中没有找到对应颜色,使用默认白色
|
||||
if (color == null) {
|
||||
color = Color.WHITE;
|
||||
}
|
||||
|
||||
if (isSelected) {
|
||||
// 通过更改RGB颜色来达成阴影效果
|
||||
component.setBackground(new Color(color.getRed() - 0x20, color.getGreen() - 0x20, color.getBlue() - 0x20));
|
||||
component.setBackground(UIManager.getColor("Table.selectionBackground"));
|
||||
} else {
|
||||
// 否则使用原始颜色
|
||||
component.setBackground(color);
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,6 @@ public class MessageTableModel extends AbstractTableModel {
|
||||
messageTable.setDefaultRenderer(Object.class, new MessageRenderer(filteredLog, messageTable));
|
||||
messageTable.setAutoCreateRowSorter(true);
|
||||
|
||||
// Length字段根据大小进行排序
|
||||
TableRowSorter<DefaultTableModel> sorter = getDefaultTableModelTableRowSorter();
|
||||
messageTable.setRowSorter(sorter);
|
||||
messageTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
|
||||
@@ -71,6 +70,8 @@ public class MessageTableModel extends AbstractTableModel {
|
||||
|
||||
private TableRowSorter<DefaultTableModel> getDefaultTableModelTableRowSorter() {
|
||||
TableRowSorter<DefaultTableModel> sorter = (TableRowSorter<DefaultTableModel>) messageTable.getRowSorter();
|
||||
|
||||
// Length字段根据大小进行排序
|
||||
sorter.setComparator(4, (Comparator<String>) (s1, s2) -> {
|
||||
Integer age1 = Integer.parseInt(s1);
|
||||
Integer age2 = Integer.parseInt(s2);
|
||||
@@ -104,6 +105,14 @@ public class MessageTableModel extends AbstractTableModel {
|
||||
return;
|
||||
}
|
||||
|
||||
if (comment == null || comment.trim().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (color == null || color.trim().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isDuplicate = false;
|
||||
try {
|
||||
if (!log.isEmpty() && flag) {
|
||||
@@ -241,131 +250,163 @@ public class MessageTableModel extends AbstractTableModel {
|
||||
}
|
||||
|
||||
public void applyHostFilter(String filterText) {
|
||||
filteredLog.clear();
|
||||
fireTableDataChanged();
|
||||
// 预分配合适的容量,避免频繁扩容
|
||||
final List<MessageEntry> newFilteredLog = new ArrayList<>(log.size() / 2);
|
||||
|
||||
// 预处理过滤条件,优化性能
|
||||
final boolean isWildcardFilter = "*".equals(filterText) || filterText.contains("*");
|
||||
final String normalizedFilter = filterText.toLowerCase().trim();
|
||||
|
||||
// 创建log的安全副本
|
||||
final List<MessageEntry> logSnapshot;
|
||||
synchronized (log) {
|
||||
logSnapshot = new ArrayList<>(log);
|
||||
}
|
||||
|
||||
int batchSize = 500;
|
||||
|
||||
// 分批处理数据
|
||||
List<MessageEntry> batch = new ArrayList<>(batchSize);
|
||||
int count = 0;
|
||||
|
||||
for (MessageEntry entry : log) {
|
||||
String host = StringProcessor.getHostByUrl(entry.getUrl());
|
||||
if (!host.isEmpty() && (StringProcessor.matchesHostPattern(host, filterText) || filterText.contains("*"))) {
|
||||
batch.add(entry);
|
||||
count++;
|
||||
|
||||
// 当批次达到指定大小时,更新UI
|
||||
if (count % batchSize == 0) {
|
||||
final List<MessageEntry> currentBatch = new ArrayList<>(batch);
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
filteredLog.addAll(currentBatch);
|
||||
fireTableDataChanged();
|
||||
});
|
||||
batch.clear();
|
||||
// 使用并行流高效过滤,但保持有序
|
||||
logSnapshot.parallelStream()
|
||||
.filter(entry -> {
|
||||
// 快速通配符检查
|
||||
if (isWildcardFilter && "*".equals(filterText)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
String host = StringProcessor.getHostByUrl(entry.getUrl());
|
||||
if (host.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 优化后的匹配逻辑
|
||||
return StringProcessor.matchesHostPattern(host, filterText) ||
|
||||
(isWildcardFilter && host.toLowerCase().contains(normalizedFilter.replace("*", "")));
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.forEachOrdered(newFilteredLog::add);
|
||||
|
||||
// 处理最后一批
|
||||
if (!batch.isEmpty()) {
|
||||
final List<MessageEntry> finalBatch = new ArrayList<>(batch);
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
filteredLog.addAll(finalBatch);
|
||||
fireTableDataChanged();
|
||||
});
|
||||
}
|
||||
// 一次性更新UI,避免频繁刷新
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
synchronized (filteredLog) {
|
||||
filteredLog.clear();
|
||||
filteredLog.addAll(newFilteredLog);
|
||||
}
|
||||
fireTableDataChanged();
|
||||
});
|
||||
}
|
||||
|
||||
public void applyMessageFilter(String tableName, String filterText) {
|
||||
filteredLog.clear();
|
||||
for (MessageEntry entry : log) {
|
||||
List<MessageEntry> newFilteredLog = new ArrayList<>();
|
||||
|
||||
// 创建log的安全副本以避免ConcurrentModificationException
|
||||
List<MessageEntry> logSnapshot;
|
||||
synchronized (log) {
|
||||
logSnapshot = new ArrayList<>(log);
|
||||
}
|
||||
|
||||
for (MessageEntry entry : logSnapshot) {
|
||||
// 标志变量,表示是否满足过滤条件
|
||||
AtomicBoolean isMatched = new AtomicBoolean(false);
|
||||
|
||||
HttpRequestResponse requestResponse = entry.getRequestResponse();
|
||||
HttpRequest httpRequest = requestResponse.request();
|
||||
HttpResponse httpResponse = requestResponse.response();
|
||||
try {
|
||||
HttpRequestResponse requestResponse = entry.getRequestResponse();
|
||||
HttpRequest httpRequest = requestResponse.request();
|
||||
HttpResponse httpResponse = requestResponse.response();
|
||||
|
||||
String requestString = new String(httpRequest.toByteArray().getBytes(), StandardCharsets.UTF_8);
|
||||
String requestBody = new String(httpRequest.body().getBytes(), StandardCharsets.UTF_8);
|
||||
String requestHeaders = httpRequest.headers().stream()
|
||||
.map(HttpHeader::toString)
|
||||
.collect(Collectors.joining("\n"));
|
||||
String requestString = new String(httpRequest.toByteArray().getBytes(), StandardCharsets.UTF_8);
|
||||
String requestBody = new String(httpRequest.body().getBytes(), StandardCharsets.UTF_8);
|
||||
String requestHeaders = httpRequest.headers().stream()
|
||||
.map(HttpHeader::toString)
|
||||
.collect(Collectors.joining("\r\n"));
|
||||
|
||||
String responseString = new String(httpResponse.toByteArray().getBytes(), StandardCharsets.UTF_8);
|
||||
String responseBody = new String(httpResponse.body().getBytes(), StandardCharsets.UTF_8);
|
||||
String responseHeaders = httpResponse.headers().stream()
|
||||
.map(HttpHeader::toString)
|
||||
.collect(Collectors.joining("\n"));
|
||||
String responseString = new String(httpResponse.toByteArray().getBytes(), StandardCharsets.UTF_8);
|
||||
String responseBody = new String(httpResponse.body().getBytes(), StandardCharsets.UTF_8);
|
||||
String responseHeaders = httpResponse.headers().stream()
|
||||
.map(HttpHeader::toString)
|
||||
.collect(Collectors.joining("\r\n"));
|
||||
|
||||
Config.globalRules.keySet().forEach(i -> {
|
||||
for (Object[] objects : Config.globalRules.get(i)) {
|
||||
String name = objects[1].toString();
|
||||
String format = objects[4].toString();
|
||||
String scope = objects[6].toString();
|
||||
Config.globalRules.keySet().forEach(i -> {
|
||||
for (Object[] objects : Config.globalRules.get(i)) {
|
||||
String name = objects[1].toString();
|
||||
String format = objects[4].toString();
|
||||
String scope = objects[6].toString();
|
||||
|
||||
// 从注释中查看是否包含当前规则名,包含的再进行查询,有效减少无意义的检索时间
|
||||
if (entry.getComment().contains(name)) {
|
||||
if (name.equals(tableName)) {
|
||||
// 标志变量,表示当前规则是否匹配
|
||||
boolean isMatch = false;
|
||||
// 从注释中查看是否包含当前规则名,包含的再进行查询,有效减少无意义的检索时间
|
||||
if (entry.getComment().contains(name)) {
|
||||
if (name.equals(tableName)) {
|
||||
// 标志变量,表示当前规则是否匹配
|
||||
boolean isMatch = false;
|
||||
|
||||
switch (scope) {
|
||||
case "any":
|
||||
isMatch = matchingString(format, filterText, requestString) || matchingString(format, filterText, responseString);
|
||||
break;
|
||||
case "request":
|
||||
isMatch = matchingString(format, filterText, requestString);
|
||||
break;
|
||||
case "response":
|
||||
isMatch = matchingString(format, filterText, responseString);
|
||||
break;
|
||||
case "any header":
|
||||
isMatch = matchingString(format, filterText, requestHeaders) || matchingString(format, filterText, responseHeaders);
|
||||
break;
|
||||
case "request header":
|
||||
isMatch = matchingString(format, filterText, requestHeaders);
|
||||
break;
|
||||
case "response header":
|
||||
isMatch = matchingString(format, filterText, responseHeaders);
|
||||
break;
|
||||
case "any body":
|
||||
isMatch = matchingString(format, filterText, requestBody) || matchingString(format, filterText, responseBody);
|
||||
break;
|
||||
case "request body":
|
||||
isMatch = matchingString(format, filterText, requestBody);
|
||||
break;
|
||||
case "response body":
|
||||
isMatch = matchingString(format, filterText, responseBody);
|
||||
break;
|
||||
case "request line":
|
||||
String requestLine = requestString.split("\\r?\\n", 2)[0];
|
||||
isMatch = matchingString(format, filterText, requestLine);
|
||||
break;
|
||||
case "response line":
|
||||
String responseLine = responseString.split("\\r?\\n", 2)[0];
|
||||
isMatch = matchingString(format, filterText, responseLine);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
switch (scope) {
|
||||
case "any":
|
||||
isMatch = matchingString(format, filterText, requestString) || matchingString(format, filterText, responseString);
|
||||
break;
|
||||
case "request":
|
||||
isMatch = matchingString(format, filterText, requestString);
|
||||
break;
|
||||
case "response":
|
||||
isMatch = matchingString(format, filterText, responseString);
|
||||
break;
|
||||
case "any header":
|
||||
isMatch = matchingString(format, filterText, requestHeaders) || matchingString(format, filterText, responseHeaders);
|
||||
break;
|
||||
case "request header":
|
||||
isMatch = matchingString(format, filterText, requestHeaders);
|
||||
break;
|
||||
case "response header":
|
||||
isMatch = matchingString(format, filterText, responseHeaders);
|
||||
break;
|
||||
case "any body":
|
||||
isMatch = matchingString(format, filterText, requestBody) || matchingString(format, filterText, responseBody);
|
||||
break;
|
||||
case "request body":
|
||||
isMatch = matchingString(format, filterText, requestBody);
|
||||
break;
|
||||
case "response body":
|
||||
isMatch = matchingString(format, filterText, responseBody);
|
||||
break;
|
||||
case "request line":
|
||||
String requestLine = requestString.split("\\r?\\n", 2)[0];
|
||||
isMatch = matchingString(format, filterText, requestLine);
|
||||
break;
|
||||
case "response line":
|
||||
String responseLine = responseString.split("\\r?\\n", 2)[0];
|
||||
isMatch = matchingString(format, filterText, responseLine);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
isMatched.set(isMatch);
|
||||
break;
|
||||
}
|
||||
|
||||
isMatched.set(isMatch);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 由于每个用户规则不同,如果进行项目文件共享则需要考虑全部匹配一下
|
||||
if (!isMatched.get()) {
|
||||
isMatched.set(matchingString("{0}", filterText, requestString) || matchingString("{0}", filterText, responseString));
|
||||
}
|
||||
|
||||
if (isMatched.get()) {
|
||||
newFilteredLog.add(entry);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
|
||||
if (isMatched.get()) {
|
||||
filteredLog.add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
fireTableDataChanged();
|
||||
messageTable.lastSelectedIndex = -1;
|
||||
// 在EDT线程中更新UI
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
synchronized (filteredLog) {
|
||||
filteredLog.clear();
|
||||
filteredLog.addAll(newFilteredLog);
|
||||
}
|
||||
fireTableDataChanged();
|
||||
messageTable.lastSelectedIndex = -1;
|
||||
});
|
||||
}
|
||||
|
||||
private boolean matchingString(String format, String filterText, String target) {
|
||||
@@ -398,7 +439,9 @@ public class MessageTableModel extends AbstractTableModel {
|
||||
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return filteredLog.size();
|
||||
synchronized (filteredLog) {
|
||||
return filteredLog.size();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -408,27 +451,31 @@ public class MessageTableModel extends AbstractTableModel {
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex) {
|
||||
if (!filteredLog.isEmpty()) {
|
||||
synchronized (filteredLog) {
|
||||
if (rowIndex < 0 || rowIndex >= filteredLog.size()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
try {
|
||||
MessageEntry messageEntry = filteredLog.get(rowIndex);
|
||||
|
||||
if (messageEntry != null) {
|
||||
return switch (columnIndex) {
|
||||
case 0 -> messageEntry.getMethod();
|
||||
case 1 -> messageEntry.getUrl();
|
||||
case 2 -> messageEntry.getComment();
|
||||
case 3 -> messageEntry.getStatus();
|
||||
case 4 -> messageEntry.getLength();
|
||||
case 5 -> messageEntry.getColor();
|
||||
default -> "";
|
||||
};
|
||||
if (messageEntry == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return switch (columnIndex) {
|
||||
case 0 -> messageEntry.getMethod();
|
||||
case 1 -> messageEntry.getUrl();
|
||||
case 2 -> messageEntry.getComment();
|
||||
case 3 -> messageEntry.getStatus();
|
||||
case 4 -> messageEntry.getLength();
|
||||
case 5 -> messageEntry.getColor();
|
||||
default -> "";
|
||||
};
|
||||
} catch (Exception e) {
|
||||
api.logging().logToError("getValueAt: " + e.getMessage());
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -162,17 +162,17 @@ public class Datatable extends JPanel {
|
||||
}
|
||||
|
||||
private void performSearch() {
|
||||
RowFilter<Object, Object> firstRowFilter = getObjectObjectRowFilter(searchField, true);
|
||||
RowFilter<Object, Object> secondRowFilter = getObjectObjectRowFilter(secondSearchField, false);
|
||||
if (searchField.getForeground().equals(Color.BLACK)) {
|
||||
sorter.setRowFilter(firstRowFilter);
|
||||
if (secondSearchField.getForeground().equals(Color.BLACK)) {
|
||||
List<RowFilter<Object, Object>> filters = new ArrayList<>();
|
||||
filters.add(firstRowFilter);
|
||||
filters.add(secondRowFilter);
|
||||
sorter.setRowFilter(RowFilter.andFilter(filters));
|
||||
}
|
||||
List<RowFilter<Object, Object>> filters = new ArrayList<>();
|
||||
|
||||
if (UIEnhancer.hasUserInput(searchField)) {
|
||||
filters.add(getObjectObjectRowFilter(searchField, true));
|
||||
}
|
||||
|
||||
if (UIEnhancer.hasUserInput(secondSearchField)) {
|
||||
filters.add(getObjectObjectRowFilter(secondSearchField, false));
|
||||
}
|
||||
|
||||
sorter.setRowFilter(filters.isEmpty() ? null : RowFilter.andFilter(filters));
|
||||
}
|
||||
|
||||
private RowFilter<Object, Object> getObjectObjectRowFilter(JTextField searchField, boolean firstFlag) {
|
||||
|
||||
@@ -7,9 +7,13 @@ import hae.utils.rule.RuleProcessor;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.table.DefaultTableModel;
|
||||
import javax.swing.table.JTableHeader;
|
||||
import javax.swing.table.TableCellRenderer;
|
||||
import javax.swing.table.TableRowSorter;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.Vector;
|
||||
|
||||
import static javax.swing.JOptionPane.YES_OPTION;
|
||||
@@ -19,6 +23,7 @@ public class Rule extends JPanel {
|
||||
private final ConfigLoader configLoader;
|
||||
private final RuleProcessor ruleProcessor;
|
||||
private final JTabbedPane tabbedPane;
|
||||
private JCheckBox headerCheckBox;
|
||||
|
||||
public Rule(MontoyaApi api, ConfigLoader configLoader, Object[][] data, JTabbedPane tabbedPane) {
|
||||
this.api = api;
|
||||
@@ -36,6 +41,7 @@ public class Rule extends JPanel {
|
||||
((GridBagLayout) getLayout()).columnWeights = new double[]{0.0, 1.0, 1.0E-4};
|
||||
((GridBagLayout) getLayout()).rowWeights = new double[]{0.0, 0.0, 0.0, 1.0, 1.0E-4};
|
||||
|
||||
JButton copyButton = new JButton("Copy");
|
||||
JButton addButton = new JButton("Add");
|
||||
JButton editButton = new JButton("Edit");
|
||||
JButton removeButton = new JButton("Remove");
|
||||
@@ -43,14 +49,13 @@ public class Rule extends JPanel {
|
||||
JTable ruleTable = new JTable();
|
||||
JScrollPane scrollPane = new JScrollPane();
|
||||
|
||||
ruleTable.setShowVerticalLines(false);
|
||||
ruleTable.setShowHorizontalLines(false);
|
||||
ruleTable.setVerifyInputWhenFocusTarget(false);
|
||||
ruleTable.setUpdateSelectionOnSort(false);
|
||||
ruleTable.setSurrendersFocusOnKeystroke(true);
|
||||
scrollPane.setViewportView(ruleTable);
|
||||
|
||||
// 按钮监听事件
|
||||
copyButton.addActionListener(e -> ruleCopyActionPerformed(e, ruleTable, tabbedPane));
|
||||
addButton.addActionListener(e -> ruleAddActionPerformed(e, ruleTable, tabbedPane));
|
||||
editButton.addActionListener(e -> ruleEditActionPerformed(e, ruleTable, tabbedPane));
|
||||
removeButton.addActionListener(e -> ruleRemoveActionPerformed(e, ruleTable, tabbedPane));
|
||||
@@ -76,88 +81,334 @@ public class Rule extends JPanel {
|
||||
if (e.getColumn() == 0 && ruleTable.getSelectedRow() != -1) {
|
||||
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
|
||||
ruleProcessor.changeRule(model.getDataVector().get(select), select, tabbedPane.getTitleAt(tabbedPane.getSelectedIndex()));
|
||||
|
||||
// 更新表头复选框状态并强制重新渲染
|
||||
updateHeaderCheckBoxState(model);
|
||||
ruleTable.getTableHeader().repaint();
|
||||
}
|
||||
});
|
||||
|
||||
add(addButton, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
|
||||
// 设置表头复选框
|
||||
setupHeaderCheckBox(ruleTable);
|
||||
|
||||
// 设置Loaded列的宽度(第一列)
|
||||
setupColumnWidths(ruleTable);
|
||||
|
||||
GridBagConstraints constraints = new GridBagConstraints();
|
||||
constraints.weightx = 1.0;
|
||||
constraints.fill = GridBagConstraints.HORIZONTAL;
|
||||
|
||||
JPanel buttonPanel = new JPanel();
|
||||
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);
|
||||
|
||||
constraints.insets = new Insets(0, 0, 3, 0);
|
||||
constraints.gridy = 0;
|
||||
buttonPanel.add(copyButton, constraints);
|
||||
constraints.gridy = 1;
|
||||
buttonPanel.add(addButton, constraints);
|
||||
constraints.gridy = 2;
|
||||
buttonPanel.add(editButton, constraints);
|
||||
constraints.gridy = 3;
|
||||
buttonPanel.add(removeButton, constraints);
|
||||
|
||||
add(buttonPanel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(15, 5, 3, 2), 0, 0));
|
||||
add(editButton, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(0, 5, 3, 2), 0, 0));
|
||||
add(removeButton, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(0, 5, 3, 2), 0, 0));
|
||||
add(scrollPane, new GridBagConstraints(1, 0, 1, 4, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(15, 5, 5, 5), 0, 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置列宽度
|
||||
*/
|
||||
private void setupColumnWidths(JTable ruleTable) {
|
||||
// 设置Loaded列(第一列)的宽度
|
||||
ruleTable.getColumnModel().getColumn(0).setPreferredWidth(50);
|
||||
ruleTable.getColumnModel().getColumn(0).setMaxWidth(50);
|
||||
ruleTable.getColumnModel().getColumn(0).setMinWidth(50);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表头复选框
|
||||
*/
|
||||
private void setupHeaderCheckBox(JTable ruleTable) {
|
||||
// 创建表头复选框
|
||||
headerCheckBox = new JCheckBox();
|
||||
headerCheckBox.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
|
||||
// 设置表头渲染器
|
||||
ruleTable.getTableHeader().setDefaultRenderer(new HeaderCheckBoxRenderer(ruleTable.getTableHeader().getDefaultRenderer()));
|
||||
|
||||
// 添加表头鼠标点击事件
|
||||
ruleTable.getTableHeader().addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (e.getClickCount() == 1) {
|
||||
JTableHeader header = (JTableHeader) e.getSource();
|
||||
JTable table = header.getTable();
|
||||
int columnIndex = header.columnAtPoint(e.getPoint());
|
||||
|
||||
if (columnIndex == 0) { // 点击的是Loaded列表头
|
||||
toggleAllRules(table);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义表头渲染器,在Loaded列显示复选框
|
||||
*/
|
||||
private class HeaderCheckBoxRenderer implements TableCellRenderer {
|
||||
private final TableCellRenderer originalRenderer;
|
||||
|
||||
public HeaderCheckBoxRenderer(TableCellRenderer originalRenderer) {
|
||||
this.originalRenderer = originalRenderer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
||||
if (column == 0) { // Loaded列
|
||||
// 获取原始表头组件作为背景
|
||||
Component originalComponent = originalRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
|
||||
// 创建一个面板来包含复选框,保持原始样式
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
panel.setOpaque(true);
|
||||
|
||||
// 复制原始组件的样式
|
||||
if (originalComponent instanceof JComponent origComp) {
|
||||
panel.setBackground(origComp.getBackground());
|
||||
panel.setBorder(origComp.getBorder());
|
||||
}
|
||||
|
||||
// 更新复选框状态并添加到面板中心
|
||||
updateHeaderCheckBoxState((DefaultTableModel) table.getModel());
|
||||
headerCheckBox.setOpaque(false); // 让复选框透明,显示背景
|
||||
panel.add(headerCheckBox, BorderLayout.CENTER);
|
||||
|
||||
return panel;
|
||||
} else {
|
||||
return originalRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换所有规则的开启/关闭状态
|
||||
*/
|
||||
private void toggleAllRules(JTable ruleTable) {
|
||||
DefaultTableModel model = (DefaultTableModel) ruleTable.getModel();
|
||||
int rowCount = model.getRowCount();
|
||||
|
||||
if (rowCount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 判断当前状态:如果所有规则都开启,则关闭所有;否则开启所有
|
||||
boolean allEnabled = true;
|
||||
for (int i = 0; i < rowCount; i++) {
|
||||
if (!(Boolean) model.getValueAt(i, 0)) {
|
||||
allEnabled = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
boolean newState = !allEnabled;
|
||||
|
||||
// 更新所有行的状态
|
||||
for (int i = 0; i < rowCount; i++) {
|
||||
model.setValueAt(newState, i, 0);
|
||||
// 通知规则处理器更新规则状态
|
||||
ruleProcessor.changeRule(model.getDataVector().get(i), i, getCurrentTabTitle());
|
||||
}
|
||||
|
||||
// 更新表头复选框状态
|
||||
updateHeaderCheckBoxState(model);
|
||||
|
||||
// 刷新表格和表头
|
||||
ruleTable.repaint();
|
||||
ruleTable.getTableHeader().repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新表头复选框的状态
|
||||
*/
|
||||
private void updateHeaderCheckBoxState(DefaultTableModel model) {
|
||||
int rowCount = model.getRowCount();
|
||||
if (rowCount == 0) {
|
||||
headerCheckBox.setSelected(false);
|
||||
headerCheckBox.getModel().setArmed(false);
|
||||
headerCheckBox.getModel().setPressed(false);
|
||||
return;
|
||||
}
|
||||
|
||||
int enabledCount = 0;
|
||||
for (int i = 0; i < rowCount; i++) {
|
||||
if ((Boolean) model.getValueAt(i, 0)) {
|
||||
enabledCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (enabledCount == 0) {
|
||||
// 全部未选中
|
||||
headerCheckBox.setSelected(false);
|
||||
headerCheckBox.getModel().setArmed(false);
|
||||
headerCheckBox.getModel().setPressed(false);
|
||||
} else if (enabledCount == rowCount) {
|
||||
// 全部选中
|
||||
headerCheckBox.setSelected(true);
|
||||
headerCheckBox.getModel().setArmed(false);
|
||||
headerCheckBox.getModel().setPressed(false);
|
||||
} else {
|
||||
// 部分选中 - 显示为按下但未选中的状态
|
||||
headerCheckBox.setSelected(false);
|
||||
headerCheckBox.getModel().setArmed(true);
|
||||
headerCheckBox.getModel().setPressed(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充Display对象的字段值
|
||||
*/
|
||||
private void populateDisplayFromTable(Display ruleDisplay, JTable ruleTable, int selectedRow) {
|
||||
ruleDisplay.ruleNameTextField.setText(ruleTable.getValueAt(selectedRow, 1).toString());
|
||||
ruleDisplay.firstRegexTextField.setText(ruleTable.getValueAt(selectedRow, 2).toString());
|
||||
ruleDisplay.secondRegexTextField.setText(ruleTable.getValueAt(selectedRow, 3).toString());
|
||||
ruleDisplay.formatTextField.setText(ruleTable.getValueAt(selectedRow, 4).toString());
|
||||
ruleDisplay.colorComboBox.setSelectedItem(ruleTable.getValueAt(selectedRow, 5).toString());
|
||||
ruleDisplay.scopeComboBox.setSelectedItem(ruleTable.getValueAt(selectedRow, 6).toString());
|
||||
ruleDisplay.engineComboBox.setSelectedItem(ruleTable.getValueAt(selectedRow, 7).toString());
|
||||
ruleDisplay.sensitiveComboBox.setSelectedItem(ruleTable.getValueAt(selectedRow, 8));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Display对象创建规则数据Vector
|
||||
*/
|
||||
private Vector<Object> createRuleDataFromDisplay(Display ruleDisplay) {
|
||||
Vector<Object> ruleData = new Vector<>();
|
||||
ruleData.add(false);
|
||||
ruleData.add(ruleDisplay.ruleNameTextField.getText());
|
||||
ruleData.add(ruleDisplay.firstRegexTextField.getText());
|
||||
ruleData.add(ruleDisplay.secondRegexTextField.getText());
|
||||
ruleData.add(ruleDisplay.formatTextField.getText());
|
||||
ruleData.add(ruleDisplay.colorComboBox.getSelectedItem().toString());
|
||||
ruleData.add(ruleDisplay.scopeComboBox.getSelectedItem().toString());
|
||||
ruleData.add(ruleDisplay.engineComboBox.getSelectedItem().toString());
|
||||
ruleData.add(ruleDisplay.sensitiveComboBox.getSelectedItem());
|
||||
return ruleData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示规则编辑对话框
|
||||
*/
|
||||
private boolean showRuleDialog(Display ruleDisplay, String title) {
|
||||
ruleDisplay.formatTextField.setEnabled(ruleDisplay.engineComboBox.getSelectedItem().toString().equals("nfa"));
|
||||
int showState = JOptionPane.showConfirmDialog(this, ruleDisplay, title, JOptionPane.YES_NO_OPTION);
|
||||
return showState == YES_OPTION;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有选中的行
|
||||
*/
|
||||
private boolean hasSelectedRow(JTable ruleTable) {
|
||||
return ruleTable.getSelectedRowCount() >= 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前选中的Tab标题
|
||||
*/
|
||||
private String getCurrentTabTitle() {
|
||||
return tabbedPane.getTitleAt(tabbedPane.getSelectedIndex());
|
||||
}
|
||||
|
||||
private void ruleCopyActionPerformed(ActionEvent e, JTable ruleTable, JTabbedPane tabbedPane) {
|
||||
if (!hasSelectedRow(ruleTable)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Display ruleDisplay = new Display();
|
||||
int selectedRow = ruleTable.getSelectedRow();
|
||||
|
||||
populateDisplayFromTable(ruleDisplay, ruleTable, selectedRow);
|
||||
// 为复制的规则名称添加前缀
|
||||
ruleDisplay.ruleNameTextField.setText(String.format("Copy of %s", ruleDisplay.ruleNameTextField.getText()));
|
||||
|
||||
if (showRuleDialog(ruleDisplay, "Copy Rule")) {
|
||||
Vector<Object> ruleData = createRuleDataFromDisplay(ruleDisplay);
|
||||
DefaultTableModel model = (DefaultTableModel) ruleTable.getModel();
|
||||
model.insertRow(model.getRowCount(), ruleData);
|
||||
ruleProcessor.addRule(ruleData, getCurrentTabTitle());
|
||||
|
||||
// 复制规则后更新表头复选框状态
|
||||
updateHeaderCheckBoxState(model);
|
||||
ruleTable.getTableHeader().repaint();
|
||||
}
|
||||
}
|
||||
|
||||
private void ruleAddActionPerformed(ActionEvent e, JTable ruleTable, JTabbedPane tabbedPane) {
|
||||
Display ruleDisplay = new Display();
|
||||
ruleDisplay.formatTextField.setText("{0}");
|
||||
|
||||
int showState = JOptionPane.showConfirmDialog(this, ruleDisplay, "Add Rule", JOptionPane.YES_NO_OPTION);
|
||||
if (showState == YES_OPTION) {
|
||||
Vector<Object> ruleData = new Vector<>();
|
||||
ruleData.add(false);
|
||||
ruleData.add(ruleDisplay.ruleNameTextField.getText());
|
||||
ruleData.add(ruleDisplay.firstRegexTextField.getText());
|
||||
ruleData.add(ruleDisplay.secondRegexTextField.getText());
|
||||
ruleData.add(ruleDisplay.formatTextField.getText());
|
||||
ruleData.add(ruleDisplay.colorComboBox.getSelectedItem().toString());
|
||||
ruleData.add(ruleDisplay.scopeComboBox.getSelectedItem().toString());
|
||||
ruleData.add(ruleDisplay.engineComboBox.getSelectedItem().toString());
|
||||
ruleData.add(ruleDisplay.sensitiveComboBox.getSelectedItem());
|
||||
|
||||
if (showRuleDialog(ruleDisplay, "Add Rule")) {
|
||||
Vector<Object> ruleData = createRuleDataFromDisplay(ruleDisplay);
|
||||
DefaultTableModel model = (DefaultTableModel) ruleTable.getModel();
|
||||
model.insertRow(model.getRowCount(), ruleData);
|
||||
ruleProcessor.addRule(ruleData, tabbedPane.getTitleAt(tabbedPane.getSelectedIndex()));
|
||||
ruleProcessor.addRule(ruleData, getCurrentTabTitle());
|
||||
|
||||
// 添加规则后更新表头复选框状态
|
||||
updateHeaderCheckBoxState(model);
|
||||
ruleTable.getTableHeader().repaint();
|
||||
}
|
||||
}
|
||||
|
||||
private void ruleEditActionPerformed(ActionEvent e, JTable ruleTable, JTabbedPane tabbedPane) {
|
||||
if (ruleTable.getSelectedRowCount() >= 1) {
|
||||
if (!hasSelectedRow(ruleTable)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Display ruleDisplay = new Display();
|
||||
int selectedRow = ruleTable.getSelectedRow();
|
||||
|
||||
populateDisplayFromTable(ruleDisplay, ruleTable, selectedRow);
|
||||
|
||||
if (showRuleDialog(ruleDisplay, "Edit Rule")) {
|
||||
DefaultTableModel model = (DefaultTableModel) ruleTable.getModel();
|
||||
Display ruleDisplay = new Display();
|
||||
int modelIndex = ruleTable.convertRowIndexToModel(selectedRow);
|
||||
|
||||
ruleDisplay.ruleNameTextField.setText(ruleTable.getValueAt(ruleTable.getSelectedRow(), 1).toString());
|
||||
ruleDisplay.firstRegexTextField.setText(ruleTable.getValueAt(ruleTable.getSelectedRow(), 2).toString());
|
||||
ruleDisplay.secondRegexTextField.setText(ruleTable.getValueAt(ruleTable.getSelectedRow(), 3).toString());
|
||||
ruleDisplay.formatTextField.setText(ruleTable.getValueAt(ruleTable.getSelectedRow(), 4).toString());
|
||||
ruleDisplay.colorComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(), 5).toString());
|
||||
ruleDisplay.scopeComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(), 6).toString());
|
||||
ruleDisplay.engineComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(), 7).toString());
|
||||
ruleDisplay.sensitiveComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(), 8));
|
||||
|
||||
ruleDisplay.formatTextField.setEnabled(ruleDisplay.engineComboBox.getSelectedItem().toString().equals("nfa"));
|
||||
|
||||
int showState = JOptionPane.showConfirmDialog(this, ruleDisplay, "Edit Rule", JOptionPane.YES_NO_OPTION);
|
||||
if (showState == 0) {
|
||||
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
|
||||
model.setValueAt(ruleDisplay.ruleNameTextField.getText(), select, 1);
|
||||
model.setValueAt(ruleDisplay.firstRegexTextField.getText(), select, 2);
|
||||
model.setValueAt(ruleDisplay.secondRegexTextField.getText(), select, 3);
|
||||
model.setValueAt(ruleDisplay.formatTextField.getText(), select, 4);
|
||||
model.setValueAt(ruleDisplay.colorComboBox.getSelectedItem().toString(), select, 5);
|
||||
model.setValueAt(ruleDisplay.scopeComboBox.getSelectedItem().toString(), select, 6);
|
||||
model.setValueAt(ruleDisplay.engineComboBox.getSelectedItem().toString(), select, 7);
|
||||
model.setValueAt(ruleDisplay.sensitiveComboBox.getSelectedItem(), select, 8);
|
||||
model = (DefaultTableModel) ruleTable.getModel();
|
||||
ruleProcessor.changeRule(model.getDataVector().get(select), select, tabbedPane.getTitleAt(tabbedPane.getSelectedIndex()));
|
||||
// 更新表格数据
|
||||
Vector<Object> ruleData = createRuleDataFromDisplay(ruleDisplay);
|
||||
for (int i = 1; i < ruleData.size(); i++) {
|
||||
model.setValueAt(ruleData.get(i), modelIndex, i);
|
||||
}
|
||||
|
||||
ruleProcessor.changeRule(model.getDataVector().get(modelIndex), modelIndex, getCurrentTabTitle());
|
||||
|
||||
// 编辑规则后更新表头复选框状态(如果编辑影响了启用状态)
|
||||
updateHeaderCheckBoxState(model);
|
||||
ruleTable.getTableHeader().repaint();
|
||||
}
|
||||
}
|
||||
|
||||
private void ruleRemoveActionPerformed(ActionEvent e, JTable ruleTable, JTabbedPane tabbedPane) {
|
||||
if (ruleTable.getSelectedRowCount() >= 1) {
|
||||
if (JOptionPane.showConfirmDialog(this, "Are you sure you want to remove this rule?", "Info", JOptionPane.YES_NO_OPTION) == 0) {
|
||||
DefaultTableModel model = (DefaultTableModel) ruleTable.getModel();
|
||||
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
|
||||
if (!hasSelectedRow(ruleTable)) {
|
||||
return;
|
||||
}
|
||||
|
||||
model.removeRow(select);
|
||||
ruleProcessor.removeRule(select, tabbedPane.getTitleAt(tabbedPane.getSelectedIndex()));
|
||||
}
|
||||
if (JOptionPane.showConfirmDialog(this, "Are you sure you want to remove this rule?", "Info", JOptionPane.YES_NO_OPTION) == 0) {
|
||||
DefaultTableModel model = (DefaultTableModel) ruleTable.getModel();
|
||||
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
|
||||
|
||||
model.removeRow(select);
|
||||
ruleProcessor.removeRule(select, getCurrentTabTitle());
|
||||
|
||||
// 删除规则后更新表头复选框状态
|
||||
updateHeaderCheckBoxState(model);
|
||||
ruleTable.getTableHeader().repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,7 @@ public class RequestEditor implements HttpRequestEditorProvider {
|
||||
this.configLoader = configLoader;
|
||||
this.httpUtils = new HttpUtils(api, configLoader);
|
||||
this.creationContext = creationContext;
|
||||
this.messageProcessor = new MessageProcessor(api);
|
||||
this.messageProcessor = new MessageProcessor(api, configLoader);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -50,7 +50,7 @@ public class ResponseEditor implements HttpResponseEditorProvider {
|
||||
this.configLoader = configLoader;
|
||||
this.httpUtils = new HttpUtils(api, configLoader);
|
||||
this.creationContext = creationContext;
|
||||
this.messageProcessor = new MessageProcessor(api);
|
||||
this.messageProcessor = new MessageProcessor(api, configLoader);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -44,7 +44,7 @@ public class WebSocketEditor implements WebSocketMessageEditorProvider {
|
||||
this.api = api;
|
||||
this.configLoader = configLoader;
|
||||
this.creationContext = creationContext;
|
||||
this.messageProcessor = new MessageProcessor(api);
|
||||
this.messageProcessor = new MessageProcessor(api, configLoader);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -35,7 +35,7 @@ public class HttpMessageActiveHandler implements HttpHandler {
|
||||
this.configLoader = configLoader;
|
||||
this.httpUtils = new HttpUtils(api, configLoader);
|
||||
this.messageTableModel = messageTableModel;
|
||||
this.messageProcessor = new MessageProcessor(api);
|
||||
this.messageProcessor = new MessageProcessor(api, configLoader);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -37,7 +37,7 @@ public class HttpMessagePassiveHandler implements ScanCheck {
|
||||
this.configLoader = configLoader;
|
||||
this.httpUtils = new HttpUtils(api, configLoader);
|
||||
this.messageTableModel = messageTableModel;
|
||||
this.messageProcessor = new MessageProcessor(api);
|
||||
this.messageProcessor = new MessageProcessor(api, configLoader);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -5,6 +5,7 @@ import burp.api.montoya.http.message.HttpHeader;
|
||||
import burp.api.montoya.http.message.requests.HttpRequest;
|
||||
import burp.api.montoya.http.message.responses.HttpResponse;
|
||||
import hae.Config;
|
||||
import hae.utils.ConfigLoader;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
@@ -14,18 +15,16 @@ public class MessageProcessor {
|
||||
private final MontoyaApi api;
|
||||
private final RegularMatcher regularMatcher;
|
||||
|
||||
private String finalColor = "";
|
||||
|
||||
public MessageProcessor(MontoyaApi api) {
|
||||
public MessageProcessor(MontoyaApi api, ConfigLoader configLoader) {
|
||||
this.api = api;
|
||||
this.regularMatcher = new RegularMatcher(api);
|
||||
this.regularMatcher = new RegularMatcher(api, configLoader);
|
||||
}
|
||||
|
||||
public List<Map<String, String>> processMessage(String host, String message, boolean flag) {
|
||||
Map<String, Map<String, Object>> obj = null;
|
||||
|
||||
try {
|
||||
obj = regularMatcher.match(host, "any", message, message, message);
|
||||
obj = regularMatcher.performRegexMatching(host, "any", message, message, message);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
@@ -40,9 +39,9 @@ public class MessageProcessor {
|
||||
String body = new String(httpResponse.body().getBytes(), StandardCharsets.UTF_8);
|
||||
String header = httpResponse.headers().stream()
|
||||
.map(HttpHeader::toString)
|
||||
.collect(Collectors.joining("\n"));
|
||||
.collect(Collectors.joining("\r\n"));
|
||||
|
||||
obj = regularMatcher.match(host, "response", response, header, body);
|
||||
obj = regularMatcher.performRegexMatching(host, "response", response, header, body);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
@@ -57,9 +56,9 @@ public class MessageProcessor {
|
||||
String body = new String(httpRequest.body().getBytes(), StandardCharsets.UTF_8);
|
||||
String header = httpRequest.headers().stream()
|
||||
.map(HttpHeader::toString)
|
||||
.collect(Collectors.joining("\n"));
|
||||
.collect(Collectors.joining("\r\n"));
|
||||
|
||||
obj = regularMatcher.match(host, "request", request, header, body);
|
||||
obj = regularMatcher.performRegexMatching(host, "request", request, header, body);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
@@ -137,41 +136,38 @@ public class MessageProcessor {
|
||||
return indices;
|
||||
}
|
||||
|
||||
private void upgradeColors(List<Integer> colorList) {
|
||||
int colorSize = colorList.size();
|
||||
String[] colorArray = Config.color;
|
||||
colorList.sort(Comparator.comparingInt(Integer::intValue));
|
||||
int i = 0;
|
||||
List<Integer> stack = new ArrayList<>();
|
||||
while (i < colorSize) {
|
||||
if (stack.isEmpty()) {
|
||||
stack.add(colorList.get(i));
|
||||
} else {
|
||||
if (!Objects.equals(colorList.get(i), stack.stream().reduce((first, second) -> second).orElse(99999999))) {
|
||||
stack.add(colorList.get(i));
|
||||
} else {
|
||||
stack.set(stack.size() - 1, stack.get(stack.size() - 1) - 1);
|
||||
}
|
||||
}
|
||||
i++;
|
||||
private String upgradeColors(List<Integer> colorList) {
|
||||
if (colorList == null || colorList.isEmpty()) {
|
||||
return Config.color[0];
|
||||
}
|
||||
// 利用HashSet删除重复元素
|
||||
HashSet tmpList = new HashSet(stack);
|
||||
if (stack.size() == tmpList.size()) {
|
||||
stack.sort(Comparator.comparingInt(Integer::intValue));
|
||||
if (stack.get(0) < 0) {
|
||||
finalColor = colorArray[0];
|
||||
} else {
|
||||
finalColor = colorArray[stack.get(0)];
|
||||
|
||||
// 创建副本避免修改原始数据
|
||||
List<Integer> indices = new ArrayList<>(colorList);
|
||||
indices.sort(Comparator.comparingInt(Integer::intValue));
|
||||
|
||||
// 处理颜色升级
|
||||
for (int i = 1; i < indices.size(); i++) {
|
||||
if (indices.get(i).equals(indices.get(i - 1))) {
|
||||
// 如果发现重复的颜色索引,将当前索引降级
|
||||
indices.set(i - 1, indices.get(i - 1) - 1);
|
||||
}
|
||||
} else {
|
||||
upgradeColors(stack);
|
||||
}
|
||||
|
||||
// 获取最终的颜色索引
|
||||
int finalIndex = indices.stream()
|
||||
.min(Integer::compareTo)
|
||||
.orElse(0);
|
||||
|
||||
// 处理负数索引情况
|
||||
if (finalIndex < 0) {
|
||||
return Config.color[0];
|
||||
}
|
||||
|
||||
return Config.color[finalIndex];
|
||||
}
|
||||
|
||||
public String retrieveFinalColor(List<Integer> colorList) {
|
||||
upgradeColors(colorList);
|
||||
return finalColor;
|
||||
return upgradeColors(colorList);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@ import dk.brics.automaton.AutomatonMatcher;
|
||||
import dk.brics.automaton.RegExp;
|
||||
import dk.brics.automaton.RunAutomaton;
|
||||
import hae.Config;
|
||||
import hae.cache.MessageCache;
|
||||
import hae.cache.DataCache;
|
||||
import hae.utils.ConfigLoader;
|
||||
import hae.utils.DataManager;
|
||||
import hae.utils.string.HashCalculator;
|
||||
import hae.utils.string.StringProcessor;
|
||||
@@ -20,14 +21,18 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class RegularMatcher {
|
||||
private static final Map<String, Pattern> nfaPatternCache = new ConcurrentHashMap<>();
|
||||
private static final Map<String, RunAutomaton> dfaAutomatonCache = new ConcurrentHashMap<>();
|
||||
private static final Pattern formatIndexPattern = Pattern.compile("\\{(\\d+)}");
|
||||
private final MontoyaApi api;
|
||||
private final ConfigLoader configLoader;
|
||||
|
||||
public RegularMatcher(MontoyaApi api) {
|
||||
public RegularMatcher(MontoyaApi api, ConfigLoader configLoader) {
|
||||
this.api = api;
|
||||
|
||||
this.configLoader = configLoader;
|
||||
}
|
||||
|
||||
public synchronized static void putDataToGlobalMap(MontoyaApi api, String host, String name, List<String> dataList, boolean flag) {
|
||||
public synchronized static void updateGlobalMatchCache(MontoyaApi api, String host, String name, List<String> dataList, boolean flag) {
|
||||
// 添加到全局变量中,便于Databoard检索
|
||||
if (!Objects.equals(host, "") && host != null) {
|
||||
Config.globalDataMap.compute(host, (existingHost, existingMap) -> {
|
||||
@@ -74,95 +79,123 @@ public class RegularMatcher {
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Map<String, Object>> match(String host, String type, String message, String header, String body) {
|
||||
// 先从缓存池里判断是否有已经匹配好的结果
|
||||
String messageIndex = HashCalculator.calculateHash(message.getBytes());
|
||||
Map<String, Map<String, Object>> map = MessageCache.get(messageIndex);
|
||||
if (map != null) {
|
||||
return map;
|
||||
} else {
|
||||
// 最终返回的结果
|
||||
Map<String, Map<String, Object>> finalMap = new HashMap<>();
|
||||
Config.globalRules.keySet().parallelStream().forEach(i -> {
|
||||
for (Object[] objects : Config.globalRules.get(i)) {
|
||||
// 多线程执行,一定程度上减少阻塞现象
|
||||
String matchContent = "";
|
||||
// 遍历获取规则
|
||||
List<String> result;
|
||||
Map<String, Object> tmpMap = new HashMap<>();
|
||||
public Map<String, Map<String, Object>> performRegexMatching(String host, String type, String message, String header, String body) {
|
||||
// 删除动态响应头再进行存储
|
||||
String originalMessage = message;
|
||||
String dynamicHeader = configLoader.getDynamicHeader();
|
||||
|
||||
boolean loaded = (Boolean) objects[0];
|
||||
String name = objects[1].toString();
|
||||
String f_regex = objects[2].toString();
|
||||
String s_regex = objects[3].toString();
|
||||
String format = objects[4].toString();
|
||||
String color = objects[5].toString();
|
||||
String scope = objects[6].toString();
|
||||
String engine = objects[7].toString();
|
||||
boolean sensitive = (Boolean) objects[8];
|
||||
|
||||
// 判断规则是否开启与作用域
|
||||
if (loaded && (scope.contains(type) || scope.contains("any") || type.equals("any"))) {
|
||||
switch (scope) {
|
||||
case "any":
|
||||
case "request":
|
||||
case "response":
|
||||
matchContent = message;
|
||||
break;
|
||||
case "any header":
|
||||
case "request header":
|
||||
case "response header":
|
||||
matchContent = header;
|
||||
break;
|
||||
case "any body":
|
||||
case "request body":
|
||||
case "response body":
|
||||
matchContent = body;
|
||||
break;
|
||||
case "request line":
|
||||
case "response line":
|
||||
matchContent = message.split("\\r?\\n", 2)[0];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
result = new ArrayList<>(matchByRegex(f_regex, s_regex, matchContent, format, engine, sensitive));
|
||||
} catch (Exception e) {
|
||||
api.logging().logToError(String.format("[x] Error Info:\nName: %s\nRegex: %s", name, f_regex));
|
||||
api.logging().logToError(e.getMessage());
|
||||
continue;
|
||||
}
|
||||
|
||||
// 去除重复内容
|
||||
HashSet tmpList = new HashSet(result);
|
||||
result.clear();
|
||||
result.addAll(tmpList);
|
||||
|
||||
if (!result.isEmpty()) {
|
||||
tmpMap.put("color", color);
|
||||
String dataStr = String.join(Config.boundary, result);
|
||||
tmpMap.put("data", dataStr);
|
||||
|
||||
String nameAndSize = String.format("%s (%s)", name, result.size());
|
||||
finalMap.put(nameAndSize, tmpMap);
|
||||
|
||||
putDataToGlobalMap(api, host, name, result, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
MessageCache.put(messageIndex, finalMap);
|
||||
return finalMap;
|
||||
if (!dynamicHeader.isBlank()) {
|
||||
String modifiedHeader = header.replaceAll(String.format("(%s):.*?\r\n", configLoader.getDynamicHeader()), "");
|
||||
message = message.replace(header, modifiedHeader);
|
||||
}
|
||||
|
||||
String messageIndex = HashCalculator.calculateHash(message.getBytes());
|
||||
|
||||
// 从数据缓存中读取
|
||||
Map<String, Map<String, Object>> dataCacheMap = DataCache.get(messageIndex);
|
||||
|
||||
// 存在则返回
|
||||
if (dataCacheMap != null) {
|
||||
return dataCacheMap;
|
||||
}
|
||||
|
||||
// 最终返回的结果
|
||||
String firstLine = originalMessage.split("\\r?\\n", 2)[0];
|
||||
Map<String, Map<String, Object>> finalMap = applyMatchingRules(host, type, originalMessage, firstLine, header, body);
|
||||
|
||||
// 数据缓存写入,有可能是空值,当作匹配过的索引不再匹配
|
||||
DataCache.put(messageIndex, finalMap);
|
||||
|
||||
return finalMap;
|
||||
}
|
||||
|
||||
private List<String> matchByRegex(String f_regex, String s_regex, String content, String format, String engine, boolean sensitive) {
|
||||
private Map<String, Map<String, Object>> applyMatchingRules(String host, String type, String message, String firstLine, String header, String body) {
|
||||
Map<String, Map<String, Object>> finalMap = new HashMap<>();
|
||||
|
||||
Config.globalRules.keySet().parallelStream().forEach(i -> {
|
||||
for (Object[] objects : Config.globalRules.get(i)) {
|
||||
String matchContent = "";
|
||||
// 遍历获取规则
|
||||
List<String> result;
|
||||
Map<String, Object> tmpMap = new HashMap<>();
|
||||
|
||||
boolean loaded = (Boolean) objects[0];
|
||||
String name = objects[1].toString();
|
||||
String f_regex = objects[2].toString();
|
||||
String s_regex = objects[3].toString();
|
||||
String format = objects[4].toString();
|
||||
String color = objects[5].toString();
|
||||
String scope = objects[6].toString();
|
||||
String engine = objects[7].toString();
|
||||
boolean sensitive = (Boolean) objects[8];
|
||||
|
||||
// 判断规则是否开启与作用域
|
||||
if (loaded && (scope.contains(type) || scope.contains("any") || type.equals("any"))) {
|
||||
// 在此处检查内容是否缓存,缓存则返回为空
|
||||
switch (scope) {
|
||||
case "any":
|
||||
case "request":
|
||||
case "response":
|
||||
matchContent = message;
|
||||
break;
|
||||
case "any header":
|
||||
case "request header":
|
||||
case "response header":
|
||||
matchContent = header;
|
||||
break;
|
||||
case "any body":
|
||||
case "request body":
|
||||
case "response body":
|
||||
matchContent = body;
|
||||
break;
|
||||
case "request line":
|
||||
case "response line":
|
||||
matchContent = firstLine;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// 匹配内容为空则跳出
|
||||
if (matchContent.isBlank()) {
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
result = new ArrayList<>(executeRegexEngine(f_regex, s_regex, matchContent, format, engine, sensitive));
|
||||
} catch (Exception e) {
|
||||
api.logging().logToError(String.format("[x] Error Info:\nName: %s\nRegex: %s", name, f_regex));
|
||||
api.logging().logToError(e.getMessage());
|
||||
continue;
|
||||
}
|
||||
|
||||
// 去除重复内容
|
||||
HashSet tmpList = new HashSet(result);
|
||||
result.clear();
|
||||
result.addAll(tmpList);
|
||||
|
||||
if (!result.isEmpty()) {
|
||||
tmpMap.put("color", color);
|
||||
String dataStr = String.join(Config.boundary, result);
|
||||
tmpMap.put("data", dataStr);
|
||||
|
||||
String nameAndSize = String.format("%s (%s)", name, result.size());
|
||||
finalMap.put(nameAndSize, tmpMap);
|
||||
|
||||
updateGlobalMatchCache(api, host, name, result, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return finalMap;
|
||||
}
|
||||
|
||||
private List<String> executeRegexEngine(String f_regex, String s_regex, String content, String format, String engine, boolean sensitive) {
|
||||
List<String> retList = new ArrayList<>();
|
||||
if ("nfa".equals(engine)) {
|
||||
Matcher matcher = createPatternMatcher(f_regex, content, sensitive);
|
||||
retList.addAll(extractMatches(s_regex, format, sensitive, matcher));
|
||||
retList.addAll(extractRegexMatchResults(s_regex, format, sensitive, matcher));
|
||||
} else {
|
||||
// DFA不支持格式化输出,因此不关注format
|
||||
String newContent = content;
|
||||
@@ -172,47 +205,58 @@ public class RegularMatcher {
|
||||
newFirstRegex = f_regex.toLowerCase();
|
||||
}
|
||||
AutomatonMatcher autoMatcher = createAutomatonMatcher(newFirstRegex, newContent);
|
||||
retList.addAll(extractMatches(s_regex, autoMatcher, content));
|
||||
retList.addAll(extractRegexMatchResults(s_regex, autoMatcher, content));
|
||||
}
|
||||
return retList;
|
||||
}
|
||||
|
||||
private List<String> extractMatches(String s_regex, String format, boolean sensitive, Matcher matcher) {
|
||||
private List<String> extractRegexMatchResults(String s_regex, String format, boolean sensitive, Matcher matcher) {
|
||||
List<String> matches = new ArrayList<>();
|
||||
if (s_regex.isEmpty()) {
|
||||
matches.addAll(getFormatString(matcher, format));
|
||||
matches.addAll(formatMatchResults(matcher, format));
|
||||
} else {
|
||||
while (matcher.find()) {
|
||||
String matchContent = matcher.group(1);
|
||||
if (!matchContent.isEmpty()) {
|
||||
matcher = createPatternMatcher(s_regex, matchContent, sensitive);
|
||||
matches.addAll(getFormatString(matcher, format));
|
||||
Matcher secondMatcher = createPatternMatcher(s_regex, matchContent, sensitive);
|
||||
matches.addAll(formatMatchResults(secondMatcher, format));
|
||||
}
|
||||
}
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
private List<String> extractMatches(String s_regex, AutomatonMatcher autoMatcher, String content) {
|
||||
private List<String> extractRegexMatchResults(String s_regex, AutomatonMatcher autoMatcher, String content) {
|
||||
List<String> matches = new ArrayList<>();
|
||||
if (s_regex.isEmpty()) {
|
||||
matches.addAll(getFormatString(autoMatcher, content));
|
||||
matches.addAll(formatMatchResults(autoMatcher, content));
|
||||
} else {
|
||||
while (autoMatcher.find()) {
|
||||
String s = autoMatcher.group();
|
||||
if (!s.isEmpty()) {
|
||||
autoMatcher = createAutomatonMatcher(s_regex, getSubString(content, s));
|
||||
matches.addAll(getFormatString(autoMatcher, content));
|
||||
autoMatcher = createAutomatonMatcher(s_regex, extractMatchedContent(content, s));
|
||||
matches.addAll(formatMatchResults(autoMatcher, content));
|
||||
}
|
||||
}
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
private List<String> getFormatString(Matcher matcher, String format) {
|
||||
List<Integer> indexList = parseIndexesFromString(format);
|
||||
private List<String> formatMatchResults(Matcher matcher, String format) {
|
||||
List<String> stringList = new ArrayList<>();
|
||||
|
||||
// 当format为{0}时,直接返回第一个捕获组,避免格式化开销
|
||||
if ("{0}".equals(format)) {
|
||||
while (matcher.find()) {
|
||||
if (matcher.groupCount() > 0 && !matcher.group(1).isEmpty()) {
|
||||
stringList.add(matcher.group(1));
|
||||
}
|
||||
}
|
||||
return stringList;
|
||||
}
|
||||
|
||||
// 需要复杂格式化的情况
|
||||
List<Integer> indexList = parseIndexesFromString(format);
|
||||
while (matcher.find()) {
|
||||
if (!matcher.group(1).isEmpty()) {
|
||||
Object[] params = indexList.stream().map(i -> {
|
||||
@@ -222,20 +266,20 @@ public class RegularMatcher {
|
||||
return "";
|
||||
}).toArray();
|
||||
|
||||
stringList.add(MessageFormat.format(reorderIndex(format), params));
|
||||
stringList.add(MessageFormat.format(normalizeFormatIndexes(format), params));
|
||||
}
|
||||
}
|
||||
|
||||
return stringList;
|
||||
}
|
||||
|
||||
private List<String> getFormatString(AutomatonMatcher matcher, String content) {
|
||||
private List<String> formatMatchResults(AutomatonMatcher matcher, String content) {
|
||||
List<String> stringList = new ArrayList<>();
|
||||
|
||||
while (matcher.find()) {
|
||||
String s = matcher.group(0);
|
||||
if (!s.isEmpty()) {
|
||||
stringList.add(getSubString(content, s));
|
||||
stringList.add(extractMatchedContent(content, s));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,21 +287,27 @@ public class RegularMatcher {
|
||||
}
|
||||
|
||||
private Matcher createPatternMatcher(String regex, String content, boolean sensitive) {
|
||||
Pattern pattern = sensitive ? Pattern.compile(regex) : Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
|
||||
Pattern pattern = nfaPatternCache.computeIfAbsent(regex, k -> {
|
||||
int flags = sensitive ? 0 : Pattern.CASE_INSENSITIVE;
|
||||
return Pattern.compile(regex, flags);
|
||||
});
|
||||
|
||||
return pattern.matcher(content);
|
||||
}
|
||||
|
||||
private AutomatonMatcher createAutomatonMatcher(String regex, String content) {
|
||||
RegExp regexp = new RegExp(regex);
|
||||
Automaton auto = regexp.toAutomaton();
|
||||
RunAutomaton runAuto = new RunAutomaton(auto, true);
|
||||
RunAutomaton runAuto = dfaAutomatonCache.computeIfAbsent(regex, k -> {
|
||||
RegExp regexp = new RegExp(regex);
|
||||
Automaton auto = regexp.toAutomaton();
|
||||
return new RunAutomaton(auto, true);
|
||||
});
|
||||
|
||||
return runAuto.newMatcher(content);
|
||||
}
|
||||
|
||||
private LinkedList<Integer> parseIndexesFromString(String input) {
|
||||
LinkedList<Integer> indexes = new LinkedList<>();
|
||||
Pattern pattern = Pattern.compile("\\{(\\d+)}");
|
||||
Matcher matcher = pattern.matcher(input);
|
||||
Matcher matcher = formatIndexPattern.matcher(input);
|
||||
|
||||
while (matcher.find()) {
|
||||
String index = matcher.group(1);
|
||||
@@ -269,17 +319,17 @@ public class RegularMatcher {
|
||||
return indexes;
|
||||
}
|
||||
|
||||
private String getSubString(String content, String s) {
|
||||
private String extractMatchedContent(String content, String s) {
|
||||
byte[] contentByte = api.utilities().byteUtils().convertFromString(content);
|
||||
byte[] sByte = api.utilities().byteUtils().convertFromString(s);
|
||||
int startIndex = api.utilities().byteUtils().indexOf(contentByte, sByte, false, 1, contentByte.length);
|
||||
int endIndex = startIndex + s.length();
|
||||
|
||||
return content.substring(startIndex, endIndex);
|
||||
}
|
||||
|
||||
private String reorderIndex(String format) {
|
||||
Pattern pattern = Pattern.compile("\\{(\\d+)}");
|
||||
Matcher matcher = pattern.matcher(format);
|
||||
private String normalizeFormatIndexes(String format) {
|
||||
Matcher matcher = formatIndexPattern.matcher(format);
|
||||
int count = 0;
|
||||
while (matcher.find()) {
|
||||
String newStr = String.format("{%s}", count);
|
||||
@@ -287,6 +337,7 @@ public class RegularMatcher {
|
||||
format = format.replace(matchStr, newStr);
|
||||
count++;
|
||||
}
|
||||
|
||||
return format;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import burp.api.montoya.MontoyaApi;
|
||||
import burp.api.montoya.core.HighlightColor;
|
||||
import burp.api.montoya.proxy.websocket.*;
|
||||
import hae.instances.http.utils.MessageProcessor;
|
||||
import hae.utils.ConfigLoader;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -12,9 +13,9 @@ public class WebSocketMessageHandler implements ProxyMessageHandler {
|
||||
private final MontoyaApi api;
|
||||
private final MessageProcessor messageProcessor;
|
||||
|
||||
public WebSocketMessageHandler(MontoyaApi api) {
|
||||
public WebSocketMessageHandler(MontoyaApi api, ConfigLoader configLoader) {
|
||||
this.api = api;
|
||||
this.messageProcessor = new MessageProcessor(api);
|
||||
this.messageProcessor = new MessageProcessor(api, configLoader);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -3,7 +3,9 @@ package hae.utils;
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import hae.Config;
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
import org.yaml.snakeyaml.LoaderOptions;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.constructor.SafeConstructor;
|
||||
import org.yaml.snakeyaml.representer.Representer;
|
||||
|
||||
import java.io.*;
|
||||
@@ -21,10 +23,7 @@ public class ConfigLoader {
|
||||
|
||||
public ConfigLoader(MontoyaApi api) {
|
||||
this.api = api;
|
||||
DumperOptions dop = new DumperOptions();
|
||||
dop.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||
Representer representer = new Representer(dop);
|
||||
this.yaml = new Yaml(representer, dop);
|
||||
this.yaml = createSecureYaml();
|
||||
|
||||
String configPath = determineConfigPath();
|
||||
this.configFilePath = String.format("%s/%s", configPath, "Config.yml");
|
||||
@@ -54,6 +53,25 @@ public class ConfigLoader {
|
||||
return configPathFile.exists() && configPathFile.isDirectory();
|
||||
}
|
||||
|
||||
private Yaml createSecureYaml() {
|
||||
// 配置 LoaderOptions 进行安全限制
|
||||
LoaderOptions loaderOptions = new LoaderOptions();
|
||||
// 禁用注释处理
|
||||
loaderOptions.setProcessComments(false);
|
||||
// 禁止递归键
|
||||
loaderOptions.setAllowRecursiveKeys(false);
|
||||
|
||||
// 配置 DumperOptions 控制输出格式
|
||||
DumperOptions dop = new DumperOptions();
|
||||
dop.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||
|
||||
// 创建 Representer
|
||||
Representer representer = new Representer(dop);
|
||||
|
||||
// 使用 SafeConstructor创建安全的 YAML 实例
|
||||
return new Yaml(new SafeConstructor(loaderOptions), representer, dop);
|
||||
}
|
||||
|
||||
private String determineConfigPath() {
|
||||
// 优先级1:用户根目录
|
||||
String userConfigPath = String.format("%s/.config/HaE", System.getProperty("user.home"));
|
||||
@@ -79,6 +97,8 @@ public class ConfigLoader {
|
||||
r.put("ExcludeStatus", getExcludeStatus());
|
||||
r.put("LimitSize", getLimitSize());
|
||||
r.put("HaEScope", getScope());
|
||||
r.put("DynamicHeader", getDynamicHeader());
|
||||
|
||||
try {
|
||||
Writer ws = new OutputStreamWriter(Files.newOutputStream(Paths.get(configFilePath)), StandardCharsets.UTF_8);
|
||||
yaml.dump(r, ws);
|
||||
@@ -97,10 +117,7 @@ public class ConfigLoader {
|
||||
|
||||
try {
|
||||
InputStream inputStream = Files.newInputStream(Paths.get(getRulesFilePath()));
|
||||
DumperOptions dop = new DumperOptions();
|
||||
dop.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||
Representer representer = new Representer(dop);
|
||||
Map<String, Object> rulesMap = new Yaml(representer, dop).load(inputStream);
|
||||
Map<String, Object> rulesMap = yaml.load(inputStream);
|
||||
|
||||
Object rulesObj = rulesMap.get("rules");
|
||||
if (rulesObj instanceof List) {
|
||||
@@ -156,6 +173,14 @@ public class ConfigLoader {
|
||||
setValueToConfig("ExcludeStatus", status);
|
||||
}
|
||||
|
||||
public String getDynamicHeader() {
|
||||
return getValueFromConfig("DynamicHeader", Config.header);
|
||||
}
|
||||
|
||||
public void setDynamicHeader(String header) {
|
||||
setValueToConfig("DynamicHeader", header);
|
||||
}
|
||||
|
||||
public String getLimitSize() {
|
||||
return getValueFromConfig("LimitSize", Config.size);
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ public class DataManager {
|
||||
dataIndex.forEach(index -> {
|
||||
PersistedObject dataObj = persistence.extensionData().getChildObject(index);
|
||||
try {
|
||||
dataObj.stringListKeys().forEach(dataKey -> RegularMatcher.putDataToGlobalMap(api, index, dataKey, dataObj.getStringList(dataKey).stream().toList(), false));
|
||||
dataObj.stringListKeys().forEach(dataKey -> RegularMatcher.updateGlobalMatchCache(api, index, dataKey, dataObj.getStringList(dataKey).stream().toList(), false));
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
});
|
||||
@@ -114,7 +114,7 @@ public class DataManager {
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
api.logging().logToError("处理消息数据时出错: " + e.getMessage() + ", index: " + index);
|
||||
api.logging().logToError("processBatch: " + e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,38 +7,75 @@ import java.awt.event.FocusListener;
|
||||
|
||||
public class UIEnhancer {
|
||||
public static void setTextFieldPlaceholder(JTextField textField, String placeholderText) {
|
||||
// 使用客户端属性来存储占位符文本和占位符状态
|
||||
// 存储占位符文本
|
||||
textField.putClientProperty("placeholderText", placeholderText);
|
||||
textField.putClientProperty("isPlaceholder", true);
|
||||
|
||||
// 设置占位符文本和颜色
|
||||
setPlaceholderText(textField);
|
||||
updatePlaceholderText(textField);
|
||||
|
||||
textField.addPropertyChangeListener("background", evt -> {
|
||||
updateForeground(textField);
|
||||
});
|
||||
|
||||
textField.addFocusListener(new FocusListener() {
|
||||
@Override
|
||||
public void focusGained(FocusEvent e) {
|
||||
// 当获得焦点且文本是占位符时,清除文本并更改颜色
|
||||
if ((boolean) textField.getClientProperty("isPlaceholder")) {
|
||||
textField.setText("");
|
||||
textField.setForeground(Color.BLACK);
|
||||
if (Boolean.TRUE.equals(textField.getClientProperty("isPlaceholder"))) {
|
||||
textField.putClientProperty("isPlaceholder", false);
|
||||
updateForeground(textField);
|
||||
|
||||
textField.setText("");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void focusLost(FocusEvent e) {
|
||||
// 当失去焦点且文本为空时,设置占位符文本和颜色
|
||||
if (textField.getText().isEmpty()) {
|
||||
setPlaceholderText(textField);
|
||||
updatePlaceholderText(textField);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
textField.addPropertyChangeListener("text", evt -> {
|
||||
if (Boolean.TRUE.equals(textField.getClientProperty("isPlaceholder"))) {
|
||||
if (!textField.getText().isEmpty()) {
|
||||
textField.putClientProperty("isPlaceholder", false);
|
||||
updateForeground(textField);
|
||||
}
|
||||
} else {
|
||||
if (textField.getText().isEmpty()) {
|
||||
updatePlaceholderText(textField);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void setPlaceholderText(JTextField textField) {
|
||||
private static void updatePlaceholderText(JTextField textField) {
|
||||
String placeholderText = (String) textField.getClientProperty("placeholderText");
|
||||
textField.setForeground(Color.GRAY);
|
||||
textField.setText(placeholderText);
|
||||
textField.putClientProperty("isPlaceholder", true);
|
||||
textField.setText(placeholderText);
|
||||
textField.setForeground(Color.GRAY);
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateForeground(JTextField textField) {
|
||||
Color bg = textField.getBackground();
|
||||
Color fg = isDarkColor(bg) ? Color.WHITE : Color.BLACK;
|
||||
|
||||
if (!Boolean.TRUE.equals(textField.getClientProperty("isPlaceholder"))) {
|
||||
textField.setForeground(fg);
|
||||
textField.putClientProperty("isPlaceholder", false);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isDarkColor(Color color) {
|
||||
double brightness = 0.299 * color.getRed()
|
||||
+ 0.587 * color.getGreen()
|
||||
+ 0.114 * color.getBlue();
|
||||
return brightness < 128;
|
||||
}
|
||||
|
||||
public static boolean hasUserInput(JTextField field) {
|
||||
Object prop = field.getClientProperty("isPlaceholder");
|
||||
return prop instanceof Boolean && !((Boolean) prop);
|
||||
}
|
||||
}
|
||||
@@ -25,16 +25,29 @@ public class HttpUtils {
|
||||
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 isBlockHost = false;
|
||||
String blockHost = configLoader.getBlockHost();
|
||||
if (!blockHost.isBlank()) {
|
||||
String[] hostList = configLoader.getBlockHost().split("\\|");
|
||||
isBlockHost = isBlockHost(hostList, host);
|
||||
}
|
||||
|
||||
boolean isExcludeSuffix = false;
|
||||
String suffix = configLoader.getExcludeSuffix();
|
||||
if (!suffix.isBlank()) {
|
||||
List<String> suffixList = Arrays.asList(configLoader.getExcludeSuffix().split("\\|"));
|
||||
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()));
|
||||
boolean isExcludeStatus = false;
|
||||
String status = configLoader.getExcludeStatus();
|
||||
if (!status.isBlank()) {
|
||||
List<String> statusList = Arrays.asList(configLoader.getExcludeStatus().split("\\|"));
|
||||
isExcludeStatus = statusList.contains(String.valueOf(response.statusCode()));
|
||||
}
|
||||
|
||||
retStatus = isExcludeSuffix || isBlockHost || isToolScope || isExcludeStatus;
|
||||
} catch (Exception ignored) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package hae.utils.rule;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import hae.Config;
|
||||
import hae.cache.DataCache;
|
||||
import hae.utils.ConfigLoader;
|
||||
import hae.utils.rule.model.Group;
|
||||
import hae.utils.rule.model.Info;
|
||||
@@ -27,6 +28,8 @@ public class RuleProcessor {
|
||||
}
|
||||
|
||||
public void rulesFormatAndSave() {
|
||||
DataCache.clear();
|
||||
|
||||
DumperOptions dop = new DumperOptions();
|
||||
dop.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||
Representer representer = new Representer(dop);
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
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.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
@@ -64,19 +56,6 @@ public class StringProcessor {
|
||||
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();
|
||||
|
||||
@@ -55,6 +55,15 @@ rules:
|
||||
scope: response body
|
||||
engine: dfa
|
||||
sensitive: false
|
||||
- name: Vite DevMode
|
||||
loaded: true
|
||||
f_regex: (/\@vite/client)
|
||||
s_regex: ''
|
||||
format: '{0}'
|
||||
color: red
|
||||
scope: response body
|
||||
engine: dfa
|
||||
sensitive: true
|
||||
- group: Maybe Vulnerability
|
||||
rule:
|
||||
- name: Java Deserialization
|
||||
@@ -102,12 +111,30 @@ rules:
|
||||
scope: request
|
||||
engine: dfa
|
||||
sensitive: false
|
||||
- name: Passwd File
|
||||
loaded: true
|
||||
f_regex: (/root:/bin/bash)
|
||||
s_regex: ''
|
||||
format: '{0}'
|
||||
color: red
|
||||
scope: response body
|
||||
engine: dfa
|
||||
sensitive: true
|
||||
- name: Win.ini File
|
||||
loaded: true
|
||||
f_regex: (for 16-bit app)
|
||||
s_regex: ''
|
||||
format: '{0}'
|
||||
color: red
|
||||
scope: response body
|
||||
engine: dfa
|
||||
sensitive: true
|
||||
- group: Basic Information
|
||||
rule:
|
||||
- name: Email
|
||||
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}))
|
||||
s_regex: ''
|
||||
f_regex: (\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,5}\b)
|
||||
s_regex: ^((?!.*\.(jpg|jpeg|png|gif|bmp|webp|svg|tiff|ico?)$).*@.*\..*)$
|
||||
format: '{0}'
|
||||
color: yellow
|
||||
scope: response
|
||||
@@ -171,9 +198,9 @@ rules:
|
||||
sensitive: true
|
||||
- name: Password Field
|
||||
loaded: true
|
||||
f_regex: (((|\\)(|'|")(|[\.\w]{1,10})([p](ass|wd|asswd|assword))(|[\.\w]{1,10})(|\\)(|'|")(
|
||||
f_regex: (((|\\)(|'|")(|[\.\w]{1,32})([p](ass|wd|asswd|assword))(|[\.\w]{1,32})(|\\)(|'|")(
|
||||
|)(:|[=]{1,3}|![=]{1,2}|[\)]{0,1}\.val\()( |)(|\\)('|")([^'"]+?)(|\\)('|")(|,|\)))|((|\\)('|")([^'"]+?)(|\\)('|")(|\\)(|'|")(
|
||||
|)(:|[=]{1,3}|![=]{1,2})( |)(|[\.\w]{1,10})([p](ass|wd|asswd|assword))(|[\.\w]{1,10})(|\\)(|'|")))
|
||||
|)(:|[=]{1,3}|![=]{1,2})( |)(|[\.\w]{1,32})([p](ass|wd|asswd|assword))(|[\.\w]{1,32})(|\\)(|'|")))
|
||||
s_regex: ''
|
||||
format: '{0}'
|
||||
color: yellow
|
||||
@@ -182,9 +209,9 @@ rules:
|
||||
sensitive: false
|
||||
- name: Username Field
|
||||
loaded: true
|
||||
f_regex: (((|\\)(|'|")(|[\.\w]{1,10})(([u](ser|name|sername))|(account)|((((create|update)((d|r)|(by|on|at)))|(creator))))(|[\.\w]{1,10})(|\\)(|'|")(
|
||||
|)(:|=|!=|[\)]{0,1}\.val\()( |)(|\\)('|")([^'"]+?)(|\\)('|")(|,|\)))|((|\\)('|")([^'"]+?)(|\\)('|")(|\\)(|'|")(
|
||||
|)(:|[=]{1,3}|![=]{1,2})( |)(|[\.\w]{1,10})(([u](ser|name|sername))|(account)|((((create|update)((d|r)|(by|on|at)))|(creator))))(|[\.\w]{1,10})(|\\)(|'|")))
|
||||
f_regex: (((|\\)(|'|")(|[\.\w]{1,32})(([u](ser|name|sername))|(account)|((((create|update)((d|r)|(by|on|at)))|(creator))))(|[\.\w]{1,32})(|\\)(|'|")(
|
||||
|)(:|[=]{1,3}|![=]{1,2}|[\)]{0,1}\.val\()( |)(|\\)('|")([^'"]+?)(|\\)('|")(|,|\)))|((|\\)('|")([^'"]+?)(|\\)('|")(|\\)(|'|")(
|
||||
|)(:|[=]{1,3}|![=]{1,2})( |)(|[\.\w]{1,32})(([u](ser|name|sername))|(account)|((((create|update)((d|r)|(by|on|at)))|(creator))))(|[\.\w]{1,32})(|\\)(|'|")))
|
||||
s_regex: ''
|
||||
format: '{0}'
|
||||
color: green
|
||||
@@ -220,9 +247,9 @@ rules:
|
||||
sensitive: false
|
||||
- name: Sensitive Field
|
||||
loaded: true
|
||||
f_regex: (((\[)?('|")?([\.\w]{0,10})(key|secret|token|config|auth|access|admin|ticket)([\.\w]{0,10})('|")?(\])?(
|
||||
|)(:|=|!=|[\)]{0,1}\.val\()( |)('|")([^'"]+?)('|")(|,|\)))|((|\\)('|")([^'"]+?)(|\\)('|")(|\\)(|'|")(
|
||||
|)(:|[=]{1,3}|![=]{1,2})( |)(|[\.\w]{1,10})(key|secret|token|config|auth|access|admin|ticket)(|[\.\w]{1,10})(|\\)(|'|")))
|
||||
f_regex: (((|\\)(|'|")(|[\.\w]{1,32})(key|secret|token|config|auth|access|admin|ticket)(|[\.\w]{1,32})(|\\)(|'|")(
|
||||
|)(:|[=]{1,3}|![=]{1,2}|[\)]{0,1}\.val\()( |)(|\\)('|")([^'"]+?)(|\\)('|")(|,|\)))|((|\\)('|")([^'"]+?)(|\\)('|")(|\\)(|'|")(
|
||||
|)(:|[=]{1,3}|![=]{1,2})( |)(|[\.\w]{1,32})(key|secret|token|config|auth|access|admin|ticket)(|[\.\w]{1,32})(|\\)(|'|")))
|
||||
s_regex: ''
|
||||
format: '{0}'
|
||||
color: yellow
|
||||
@@ -231,20 +258,29 @@ rules:
|
||||
sensitive: false
|
||||
- name: Mobile Number Field
|
||||
loaded: true
|
||||
f_regex: '(((|\\)(|''|")(|[\w]{1,10})(mobile|phone|sjh|shoujihao|concat)(|[\.\w]{1,10})(|\\)(|''|")(
|
||||
|)(:|=|!=|[\)]{0,1}\.val\()( |)(|\\)(''|")([^''"]+?)(|\\)(''|")(|,|\)))|((|\\)(''|")([^''"]+?)(|\\)(''|")(|\\)(|''|")(
|
||||
|)(:|[=]{1,3}|![=]{1,2})( |)(|[\.\w]{1,10})(mobile|phone|sjh|shoujihao|concat)(|[\.\w]{1,10})(|\\)(|''|"))) '
|
||||
f_regex: (((|\\)(|'|")(|[\.\w]{1,32})(mobile|phone|sjh|shoujihao|concat)(|[\.\w]{1,32})(|\\)(|'|")(
|
||||
|)(:|[=]{1,3}|![=]{1,2}|[\)]{0,1}\.val\()( |)(|\\)('|")([^'"]+?)(|\\)('|")(|,|\)))|((|\\)('|")([^'"]+?)(|\\)('|")(|\\)(|'|")(
|
||||
|)(:|[=]{1,3}|![=]{1,2})( |)(|[\.\w]{1,32})(mobile|phone|sjh|shoujihao|concat)(|[\.\w]{1,32})(|\\)(|'|")))
|
||||
s_regex: ''
|
||||
format: '{0}'
|
||||
color: green
|
||||
scope: response body
|
||||
engine: nfa
|
||||
sensitive: false
|
||||
- name: Userinfo In Link
|
||||
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,}|)))(?:"|'|\`)
|
||||
s_regex: ((([p](ass|wd|asswd|assword))|(([u](ser|name|sername))|(account)|((((create|update)((d|r)|(by|on|at)))|(creator)))))=[\.\w]{1,32})
|
||||
format: '{0}'
|
||||
color: green
|
||||
scope: response body
|
||||
engine: nfa
|
||||
sensitive: false
|
||||
- group: Other
|
||||
rule:
|
||||
- name: Linkfinder
|
||||
loaded: true
|
||||
f_regex: (?:"|')((?:(?:[a-zA-Z]{1,10}://|//)[^"'/]{1,}\.[a-zA-Z]{2,}[^"']{0,})|(?:(?:(?:/|\.\./|\./)?[^"'><,;|*()(%%$^/\\\[\]][^"'><,;|()]{1,}\.[a-zA-Z]{1,4})|(?:(?:/|\.\./|\./)?[^"'><,;|*()(%%$^/\\\[\]][^"'><,;|()]{1,}/[^"'><,;|()]{1,}(?:\.[a-zA-Z]{1,4}|action)?)))(?:[\?|#][^"|']{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: ''
|
||||
format: '{0}'
|
||||
color: gray
|
||||
@@ -271,7 +307,7 @@ rules:
|
||||
sensitive: false
|
||||
- name: URL Schemes
|
||||
loaded: true
|
||||
f_regex: (\b(?![\w]{0,10}?https?://)(([-A-Za-z0-9]{1,20})://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]))
|
||||
f_regex: (\b(?![\w]{0,10}?https?://)(([A-Za-z0-9-\.]{1,20})://([-\w+&@#/%?=~_|!:,.;]*[-\w+&@#/%=~_|])?))
|
||||
s_regex: ''
|
||||
format: '{0}'
|
||||
color: yellow
|
||||
@@ -307,10 +343,19 @@ rules:
|
||||
sensitive: false
|
||||
- name: 302 Location
|
||||
loaded: true
|
||||
f_regex: 'Location: (.*?)\n'
|
||||
f_regex: 'Location: (.*?)\r\n'
|
||||
s_regex: ''
|
||||
format: '{0}'
|
||||
color: gray
|
||||
scope: response header
|
||||
engine: nfa
|
||||
sensitive: false
|
||||
- name: OSKeys
|
||||
loaded: false
|
||||
f_regex: <Key>(.*?)</Key>
|
||||
s_regex: ''
|
||||
format: '{0}'
|
||||
color: gray
|
||||
scope: response body
|
||||
engine: nfa
|
||||
sensitive: true
|
||||
|
||||
Reference in New Issue
Block a user