Version: 4.1 Update

This commit is contained in:
gh0stkey
2025-03-21 21:22:11 +08:00
parent 1a5ed2a6a3
commit 20afa30822
13 changed files with 393 additions and 220 deletions

View File

@@ -4,7 +4,7 @@ import burp.api.montoya.BurpExtension;
import burp.api.montoya.MontoyaApi; import burp.api.montoya.MontoyaApi;
import burp.api.montoya.extension.ExtensionUnloadingHandler; import burp.api.montoya.extension.ExtensionUnloadingHandler;
import burp.api.montoya.logging.Logging; import burp.api.montoya.logging.Logging;
import hae.cache.CachePool; import hae.cache.MessageCache;
import hae.component.Main; import hae.component.Main;
import hae.component.board.message.MessageTableModel; import hae.component.board.message.MessageTableModel;
import hae.instances.editor.RequestEditor; import hae.instances.editor.RequestEditor;
@@ -20,7 +20,7 @@ public class HaE implements BurpExtension {
public void initialize(MontoyaApi api) { public void initialize(MontoyaApi api) {
// 设置扩展名称 // 设置扩展名称
api.extension().setName("HaE - Highlighter and Extractor"); api.extension().setName("HaE - Highlighter and Extractor");
String version = "4.0.5"; String version = "4.0.6";
// 加载扩展后输出的项目信息 // 加载扩展后输出的项目信息
Logging logging = api.logging(); Logging logging = api.logging();
@@ -58,7 +58,7 @@ public class HaE implements BurpExtension {
public void extensionUnloaded() { public void extensionUnloaded() {
// 卸载清空数据 // 卸载清空数据
Config.globalDataMap.clear(); Config.globalDataMap.clear();
CachePool.clear(); MessageCache.clear();
} }
}); });
} }

View File

@@ -0,0 +1,46 @@
package hae.cache;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class DataQueryCache {
private static final int MAX_SIZE = 1000;
private static final int EXPIRE_DURATION = 30;
private static final Cache<String, Map<String, List<String>>> hostQueryCache =
Caffeine.newBuilder()
.maximumSize(MAX_SIZE)
.expireAfterWrite(EXPIRE_DURATION, TimeUnit.MINUTES)
.build();
private static final Cache<String, List<String>> hostFilterCache =
Caffeine.newBuilder()
.maximumSize(MAX_SIZE)
.expireAfterWrite(EXPIRE_DURATION, TimeUnit.MINUTES)
.build();
public static void putHostQueryResult(String host, Map<String, List<String>> result) {
hostQueryCache.put(host, result);
}
public static Map<String, List<String>> getHostQueryResult(String host) {
return hostQueryCache.getIfPresent(host);
}
public static void putHostFilterResult(String input, List<String> result) {
hostFilterCache.put(input, result);
}
public static List<String> getHostFilterResult(String input) {
return hostFilterCache.getIfPresent(input);
}
public static void clearCache() {
hostQueryCache.invalidateAll();
hostFilterCache.invalidateAll();
}
}

View File

@@ -6,7 +6,7 @@ import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public class CachePool { public class MessageCache {
private static final int MAX_SIZE = 100000; private static final int MAX_SIZE = 100000;
private static final int EXPIRE_DURATION = 5; private static final int EXPIRE_DURATION = 5;

View File

@@ -2,6 +2,7 @@ package hae.component.board;
import burp.api.montoya.MontoyaApi; import burp.api.montoya.MontoyaApi;
import hae.Config; import hae.Config;
import hae.cache.DataQueryCache;
import hae.component.board.message.MessageTableModel; import hae.component.board.message.MessageTableModel;
import hae.component.board.message.MessageTableModel.MessageTable; import hae.component.board.message.MessageTableModel.MessageTable;
import hae.component.board.table.Datatable; import hae.component.board.table.Datatable;
@@ -23,19 +24,17 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class Databoard extends JPanel { public class Databoard extends JPanel {
private static Boolean isMatchHost = false;
private final MontoyaApi api; private final MontoyaApi api;
private final ConfigLoader configLoader; private final ConfigLoader configLoader;
private final MessageTableModel messageTableModel; private final MessageTableModel messageTableModel;
private final DefaultComboBoxModel comboBoxModel = new DefaultComboBoxModel();
private final JComboBox hostComboBox = new JComboBox(comboBoxModel);
private JTextField hostTextField; private JTextField hostTextField;
private JTabbedPane dataTabbedPane; private JTabbedPane dataTabbedPane;
private JSplitPane splitPane; private JSplitPane splitPane;
private MessageTable messageTable; private MessageTable messageTable;
private JProgressBar progressBar;
private static Boolean isMatchHost = false;
private final DefaultComboBoxModel comboBoxModel = new DefaultComboBoxModel();
private final JComboBox hostComboBox = new JComboBox(comboBoxModel);
private SwingWorker<Map<String, List<String>>, Void> handleComboBoxWorker; private SwingWorker<Map<String, List<String>>, Void> handleComboBoxWorker;
private SwingWorker<Void, Void> applyHostFilterWorker; private SwingWorker<Void, Void> applyHostFilterWorker;
@@ -47,13 +46,25 @@ public class Databoard extends JPanel {
initComponents(); initComponents();
} }
public static void setProgressBar(boolean status, JProgressBar progressBar, String showString) {
progressBar.setIndeterminate(status);
if (!status) {
progressBar.setMaximum(100);
progressBar.setString("OK");
progressBar.setStringPainted(true);
progressBar.setValue(progressBar.getMaximum());
} else {
progressBar.setString(showString);
progressBar.setStringPainted(true);
}
}
private void initComponents() { private void initComponents() {
setLayout(new GridBagLayout()); setLayout(new GridBagLayout());
((GridBagLayout) getLayout()).columnWidths = new int[]{25, 0, 0, 0, 20, 0}; ((GridBagLayout) getLayout()).columnWidths = new int[]{25, 0, 0, 0, 20, 0};
((GridBagLayout) getLayout()).rowHeights = new int[]{0, 65, 20, 0}; ((GridBagLayout) getLayout()).rowHeights = new int[]{0, 65, 20, 0, 0};
((GridBagLayout) getLayout()).columnWeights = new double[]{0.0, 0.0, 1.0, 0.0, 0.0, 1.0E-4}; ((GridBagLayout) getLayout()).columnWeights = new double[]{0.0, 0.0, 1.0, 0.0, 0.0, 1.0E-4};
((GridBagLayout) getLayout()).rowWeights = new double[]{0.0, 1.0, 0.0, 1.0E-4}; ((GridBagLayout) getLayout()).rowWeights = new double[]{0.0, 1.0, 0.0, 0.0, 1.0E-4};
JLabel hostLabel = new JLabel("Host:"); JLabel hostLabel = new JLabel("Host:");
JButton clearButton = new JButton("Clear"); JButton clearButton = new JButton("Clear");
@@ -81,7 +92,7 @@ public class Databoard extends JPanel {
clearButton.addActionListener(this::clearActionPerformed); clearButton.addActionListener(this::clearActionPerformed);
progressBar = new JProgressBar();
splitPane.addComponentListener(new ComponentAdapter() { splitPane.addComponentListener(new ComponentAdapter() {
@Override @Override
public void componentResized(ComponentEvent e) { public void componentResized(ComponentEvent e) {
@@ -90,6 +101,7 @@ public class Databoard extends JPanel {
}); });
splitPane.setVisible(false); splitPane.setVisible(false);
progressBar.setVisible(false);
add(hostLabel, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, add(hostLabel, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
new Insets(8, 0, 5, 5), 0, 0)); new Insets(8, 0, 5, 5), 0, 0));
@@ -98,9 +110,12 @@ public class Databoard extends JPanel {
add(actionButton, new GridBagConstraints(3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, add(actionButton, new GridBagConstraints(3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
new Insets(8, 0, 5, 5), 0, 0)); new Insets(8, 0, 5, 5), 0, 0));
add(splitPane, new GridBagConstraints(1, 1, 3, 2, 0.0, 1.0, add(splitPane, new GridBagConstraints(1, 1, 3, 1, 0.0, 1.0,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
new Insets(0, 5, 0, 5), 0, 0)); new Insets(0, 5, 0, 5), 0, 0));
add(progressBar, new GridBagConstraints(1, 2, 3, 1, 1.0, 0.0,
GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,
new Insets(0, 5, 0, 5), 0, 0));
hostComboBox.setMaximumRowCount(5); hostComboBox.setMaximumRowCount(5);
add(hostComboBox, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, add(hostComboBox, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
new Insets(8, 0, 5, 5), 0, 0)); new Insets(8, 0, 5, 5), 0, 0));
@@ -120,6 +135,10 @@ public class Databoard extends JPanel {
columnModel.getColumn(5).setPreferredWidth((int) (totalWidth * 0.1)); columnModel.getColumn(5).setPreferredWidth((int) (totalWidth * 0.1));
} }
private void setProgressBar(boolean status) {
setProgressBar(status, progressBar, "Loading ...");
}
private void setAutoMatch() { private void setAutoMatch() {
hostComboBox.setSelectedItem(null); hostComboBox.setSelectedItem(null);
hostComboBox.addActionListener(this::handleComboBoxAction); hostComboBox.addActionListener(this::handleComboBoxAction);
@@ -155,6 +174,8 @@ public class Databoard extends JPanel {
String selectedHost = hostComboBox.getSelectedItem().toString(); String selectedHost = hostComboBox.getSelectedItem().toString();
if (getHostByList().contains(selectedHost)) { if (getHostByList().contains(selectedHost)) {
progressBar.setVisible(true);
setProgressBar(true);
hostTextField.setText(selectedHost); hostTextField.setText(selectedHost);
if (handleComboBoxWorker != null && !handleComboBoxWorker.isDone()) { if (handleComboBoxWorker != null && !handleComboBoxWorker.isDone()) {
@@ -193,6 +214,8 @@ public class Databoard extends JPanel {
hostComboBox.setPopupVisible(false); hostComboBox.setPopupVisible(false);
applyHostFilter(selectedHost); applyHostFilter(selectedHost);
setProgressBar(false);
} }
} catch (Exception ignored) { } catch (Exception ignored) {
} }
@@ -230,6 +253,12 @@ public class Databoard extends JPanel {
} }
private Map<String, List<String>> getSelectedMapByHost(String selectedHost) { private Map<String, List<String>> getSelectedMapByHost(String selectedHost) {
// 先尝试从缓存获取结果
Map<String, List<String>> cachedResult = DataQueryCache.getHostQueryResult(selectedHost);
if (cachedResult != null) {
return cachedResult;
}
ConcurrentHashMap<String, Map<String, List<String>>> dataMap = Config.globalDataMap; ConcurrentHashMap<String, Map<String, List<String>>> dataMap = Config.globalDataMap;
Map<String, List<String>> selectedDataMap; Map<String, List<String>> selectedDataMap;
@@ -255,6 +284,11 @@ public class Databoard extends JPanel {
selectedDataMap = dataMap.get(selectedHost); selectedDataMap = dataMap.get(selectedHost);
} }
// 将结果存入缓存
if (selectedDataMap != null) {
DataQueryCache.putHostQueryResult(selectedHost, selectedDataMap);
}
return selectedDataMap; return selectedDataMap;
} }
@@ -314,19 +348,31 @@ public class Databoard extends JPanel {
} }
private List<String> getHostByList() { private List<String> getHostByList() {
if (!Config.globalDataMap.keySet().isEmpty()) { // 先尝试从缓存获取结果
return new ArrayList<>(Config.globalDataMap.keySet()); List<String> cachedResult = DataQueryCache.getHostFilterResult("all_hosts");
if (cachedResult != null) {
return cachedResult;
} }
return new ArrayList<>();
List<String> result = new ArrayList<>();
if (!Config.globalDataMap.isEmpty()) {
result = new ArrayList<>(Config.globalDataMap.keySet());
// 将结果存入缓存
DataQueryCache.putHostFilterResult("all_hosts", result);
}
return result;
} }
private void clearActionPerformed(ActionEvent e) { private void clearActionPerformed(ActionEvent e) {
// 清除缓存
DataQueryCache.clearCache();
int retCode = JOptionPane.showConfirmDialog(this, "Do you want to clear data?", "Info", int retCode = JOptionPane.showConfirmDialog(this, "Do you want to clear data?", "Info",
JOptionPane.YES_NO_OPTION); JOptionPane.YES_NO_OPTION);
String host = hostTextField.getText(); String host = hostTextField.getText();
if (retCode == JOptionPane.YES_OPTION && !host.isEmpty()) { if (retCode == JOptionPane.YES_OPTION && !host.isEmpty()) {
dataTabbedPane.removeAll(); dataTabbedPane.removeAll();
splitPane.setVisible(false); splitPane.setVisible(false);
progressBar.setVisible(false);
Config.globalDataMap.keySet().parallelStream().forEach(key -> { Config.globalDataMap.keySet().parallelStream().forEach(key -> {
if (StringProcessor.matchesHostPattern(key, host) || host.equals("*")) { if (StringProcessor.matchesHostPattern(key, host) || host.equals("*")) {
@@ -353,8 +399,8 @@ public class Databoard extends JPanel {
keysToRemove.forEach(Config.globalDataMap::remove); keysToRemove.forEach(Config.globalDataMap::remove);
if (Config.globalDataMap.keySet().size() == 1 && Config.globalDataMap.keySet().stream().anyMatch(key -> key.equals("*"))) { if (Config.globalDataMap.size() == 1 && Config.globalDataMap.keySet().stream().anyMatch(key -> key.equals("*"))) {
Config.globalDataMap.keySet().remove("*"); Config.globalDataMap.remove("*");
} }
messageTableModel.deleteByHost(host); messageTableModel.deleteByHost(host);

View File

@@ -10,7 +10,7 @@ import burp.api.montoya.ui.UserInterface;
import burp.api.montoya.ui.editor.HttpRequestEditor; import burp.api.montoya.ui.editor.HttpRequestEditor;
import burp.api.montoya.ui.editor.HttpResponseEditor; import burp.api.montoya.ui.editor.HttpResponseEditor;
import hae.Config; import hae.Config;
import hae.cache.CachePool; import hae.cache.MessageCache;
import hae.utils.ConfigLoader; import hae.utils.ConfigLoader;
import hae.utils.DataManager; import hae.utils.DataManager;
import hae.utils.string.HashCalculator; import hae.utils.string.HashCalculator;
@@ -90,7 +90,7 @@ public class MessageTableModel extends AbstractTableModel {
messageTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); messageTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
// 请求/应文本框 // 请求/应文本框
JScrollPane scrollPane = new JScrollPane(messageTable); JScrollPane scrollPane = new JScrollPane(messageTable);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
@@ -98,7 +98,7 @@ public class MessageTableModel extends AbstractTableModel {
splitPane.setRightComponent(messageTab); splitPane.setRightComponent(messageTab);
} }
public void add(HttpRequestResponse messageInfo, String url, String method, String status, String length, String comment, String color, boolean flag) { public synchronized void add(HttpRequestResponse messageInfo, String url, String method, String status, String length, String comment, String color, boolean flag) {
synchronized (log) { synchronized (log) {
boolean isDuplicate = false; boolean isDuplicate = false;
MessageEntry logEntry = new MessageEntry(messageInfo, method, url, comment, length, color, status); MessageEntry logEntry = new MessageEntry(messageInfo, method, url, comment, length, color, status);
@@ -157,6 +157,26 @@ public class MessageTableModel extends AbstractTableModel {
} }
public synchronized void addBatch(List<Object[]> batchData) {
synchronized (log) {
for (Object[] data : batchData) {
HttpRequestResponse messageInfo = (HttpRequestResponse) data[0];
String url = (String) data[1];
String method = (String) data[2];
String status = (String) data[3];
String length = (String) data[4];
String comment = (String) data[5];
String color = (String) data[6];
// 复用现有的 add 方法逻辑,但跳过重复检查
MessageEntry logEntry = new MessageEntry(messageInfo, method, url, comment, length, color, status);
log.add(logEntry);
}
}
// 批量更新完成后一次性通知表格更新
fireTableDataChanged();
}
public void deleteByHost(String filterText) { public void deleteByHost(String filterText) {
filteredLog.clear(); filteredLog.clear();
List<Integer> rowsToRemove = new ArrayList<>(); List<Integer> rowsToRemove = new ArrayList<>();
@@ -317,7 +337,7 @@ public class MessageTableModel extends AbstractTableModel {
private Map<String, Map<String, Object>> getCacheData(byte[] content) { private Map<String, Map<String, Object>> getCacheData(byte[] content) {
String hashIndex = HashCalculator.calculateHash(content); String hashIndex = HashCalculator.calculateHash(content);
return CachePool.get(hashIndex); return MessageCache.get(hashIndex);
} }
private boolean areMapsEqual(Map<String, Map<String, Object>> map1, Map<String, Map<String, Object>> map2) { private boolean areMapsEqual(Map<String, Map<String, Object>> map1, Map<String, Map<String, Object>> map2) {
@@ -426,11 +446,11 @@ public class MessageTableModel extends AbstractTableModel {
} }
public class MessageTable extends JTable { public class MessageTable extends JTable {
private MessageEntry messageEntry;
private final ExecutorService executorService; private final ExecutorService executorService;
private int lastSelectedIndex = -1;
private final HttpRequestEditor requestEditor; private final HttpRequestEditor requestEditor;
private final HttpResponseEditor responseEditor; private final HttpResponseEditor responseEditor;
private MessageEntry messageEntry;
private int lastSelectedIndex = -1;
public MessageTable(TableModel messageTableModel, HttpRequestEditor requestEditor, HttpResponseEditor responseEditor) { public MessageTable(TableModel messageTableModel, HttpRequestEditor requestEditor, HttpResponseEditor responseEditor) {
super(messageTableModel); super(messageTableModel);

View File

@@ -11,12 +11,41 @@ import java.awt.event.*;
public class Rules extends JTabbedPane { public class Rules extends JTabbedPane {
private final MontoyaApi api; private final MontoyaApi api;
private ConfigLoader configLoader;
private final RuleProcessor ruleProcessor; private final RuleProcessor ruleProcessor;
private final JTextField ruleGroupNameTextField; private final JTextField ruleGroupNameTextField;
private ConfigLoader configLoader;
private Component tabComponent; private Component tabComponent;
private int selectedIndex; private int selectedIndex;
private final Action cancelActionPerformed = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
if (selectedIndex >= 0) {
setTabComponentAt(selectedIndex, tabComponent);
ruleGroupNameTextField.setVisible(false);
ruleGroupNameTextField.setPreferredSize(null);
selectedIndex = -1;
tabComponent = null;
requestFocusInWindow();
}
}
};
private final Action renameTitleActionPerformed = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
String title = ruleGroupNameTextField.getText();
if (!title.isEmpty() && selectedIndex >= 0) {
String oldName = getTitleAt(selectedIndex);
setTitleAt(selectedIndex, title);
if (!oldName.equals(title)) {
ruleProcessor.renameRuleGroup(oldName, title);
}
}
cancelActionPerformed.actionPerformed(null);
}
};
public Rules(MontoyaApi api, ConfigLoader configLoader) { public Rules(MontoyaApi api, ConfigLoader configLoader) {
this.api = api; this.api = api;
@@ -49,43 +78,48 @@ public class Rules extends JTabbedPane {
addMouseListener(new MouseAdapter() { addMouseListener(new MouseAdapter() {
@Override @Override
public void mousePressed(MouseEvent e) { public void mousePressed(MouseEvent e) {
int index = getSelectedIndex(); int index = indexAtLocation(e.getX(), e.getY());
Rectangle r = getBoundsAt(index); if (index < 0) {
if (r.contains(e.getPoint()) && index >= 0) { return;
switch (e.getButton()) { }
case MouseEvent.BUTTON1:
if (e.getClickCount() == 2) {
selectedIndex = index;
tabComponent = getTabComponentAt(selectedIndex);
String ruleGroupName = getTitleAt(selectedIndex);
if (!"...".equals(ruleGroupName)) { switch (e.getButton()) {
setTabComponentAt(selectedIndex, ruleGroupNameTextField); case MouseEvent.BUTTON1:
ruleGroupNameTextField.setVisible(true); if (e.getClickCount() == 2) {
ruleGroupNameTextField.setText(ruleGroupName); selectedIndex = index;
ruleGroupNameTextField.selectAll(); tabComponent = getTabComponentAt(selectedIndex);
ruleGroupNameTextField.requestFocusInWindow(); String ruleGroupName = getTitleAt(selectedIndex);
ruleGroupNameTextField.setMinimumSize(ruleGroupNameTextField.getPreferredSize());
} if (!"...".equals(ruleGroupName)) {
} else if (e.getClickCount() == 1) { setTabComponentAt(selectedIndex, ruleGroupNameTextField);
if ("...".equals(getTitleAt(getSelectedIndex()))) { ruleGroupNameTextField.setVisible(true);
String title = ruleProcessor.newRule(); ruleGroupNameTextField.setText(ruleGroupName);
Rule newRule = new Rule(api, configLoader, Config.ruleTemplate, tabbedPane); ruleGroupNameTextField.selectAll();
insertTab(title, null, newRule, null, getTabCount() - 1); ruleGroupNameTextField.requestFocusInWindow();
setSelectedIndex(getTabCount() - 2); ruleGroupNameTextField.setMinimumSize(ruleGroupNameTextField.getPreferredSize());
} else {
renameTitleActionPerformed.actionPerformed(null);
}
} }
break; } else if (e.getClickCount() == 1) {
case MouseEvent.BUTTON3: String title = getTitleAt(index);
if (!"...".equals(getTitleAt(getSelectedIndex()))) { if ("...".equals(title)) {
popupMenu.show(e.getComponent(), e.getX(), e.getY()); // 阻止默认的选中行为
e.consume();
// 直接创建新标签
String newTitle = ruleProcessor.newRule();
Rule newRule = new Rule(api, configLoader, Config.ruleTemplate, Rules.this);
insertTab(newTitle, null, newRule, null, getTabCount() - 1);
setSelectedIndex(getTabCount() - 2);
} else {
renameTitleActionPerformed.actionPerformed(null);
} }
break; }
default: break;
break; case MouseEvent.BUTTON3:
} if (!"...".equals(getTitleAt(index))) {
popupMenu.show(e.getComponent(), e.getX(), e.getY());
}
break;
default:
break;
} }
} }
}); });
@@ -119,38 +153,6 @@ public class Rules extends JTabbedPane {
} }
} }
} }
private final Action renameTitleActionPerformed = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
String title = ruleGroupNameTextField.getText();
if (!title.isEmpty() && selectedIndex >= 0) {
String oldName = getTitleAt(selectedIndex);
setTitleAt(selectedIndex, title);
if (!oldName.equals(title)) {
ruleProcessor.renameRuleGroup(oldName, title);
}
}
cancelActionPerformed.actionPerformed(null);
}
};
private final Action cancelActionPerformed = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
if (selectedIndex >= 0) {
setTabComponentAt(selectedIndex, tabComponent);
ruleGroupNameTextField.setVisible(false);
ruleGroupNameTextField.setPreferredSize(null);
selectedIndex = -1;
tabComponent = null;
requestFocusInWindow();
}
}
};
} }

View File

@@ -31,6 +31,28 @@ public class RequestEditor implements HttpRequestEditorProvider {
this.configLoader = configLoader; this.configLoader = configLoader;
} }
public static boolean isListHasData(List<Map<String, String>> dataList) {
if (dataList != null && !dataList.isEmpty()) {
Map<String, String> dataMap = dataList.get(0);
return dataMap != null && !dataMap.isEmpty();
}
return false;
}
public static void generateTabbedPaneFromResultMap(MontoyaApi api, ConfigLoader configLoader, JTabbedPane tabbedPane, List<Map<String, String>> result) {
tabbedPane.removeAll();
if (result != null && !result.isEmpty()) {
Map<String, String> dataMap = result.get(0);
if (dataMap != null && !dataMap.isEmpty()) {
dataMap.keySet().forEach(i -> {
String[] extractData = dataMap.get(i).split(Config.boundary);
Datatable dataPanel = new Datatable(api, configLoader, i, Arrays.asList(extractData));
tabbedPane.addTab(i, dataPanel);
});
}
}
}
@Override @Override
public ExtensionProvidedHttpRequestEditor provideHttpRequestEditor(EditorCreationContext editorCreationContext) { public ExtensionProvidedHttpRequestEditor provideHttpRequestEditor(EditorCreationContext editorCreationContext) {
return new Editor(api, configLoader, editorCreationContext); return new Editor(api, configLoader, editorCreationContext);
@@ -42,11 +64,10 @@ public class RequestEditor implements HttpRequestEditorProvider {
private final HttpUtils httpUtils; private final HttpUtils httpUtils;
private final EditorCreationContext creationContext; private final EditorCreationContext creationContext;
private final MessageProcessor messageProcessor; private final MessageProcessor messageProcessor;
private final JTabbedPane jTabbedPane = new JTabbedPane();
private HttpRequestResponse requestResponse; private HttpRequestResponse requestResponse;
private List<Map<String, String>> dataList; private List<Map<String, String>> dataList;
private final JTabbedPane jTabbedPane = new JTabbedPane();
public Editor(MontoyaApi api, ConfigLoader configLoader, EditorCreationContext creationContext) { public Editor(MontoyaApi api, ConfigLoader configLoader, EditorCreationContext creationContext) {
this.api = api; this.api = api;
this.configLoader = configLoader; this.configLoader = configLoader;
@@ -118,26 +139,4 @@ public class RequestEditor implements HttpRequestEditorProvider {
return false; return false;
} }
} }
public static boolean isListHasData(List<Map<String, String>> dataList) {
if (dataList != null && !dataList.isEmpty()) {
Map<String, String> dataMap = dataList.get(0);
return dataMap != null && !dataMap.isEmpty();
}
return false;
}
public static void generateTabbedPaneFromResultMap(MontoyaApi api, ConfigLoader configLoader, JTabbedPane tabbedPane, List<Map<String, String>> result) {
tabbedPane.removeAll();
if (result != null && !result.isEmpty()) {
Map<String, String> dataMap = result.get(0);
if (dataMap != null && !dataMap.isEmpty()) {
dataMap.keySet().forEach(i -> {
String[] extractData = dataMap.get(i).split(Config.boundary);
Datatable dataPanel = new Datatable(api, configLoader, i, Arrays.asList(extractData));
tabbedPane.addTab(i, dataPanel);
});
}
}
}
} }

View File

@@ -41,11 +41,10 @@ public class ResponseEditor implements HttpResponseEditorProvider {
private final HttpUtils httpUtils; private final HttpUtils httpUtils;
private final EditorCreationContext creationContext; private final EditorCreationContext creationContext;
private final MessageProcessor messageProcessor; private final MessageProcessor messageProcessor;
private final JTabbedPane jTabbedPane = new JTabbedPane();
private HttpRequestResponse requestResponse; private HttpRequestResponse requestResponse;
private List<Map<String, String>> dataList; private List<Map<String, String>> dataList;
private final JTabbedPane jTabbedPane = new JTabbedPane();
public Editor(MontoyaApi api, ConfigLoader configLoader, EditorCreationContext creationContext) { public Editor(MontoyaApi api, ConfigLoader configLoader, EditorCreationContext creationContext) {
this.api = api; this.api = api;
this.configLoader = configLoader; this.configLoader = configLoader;

View File

@@ -36,11 +36,10 @@ public class WebSocketEditor implements WebSocketMessageEditorProvider {
private final ConfigLoader configLoader; private final ConfigLoader configLoader;
private final EditorCreationContext creationContext; private final EditorCreationContext creationContext;
private final MessageProcessor messageProcessor; private final MessageProcessor messageProcessor;
private final JTabbedPane jTabbedPane = new JTabbedPane();
private ByteArray message; private ByteArray message;
private List<Map<String, String>> dataList; private List<Map<String, String>> dataList;
private final JTabbedPane jTabbedPane = new JTabbedPane();
public Editor(MontoyaApi api, ConfigLoader configLoader, EditorCreationContext creationContext) { public Editor(MontoyaApi api, ConfigLoader configLoader, EditorCreationContext creationContext) {
this.api = api; this.api = api;
this.configLoader = configLoader; this.configLoader = configLoader;

View File

@@ -10,7 +10,6 @@ import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class MessageProcessor { public class MessageProcessor {
private final MontoyaApi api; private final MontoyaApi api;
private final RegularMatcher regularMatcher; private final RegularMatcher regularMatcher;

View File

@@ -8,7 +8,7 @@ import dk.brics.automaton.AutomatonMatcher;
import dk.brics.automaton.RegExp; import dk.brics.automaton.RegExp;
import dk.brics.automaton.RunAutomaton; import dk.brics.automaton.RunAutomaton;
import hae.Config; import hae.Config;
import hae.cache.CachePool; import hae.cache.MessageCache;
import hae.utils.DataManager; import hae.utils.DataManager;
import hae.utils.string.HashCalculator; import hae.utils.string.HashCalculator;
import hae.utils.string.StringProcessor; import hae.utils.string.StringProcessor;
@@ -27,10 +27,57 @@ public class RegularMatcher {
} }
public synchronized static void putDataToGlobalMap(MontoyaApi api, String host, String name, List<String> dataList, boolean flag) {
// 添加到全局变量中便于Databoard检索
if (!Objects.equals(host, "") && host != null) {
Config.globalDataMap.compute(host, (existingHost, existingMap) -> {
Map<String, List<String>> gRuleMap = Optional.ofNullable(existingMap).orElse(new ConcurrentHashMap<>());
gRuleMap.merge(name, new ArrayList<>(dataList), (existingList, newList) -> {
Set<String> combinedSet = new LinkedHashSet<>(existingList);
combinedSet.addAll(newList);
return new ArrayList<>(combinedSet);
});
if (flag) {
// 数据存储在BurpSuite空间内
try {
DataManager dataManager = new DataManager(api);
PersistedObject persistedObject = PersistedObject.persistedObject();
gRuleMap.forEach((kName, vList) -> {
PersistedList<String> persistedList = PersistedList.persistedStringList();
persistedList.addAll(vList);
persistedObject.setStringList(kName, persistedList);
});
dataManager.putData("data", host, persistedObject);
} catch (Exception ignored) {
}
}
return gRuleMap;
});
String[] splitHost = host.split("\\.");
String onlyHost = host.split(":")[0];
String anyHost = (splitHost.length > 2 && !StringProcessor.matchHostIsIp(onlyHost)) ? StringProcessor.replaceFirstOccurrence(onlyHost, splitHost[0], "*") : "";
if (!Config.globalDataMap.containsKey(anyHost) && !anyHost.isEmpty()) {
// 添加通配符Host实际数据从查询哪里将所有数据提取
Config.globalDataMap.put(anyHost, new HashMap<>());
}
if (!Config.globalDataMap.containsKey("*")) {
// 添加通配符全匹配,同上
Config.globalDataMap.put("*", new HashMap<>());
}
}
}
public Map<String, Map<String, Object>> match(String host, String type, String message, String header, String body) { public Map<String, Map<String, Object>> match(String host, String type, String message, String header, String body) {
// 先从缓存池里判断是否有已经匹配好的结果 // 先从缓存池里判断是否有已经匹配好的结果
String messageIndex = HashCalculator.calculateHash(message.getBytes()); String messageIndex = HashCalculator.calculateHash(message.getBytes());
Map<String, Map<String, Object>> map = CachePool.get(messageIndex); Map<String, Map<String, Object>> map = MessageCache.get(messageIndex);
if (map != null) { if (map != null) {
return map; return map;
} else { } else {
@@ -106,58 +153,11 @@ public class RegularMatcher {
} }
} }
}); });
CachePool.put(messageIndex, finalMap); MessageCache.put(messageIndex, finalMap);
return finalMap; return finalMap;
} }
} }
public synchronized static void putDataToGlobalMap(MontoyaApi api, String host, String name, List<String> dataList, boolean flag) {
// 添加到全局变量中便于Databoard检索
if (!Objects.equals(host, "") && host != null) {
Config.globalDataMap.compute(host, (existingHost, existingMap) -> {
Map<String, List<String>> gRuleMap = Optional.ofNullable(existingMap).orElse(new ConcurrentHashMap<>());
gRuleMap.merge(name, new ArrayList<>(dataList), (existingList, newList) -> {
Set<String> combinedSet = new LinkedHashSet<>(existingList);
combinedSet.addAll(newList);
return new ArrayList<>(combinedSet);
});
if (flag) {
// 数据存储在BurpSuite空间内
try {
DataManager dataManager = new DataManager(api);
PersistedObject persistedObject = PersistedObject.persistedObject();
gRuleMap.forEach((kName, vList) -> {
PersistedList<String> persistedList = PersistedList.persistedStringList();
persistedList.addAll(vList);
persistedObject.setStringList(kName, persistedList);
});
dataManager.putData("data", host, persistedObject);
} catch (Exception ignored) {
}
}
return gRuleMap;
});
String[] splitHost = host.split("\\.");
String onlyHost = host.split(":")[0];
String anyHost = (splitHost.length > 2 && !StringProcessor.matchHostIsIp(onlyHost)) ? StringProcessor.replaceFirstOccurrence(onlyHost, splitHost[0], "*") : "";
if (!Config.globalDataMap.containsKey(anyHost) && !anyHost.isEmpty()) {
// 添加通配符Host实际数据从查询哪里将所有数据提取
Config.globalDataMap.put(anyHost, new HashMap<>());
}
if (!Config.globalDataMap.containsKey("*")) {
// 添加通配符全匹配,同上
Config.globalDataMap.put("*", new HashMap<>());
}
}
}
private List<String> matchByRegex(String f_regex, String s_regex, String content, String format, String engine, boolean sensitive) { private List<String> matchByRegex(String f_regex, String s_regex, String content, String format, String engine, boolean sensitive) {
List<String> retList = new ArrayList<>(); List<String> retList = new ArrayList<>();
if ("nfa".equals(engine)) { if ("nfa".equals(engine)) {

View File

@@ -49,6 +49,11 @@ public class ConfigLoader {
Config.globalRules = getRules(); Config.globalRules = getRules();
} }
private static boolean isValidConfigPath(String configPath) {
File configPathFile = new File(configPath);
return configPathFile.exists() && configPathFile.isDirectory();
}
private String determineConfigPath() { private String determineConfigPath() {
// 优先级1用户根目录 // 优先级1用户根目录
String userConfigPath = String.format("%s/.config/HaE", System.getProperty("user.home")); String userConfigPath = String.format("%s/.config/HaE", System.getProperty("user.home"));
@@ -67,11 +72,6 @@ public class ConfigLoader {
return userConfigPath; return userConfigPath;
} }
private static boolean isValidConfigPath(String configPath) {
File configPathFile = new File(configPath);
return configPathFile.exists() && configPathFile.isDirectory();
}
public void initConfig() { public void initConfig() {
Map<String, Object> r = new LinkedHashMap<>(); Map<String, Object> r = new LinkedHashMap<>();
r.put("ExcludeSuffix", getExcludeSuffix()); r.put("ExcludeSuffix", getExcludeSuffix());
@@ -102,8 +102,6 @@ public class ConfigLoader {
Representer representer = new Representer(dop); Representer representer = new Representer(dop);
Map<String, Object> rulesMap = new Yaml(representer, dop).load(inputStream); Map<String, Object> rulesMap = new Yaml(representer, dop).load(inputStream);
String[] fieldKeys = {"loaded", "name", "f_regex", "s_regex", "format", "color", "scope", "engine", "sensitive"};
Object rulesObj = rulesMap.get("rules"); Object rulesObj = rulesMap.get("rules");
if (rulesObj instanceof List) { if (rulesObj instanceof List) {
List<Map<String, Object>> groupData = (List<Map<String, Object>>) rulesObj; List<Map<String, Object>> groupData = (List<Map<String, Object>>) rulesObj;
@@ -114,9 +112,9 @@ public class ConfigLoader {
if (ruleObj instanceof List) { if (ruleObj instanceof List) {
List<Map<String, Object>> ruleData = (List<Map<String, Object>>) ruleObj; List<Map<String, Object>> ruleData = (List<Map<String, Object>>) ruleObj;
for (Map<String, Object> ruleFields : ruleData) { for (Map<String, Object> ruleFields : ruleData) {
Object[] valuesArray = new Object[fieldKeys.length]; Object[] valuesArray = new Object[Config.ruleFields.length];
for (int i = 0; i < fieldKeys.length; i++) { for (int i = 0; i < Config.ruleFields.length; i++) {
valuesArray[i] = ruleFields.get(fieldKeys[i]); valuesArray[i] = ruleFields.get(Config.ruleFields[i].toLowerCase().replace("-", "_"));
} }
data.add(valuesArray); data.add(valuesArray);
} }
@@ -138,26 +136,50 @@ public class ConfigLoader {
return getValueFromConfig("BlockHost", Config.host); return getValueFromConfig("BlockHost", Config.host);
} }
public void setBlockHost(String blockHost) {
setValueToConfig("BlockHost", blockHost);
}
public String getExcludeSuffix() { public String getExcludeSuffix() {
return getValueFromConfig("ExcludeSuffix", Config.suffix); return getValueFromConfig("ExcludeSuffix", Config.suffix);
} }
public void setExcludeSuffix(String excludeSuffix) {
setValueToConfig("ExcludeSuffix", excludeSuffix);
}
public String getExcludeStatus() { public String getExcludeStatus() {
return getValueFromConfig("ExcludeStatus", Config.status); return getValueFromConfig("ExcludeStatus", Config.status);
} }
public void setExcludeStatus(String status) {
setValueToConfig("ExcludeStatus", status);
}
public String getLimitSize() { public String getLimitSize() {
return getValueFromConfig("LimitSize", Config.size); return getValueFromConfig("LimitSize", Config.size);
} }
public void setLimitSize(String size) {
setValueToConfig("LimitSize", size);
}
public String getScope() { public String getScope() {
return getValueFromConfig("HaEScope", Config.scopeOptions); return getValueFromConfig("HaEScope", Config.scopeOptions);
} }
public void setScope(String scope) {
setValueToConfig("HaEScope", scope);
}
public boolean getMode() { public boolean getMode() {
return getValueFromConfig("HaEModeStatus", Config.modeStatus).equals("true"); return getValueFromConfig("HaEModeStatus", Config.modeStatus).equals("true");
} }
public void setMode(String mode) {
setValueToConfig("HaEModeStatus", mode);
}
private String getValueFromConfig(String name, String defaultValue) { private String getValueFromConfig(String name, String defaultValue) {
File yamlSetting = new File(configFilePath); File yamlSetting = new File(configFilePath);
if (!yamlSetting.exists() || !yamlSetting.isFile()) { if (!yamlSetting.exists() || !yamlSetting.isFile()) {
@@ -176,30 +198,6 @@ public class ConfigLoader {
return defaultValue; return defaultValue;
} }
public void setExcludeSuffix(String excludeSuffix) {
setValueToConfig("ExcludeSuffix", excludeSuffix);
}
public void setBlockHost(String blockHost) {
setValueToConfig("BlockHost", blockHost);
}
public void setExcludeStatus(String status) {
setValueToConfig("ExcludeStatus", status);
}
public void setLimitSize(String size) {
setValueToConfig("LimitSize", size);
}
public void setScope(String scope) {
setValueToConfig("HaEScope", scope);
}
public void setMode(String mode) {
setValueToConfig("HaEModeStatus", mode);
}
private void setValueToConfig(String name, String value) { private void setValueToConfig(String name, String value) {
Map<String, Object> currentConfig = loadCurrentConfig(); Map<String, Object> currentConfig = loadCurrentConfig();
currentConfig.put(name, value); currentConfig.put(name, value);

View File

@@ -10,6 +10,12 @@ import burp.api.montoya.persistence.Persistence;
import hae.component.board.message.MessageTableModel; import hae.component.board.message.MessageTableModel;
import hae.instances.http.utils.RegularMatcher; import hae.instances.http.utils.RegularMatcher;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class DataManager { public class DataManager {
private final MontoyaApi api; private final MontoyaApi api;
private final Persistence persistence; private final Persistence persistence;
@@ -69,22 +75,81 @@ public class DataManager {
} }
private void loadMessageData(PersistedList<String> messageIndex, MessageTableModel messageTableModel) { private void loadMessageData(PersistedList<String> messageIndex, MessageTableModel messageTableModel) {
if (messageIndex != null && !messageIndex.isEmpty()) { if (messageIndex == null || messageIndex.isEmpty()) {
messageIndex.forEach(index -> { return;
}
List<String> indexList = new ArrayList<>();
for (Object item : messageIndex) {
try {
if (item != null) {
indexList.add(item.toString());
}
} catch (Exception e) {
api.logging().logToError("转换索引时出错: " + e.getMessage());
}
}
final int batchSize = 2000; // 增加批处理大小
final int threadCount = Math.max(8, Runtime.getRuntime().availableProcessors() * 2); // 增加线程数
int totalSize = indexList.size();
// 使用更高效的线程池
ExecutorService executorService = Executors.newWorkStealingPool(threadCount);
List<Future<List<Object[]>>> futures = new ArrayList<>();
// 分批并行处理数据
for (int i = 0; i < totalSize; i += batchSize) {
int endIndex = Math.min(i + batchSize, totalSize);
List<String> batch = indexList.subList(i, endIndex);
Future<List<Object[]>> future = executorService.submit(() -> processBatchParallel(batch));
futures.add(future);
}
// 批量添加数据到模型
try {
for (Future<List<Object[]>> future : futures) {
List<Object[]> batchData = future.get();
messageTableModel.addBatch(batchData);
}
} catch (Exception e) {
api.logging().logToError("批量添加数据时出错: " + e.getMessage());
} finally {
executorService.shutdown();
}
}
private List<Object[]> processBatchParallel(List<String> batch) {
List<Object[]> batchData = new ArrayList<>();
for (String index : batch) {
try {
PersistedObject dataObj = persistence.extensionData().getChildObject(index); PersistedObject dataObj = persistence.extensionData().getChildObject(index);
if (dataObj != null) { if (dataObj != null) {
HttpRequestResponse messageInfo = dataObj.getHttpRequestResponse("messageInfo"); HttpRequestResponse messageInfo = dataObj.getHttpRequestResponse("messageInfo");
String comment = dataObj.getString("comment"); if (messageInfo != null) {
String color = dataObj.getString("color"); batchData.add(prepareMessageData(messageInfo, dataObj));
HttpRequest request = messageInfo.request(); }
HttpResponse response = messageInfo.response();
String method = request.method();
String url = request.url();
String status = String.valueOf(response.statusCode());
String length = String.valueOf(response.toByteArray().length());
messageTableModel.add(messageInfo, url, method, status, length, comment, color, false);
} }
}); } catch (Exception e) {
api.logging().logToError("处理消息数据时出错: " + e.getMessage() + ", index: " + index);
}
} }
return batchData;
} }
}
private Object[] prepareMessageData(HttpRequestResponse messageInfo, PersistedObject dataObj) {
HttpRequest request = messageInfo.request();
HttpResponse response = messageInfo.response();
return new Object[]{
messageInfo,
request.url(),
request.method(),
String.valueOf(response.statusCode()),
String.valueOf(response.toByteArray().length()),
dataObj.getString("comment"),
dataObj.getString("color"),
false
};
}
}