Files
HaE/src/main/java/hae/component/board/message/MessageTableModel.java

462 lines
19 KiB
Java
Raw Normal View History

2024-05-06 12:56:56 +08:00
package hae.component.board.message;
import burp.api.montoya.MontoyaApi;
import burp.api.montoya.http.message.HttpHeader;
import burp.api.montoya.http.message.HttpRequestResponse;
import burp.api.montoya.http.message.requests.HttpRequest;
import burp.api.montoya.http.message.responses.HttpResponse;
2024-12-21 15:34:45 +08:00
import burp.api.montoya.persistence.PersistedObject;
2024-05-06 12:56:56 +08:00
import burp.api.montoya.ui.UserInterface;
import burp.api.montoya.ui.editor.HttpRequestEditor;
import burp.api.montoya.ui.editor.HttpResponseEditor;
import hae.Config;
2024-09-19 17:08:46 +08:00
import hae.utils.ConfigLoader;
2024-12-21 15:34:45 +08:00
import hae.utils.DataManager;
2024-05-06 12:56:56 +08:00
import hae.utils.string.StringProcessor;
2024-05-12 19:02:38 +08:00
import javax.swing.*;
2023-10-12 21:38:27 +08:00
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
2024-05-12 19:02:38 +08:00
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.util.*;
2024-09-19 17:08:46 +08:00
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
2024-05-06 12:56:56 +08:00
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import static burp.api.montoya.ui.editor.EditorOptions.READ_ONLY;
public class MessageTableModel extends AbstractTableModel {
private final MontoyaApi api;
2024-09-19 17:08:46 +08:00
private final ConfigLoader configLoader;
2024-05-06 12:56:56 +08:00
private final MessageTable messageTable;
private final JSplitPane splitPane;
2024-05-30 14:37:01 +08:00
private final LinkedList<MessageEntry> log = new LinkedList<>();
2024-05-12 19:02:38 +08:00
private final LinkedList<MessageEntry> filteredLog;
2024-06-19 22:16:57 +08:00
private SwingWorker<Void, Void> currentWorker;
2024-05-06 12:56:56 +08:00
2024-09-19 17:08:46 +08:00
public MessageTableModel(MontoyaApi api, ConfigLoader configLoader) {
2024-05-06 12:56:56 +08:00
this.filteredLog = new LinkedList<>();
this.api = api;
2024-09-19 17:08:46 +08:00
this.configLoader = configLoader;
2024-05-06 12:56:56 +08:00
2024-06-19 22:16:57 +08:00
JTabbedPane messageTab = new JTabbedPane();
2024-05-06 12:56:56 +08:00
UserInterface userInterface = api.userInterface();
HttpRequestEditor requestViewer = userInterface.createHttpRequestEditor(READ_ONLY);
HttpResponseEditor responseViewer = userInterface.createHttpResponseEditor(READ_ONLY);
messageTab.addTab("Request", requestViewer.uiComponent());
messageTab.addTab("Response", responseViewer.uiComponent());
// 请求条目表格
messageTable = new MessageTable(MessageTableModel.this, requestViewer, responseViewer);
messageTable.setDefaultRenderer(Object.class, new MessageRenderer(filteredLog, messageTable));
messageTable.setAutoCreateRowSorter(true);
2023-10-12 21:38:27 +08:00
// Length字段根据大小进行排序
2025-03-25 11:44:07 +08:00
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() {
2024-05-06 12:56:56 +08:00
TableRowSorter<DefaultTableModel> sorter = (TableRowSorter<DefaultTableModel>) messageTable.getRowSorter();
2025-03-25 11:44:07 +08:00
sorter.setComparator(4, (Comparator<String>) (s1, s2) -> {
Integer age1 = Integer.parseInt(s1);
Integer age2 = Integer.parseInt(s2);
return age1.compareTo(age2);
2023-10-12 21:38:27 +08:00
});
2024-01-18 12:07:20 +08:00
2023-10-12 21:38:27 +08:00
// Color字段根据颜色顺序进行排序
2023-10-26 14:17:56 +08:00
sorter.setComparator(5, new Comparator<String>() {
2023-10-12 21:38:27 +08:00
@Override
public int compare(String s1, String s2) {
int index1 = getIndex(s1);
int index2 = getIndex(s2);
return Integer.compare(index1, index2);
}
2024-05-12 19:02:38 +08:00
2023-10-12 21:38:27 +08:00
private int getIndex(String color) {
2024-05-06 12:56:56 +08:00
for (int i = 0; i < Config.color.length; i++) {
if (Config.color[i].equals(color)) {
2023-10-12 21:38:27 +08:00
return i;
}
}
return -1;
}
});
2025-03-25 11:44:07 +08:00
return sorter;
2023-10-12 21:38:27 +08:00
}
2025-03-21 21:22:11 +08:00
public synchronized void add(HttpRequestResponse messageInfo, String url, String method, String status, String length, String comment, String color, boolean flag) {
2024-05-12 19:02:38 +08:00
synchronized (log) {
2025-03-25 11:44:07 +08:00
if (messageInfo == null) {
return;
2024-05-30 14:37:01 +08:00
}
2023-10-12 21:38:27 +08:00
2025-03-25 11:44:07 +08:00
boolean isDuplicate = false;
2024-05-30 14:37:01 +08:00
try {
2025-03-25 11:44:07 +08:00
if (!log.isEmpty() && flag) {
String host = StringProcessor.getHostByUrl(url);
2024-05-06 12:56:56 +08:00
for (MessageEntry entry : log) {
2025-03-25 11:44:07 +08:00
if (host.equals(StringProcessor.getHostByUrl(entry.getUrl()))) {
if (isRequestDuplicate(
messageInfo, entry.getRequestResponse(),
url, entry.getUrl(),
comment, entry.getComment(),
color, entry.getColor()
)) {
2024-05-06 12:56:56 +08:00
isDuplicate = true;
break;
}
}
}
}
} catch (Exception ignored) {
}
2024-05-30 14:37:01 +08:00
if (!isDuplicate) {
2024-12-21 15:34:45 +08:00
if (flag) {
2025-03-25 11:44:07 +08:00
persistData(messageInfo, comment, color);
2024-12-21 15:34:45 +08:00
}
2025-03-25 11:44:07 +08:00
log.add(new MessageEntry(messageInfo, method, url, comment, length, color, status));
2024-05-30 14:37:01 +08:00
}
2023-10-12 21:38:27 +08:00
}
2025-03-25 11:44:07 +08:00
}
2023-10-12 21:38:27 +08:00
2025-03-25 11:44:07 +08:00
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);
// 请求响应内容匹配
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;
}
2023-10-12 21:38:27 +08:00
}
2025-03-25 11:44:07 +08:00
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());
2025-03-21 21:22:11 +08:00
}
}
2024-05-06 12:56:56 +08:00
public void deleteByHost(String filterText) {
filteredLog.clear();
List<Integer> rowsToRemove = new ArrayList<>();
2024-05-30 14:37:01 +08:00
2024-06-19 22:16:57 +08:00
if (currentWorker != null && !currentWorker.isDone()) {
currentWorker.cancel(true);
}
2025-03-25 11:44:07 +08:00
currentWorker = new SwingWorker<>() {
2024-05-30 14:37:01 +08:00
@Override
protected Void doInBackground() {
for (int i = 0; i < log.size(); i++) {
MessageEntry entry = log.get(i);
String host = StringProcessor.getHostByUrl(entry.getUrl());
if (!host.isEmpty()) {
if (StringProcessor.matchesHostPattern(host, filterText) || filterText.equals("*")) {
rowsToRemove.add(i);
}
}
2024-05-06 12:56:56 +08:00
}
2024-05-30 14:37:01 +08:00
for (int i = rowsToRemove.size() - 1; i >= 0; i--) {
int row = rowsToRemove.get(i);
log.remove(row);
}
2024-05-06 12:56:56 +08:00
2024-05-30 14:37:01 +08:00
return null;
}
2024-06-19 22:16:57 +08:00
};
2024-05-30 14:37:01 +08:00
2024-06-19 22:16:57 +08:00
currentWorker.execute();
2023-10-12 21:38:27 +08:00
}
public void applyHostFilter(String filterText) {
filteredLog.clear();
2024-05-06 12:56:56 +08:00
2024-06-19 22:16:57 +08:00
log.forEach(entry -> {
2024-12-21 15:34:45 +08:00
String host = StringProcessor.getHostByUrl(entry.getUrl());
2024-06-19 22:16:57 +08:00
if (!host.isEmpty()) {
if (StringProcessor.matchesHostPattern(host, filterText) || filterText.contains("*")) {
2024-12-21 15:34:45 +08:00
filteredLog.add(entry);
2024-05-06 12:56:56 +08:00
}
2023-10-12 21:38:27 +08:00
}
2024-06-19 22:16:57 +08:00
});
2024-05-30 14:37:01 +08:00
2024-06-19 22:16:57 +08:00
fireTableDataChanged();
2024-05-30 14:37:01 +08:00
}
2024-05-06 12:56:56 +08:00
2023-10-12 21:38:27 +08:00
public void applyMessageFilter(String tableName, String filterText) {
filteredLog.clear();
2024-05-06 12:56:56 +08:00
for (MessageEntry entry : log) {
2024-05-30 14:37:01 +08:00
// 标志变量,表示是否满足过滤条件
AtomicBoolean isMatched = new AtomicBoolean(false);
2024-06-19 22:16:57 +08:00
HttpRequestResponse requestResponse = entry.getRequestResponse();
HttpRequest httpRequest = requestResponse.request();
HttpResponse httpResponse = requestResponse.response();
2024-05-06 12:56:56 +08:00
String requestString = new String(httpRequest.toByteArray().getBytes(), StandardCharsets.UTF_8);
String requestBody = new String(httpRequest.body().getBytes(), StandardCharsets.UTF_8);
String requestHeaders = httpRequest.headers().stream()
.map(HttpHeader::toString)
.collect(Collectors.joining("\n"));
String responseString = new String(httpResponse.toByteArray().getBytes(), StandardCharsets.UTF_8);
String responseBody = new String(httpResponse.body().getBytes(), StandardCharsets.UTF_8);
String responseHeaders = httpResponse.headers().stream()
.map(HttpHeader::toString)
.collect(Collectors.joining("\n"));
2023-10-12 21:38:27 +08:00
2024-05-06 12:56:56 +08:00
Config.globalRules.keySet().forEach(i -> {
for (Object[] objects : Config.globalRules.get(i)) {
2023-10-12 21:38:27 +08:00
String name = objects[1].toString();
2024-02-02 19:07:03 +08:00
String format = objects[4].toString();
String scope = objects[6].toString();
// 从注释中查看是否包含当前规则名,包含的再进行查询,有效减少无意义的检索时间
2024-06-19 22:16:57 +08:00
if (entry.getComment().contains(name)) {
2024-02-02 19:07:03 +08:00
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;
2024-05-12 19:02:38 +08:00
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;
2024-02-02 19:07:03 +08:00
default:
break;
}
isMatched.set(isMatch);
2023-10-12 21:38:27 +08:00
break;
}
}
}
});
2024-02-02 19:07:03 +08:00
if (isMatched.get()) {
2023-10-12 21:38:27 +08:00
filteredLog.add(entry);
}
}
2024-05-06 12:56:56 +08:00
2023-10-12 21:38:27 +08:00
fireTableDataChanged();
2024-05-06 12:56:56 +08:00
messageTable.lastSelectedIndex = -1;
2023-10-12 21:38:27 +08:00
}
2024-02-02 19:07:03 +08:00
private boolean matchingString(String format, String filterText, String target) {
boolean isMatch = true;
try {
MessageFormat mf = new MessageFormat(format);
Object[] parsedObjects = mf.parse(filterText);
for (Object parsedObject : parsedObjects) {
if (!target.contains(parsedObject.toString())) {
isMatch = false;
break;
}
}
} catch (Exception e) {
isMatch = false;
}
return isMatch;
}
2024-05-12 19:02:38 +08:00
public JSplitPane getSplitPane() {
2024-05-06 12:56:56 +08:00
return splitPane;
}
2024-05-12 19:02:38 +08:00
public MessageTable getMessageTable() {
2024-05-06 12:56:56 +08:00
return messageTable;
}
@Override
public int getRowCount() {
return filteredLog.size();
}
@Override
public int getColumnCount() {
return 6;
}
@Override
2024-05-12 19:02:38 +08:00
public Object getValueAt(int rowIndex, int columnIndex) {
2024-06-19 22:16:57 +08:00
if (!filteredLog.isEmpty()) {
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 -> "";
};
}
} catch (Exception e) {
api.logging().logToError("getValueAt: " + e.getMessage());
}
2024-05-06 12:56:56 +08:00
}
2024-05-30 14:37:01 +08:00
2024-06-19 22:16:57 +08:00
return "";
2024-05-06 12:56:56 +08:00
}
@Override
2024-05-12 19:02:38 +08:00
public String getColumnName(int columnIndex) {
2024-05-06 12:56:56 +08:00
return switch (columnIndex) {
case 0 -> "Method";
case 1 -> "URL";
case 2 -> "Comment";
case 3 -> "Status";
case 4 -> "Length";
case 5 -> "Color";
default -> "";
};
}
public class MessageTable extends JTable {
2024-09-19 17:08:46 +08:00
private final ExecutorService executorService;
2024-05-06 12:56:56 +08:00
private final HttpRequestEditor requestEditor;
private final HttpResponseEditor responseEditor;
2025-03-21 21:22:11 +08:00
private int lastSelectedIndex = -1;
2023-10-12 21:38:27 +08:00
2024-05-06 12:56:56 +08:00
public MessageTable(TableModel messageTableModel, HttpRequestEditor requestEditor, HttpResponseEditor responseEditor) {
super(messageTableModel);
this.requestEditor = requestEditor;
this.responseEditor = responseEditor;
2024-09-19 17:08:46 +08:00
this.executorService = Executors.newSingleThreadExecutor();
2023-10-12 21:38:27 +08:00
}
@Override
public void changeSelection(int row, int col, boolean toggle, boolean extend) {
2023-10-23 21:51:12 +08:00
super.changeSelection(row, col, toggle, extend);
2024-09-19 17:08:46 +08:00
int selectedIndex = convertRowIndexToModel(row);
if (lastSelectedIndex != selectedIndex) {
lastSelectedIndex = selectedIndex;
executorService.execute(this::getSelectedMessage);
2024-05-30 15:56:49 +08:00
}
2024-09-19 17:08:46 +08:00
}
2023-10-12 21:38:27 +08:00
2024-09-19 17:08:46 +08:00
private void getSelectedMessage() {
2025-03-25 11:44:07 +08:00
MessageEntry messageEntry = filteredLog.get(lastSelectedIndex);
2024-08-12 10:34:26 +08:00
2024-09-19 17:08:46 +08:00
HttpRequestResponse httpRequestResponse = messageEntry.getRequestResponse();
2024-08-12 10:34:26 +08:00
2024-09-19 17:08:46 +08:00
requestEditor.setRequest(HttpRequest.httpRequest(messageEntry.getRequestResponse().httpService(), httpRequestResponse.request().toByteArray()));
int responseSizeWithMb = httpRequestResponse.response().toString().length() / 1024 / 1024;
if ((responseSizeWithMb < Integer.parseInt(configLoader.getLimitSize())) || configLoader.getLimitSize().equals("0")) {
responseEditor.setResponse(httpRequestResponse.response());
} else {
responseEditor.setResponse(HttpResponse.httpResponse("Exceeds length limit."));
}
2023-10-12 21:38:27 +08:00
}
}
2024-05-06 12:56:56 +08:00
}