Version: 3.0 Update
This commit is contained in:
87
src/main/java/hae/component/Main.java
Normal file
87
src/main/java/hae/component/Main.java
Normal file
@@ -0,0 +1,87 @@
|
||||
package hae.component;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import hae.component.board.Databoard;
|
||||
import hae.component.board.message.MessageTableModel;
|
||||
import hae.component.config.Config;
|
||||
import hae.component.rule.Rules;
|
||||
import hae.utils.config.ConfigLoader;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.net.URL;
|
||||
|
||||
public class Main extends JPanel {
|
||||
private final MontoyaApi api;
|
||||
private final ConfigLoader configLoader;
|
||||
private final MessageTableModel messageTableModel;
|
||||
|
||||
public Main(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) {
|
||||
this.api = api;
|
||||
this.configLoader = configLoader;
|
||||
this.messageTableModel = messageTableModel;
|
||||
|
||||
initComponents();
|
||||
}
|
||||
|
||||
private void initComponents() {
|
||||
setLayout(new GridBagLayout());
|
||||
((GridBagLayout)getLayout()).columnWidths = new int[] {0, 0};
|
||||
((GridBagLayout)getLayout()).rowHeights = new int[] {0, 0};
|
||||
((GridBagLayout)getLayout()).columnWeights = new double[] {1.0, 1.0E-4};
|
||||
((GridBagLayout)getLayout()).rowWeights = new double[] {1.0, 1.0E-4};
|
||||
|
||||
JTabbedPane mainTabbedPane = new JTabbedPane();
|
||||
|
||||
// 新增Logo
|
||||
JTabbedPane HaETabbedPane = new JTabbedPane();
|
||||
HaETabbedPane.addTab("", getImageIcon(false), mainTabbedPane);
|
||||
// 中文Slogan:赋能白帽,高效作战
|
||||
HaETabbedPane.addTab(" Highlighter and Extractor - Empower ethical hacker for efficient operations. ", null);
|
||||
HaETabbedPane.setEnabledAt(1, false);
|
||||
HaETabbedPane.addPropertyChangeListener("background", new PropertyChangeListener() {
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent e) {
|
||||
boolean isDarkBg = isDarkBg();
|
||||
HaETabbedPane.setIconAt(0, getImageIcon(isDarkBg));
|
||||
}
|
||||
|
||||
private boolean isDarkBg() {
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
add(HaETabbedPane, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(0, 0, 0, 0), 0, 0));
|
||||
|
||||
// 依次添加Rules、Config、Databoard
|
||||
Rules rules = new Rules(api, configLoader);
|
||||
mainTabbedPane.addTab("Rules", rules);
|
||||
mainTabbedPane.addTab("Config", new Config(api, configLoader, rules));
|
||||
mainTabbedPane.addTab("Databoard", new Databoard(api, configLoader, messageTableModel));
|
||||
}
|
||||
|
||||
private ImageIcon getImageIcon(boolean isDark) {
|
||||
ClassLoader classLoader = getClass().getClassLoader();
|
||||
URL imageURL;
|
||||
if (isDark) {
|
||||
imageURL = classLoader.getResource("logo.png");
|
||||
} else {
|
||||
imageURL = classLoader.getResource("logo_black.png");
|
||||
}
|
||||
ImageIcon originalIcon = new ImageIcon(imageURL);
|
||||
Image originalImage = originalIcon.getImage();
|
||||
Image scaledImage = originalImage.getScaledInstance(30, 20, Image.SCALE_FAST);
|
||||
ImageIcon scaledIcon = new ImageIcon(scaledImage);
|
||||
return scaledIcon;
|
||||
}
|
||||
}
|
||||
285
src/main/java/hae/component/board/Databoard.java
Normal file
285
src/main/java/hae/component/board/Databoard.java
Normal file
@@ -0,0 +1,285 @@
|
||||
package hae.component.board;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import hae.Config;
|
||||
import hae.component.board.message.MessageTableModel;
|
||||
import hae.utils.string.StringProcessor;
|
||||
import hae.utils.config.ConfigLoader;
|
||||
import hae.component.board.message.MessageTableModel.MessageTable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import javax.swing.event.*;
|
||||
import javax.swing.table.TableColumnModel;
|
||||
import javax.swing.table.TableModel;
|
||||
import javax.swing.table.TableRowSorter;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.util.List;
|
||||
import javax.swing.*;
|
||||
|
||||
public class Databoard extends JPanel {
|
||||
private final MontoyaApi api;
|
||||
private final ConfigLoader configLoader;
|
||||
private final MessageTableModel messageTableModel;
|
||||
private JTextField hostTextField;
|
||||
private JTabbedPane dataTabbedPane;
|
||||
private JSplitPane splitPane;
|
||||
private MessageTable messageTable;
|
||||
|
||||
private static Boolean isMatchHost = false;
|
||||
private DefaultComboBoxModel comboBoxModel = new DefaultComboBoxModel();
|
||||
private JComboBox hostComboBox = new JComboBox(comboBoxModel);
|
||||
|
||||
public Databoard(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) {
|
||||
this.api = api;
|
||||
this.configLoader = configLoader;
|
||||
this.messageTableModel = messageTableModel;
|
||||
|
||||
initComponents();
|
||||
}
|
||||
|
||||
private void initComponents() {
|
||||
setLayout(new GridBagLayout());
|
||||
((GridBagLayout)getLayout()).columnWidths = new int[] {25, 0, 0, 0,20, 0};
|
||||
((GridBagLayout)getLayout()).rowHeights = new int[] {0, 65, 20, 0};
|
||||
((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};
|
||||
|
||||
JLabel hostLabel = new JLabel("Host:");
|
||||
|
||||
JButton clearButton = new JButton("Clear");
|
||||
JButton actionButton = new JButton("Action");
|
||||
JPanel menuPanel = new JPanel(new GridLayout(1, 1));
|
||||
menuPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
|
||||
JPopupMenu menu = new JPopupMenu();
|
||||
menuPanel.add(clearButton);
|
||||
menu.add(menuPanel);
|
||||
|
||||
hostTextField = new JTextField();
|
||||
splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
|
||||
dataTabbedPane = new JTabbedPane(JTabbedPane.TOP);
|
||||
|
||||
actionButton.addActionListener(e -> {
|
||||
int x = 0;
|
||||
int y = actionButton.getHeight();
|
||||
menu.show(actionButton, x, y);
|
||||
});
|
||||
|
||||
clearButton.addActionListener(this::clearActionPerformed);
|
||||
|
||||
splitPane.addComponentListener(new ComponentAdapter() {
|
||||
@Override
|
||||
public void componentResized(ComponentEvent e) {
|
||||
resizePanel();
|
||||
}
|
||||
});
|
||||
|
||||
splitPane.setVisible(false);
|
||||
|
||||
add(hostLabel, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(8, 0, 5, 5), 0, 0));
|
||||
add(hostTextField, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(8, 0, 5, 5), 0, 0));
|
||||
add(actionButton, new GridBagConstraints(3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(8, 0, 5, 5), 0, 0));
|
||||
add(splitPane, new GridBagConstraints(1, 1, 3, 3, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(8, 0, 5, 5), 0, 0));
|
||||
hostComboBox.setMaximumRowCount(5);
|
||||
add(hostComboBox, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(8, 0, 5, 5), 0, 0));
|
||||
|
||||
setAutoMatch();
|
||||
}
|
||||
|
||||
private void resizePanel() {
|
||||
splitPane.setDividerLocation(0.4);
|
||||
TableColumnModel columnModel = messageTable.getColumnModel();
|
||||
int totalWidth = (int) (getWidth() * 0.6);
|
||||
columnModel.getColumn(0).setPreferredWidth((int) (totalWidth * 0.1));
|
||||
columnModel.getColumn(1).setPreferredWidth((int) (totalWidth * 0.3));
|
||||
columnModel.getColumn(2).setPreferredWidth((int) (totalWidth * 0.3));
|
||||
columnModel.getColumn(3).setPreferredWidth((int) (totalWidth * 0.1));
|
||||
columnModel.getColumn(4).setPreferredWidth((int) (totalWidth * 0.1));
|
||||
columnModel.getColumn(5).setPreferredWidth((int) (totalWidth * 0.1));
|
||||
}
|
||||
|
||||
private void setAutoMatch() {
|
||||
hostComboBox.setSelectedItem(null);
|
||||
hostComboBox.addActionListener(this::handleComboBoxAction);
|
||||
|
||||
hostTextField.addKeyListener(new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
handleKeyEvents(e);
|
||||
}
|
||||
});
|
||||
|
||||
hostTextField.getDocument().addDocumentListener(new DocumentListener() {
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
filterComboBoxList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
filterComboBoxList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
filterComboBoxList();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private void handleComboBoxAction(ActionEvent e) {
|
||||
if (!isMatchHost && hostComboBox.getSelectedItem() != null) {
|
||||
String selectedHost = hostComboBox.getSelectedItem().toString();
|
||||
hostTextField.setText(selectedHost);
|
||||
populateTabbedPaneByHost(selectedHost);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleKeyEvents(KeyEvent e) {
|
||||
isMatchHost = true;
|
||||
int keyCode = e.getKeyCode();
|
||||
|
||||
if (keyCode == KeyEvent.VK_SPACE && hostComboBox.isPopupVisible()) {
|
||||
e.setKeyCode(KeyEvent.VK_ENTER);
|
||||
}
|
||||
|
||||
if (Arrays.asList(KeyEvent.VK_DOWN, KeyEvent.VK_UP).contains(keyCode)) {
|
||||
hostComboBox.dispatchEvent(e);
|
||||
}
|
||||
|
||||
if (keyCode == KeyEvent.VK_ENTER) {
|
||||
isMatchHost = false;
|
||||
handleComboBoxAction(null);
|
||||
hostComboBox.setPopupVisible(false);
|
||||
}
|
||||
|
||||
if (keyCode == KeyEvent.VK_ESCAPE) {
|
||||
hostComboBox.setPopupVisible(false);
|
||||
}
|
||||
|
||||
isMatchHost = false;
|
||||
}
|
||||
|
||||
private void filterComboBoxList() {
|
||||
isMatchHost = true;
|
||||
comboBoxModel.removeAllElements();
|
||||
String input = hostTextField.getText().toLowerCase();
|
||||
if (!input.isEmpty()) {
|
||||
for (String host : getHostByList()) {
|
||||
String lowerCaseHost = host.toLowerCase();
|
||||
if (lowerCaseHost.contains(input)) {
|
||||
if (lowerCaseHost.equals(input)) {
|
||||
comboBoxModel.insertElementAt(lowerCaseHost, 0);
|
||||
comboBoxModel.setSelectedItem(lowerCaseHost);
|
||||
} else {
|
||||
comboBoxModel.addElement(host);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hostComboBox.setPopupVisible(comboBoxModel.getSize() > 0);
|
||||
isMatchHost = false;
|
||||
}
|
||||
|
||||
private void populateTabbedPaneByHost(String selectedHost) {
|
||||
if (!Objects.equals(selectedHost, "")) {
|
||||
ConcurrentHashMap<String, Map<String, List<String>>> dataMap = Config.globalDataMap;
|
||||
Map<String, List<String>> selectedDataMap;
|
||||
|
||||
dataTabbedPane.removeAll();
|
||||
dataTabbedPane.setPreferredSize(new Dimension(500,0));
|
||||
dataTabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
|
||||
splitPane.setLeftComponent(dataTabbedPane);
|
||||
|
||||
if (selectedHost.contains("*")) {
|
||||
// 通配符数据
|
||||
selectedDataMap = new HashMap<>();
|
||||
String hostPattern = StringProcessor.replaceFirstOccurrence(selectedHost, "*.", "");
|
||||
for (String key : dataMap.keySet()) {
|
||||
if (key.contains(hostPattern) || selectedHost.equals("*")) {
|
||||
Map<String, List<String>> ruleMap = dataMap.get(key);
|
||||
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<String> uniqueSet = new HashSet<>(mergedList);
|
||||
selectedDataMap.put(ruleKey, new ArrayList<>(uniqueSet));
|
||||
} else {
|
||||
selectedDataMap.put(ruleKey, dataList);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
selectedDataMap = dataMap.get(selectedHost);
|
||||
}
|
||||
|
||||
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, entry.getKey(), entry.getValue());
|
||||
datatablePanel.setTableListener(messageTableModel);
|
||||
dataTabbedPane.addTab(tabTitle, datatablePanel);
|
||||
}
|
||||
|
||||
// 展示请求消息表单
|
||||
JSplitPane messageSplitPane = messageTableModel.getSplitPane();
|
||||
this.splitPane.setRightComponent(messageSplitPane);
|
||||
messageTable = messageTableModel.getMessageTable();
|
||||
|
||||
resizePanel();
|
||||
splitPane.setVisible(true);
|
||||
|
||||
applyHostFilter(selectedHost);
|
||||
hostTextField.setText(selectedHost);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyHostFilter(String filterText) {
|
||||
TableRowSorter<TableModel> sorter = (TableRowSorter<TableModel>) messageTable.getRowSorter();
|
||||
|
||||
String cleanedText = StringProcessor.replaceFirstOccurrence(filterText, "*.", "");
|
||||
|
||||
if (cleanedText.contains("*")) {
|
||||
cleanedText = "";
|
||||
}
|
||||
|
||||
RowFilter<TableModel, Integer> filter = RowFilter.regexFilter(cleanedText, 1);
|
||||
sorter.setRowFilter(filter);
|
||||
|
||||
messageTableModel.applyHostFilter(filterText);
|
||||
}
|
||||
|
||||
private List<String> getHostByList() {
|
||||
return new ArrayList<>(Config.globalDataMap.keySet());
|
||||
}
|
||||
|
||||
private void clearActionPerformed(ActionEvent e) {
|
||||
int retCode = JOptionPane.showConfirmDialog(null, "Do you want to clear data?", "Info",
|
||||
JOptionPane.YES_NO_OPTION);
|
||||
String host = hostTextField.getText();
|
||||
if (retCode == JOptionPane.YES_OPTION && !host.isEmpty()) {
|
||||
dataTabbedPane.removeAll();
|
||||
splitPane.setVisible(false);
|
||||
|
||||
String cleanedHost = StringProcessor.replaceFirstOccurrence(host, "*.", "");
|
||||
|
||||
if (host.contains("*")) {
|
||||
Config.globalDataMap.keySet().removeIf(i -> i.contains(cleanedHost) || cleanedHost.contains("*"));
|
||||
} else {
|
||||
Config.globalDataMap.remove(host);
|
||||
}
|
||||
|
||||
messageTableModel.deleteByHost(cleanedHost);
|
||||
}
|
||||
}
|
||||
}
|
||||
226
src/main/java/hae/component/board/Datatable.java
Normal file
226
src/main/java/hae/component/board/Datatable.java
Normal file
@@ -0,0 +1,226 @@
|
||||
package hae.component.board;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import hae.component.board.message.MessageTableModel;
|
||||
import jregex.Pattern;
|
||||
import jregex.REFlags;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.FocusEvent;
|
||||
import java.awt.event.FocusListener;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import javax.swing.*;
|
||||
import java.awt.datatransfer.*;
|
||||
import javax.swing.event.*;
|
||||
import javax.swing.table.*;
|
||||
|
||||
public class Datatable extends JPanel {
|
||||
private final MontoyaApi api;
|
||||
private final JTable dataTable;
|
||||
private final DefaultTableModel dataTableModel;
|
||||
private final JTextField searchField;
|
||||
private final TableRowSorter<DefaultTableModel> sorter;
|
||||
private final JCheckBox searchMode = new JCheckBox("Reverse search");
|
||||
private final String tabName;
|
||||
|
||||
public Datatable(MontoyaApi api, String tabName, List<String> dataList) {
|
||||
this.api = api;
|
||||
this.tabName = tabName;
|
||||
|
||||
|
||||
String[] columnNames = {"#", "Information"};
|
||||
|
||||
dataTableModel = new DefaultTableModel(columnNames, 0);
|
||||
dataTable = new JTable(dataTableModel);
|
||||
sorter = new TableRowSorter<>(dataTableModel);
|
||||
|
||||
searchField = new JTextField();
|
||||
|
||||
initComponents(dataList);
|
||||
}
|
||||
|
||||
private void initComponents(List<String> dataList) {
|
||||
// 设置ID排序
|
||||
sorter.setComparator(0, new Comparator<Integer>() {
|
||||
@Override
|
||||
public int compare(Integer s1, Integer s2) {
|
||||
return s1.compareTo(s2);
|
||||
}
|
||||
});
|
||||
|
||||
dataTable.setRowSorter(sorter);
|
||||
TableColumn idColumn = dataTable.getColumnModel().getColumn(0);
|
||||
idColumn.setMaxWidth(50);
|
||||
|
||||
for (String item : dataList) {
|
||||
if (!item.isEmpty()) {
|
||||
addRowToTable(new Object[]{item});
|
||||
}
|
||||
}
|
||||
|
||||
// 设置灰色默认文本
|
||||
String searchText = "Search";
|
||||
addPlaceholder(searchField, searchText);
|
||||
|
||||
// 监听输入框内容输入、更新、删除
|
||||
searchField.getDocument().addDocumentListener(new DocumentListener() {
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
performSearch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
performSearch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
performSearch();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// 设置布局
|
||||
JScrollPane scrollPane = new JScrollPane(dataTable);
|
||||
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
|
||||
|
||||
searchMode.addItemListener(e -> performSearch());
|
||||
|
||||
setLayout(new BorderLayout(0, 5));
|
||||
|
||||
JPanel optionsPanel = new JPanel();
|
||||
optionsPanel.setBorder(BorderFactory.createEmptyBorder(2, 3, 5, 5));
|
||||
optionsPanel.setLayout(new BoxLayout(optionsPanel, BoxLayout.X_AXIS));
|
||||
|
||||
// 新增复选框要在这修改rows
|
||||
JPanel menuPanel = new JPanel(new GridLayout(1, 1));
|
||||
menuPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
|
||||
JPopupMenu menu = new JPopupMenu();
|
||||
menuPanel.add(searchMode);
|
||||
menu.add(menuPanel);
|
||||
|
||||
JButton settingsButton = new JButton("Settings");
|
||||
settingsButton.addActionListener(e -> {
|
||||
int x = settingsButton.getX();
|
||||
int y = settingsButton.getY() - menu.getPreferredSize().height;
|
||||
menu.show(settingsButton, x, y);
|
||||
});
|
||||
|
||||
optionsPanel.add(settingsButton);
|
||||
optionsPanel.add(Box.createHorizontalStrut(5));
|
||||
optionsPanel.add(searchField);
|
||||
|
||||
dataTable.setTransferHandler(new TransferHandler() {
|
||||
@Override
|
||||
public void exportToClipboard(JComponent comp, Clipboard clip, int action) throws IllegalStateException {
|
||||
if (comp instanceof JTable) {
|
||||
StringSelection stringSelection = new StringSelection(getSelectedData(
|
||||
(JTable) comp));
|
||||
clip.setContents(stringSelection, null);
|
||||
} else {
|
||||
super.exportToClipboard(comp, clip, action);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
add(scrollPane, BorderLayout.CENTER);
|
||||
add(optionsPanel, BorderLayout.SOUTH);
|
||||
}
|
||||
|
||||
public static void addPlaceholder(JTextField textField, String placeholderText) {
|
||||
textField.setForeground(Color.GRAY);
|
||||
textField.setText(placeholderText);
|
||||
textField.addFocusListener(new FocusListener() {
|
||||
@Override
|
||||
public void focusGained(FocusEvent e) {
|
||||
if (textField.getText().equals(placeholderText)) {
|
||||
textField.setText("");
|
||||
textField.setForeground(Color.BLACK);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void focusLost(FocusEvent e) {
|
||||
if (textField.getText().isEmpty()) {
|
||||
textField.setForeground(Color.GRAY);
|
||||
textField.setText(placeholderText);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addRowToTable(Object[] data) {
|
||||
int rowCount = dataTableModel.getRowCount();
|
||||
int id = rowCount > 0 ? (Integer) dataTableModel.getValueAt(rowCount - 1, 0) + 1 : 1;
|
||||
Object[] rowData = new Object[data.length + 1];
|
||||
rowData[0] = id;
|
||||
System.arraycopy(data, 0, rowData, 1, data.length);
|
||||
dataTableModel.addRow(rowData);
|
||||
}
|
||||
|
||||
private void performSearch() {
|
||||
if (searchField.getForeground().equals(Color.BLACK)) {
|
||||
RowFilter<Object, Object> rowFilter = new RowFilter<Object, Object>() {
|
||||
public boolean include(Entry<?, ?> entry) {
|
||||
String searchFieldTextText = searchField.getText();
|
||||
Pattern pattern = null;
|
||||
try {
|
||||
pattern = new Pattern(searchFieldTextText, REFlags.IGNORE_CASE);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
String entryValue = ((String) entry.getValue(1)).toLowerCase();
|
||||
searchFieldTextText = searchFieldTextText.toLowerCase();
|
||||
if (pattern != null) {
|
||||
return searchFieldTextText.isEmpty() || pattern.matcher(entryValue).find() != searchMode.isSelected();
|
||||
} else {
|
||||
return searchFieldTextText.isEmpty() || entryValue.contains(searchFieldTextText) != searchMode.isSelected();
|
||||
}
|
||||
}
|
||||
};
|
||||
sorter.setRowFilter(rowFilter);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getSelectedData(JTable table) {
|
||||
int[] selectRows = table.getSelectedRows();
|
||||
StringBuilder selectData = new StringBuilder();
|
||||
for (int row : selectRows) {
|
||||
selectData.append(table.getValueAt(row, 1).toString()).append("\n");
|
||||
}
|
||||
|
||||
// 便于单行复制,去除最后一个换行符
|
||||
if (!selectData.isEmpty()){
|
||||
selectData.deleteCharAt(selectData.length() - 1);
|
||||
}
|
||||
|
||||
return selectData.toString();
|
||||
}
|
||||
|
||||
public JTable getDataTable() {
|
||||
return this.dataTable;
|
||||
}
|
||||
|
||||
public void setTableListener(MessageTableModel messagePanel) {
|
||||
dataTable.setDefaultEditor(Object.class, null);
|
||||
|
||||
// 表格内容双击事件
|
||||
dataTable.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (e.getClickCount() == 2) {
|
||||
int selectedRow = dataTable.getSelectedRow();
|
||||
if (selectedRow != -1) {
|
||||
String rowData = dataTable.getValueAt(selectedRow, 1).toString();
|
||||
messagePanel.applyMessageFilter(tabName, rowData);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
52
src/main/java/hae/component/board/message/MessageEntry.java
Normal file
52
src/main/java/hae/component/board/message/MessageEntry.java
Normal file
@@ -0,0 +1,52 @@
|
||||
package hae.component.board.message;
|
||||
|
||||
import burp.api.montoya.http.message.HttpRequestResponse;
|
||||
|
||||
public class MessageEntry {
|
||||
|
||||
private final String comment;
|
||||
private final HttpRequestResponse requestResponse;
|
||||
private final String url;
|
||||
private final String length;
|
||||
private final String status;
|
||||
private final String color;
|
||||
private final String method;
|
||||
|
||||
MessageEntry(HttpRequestResponse requestResponse, String method, String url, String comment, String length, String color, String status) {
|
||||
this.requestResponse = requestResponse;
|
||||
this.method = method;
|
||||
this.url = url;
|
||||
this.comment = comment;
|
||||
this.length = length;
|
||||
this.color = color;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getColor() {
|
||||
return this.color;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return this.url;
|
||||
}
|
||||
|
||||
public String getLength() {
|
||||
return this.length;
|
||||
}
|
||||
|
||||
public String getComment() {
|
||||
return this.comment;
|
||||
}
|
||||
|
||||
public String getMethod() {
|
||||
return this.method;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return this.status;
|
||||
}
|
||||
|
||||
public HttpRequestResponse getRequestResponse() {
|
||||
return this.requestResponse;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package hae.component.board.message;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
|
||||
public class MessageRenderer extends DefaultTableCellRenderer {
|
||||
|
||||
private List<MessageEntry> log;
|
||||
private Map<String, Color> colorMap = new HashMap<>();
|
||||
private JTable table; // 保存对表格的引用
|
||||
|
||||
public MessageRenderer(List<MessageEntry> log, JTable table) {
|
||||
this.log = log;
|
||||
// 与BurpSuite的颜色保持一致
|
||||
this.colorMap.put("red", new Color(0xFF, 0x64, 0x64));
|
||||
this.colorMap.put("orange", new Color(0xFF, 0xC8, 0x64));
|
||||
this.colorMap.put("yellow", new Color(0xFF, 0xFF, 0x64));
|
||||
this.colorMap.put("green", new Color(0x64, 0xFF, 0x64));
|
||||
this.colorMap.put("cyan", new Color(0x64, 0xFF, 0xFF));
|
||||
this.colorMap.put("blue", new Color(0x64, 0x64, 0xFF));
|
||||
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.table = table;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
|
||||
boolean hasFocus, int row, int column) {
|
||||
Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
|
||||
MessageEntry messageEntry = log.get(table.convertRowIndexToModel(row)); // 使用convertRowIndexToModel方法转换行索引
|
||||
|
||||
// 设置颜色
|
||||
String colorByLog = messageEntry.getColor();
|
||||
Color color = colorMap.get(colorByLog);
|
||||
|
||||
if (isSelected) {
|
||||
// 通过更改RGB颜色来达成阴影效果
|
||||
component.setBackground(new Color(color.getRed()-0x20, color.getGreen()-0x20, color.getBlue()-0x20));
|
||||
} else {
|
||||
// 否则使用原始颜色
|
||||
component.setBackground(color);
|
||||
}
|
||||
|
||||
component.setForeground(Color.BLACK);
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
|
||||
super.firePropertyChange(propertyName, oldValue, newValue);
|
||||
// 监听表格排序的属性变化
|
||||
if ("tableCellRenderer".equals(propertyName)) {
|
||||
// 更新每一行数据的颜色
|
||||
for (int i = 0; i < table.getRowCount(); i++) {
|
||||
table.repaint(table.getCellRect(i, 0, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
456
src/main/java/hae/component/board/message/MessageTableModel.java
Normal file
456
src/main/java/hae/component/board/message/MessageTableModel.java
Normal file
@@ -0,0 +1,456 @@
|
||||
package hae.component.board.message;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import burp.api.montoya.core.ByteArray;
|
||||
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;
|
||||
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.CachePool;
|
||||
import hae.utils.string.HashCalculator;
|
||||
import hae.utils.string.StringProcessor;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.*;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JSplitPane;
|
||||
import javax.swing.JTabbedPane;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.SwingWorker;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import javax.swing.table.DefaultTableModel;
|
||||
import javax.swing.table.TableModel;
|
||||
import javax.swing.table.TableRowSorter;
|
||||
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;
|
||||
private final MessageTable messageTable;
|
||||
private final JTabbedPane messageTab;
|
||||
private final JSplitPane splitPane;
|
||||
private final List<MessageEntry> log = new ArrayList<MessageEntry>();
|
||||
private LinkedList<MessageEntry> filteredLog;
|
||||
|
||||
public MessageTableModel(MontoyaApi api) {
|
||||
this.filteredLog = new LinkedList<>();
|
||||
this.api = api;
|
||||
|
||||
messageTab = new JTabbedPane();
|
||||
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);
|
||||
|
||||
// Length字段根据大小进行排序
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
// Color字段根据颜色顺序进行排序
|
||||
sorter.setComparator(5, new Comparator<String>() {
|
||||
@Override
|
||||
public int compare(String s1, String s2) {
|
||||
int index1 = getIndex(s1);
|
||||
int index2 = getIndex(s2);
|
||||
return Integer.compare(index1, index2);
|
||||
}
|
||||
private int getIndex(String color) {
|
||||
for (int i = 0; i < Config.color.length; i++) {
|
||||
if (Config.color[i].equals(color)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
public void add(HttpRequestResponse messageInfo, String comment, String color) {
|
||||
synchronized(log) {
|
||||
HttpRequest httpRequest = messageInfo.request();
|
||||
String url = httpRequest.url();
|
||||
String method = httpRequest.method();
|
||||
|
||||
HttpResponse httpResponse = messageInfo.response();
|
||||
String status = String.valueOf(httpResponse.statusCode());
|
||||
String length = String.valueOf(httpResponse.body().length());
|
||||
|
||||
MessageEntry logEntry = new MessageEntry(messageInfo, method, url, comment, length, color, status);
|
||||
|
||||
try {
|
||||
// 比较Hash,如若存在重复的请求或响应,则不放入消息内容里
|
||||
byte[] reqByteA = httpRequest.toByteArray().getBytes();
|
||||
byte[] resByteA = httpResponse.toByteArray().getBytes();
|
||||
boolean isDuplicate = false;
|
||||
|
||||
if (log.size() > 0) {
|
||||
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().toString().equals(url.toString()) || (Arrays.equals(reqByteB, reqByteA) || Arrays.equals(resByteB, resByteA))) && (areMapsEqual(getCacheData(reqByteB), getCacheData(reqByteA)) && areMapsEqual(getCacheData(resByteB), getCacheData(resByteA)))) {
|
||||
isDuplicate = true;
|
||||
break;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isDuplicate) {
|
||||
log.add(logEntry);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void deleteByHost(String filterText) {
|
||||
filteredLog.clear();
|
||||
List<Integer> rowsToRemove = new ArrayList<>();
|
||||
for (int i = 0; i < log.size(); i++) {
|
||||
MessageEntry entry = log.get(i);
|
||||
String host = StringProcessor.getHostByUrl(entry.getUrl());
|
||||
if (!host.isEmpty()) {
|
||||
if (StringProcessor.matchFromEnd(host, filterText) || filterText.contains("*")) {
|
||||
rowsToRemove.add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = rowsToRemove.size() - 1; i >= 0; i--) {
|
||||
int row = rowsToRemove.get(i);
|
||||
log.remove(row);
|
||||
}
|
||||
|
||||
if (!rowsToRemove.isEmpty()) {
|
||||
int[] rows = rowsToRemove.stream().mapToInt(Integer::intValue).toArray();
|
||||
fireTableRowsDeleted(rows[0], rows[rows.length - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
public void applyHostFilter(String filterText) {
|
||||
filteredLog.clear();
|
||||
fireTableDataChanged();
|
||||
String cleanedText = StringProcessor.replaceFirstOccurrence(filterText, "*.", "");
|
||||
|
||||
for (MessageEntry entry : log) {
|
||||
String host = StringProcessor.getHostByUrl(entry.getUrl());
|
||||
if (!host.isEmpty()) {
|
||||
if (filterText.contains("*.") && StringProcessor.matchFromEnd(host, cleanedText)) {
|
||||
filteredLog.add(entry);
|
||||
} else if (host.equals(filterText) || filterText.contains("*")) {
|
||||
filteredLog.add(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
public void applyMessageFilter(String tableName, String filterText) {
|
||||
filteredLog.clear();
|
||||
for (MessageEntry entry : log) {
|
||||
HttpRequestResponse requestResponse = entry.getRequestResponse();
|
||||
HttpRequest httpRequest = requestResponse.request();
|
||||
HttpResponse httpResponse = requestResponse.response();
|
||||
|
||||
String requestString = new String(httpRequest.toByteArray().getBytes(), StandardCharsets.UTF_8);
|
||||
String requestBody = new String(httpRequest.body().getBytes(), StandardCharsets.UTF_8);
|
||||
String requestHeaders = httpRequest.headers().stream()
|
||||
.map(HttpHeader::toString)
|
||||
.collect(Collectors.joining("\n"));
|
||||
|
||||
String 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"));
|
||||
|
||||
// 标志变量,表示是否满足过滤条件
|
||||
AtomicBoolean isMatched = new AtomicBoolean(false);
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
isMatched.set(isMatch);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (isMatched.get()) {
|
||||
filteredLog.add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
fireTableDataChanged();
|
||||
messageTable.lastSelectedIndex = -1;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private Map<String, Map<String, Object>> getCacheData(byte[] content) {
|
||||
String hashIndex = HashCalculator.calculateHash(content);
|
||||
return CachePool.getFromCache(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 false;
|
||||
}
|
||||
|
||||
for (String key : innerMap1.keySet()) {
|
||||
if (!innerMap2.containsKey(key)) {
|
||||
return false;
|
||||
}
|
||||
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 false;
|
||||
}
|
||||
} else if (!value1.equals(value2)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public JSplitPane getSplitPane()
|
||||
{
|
||||
return splitPane;
|
||||
}
|
||||
|
||||
public MessageTable getMessageTable()
|
||||
{
|
||||
return messageTable;
|
||||
}
|
||||
|
||||
public List<MessageEntry> getLogs() {
|
||||
return log;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return filteredLog.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCount() {
|
||||
return 6;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex)
|
||||
{
|
||||
if (filteredLog.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
MessageEntry messageEntry = filteredLog.get(rowIndex);
|
||||
|
||||
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 -> "";
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName(int columnIndex)
|
||||
{
|
||||
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 {
|
||||
private MessageEntry MessageEntry;
|
||||
private SwingWorker<Object, Void> currentWorker;
|
||||
// 设置响应报文返回的最大长度为3MB
|
||||
private final int MAX_LENGTH = 3145728;
|
||||
private int lastSelectedIndex = -1;
|
||||
private final HttpRequestEditor requestEditor;
|
||||
private final HttpResponseEditor responseEditor;
|
||||
|
||||
public MessageTable(TableModel messageTableModel, HttpRequestEditor requestEditor, HttpResponseEditor responseEditor) {
|
||||
super(messageTableModel);
|
||||
this.requestEditor = requestEditor;
|
||||
this.responseEditor = responseEditor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeSelection(int row, int col, boolean toggle, boolean extend) {
|
||||
super.changeSelection(row, col, toggle, extend);
|
||||
int selectedIndex = convertRowIndexToModel(row);
|
||||
if (lastSelectedIndex != selectedIndex) {
|
||||
lastSelectedIndex = selectedIndex;
|
||||
MessageEntry = filteredLog.get(selectedIndex);
|
||||
|
||||
requestEditor.setRequest(HttpRequest.httpRequest("Loading..."));
|
||||
responseEditor.setResponse(HttpResponse.httpResponse("Loading..."));
|
||||
|
||||
if (currentWorker != null && !currentWorker.isDone()) {
|
||||
currentWorker.cancel(true);
|
||||
}
|
||||
|
||||
currentWorker = new SwingWorker<>() {
|
||||
@Override
|
||||
protected ByteArray[] doInBackground() {
|
||||
ByteArray requestByte = MessageEntry.getRequestResponse().request().toByteArray();
|
||||
ByteArray responseByte = MessageEntry.getRequestResponse().response().toByteArray();
|
||||
|
||||
if (responseByte.length() > MAX_LENGTH) {
|
||||
String ellipsis = "\r\n......";
|
||||
responseByte = responseByte.subArray(0, MAX_LENGTH).withAppended(ellipsis);
|
||||
}
|
||||
|
||||
return new ByteArray[]{requestByte, responseByte};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
if (!isCancelled()) {
|
||||
try {
|
||||
ByteArray[] result = (ByteArray[]) get();
|
||||
requestEditor.setRequest(HttpRequest.httpRequest(MessageEntry.getRequestResponse().httpService(), result[0]));
|
||||
responseEditor.setResponse(HttpResponse.httpResponse(result[1]));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
currentWorker.execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
89
src/main/java/hae/component/config/Config.java
Normal file
89
src/main/java/hae/component/config/Config.java
Normal file
@@ -0,0 +1,89 @@
|
||||
package hae.component.config;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import hae.component.rule.Rules;
|
||||
import hae.utils.config.ConfigLoader;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
|
||||
public class Config extends JPanel {
|
||||
private final MontoyaApi api;
|
||||
private final ConfigLoader configLoader;
|
||||
private final Rules rules;
|
||||
|
||||
public Config(MontoyaApi api, ConfigLoader configLoader, Rules rules) {
|
||||
this.api = api;
|
||||
this.configLoader = configLoader;
|
||||
this.rules = rules;
|
||||
|
||||
initComponents();
|
||||
}
|
||||
|
||||
private void initComponents() {
|
||||
setLayout(new GridBagLayout());
|
||||
((GridBagLayout) getLayout()).columnWidths = new int[] {0, 0, 0, 0, 0};
|
||||
((GridBagLayout) getLayout()).rowHeights = new int[] {0, 0, 0};
|
||||
((GridBagLayout) getLayout()).columnWeights = new double[] {0.0, 1.0, 0.0, 0.0, 1.0E-4};
|
||||
((GridBagLayout) getLayout()).rowWeights = new double[] {0.0, 0.0, 1.0E-4};
|
||||
|
||||
JLabel rulesFilePathLabel = new JLabel("Rules Path:");
|
||||
JTextField rulesFilePathTextField = new JTextField();
|
||||
JButton onlineUpdateButton = new JButton("Update");
|
||||
JLabel excludeSuffixLabel = new JLabel("Exclude Suffix:");
|
||||
JTextField excludeSuffixTextField = new JTextField();
|
||||
JButton excludeSuffixSaveButton = new JButton("Save");
|
||||
JButton reloadButton = new JButton("Reload");
|
||||
|
||||
rulesFilePathTextField.setEditable(false);
|
||||
|
||||
onlineUpdateButton.addActionListener(this::onlineUpdateActionPerformed);
|
||||
excludeSuffixSaveButton.addActionListener(e -> excludeSuffixSaveActionPerformed(e, excludeSuffixTextField.getText()));
|
||||
reloadButton.addActionListener(this::reloadActionPerformed);
|
||||
|
||||
rulesFilePathTextField.setText(configLoader.getRulesFilePath());
|
||||
excludeSuffixTextField.setText(configLoader.getExcludeSuffix());
|
||||
|
||||
add(rulesFilePathTextField, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(5, 0, 5, 5), 0, 0));
|
||||
add(rulesFilePathLabel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.WEST, GridBagConstraints.VERTICAL,
|
||||
new Insets(5, 5, 5, 5), 0, 0));
|
||||
add(onlineUpdateButton, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(5, 0, 5, 5), 0, 0));
|
||||
add(reloadButton, new GridBagConstraints(3, 0, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(5, 0, 5, 5), 0, 0));
|
||||
add(excludeSuffixLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.SOUTHWEST, GridBagConstraints.NONE,
|
||||
new Insets(0, 5, 5, 5), 0, 0));
|
||||
add(excludeSuffixTextField, new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.SOUTH, GridBagConstraints.HORIZONTAL,
|
||||
new Insets(0, 0, 0, 5), 0, 0));
|
||||
add(excludeSuffixSaveButton, new GridBagConstraints(2, 1, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.SOUTH, GridBagConstraints.HORIZONTAL,
|
||||
new Insets(0, 0, 0, 5), 0, 0));
|
||||
}
|
||||
|
||||
private void onlineUpdateActionPerformed(ActionEvent e) {
|
||||
// 添加提示框防止用户误触导致配置更新
|
||||
int retCode = JOptionPane.showConfirmDialog(null, "Do you want to update rules?", "Info", JOptionPane.YES_NO_OPTION);
|
||||
if (retCode == JOptionPane.YES_OPTION) {
|
||||
configLoader.initRules();
|
||||
reloadActionPerformed(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void excludeSuffixSaveActionPerformed(ActionEvent e, String suffix) {
|
||||
if (!suffix.equals(configLoader.getExcludeSuffix()) && !suffix.isEmpty()) {
|
||||
configLoader.setExcludeSuffix(suffix);
|
||||
}
|
||||
}
|
||||
|
||||
private void reloadActionPerformed(ActionEvent e) {
|
||||
rules.reloadRuleGroup();
|
||||
}
|
||||
}
|
||||
79
src/main/java/hae/component/rule/Display.java
Normal file
79
src/main/java/hae/component/rule/Display.java
Normal file
@@ -0,0 +1,79 @@
|
||||
package hae.component.rule;
|
||||
|
||||
import hae.Config;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
public class Display extends JPanel {
|
||||
public JTextField firstRegexTextField;
|
||||
public JTextField secondRegexTextField;
|
||||
public JTextField formatTextField;
|
||||
public JTextField ruleNameTextField;
|
||||
public JComboBox<String> scopeComboBox;
|
||||
public JComboBox<String> engineComboBox;
|
||||
public JComboBox<String> colorComboBox;
|
||||
public JComboBox<Boolean> sensitiveComboBox;
|
||||
|
||||
public Display() {
|
||||
initComponents();
|
||||
}
|
||||
|
||||
private void initComponents() {
|
||||
setLayout(new GridBagLayout());
|
||||
GridBagConstraints c = new GridBagConstraints();
|
||||
c.fill = GridBagConstraints.BOTH;
|
||||
|
||||
addLabel("Name:", 0, c);
|
||||
ruleNameTextField = addTextField(0, c);
|
||||
|
||||
addLabel("F-Regex:", 1, c);
|
||||
firstRegexTextField = addTextField(1, c);
|
||||
|
||||
addLabel("S-Regex:", 2, c);
|
||||
secondRegexTextField = addTextField(2, c);
|
||||
|
||||
addLabel("Format:", 3, c);
|
||||
formatTextField = addTextField(3, c);
|
||||
|
||||
addLabel("Scope:", 4, c);
|
||||
scopeComboBox = addComboBox(Config.scope, 4, c);
|
||||
|
||||
addLabel("Engine:", 5, c);
|
||||
engineComboBox = addComboBox(Config.engine, 5, c);
|
||||
engineComboBox.addActionListener(e -> {
|
||||
boolean isNfa = "nfa".equals(engineComboBox.getSelectedItem().toString());
|
||||
formatTextField.setEnabled(isNfa);
|
||||
formatTextField.setText(isNfa ? formatTextField.getText() : "{0}");
|
||||
});
|
||||
|
||||
addLabel("Color:", 6, c);
|
||||
colorComboBox = addComboBox(Config.color, 6, c);
|
||||
|
||||
addLabel("Sensitive:", 7, c);
|
||||
sensitiveComboBox = addComboBox(new Boolean[]{true, false}, 7, c);
|
||||
}
|
||||
|
||||
private void addLabel(String text, int y, GridBagConstraints c) {
|
||||
JLabel label = new JLabel(text);
|
||||
c.gridx = 0;
|
||||
c.gridy = y;
|
||||
add(label, c);
|
||||
}
|
||||
|
||||
private JTextField addTextField(int y, GridBagConstraints c) {
|
||||
JTextField textField = new JTextField(35);
|
||||
c.gridx = 1;
|
||||
c.gridy = y;
|
||||
add(textField, c);
|
||||
return textField;
|
||||
}
|
||||
|
||||
private <T> JComboBox<T> addComboBox(T[] items, int y, GridBagConstraints c) {
|
||||
JComboBox<T> comboBox = new JComboBox<>(items);
|
||||
c.gridx = 1;
|
||||
c.gridy = y;
|
||||
add(comboBox, c);
|
||||
return comboBox;
|
||||
}
|
||||
}
|
||||
164
src/main/java/hae/component/rule/Rule.java
Normal file
164
src/main/java/hae/component/rule/Rule.java
Normal file
@@ -0,0 +1,164 @@
|
||||
package hae.component.rule;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.table.DefaultTableModel;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import javax.swing.table.TableRowSorter;
|
||||
import java.util.Vector;
|
||||
|
||||
import hae.Config;
|
||||
import hae.utils.config.ConfigLoader;
|
||||
import hae.utils.rule.RuleProcessor;
|
||||
|
||||
import static javax.swing.JOptionPane.YES_OPTION;
|
||||
|
||||
public class Rule extends JPanel {
|
||||
private final MontoyaApi api;
|
||||
private final ConfigLoader configLoader;
|
||||
private final RuleProcessor ruleProcessor;
|
||||
private final JTabbedPane tabbedPane;
|
||||
|
||||
public Rule(MontoyaApi api, ConfigLoader configLoader, Object[][] data, JTabbedPane tabbedPane) {
|
||||
this.api = api;
|
||||
this.configLoader = configLoader;
|
||||
this.ruleProcessor = new RuleProcessor(api, configLoader);
|
||||
this.tabbedPane = tabbedPane;
|
||||
|
||||
initComponents(data);
|
||||
}
|
||||
|
||||
private void initComponents(Object[][] data) {
|
||||
setLayout(new GridBagLayout());
|
||||
((GridBagLayout)getLayout()).columnWidths = new int[] {0, 0, 0};
|
||||
((GridBagLayout)getLayout()).rowHeights = new int[] {0, 0, 0, 0, 0};
|
||||
((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 addButton = new JButton("Add");
|
||||
JButton editButton = new JButton("Edit");
|
||||
JButton removeButton = new JButton("Remove");
|
||||
|
||||
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);
|
||||
|
||||
// 按钮监听事件
|
||||
addButton.addActionListener(e -> ruleAddActionPerformed(e, ruleTable, tabbedPane));
|
||||
editButton.addActionListener(e -> ruleEditActionPerformed(e, ruleTable, tabbedPane));
|
||||
removeButton.addActionListener(e -> ruleRemoveActionPerformed(e, ruleTable, tabbedPane));
|
||||
|
||||
// 表格
|
||||
DefaultTableModel model = new DefaultTableModel() {
|
||||
@Override
|
||||
public Class<?> getColumnClass(int column) {
|
||||
return (column == 0) ? Boolean.class : String.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCellEditable(int row, int column) {
|
||||
return column == 0;
|
||||
}
|
||||
};
|
||||
|
||||
ruleTable.setModel(model);
|
||||
ruleTable.setRowSorter(new TableRowSorter<>(model));
|
||||
|
||||
model.setDataVector(data, Config.ruleFields);
|
||||
model.addTableModelListener(e -> {
|
||||
if (e.getColumn() == 0 && ruleTable.getSelectedRow() != -1){
|
||||
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
|
||||
ruleProcessor.changeRule(model.getDataVector().get(select), select, tabbedPane.getTitleAt(tabbedPane.getSelectedIndex()));
|
||||
}
|
||||
});
|
||||
|
||||
add(addButton, 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 ruleAddActionPerformed(ActionEvent e, JTable ruleTable, JTabbedPane tabbedPane) {
|
||||
Display ruleDisplay = new Display();
|
||||
ruleDisplay.formatTextField.setText("{0}");
|
||||
|
||||
int showState = JOptionPane.showConfirmDialog(null, ruleDisplay, "Add Rule", JOptionPane.OK_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());
|
||||
|
||||
DefaultTableModel model = (DefaultTableModel) ruleTable.getModel();
|
||||
model.insertRow(model.getRowCount(), ruleData);
|
||||
ruleProcessor.addRule(ruleData, tabbedPane.getTitleAt(tabbedPane.getSelectedIndex()));
|
||||
}
|
||||
}
|
||||
|
||||
private void ruleEditActionPerformed(ActionEvent e, JTable ruleTable, JTabbedPane tabbedPane){
|
||||
if (ruleTable.getSelectedRowCount() >= 1){
|
||||
DefaultTableModel model = (DefaultTableModel) ruleTable.getModel();
|
||||
Display ruleDisplay = new Display();
|
||||
|
||||
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(null, ruleDisplay, "Edit Rule", JOptionPane.OK_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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ruleRemoveActionPerformed(ActionEvent e, JTable ruleTable, JTabbedPane tabbedPane){
|
||||
if (ruleTable.getSelectedRowCount() >= 1){
|
||||
if (JOptionPane.showConfirmDialog(null, "Are you sure you want to delete this rule?", "Info", JOptionPane.OK_OPTION) == 0){
|
||||
DefaultTableModel model = (DefaultTableModel) ruleTable.getModel();
|
||||
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
|
||||
|
||||
model.removeRow(select);
|
||||
ruleProcessor.removeRule(select, tabbedPane.getTitleAt(tabbedPane.getSelectedIndex()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
156
src/main/java/hae/component/rule/Rules.java
Normal file
156
src/main/java/hae/component/rule/Rules.java
Normal file
@@ -0,0 +1,156 @@
|
||||
package hae.component.rule;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import hae.Config;
|
||||
import hae.utils.config.ConfigLoader;
|
||||
import hae.utils.rule.RuleProcessor;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
|
||||
public class Rules extends JTabbedPane {
|
||||
private final MontoyaApi api;
|
||||
private final ConfigLoader configLoader;
|
||||
private final RuleProcessor ruleProcessor;
|
||||
private final JTextField ruleGroupNameTextField;
|
||||
|
||||
private Component tabComponent;
|
||||
private int selectedIndex;
|
||||
|
||||
public Rules(MontoyaApi api, ConfigLoader configLoader) {
|
||||
this.api = api;
|
||||
this.configLoader = configLoader;
|
||||
this.ruleProcessor = new RuleProcessor(api, configLoader);
|
||||
this.ruleGroupNameTextField = new JTextField();
|
||||
|
||||
initComponents();
|
||||
}
|
||||
|
||||
private void initComponents() {
|
||||
reloadRuleGroup();
|
||||
|
||||
JTabbedPane tabbedPane = this;
|
||||
|
||||
JMenuItem deleteMenuItem = new JMenuItem("Delete");
|
||||
JPopupMenu popupMenu = new JPopupMenu();
|
||||
popupMenu.add(deleteMenuItem);
|
||||
|
||||
deleteMenuItem.addActionListener(this::deleteRuleGroupActionPerformed);
|
||||
|
||||
ruleGroupNameTextField.setBorder(BorderFactory.createEmptyBorder());
|
||||
ruleGroupNameTextField.addFocusListener(new FocusAdapter() {
|
||||
@Override
|
||||
public void focusLost(FocusEvent e) {
|
||||
renameTitleActionPerformed.actionPerformed(null);
|
||||
}
|
||||
});
|
||||
|
||||
addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
int index = getSelectedIndex();
|
||||
Rectangle r = getBoundsAt(index);
|
||||
if (r.contains(e.getPoint()) && index >= 0) {
|
||||
switch (e.getButton()) {
|
||||
case MouseEvent.BUTTON1:
|
||||
if (e.getClickCount() == 2) {
|
||||
selectedIndex = index;
|
||||
tabComponent = getTabComponentAt(selectedIndex);
|
||||
String ruleGroupName = getTitleAt(selectedIndex);
|
||||
|
||||
if (!"...".equals(ruleGroupName)) {
|
||||
setTabComponentAt(selectedIndex, ruleGroupNameTextField);
|
||||
ruleGroupNameTextField.setVisible(true);
|
||||
ruleGroupNameTextField.setText(ruleGroupName);
|
||||
ruleGroupNameTextField.selectAll();
|
||||
ruleGroupNameTextField.requestFocusInWindow();
|
||||
ruleGroupNameTextField.setMinimumSize(ruleGroupNameTextField.getPreferredSize());
|
||||
}
|
||||
} else if (e.getClickCount() == 1) {
|
||||
if ("...".equals(getTitleAt(getSelectedIndex()))) {
|
||||
String title = ruleProcessor.newRule();
|
||||
Rule newRule = new Rule(api, configLoader, Config.ruleTemplate, tabbedPane);
|
||||
insertTab(title, null, newRule, null, getTabCount() - 1);
|
||||
setSelectedIndex(getTabCount() - 2);
|
||||
} else {
|
||||
renameTitleActionPerformed.actionPerformed(null);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MouseEvent.BUTTON3:
|
||||
if (!"...".equals(getTitleAt(getSelectedIndex()))) {
|
||||
popupMenu.show(e.getComponent(), e.getX(), e.getY());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
InputMap im = ruleGroupNameTextField.getInputMap(JComponent.WHEN_FOCUSED);
|
||||
ActionMap am = ruleGroupNameTextField.getActionMap();
|
||||
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "cancel");
|
||||
am.put("cancel", cancelActionPerformed);
|
||||
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "rename");
|
||||
am.put("rename", renameTitleActionPerformed);
|
||||
}
|
||||
|
||||
public void reloadRuleGroup() {
|
||||
removeAll();
|
||||
Config.globalRules.keySet().forEach(i-> addTab(i, new Rule(api, configLoader, hae.Config.globalRules.get(i), this)));
|
||||
addTab("...", null);
|
||||
}
|
||||
|
||||
private void deleteRuleGroupActionPerformed(ActionEvent e) {
|
||||
if (getTabCount() > 2) {
|
||||
int retCode = JOptionPane.showConfirmDialog(null, "Do you want to delete this rule group?", "Info",
|
||||
JOptionPane.YES_NO_OPTION);
|
||||
if (retCode == JOptionPane.YES_OPTION) {
|
||||
String title = getTitleAt(getSelectedIndex());
|
||||
ruleProcessor.deleteRuleGroup(title);
|
||||
remove(getSelectedIndex());
|
||||
setSelectedIndex(getSelectedIndex() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private 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 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();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user