Compare commits

...

14 Commits
4.2.1 ... main

Author SHA1 Message Date
gh0stkey
980999a2f0 Update
Signed-off-by: gh0stkey <24655118+gh0stkey@users.noreply.github.com>
2025-10-30 10:33:51 +08:00
EvilChen
1236b9579e Update README_CN.md 2025-10-14 13:35:17 +08:00
EvilChen
d561b6815d Update README.md 2025-10-14 13:34:59 +08:00
gh0stkey
97d20bb7f5 Version: 4.3.1 Update
Signed-off-by: gh0stkey <24655118+gh0stkey@users.noreply.github.com>
2025-09-22 16:02:06 +08:00
EvilChen
74fa78db5e Update README_CN.md 2025-09-18 19:41:45 +08:00
EvilChen
230cad7f91 Update README.md 2025-09-18 19:41:30 +08:00
chen
1957599b07 Update 2025-08-14 19:15:06 +08:00
gh0stkey
5f26db2a9e Version: 4.3 Update
Signed-off-by: gh0stkey <24655118+gh0stkey@users.noreply.github.com>
2025-07-29 15:32:10 +08:00
gh0stkey
692e26044c Update 2025-07-29 13:00:20 +08:00
gh0stkey
b597a9e6d9 Version: 4.3 Update
Signed-off-by: gh0stkey <24655118+gh0stkey@users.noreply.github.com>
2025-07-29 12:04:21 +08:00
gh0stkey
fca804cb7c Update 2025-07-07 20:29:48 +08:00
gh0stkey
3cbf0591c6 Update 2025-07-07 20:25:49 +08:00
gh0stkey
186430bb35 Update 2025-07-07 20:25:14 +08:00
gh0stkey
56c7973261 Update 2025-07-07 20:18:27 +08:00
14 changed files with 789 additions and 358 deletions

View File

@@ -1,7 +1,7 @@
<div align="center">
<img src="images/logo.png" style="width: 20%" />
<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>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>
<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)\]
@@ -88,19 +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 | 132.00 CNY |
| Celvin | 150.88 CNY |
| 呱呱 | 18.80 CNY |
| 红炉点雪 | 50.00 CNY |
| 王傑 | 100.00 CNY |
| 联系不到我请拨打我手机号码 | 200.00 CNY |
| Shu2e | 50.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

View File

@@ -1,7 +1,7 @@
<div align="center">
<img src="images/logo.png" style="width: 20%" />
<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>
<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,30 +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 | 132.00元 |
| 呱呱 | 18.80元 |
| 红炉点雪 | 50.00元 |
| 王傑 | 100.00元 |
| 联系不到我请拨打我手机号码 | 200.00元 |
| Shu2e | 50.00元 |
| | 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 元 |
## 支持项目

View File

@@ -60,7 +60,8 @@ public class Config {
"blue",
"pink",
"magenta",
"gray"
"gray",
"none"
};
public static Boolean proVersionStatus = true;

View File

@@ -2,6 +2,7 @@ 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.DataCache;
import hae.component.Main;
@@ -9,7 +10,6 @@ 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.2.1";
String version = "4.3.1";
// 加载扩展后输出的项目信息
Logging logging = api.logging();
@@ -34,7 +34,7 @@ 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));
@@ -58,16 +58,12 @@ public class HaE implements BurpExtension {
});
}
private Boolean getBurpSuiteProStatus(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) {
private Boolean getBurpSuiteProStatus(MontoyaApi api) {
boolean burpSuiteProStatus = false;
try {
burpSuiteProStatus = api.burpSuite().version().edition().displayName().equals("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;

View File

@@ -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;

View File

@@ -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() {
@@ -395,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);
}
}
}

View File

@@ -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);
}

View File

@@ -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("\r\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("\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("\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

View File

@@ -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) {

View File

@@ -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();
}
}
}

View File

@@ -23,6 +23,7 @@ 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;
@@ -217,8 +218,8 @@ public class RegularMatcher {
while (matcher.find()) {
String matchContent = matcher.group(1);
if (!matchContent.isEmpty()) {
matcher = createPatternMatcher(s_regex, matchContent, sensitive);
matches.addAll(formatMatchResults(matcher, format));
Matcher secondMatcher = createPatternMatcher(s_regex, matchContent, sensitive);
matches.addAll(formatMatchResults(secondMatcher, format));
}
}
}
@@ -242,9 +243,20 @@ public class RegularMatcher {
}
private List<String> formatMatchResults(Matcher matcher, String format) {
List<Integer> indexList = parseIndexesFromString(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 -> {
@@ -295,8 +307,7 @@ public class RegularMatcher {
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);
@@ -318,8 +329,7 @@ public class RegularMatcher {
}
private String normalizeFormatIndexes(String format) {
Pattern pattern = Pattern.compile("\\{(\\d+)}");
Matcher matcher = pattern.matcher(format);
Matcher matcher = formatIndexPattern.matcher(format);
int count = 0;
while (matcher.find()) {
String newStr = String.format("{%s}", count);

View File

@@ -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);
}
}

View File

@@ -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();

View File

@@ -57,7 +57,7 @@ rules:
sensitive: false
- name: Vite DevMode
loaded: true
f_regex: (/@vite/client)
f_regex: (/\@vite/client)
s_regex: ''
format: '{0}'
color: red
@@ -133,8 +133,8 @@ rules:
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
@@ -198,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
@@ -209,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
@@ -247,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
@@ -258,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-Z0-9_\-/]{1,}/[a-zA-Z0-9_\-/]{1,}\.(?:[a-zA-Z]{1,4}|action)(?:[\?|#][^"|']{0,}|))|([a-zA-Z0-9_\-/]{1,}/[a-zA-Z0-9_\-/]{3,}(?:[\?|#][^"|']{0,}|))|([a-zA-Z0-9_\-]{1,}\.(?:\w)(?:[\?|#][^"|']{0,}|)))(?:"|')
f_regex: (?:"|'|\`)(((?:[a-zA-Z]{1,10}://|//)[^"'/]{1,}\.[a-zA-Z]{2,}[^"']{0,})|((?:/|\.\./|\./)[^"'><,;|*()(%%$^/\\\[\]][^"'><,;|()]{1,})|([a-zA-Z0-9_\-/]{1,}/[a-zA-Z0-9_\-/]{1,}\.(?:[a-zA-Z]{1,4}|action)(?:[\?|#][^"|']{0,}|))|([a-zA-Z0-9_\-/]{1,}/[a-zA-Z0-9_\-/]{3,}(?:[\?|#][^"|']{0,}|))|([a-zA-Z0-9_\-]{1,}\.(?:\w)(?:[\?|#][^"|']{0,}|)))(?:"|'|\`)
s_regex: ''
format: '{0}'
color: gray
@@ -334,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