Version: 4.1.1 Update
This commit is contained in:
@@ -10,10 +10,8 @@ import burp.api.montoya.ui.UserInterface;
|
||||
import burp.api.montoya.ui.editor.HttpRequestEditor;
|
||||
import burp.api.montoya.ui.editor.HttpResponseEditor;
|
||||
import hae.Config;
|
||||
import hae.cache.MessageCache;
|
||||
import hae.utils.ConfigLoader;
|
||||
import hae.utils.DataManager;
|
||||
import hae.utils.string.HashCalculator;
|
||||
import hae.utils.string.StringProcessor;
|
||||
|
||||
import javax.swing.*;
|
||||
@@ -58,14 +56,25 @@ public class MessageTableModel extends AbstractTableModel {
|
||||
messageTable.setAutoCreateRowSorter(true);
|
||||
|
||||
// Length字段根据大小进行排序
|
||||
TableRowSorter<DefaultTableModel> sorter = getDefaultTableModelTableRowSorter();
|
||||
messageTable.setRowSorter(sorter);
|
||||
messageTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
|
||||
|
||||
splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
|
||||
// 请求/响应文本框
|
||||
JScrollPane scrollPane = new JScrollPane(messageTable);
|
||||
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
|
||||
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
|
||||
splitPane.setLeftComponent(scrollPane);
|
||||
splitPane.setRightComponent(messageTab);
|
||||
}
|
||||
|
||||
private TableRowSorter<DefaultTableModel> getDefaultTableModelTableRowSorter() {
|
||||
TableRowSorter<DefaultTableModel> sorter = (TableRowSorter<DefaultTableModel>) messageTable.getRowSorter();
|
||||
sorter.setComparator(4, new Comparator<String>() {
|
||||
@Override
|
||||
public int compare(String s1, String s2) {
|
||||
Integer age1 = Integer.parseInt(s1);
|
||||
Integer age2 = Integer.parseInt(s2);
|
||||
return age1.compareTo(age2);
|
||||
}
|
||||
sorter.setComparator(4, (Comparator<String>) (s1, s2) -> {
|
||||
Integer age1 = Integer.parseInt(s1);
|
||||
Integer age2 = Integer.parseInt(s2);
|
||||
return age1.compareTo(age2);
|
||||
});
|
||||
|
||||
// Color字段根据颜色顺序进行排序
|
||||
@@ -86,48 +95,31 @@ public class MessageTableModel extends AbstractTableModel {
|
||||
return -1;
|
||||
}
|
||||
});
|
||||
messageTable.setRowSorter(sorter);
|
||||
messageTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
|
||||
|
||||
splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
|
||||
// 请求/响应文本框
|
||||
JScrollPane scrollPane = new JScrollPane(messageTable);
|
||||
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
|
||||
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
|
||||
splitPane.setLeftComponent(scrollPane);
|
||||
splitPane.setRightComponent(messageTab);
|
||||
return sorter;
|
||||
}
|
||||
|
||||
public synchronized void add(HttpRequestResponse messageInfo, String url, String method, String status, String length, String comment, String color, boolean flag) {
|
||||
synchronized (log) {
|
||||
boolean isDuplicate = false;
|
||||
MessageEntry logEntry = new MessageEntry(messageInfo, method, url, comment, length, color, status);
|
||||
|
||||
byte[] reqByteA = new byte[0];
|
||||
byte[] resByteA = new byte[0];
|
||||
|
||||
if (messageInfo != null) {
|
||||
HttpRequest httpRequest = messageInfo.request();
|
||||
HttpResponse httpResponse = messageInfo.response();
|
||||
|
||||
reqByteA = httpRequest.toByteArray().getBytes();
|
||||
resByteA = httpResponse.toByteArray().getBytes();
|
||||
if (messageInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 比较Hash,如若存在重复的请求或响应,则不放入消息内容里
|
||||
boolean isDuplicate = false;
|
||||
try {
|
||||
if (!log.isEmpty()) {
|
||||
if (!log.isEmpty() && flag) {
|
||||
String host = StringProcessor.getHostByUrl(url);
|
||||
|
||||
for (MessageEntry entry : log) {
|
||||
HttpRequestResponse reqResMessage = entry.getRequestResponse();
|
||||
byte[] reqByteB = reqResMessage.request().toByteArray().getBytes();
|
||||
byte[] resByteB = reqResMessage.response().toByteArray().getBytes();
|
||||
try {
|
||||
// 通过URL、请求和响应报文、匹配数据内容,多维度进行对比
|
||||
if ((entry.getUrl().equals(url) || (Arrays.equals(reqByteB, reqByteA) || Arrays.equals(resByteB, resByteA))) && (areMapsEqual(getCacheData(reqByteB), getCacheData(reqByteA)) && areMapsEqual(getCacheData(resByteB), getCacheData(resByteA)))) {
|
||||
if (host.equals(StringProcessor.getHostByUrl(entry.getUrl()))) {
|
||||
if (isRequestDuplicate(
|
||||
messageInfo, entry.getRequestResponse(),
|
||||
url, entry.getUrl(),
|
||||
comment, entry.getComment(),
|
||||
color, entry.getColor()
|
||||
)) {
|
||||
isDuplicate = true;
|
||||
break;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,42 +128,82 @@ public class MessageTableModel extends AbstractTableModel {
|
||||
|
||||
if (!isDuplicate) {
|
||||
if (flag) {
|
||||
try {
|
||||
DataManager dataManager = new DataManager(api);
|
||||
// 数据存储在BurpSuite空间内
|
||||
PersistedObject persistedObject = PersistedObject.persistedObject();
|
||||
persistedObject.setHttpRequestResponse("messageInfo", messageInfo);
|
||||
persistedObject.setString("comment", comment);
|
||||
persistedObject.setString("color", color);
|
||||
String uuidIndex = StringProcessor.getRandomUUID();
|
||||
dataManager.putData("message", uuidIndex, persistedObject);
|
||||
} catch (Exception ignored) {
|
||||
|
||||
}
|
||||
persistData(messageInfo, comment, color);
|
||||
}
|
||||
|
||||
// 添加进日志
|
||||
log.add(logEntry);
|
||||
log.add(new MessageEntry(messageInfo, method, url, comment, length, color, status));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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];
|
||||
private boolean isRequestDuplicate(
|
||||
HttpRequestResponse newReq, HttpRequestResponse existingReq,
|
||||
String newUrl, String existingUrl,
|
||||
String newComment, String existingComment,
|
||||
String newColor, String existingColor) {
|
||||
try {
|
||||
// 基础属性匹配
|
||||
String normalizedNewUrl = normalizeUrl(newUrl);
|
||||
String normalizedExistingUrl = normalizeUrl(existingUrl);
|
||||
boolean basicMatch = normalizedNewUrl.equals(normalizedExistingUrl);
|
||||
|
||||
// 复用现有的 add 方法逻辑,但跳过重复检查
|
||||
MessageEntry logEntry = new MessageEntry(messageInfo, method, url, comment, length, color, status);
|
||||
log.add(logEntry);
|
||||
}
|
||||
// 请求响应内容匹配
|
||||
byte[] newReqBytes = newReq.request().toByteArray().getBytes();
|
||||
byte[] newResBytes = newReq.response().toByteArray().getBytes();
|
||||
byte[] existingReqBytes = existingReq.request().toByteArray().getBytes();
|
||||
byte[] existingResBytes = existingReq.response().toByteArray().getBytes();
|
||||
boolean contentMatch = Arrays.equals(newReqBytes, existingReqBytes) &&
|
||||
Arrays.equals(newResBytes, existingResBytes);
|
||||
|
||||
// 注释和颜色匹配
|
||||
boolean metadataMatch = areCommentsEqual(newComment, existingComment) &&
|
||||
newColor.equals(existingColor);
|
||||
|
||||
return (basicMatch || contentMatch) && metadataMatch;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private String normalizeUrl(String url) {
|
||||
if (url == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String normalized = url.trim().toLowerCase();
|
||||
while (normalized.endsWith("/")) {
|
||||
normalized = normalized.substring(0, normalized.length() - 1);
|
||||
}
|
||||
|
||||
return normalized.replaceAll("//", "/");
|
||||
}
|
||||
|
||||
private boolean areCommentsEqual(String comment1, String comment2) {
|
||||
if (comment1 == null || comment2 == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// 将注释按规则拆分并排序
|
||||
Set<String> rules1 = new TreeSet<>(Arrays.asList(comment1.split(", ")));
|
||||
Set<String> rules2 = new TreeSet<>(Arrays.asList(comment2.split(", ")));
|
||||
|
||||
return rules1.equals(rules2);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void persistData(HttpRequestResponse messageInfo, String comment, String color) {
|
||||
try {
|
||||
DataManager dataManager = new DataManager(api);
|
||||
PersistedObject persistedObject = PersistedObject.persistedObject();
|
||||
persistedObject.setHttpRequestResponse("messageInfo", messageInfo);
|
||||
persistedObject.setString("comment", comment);
|
||||
persistedObject.setString("color", color);
|
||||
String uuidIndex = StringProcessor.getRandomUUID();
|
||||
dataManager.putData("message", uuidIndex, persistedObject);
|
||||
} catch (Exception e) {
|
||||
api.logging().logToError("Data persistence error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,7 +215,7 @@ public class MessageTableModel extends AbstractTableModel {
|
||||
currentWorker.cancel(true);
|
||||
}
|
||||
|
||||
currentWorker = new SwingWorker<Void, Void>() {
|
||||
currentWorker = new SwingWorker<>() {
|
||||
@Override
|
||||
protected Void doInBackground() {
|
||||
for (int i = 0; i < log.size(); i++) {
|
||||
@@ -333,56 +365,6 @@ public class MessageTableModel extends AbstractTableModel {
|
||||
return isMatch;
|
||||
}
|
||||
|
||||
private Map<String, Map<String, Object>> getCacheData(byte[] content) {
|
||||
String hashIndex = HashCalculator.calculateHash(content);
|
||||
return MessageCache.get(hashIndex);
|
||||
}
|
||||
|
||||
private boolean areMapsEqual(Map<String, Map<String, Object>> map1, Map<String, Map<String, Object>> map2) {
|
||||
if (map1 == null || map2 == null) {
|
||||
return false;
|
||||
}
|
||||
if (map1.size() != map2.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (String key : map1.keySet()) {
|
||||
if (!map2.containsKey(key)) {
|
||||
return false;
|
||||
}
|
||||
if (areInnerMapsEqual(map1.get(key), map2.get(key))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean areInnerMapsEqual(Map<String, Object> innerMap1, Map<String, Object> innerMap2) {
|
||||
if (innerMap1.size() != innerMap2.size()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (String key : innerMap1.keySet()) {
|
||||
if (!innerMap2.containsKey(key)) {
|
||||
return true;
|
||||
}
|
||||
Object value1 = innerMap1.get(key);
|
||||
Object value2 = innerMap2.get(key);
|
||||
|
||||
// 如果值是Map,则递归对比
|
||||
if (value1 instanceof Map && value2 instanceof Map) {
|
||||
if (areInnerMapsEqual((Map<String, Object>) value1, (Map<String, Object>) value2)) {
|
||||
return true;
|
||||
}
|
||||
} else if (!value1.equals(value2)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public JSplitPane getSplitPane() {
|
||||
return splitPane;
|
||||
}
|
||||
@@ -391,10 +373,6 @@ public class MessageTableModel extends AbstractTableModel {
|
||||
return messageTable;
|
||||
}
|
||||
|
||||
public LinkedList<MessageEntry> getLogs() {
|
||||
return log;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return filteredLog.size();
|
||||
@@ -447,7 +425,6 @@ public class MessageTableModel extends AbstractTableModel {
|
||||
private final ExecutorService executorService;
|
||||
private final HttpRequestEditor requestEditor;
|
||||
private final HttpResponseEditor responseEditor;
|
||||
private MessageEntry messageEntry;
|
||||
private int lastSelectedIndex = -1;
|
||||
|
||||
public MessageTable(TableModel messageTableModel, HttpRequestEditor requestEditor, HttpResponseEditor responseEditor) {
|
||||
@@ -468,7 +445,7 @@ public class MessageTableModel extends AbstractTableModel {
|
||||
}
|
||||
|
||||
private void getSelectedMessage() {
|
||||
messageEntry = filteredLog.get(lastSelectedIndex);
|
||||
MessageEntry messageEntry = filteredLog.get(lastSelectedIndex);
|
||||
|
||||
HttpRequestResponse httpRequestResponse = messageEntry.getRequestResponse();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user