diff --git a/src/main/java/burp/BurpExtender.java b/src/main/java/burp/BurpExtender.java index b28fb36..ccaf6f7 100644 --- a/src/main/java/burp/BurpExtender.java +++ b/src/main/java/burp/BurpExtender.java @@ -4,6 +4,7 @@ import burp.config.ConfigLoader; import burp.core.processor.ColorProcessor; import burp.core.processor.MessageProcessor; import burp.ui.MainUI; +import burp.ui.board.DatatablePanel; import burp.ui.board.MessagePanel; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; @@ -38,7 +39,7 @@ public class BurpExtender implements IBurpExtender, IHttpListener, IMessageEdito new ConfigLoader(); - String version = "2.5.5"; + String version = "2.5.6"; callbacks.setExtensionName(String.format("HaE (%s) - Highlighter and Extractor", version)); // 定义输出 @@ -217,7 +218,8 @@ public class BurpExtender implements IBurpExtender, IHttpListener, IMessageEdito class MarkInfoTab implements IMessageEditorTab { private final JTabbedPane jTabbedPane = new JTabbedPane(); - private JTable jTable = new JTable(); + private DatatablePanel dataPanel; + private JTable dataTable; private final IMessageEditorController controller; private Map extractRequestMap; private Map extractResponseMap; @@ -237,10 +239,10 @@ public class BurpExtender implements IBurpExtender, IHttpListener, IMessageEdito jTabbedPane.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent arg0) { - jTable = (JTable) ((JScrollPane)jTabbedPane.getSelectedComponent()).getViewport().getView(); + dataTable = ((DatatablePanel)jTabbedPane.getSelectedComponent()).getTable(); } }); - return this.jTabbedPane; + return jTabbedPane; } @Override @@ -280,10 +282,10 @@ public class BurpExtender implements IBurpExtender, IHttpListener, IMessageEdito */ @Override public byte[] getSelectedData() { - int[] selectRows = jTable.getSelectedRows(); + int[] selectRows = dataTable.getSelectedRows(); StringBuilder selectData = new StringBuilder(); for (int row : selectRows) { - selectData.append(jTable.getValueAt(row, 0).toString()).append("\n"); + selectData.append(dataTable.getValueAt(row, 0).toString()).append("\n"); } // 便于单行复制,去除最后一个换行符 String revData = selectData.reverse().toString().replaceFirst("\n", ""); @@ -310,18 +312,12 @@ public class BurpExtender implements IBurpExtender, IHttpListener, IMessageEdito */ public void makeTable(Map dataMap) { ArrayList lTitleList = new ArrayList<>(); + dataMap.keySet().forEach(i->{ String[] extractData = dataMap.get(i).split("\n"); - Object[][] data = new Object[extractData.length][1]; - for (int x = 0; x < extractData.length; x++) { - data[x][0] = extractData[x]; - } - JTable infoTable = new JTable(data, new Object[]{"Information"}); - infoTable.setAutoCreateRowSorter(true); - JScrollPane jScrollPane = new JScrollPane(infoTable); - lTitleList.add(i); - this.jTabbedPane.addTab(i, jScrollPane); + dataPanel = new DatatablePanel(i, Arrays.asList(extractData)); + jTabbedPane.addTab(i, dataPanel); }); /* @@ -329,9 +325,9 @@ public class BurpExtender implements IBurpExtender, IHttpListener, IMessageEdito * 采用全局ArrayList的方式遍历删除Tab,以此应对BurpSuite缓存机制导致的MarkInfo UI错误展示。 */ titleList.forEach(t->{ - int indexOfTab = this.jTabbedPane.indexOfTab(t); + int indexOfTab = jTabbedPane.indexOfTab(t); if (indexOfTab != -1) { - this.jTabbedPane.removeTabAt(indexOfTab); + jTabbedPane.removeTabAt(indexOfTab); } }); diff --git a/src/main/java/burp/core/processor/DataProcessingUnit.java b/src/main/java/burp/core/processor/DataProcessingUnit.java index c566a81..647e822 100644 --- a/src/main/java/burp/core/processor/DataProcessingUnit.java +++ b/src/main/java/burp/core/processor/DataProcessingUnit.java @@ -1,6 +1,5 @@ package burp.core.processor; -import burp.BurpExtender; import burp.core.GlobalCachePool; import burp.core.utils.HashCalculator; import burp.core.utils.MatchTool; @@ -57,136 +56,127 @@ public class DataProcessingUnit { } else { // 最终返回的结果 Map> finalMap = new HashMap<>(); - ConfigEntry.globalRules.keySet().forEach(i -> { + ConfigEntry.globalRules.keySet().parallelStream().forEach(i -> { for (Object[] objects : ConfigEntry.globalRules.get(i)) { // 多线程执行,一定程度上减少阻塞现象 - Thread t = new Thread(() -> { - String matchContent = ""; - // 遍历获取规则 - List result = new ArrayList<>(); - Map tmpMap = new HashMap<>(); + String matchContent = ""; + // 遍历获取规则 + List result = new ArrayList<>(); + Map tmpMap = new HashMap<>(); - String name = objects[1].toString(); - boolean loaded = (Boolean) objects[0]; - String regex = objects[2].toString(); - String color = objects[3].toString(); - String scope = objects[4].toString(); - String engine = objects[5].toString(); - boolean sensitive = (Boolean) objects[6]; - // 判断规则是否开启与作用域 - if (loaded && (scope.contains(scopeString) || scope.contains("any"))) { - switch (scope) { - case "any": - case "request": - case "response": - matchContent = new String(content, StandardCharsets.UTF_8); - break; - case "any header": - case "request header": - case "response header": - matchContent = headers; - break; - case "any body": - case "request body": - case "response body": - matchContent = new String(body, StandardCharsets.UTF_8); - break; - default: - break; - } + String name = objects[1].toString(); + boolean loaded = (Boolean) objects[0]; + String regex = objects[2].toString(); + String color = objects[3].toString(); + String scope = objects[4].toString(); + String engine = objects[5].toString(); + boolean sensitive = (Boolean) objects[6]; + // 判断规则是否开启与作用域 + if (loaded && (scope.contains(scopeString) || scope.contains("any"))) { + switch (scope) { + case "any": + case "request": + case "response": + matchContent = new String(content, StandardCharsets.UTF_8); + break; + case "any header": + case "request header": + case "response header": + matchContent = headers; + break; + case "any body": + case "request body": + case "response body": + matchContent = new String(body, StandardCharsets.UTF_8); + break; + default: + break; + } - if ("nfa".equals(engine)) { - Pattern pattern; - // 判断规则是否大小写敏感 - if (sensitive) { - pattern = new Pattern(regex); - } else { - pattern = new Pattern(regex, Pattern.IGNORE_CASE); - } - - Matcher matcher = pattern.matcher(matchContent); - while (matcher.find()) { - // 添加匹配数据至list - // 强制用户使用()包裹正则 - result.add(matcher.group(1)); - } + if ("nfa".equals(engine)) { + Pattern pattern; + // 判断规则是否大小写敏感 + if (sensitive) { + pattern = new Pattern(regex); } else { - RegExp regexp = new RegExp(regex); - Automaton auto = regexp.toAutomaton(); - RunAutomaton runAuto = new RunAutomaton(auto, true); - AutomatonMatcher autoMatcher = runAuto.newMatcher(matchContent); - while (autoMatcher.find()) { - // 添加匹配数据至list - // 强制用户使用()包裹正则 - result.add(autoMatcher.group()); - } + pattern = new Pattern(regex, Pattern.IGNORE_CASE); } - // 去除重复内容 - HashSet tmpList = new HashSet(result); - result.clear(); - result.addAll(tmpList); + Matcher matcher = pattern.matcher(matchContent); + while (matcher.find()) { + // 添加匹配数据至list + // 强制用户使用()包裹正则 + result.add(matcher.group(1)); + } + } else { + RegExp regexp = new RegExp(regex); + Automaton auto = regexp.toAutomaton(); + RunAutomaton runAuto = new RunAutomaton(auto, true); + AutomatonMatcher autoMatcher = runAuto.newMatcher(matchContent); + while (autoMatcher.find()) { + // 添加匹配数据至list + // 强制用户使用()包裹正则 + result.add(autoMatcher.group()); + } + } - String nameAndSize = String.format("%s (%s)", name, result.size()); - if (!result.isEmpty()) { - tmpMap.put("color", color); - String dataStr = String.join("\n", result); - tmpMap.put("data", dataStr); - finalMap.put(nameAndSize, tmpMap); - // 添加到全局变量中,便于Databoard检索 - if (!Objects.equals(host, "")) { - List dataList = Arrays.asList(dataStr.split("\n")); - if (ConfigEntry.globalDataMap.containsKey(host)) { - Map> gRuleMap = new HashMap<>(ConfigEntry.globalDataMap.get(host)); - if (gRuleMap.containsKey(name)) { - // gDataList为不可变列表,因此需要重新创建一个列表以便于使用addAll方法 - List gDataList = gRuleMap.get(name); - List newDataList = new ArrayList<>(gDataList); - newDataList.addAll(dataList); - newDataList = new ArrayList<>(new HashSet<>(newDataList)); - gRuleMap.remove(name); - gRuleMap.put(name, newDataList); - } else { - gRuleMap.put(name, dataList); - } - ConfigEntry.globalDataMap.remove(host); - ConfigEntry.globalDataMap.put(host, gRuleMap); + // 去除重复内容 + HashSet tmpList = new HashSet(result); + result.clear(); + result.addAll(tmpList); + + String nameAndSize = String.format("%s (%s)", name, result.size()); + if (!result.isEmpty()) { + tmpMap.put("color", color); + String dataStr = String.join("\n", result); + tmpMap.put("data", dataStr); + finalMap.put(nameAndSize, tmpMap); + // 添加到全局变量中,便于Databoard检索 + if (!Objects.equals(host, "")) { + List dataList = Arrays.asList(dataStr.split("\n")); + if (ConfigEntry.globalDataMap.containsKey(host)) { + Map> gRuleMap = new HashMap<>(ConfigEntry.globalDataMap.get(host)); + if (gRuleMap.containsKey(name)) { + // gDataList为不可变列表,因此需要重新创建一个列表以便于使用addAll方法 + List gDataList = gRuleMap.get(name); + List newDataList = new ArrayList<>(gDataList); + newDataList.addAll(dataList); + newDataList = new ArrayList<>(new HashSet<>(newDataList)); + gRuleMap.remove(name); + gRuleMap.put(name, newDataList); } else { - Map> ruleMap = new HashMap<>(); - ruleMap.put(name, dataList); - // 添加单一Host - ConfigEntry.globalDataMap.put(host, ruleMap); + gRuleMap.put(name, dataList); } + ConfigEntry.globalDataMap.remove(host); + ConfigEntry.globalDataMap.put(host, gRuleMap); + } else { + Map> ruleMap = new HashMap<>(); + ruleMap.put(name, dataList); + // 添加单一Host + ConfigEntry.globalDataMap.put(host, ruleMap); + } - String[] splitHost = host.split("\\."); + String[] splitHost = host.split("\\."); - String anyHost = (splitHost.length > 2 && !MatchTool.matchIP(host)) ? StringHelper.replaceFirstOccurrence(host, splitHost[0], "*") : ""; + String anyHost = (splitHost.length > 2 && !MatchTool.matchIP(host)) ? StringHelper.replaceFirstOccurrence(host, splitHost[0], "*") : ""; - if (!ConfigEntry.globalDataMap.containsKey(anyHost) && anyHost.length() > 0) { - // 添加通配符Host,实际数据从查询哪里将所有数据提取 - ConfigEntry.globalDataMap.put(anyHost, new HashMap<>()); - } + if (!ConfigEntry.globalDataMap.containsKey(anyHost) && anyHost.length() > 0) { + // 添加通配符Host,实际数据从查询哪里将所有数据提取 + ConfigEntry.globalDataMap.put(anyHost, new HashMap<>()); + } - if (!ConfigEntry.globalDataMap.containsKey("*")) { - // 添加通配符全匹配,同上 - ConfigEntry.globalDataMap.put("*", new HashMap<>()); - } + if (!ConfigEntry.globalDataMap.containsKey("*")) { + // 添加通配符全匹配,同上 + ConfigEntry.globalDataMap.put("*", new HashMap<>()); + } - if (!ConfigEntry.globalDataMap.containsKey("**")) { - // 添加通配符全匹配,同上 - ConfigEntry.globalDataMap.put("**", new HashMap<>()); - } + if (!ConfigEntry.globalDataMap.containsKey("**")) { + // 添加通配符全匹配,同上 + ConfigEntry.globalDataMap.put("**", new HashMap<>()); } } } - }); - t.start(); - try { - t.join(); - } catch (Exception e) { - e.printStackTrace(); } - } }); GlobalCachePool.addToCache(messageIndex, finalMap); diff --git a/src/main/java/burp/core/processor/MessageProcessor.java b/src/main/java/burp/core/processor/MessageProcessor.java index 6da2b37..1972b53 100644 --- a/src/main/java/burp/core/processor/MessageProcessor.java +++ b/src/main/java/burp/core/processor/MessageProcessor.java @@ -1,6 +1,8 @@ package burp.core.processor; import burp.IExtensionHelpers; +import burp.IRequestInfo; +import burp.IResponseInfo; import burp.core.utils.MatchTool; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; @@ -20,7 +22,8 @@ public class MessageProcessor { Map> obj; if (isRequest) { - List requestTmpHeaders = helpers.analyzeRequest(content).getHeaders(); + IRequestInfo requestInfo = helpers.analyzeRequest(content); + List requestTmpHeaders = requestInfo.getHeaders(); String requestHeaders = String.join("\n", requestTmpHeaders); try { @@ -33,22 +36,23 @@ public class MessageProcessor { return result; } - int requestBodyOffset = helpers.analyzeRequest(content).getBodyOffset(); + int requestBodyOffset = requestInfo.getBodyOffset(); byte[] requestBody = Arrays.copyOfRange(content, requestBodyOffset, content.length); obj = dataProcessingUnit.matchContentByRegex(content, requestHeaders, requestBody, "request", host); } else { + IResponseInfo responseInfo = helpers.analyzeResponse(content); try { - String inferredMimeType = String.format("hae.%s", helpers.analyzeResponse(content).getInferredMimeType().toLowerCase()); - String statedMimeType = String.format("hae.%s", helpers.analyzeResponse(content).getStatedMimeType().toLowerCase()); + String inferredMimeType = String.format("hae.%s", responseInfo.getInferredMimeType().toLowerCase()); + String statedMimeType = String.format("hae.%s", responseInfo.getStatedMimeType().toLowerCase()); if (matcher.matchUrlSuffix(statedMimeType) || matcher.matchUrlSuffix(inferredMimeType)) { return result; } } catch (Exception e) { return result; } - List responseTmpHeaders = helpers.analyzeResponse(content).getHeaders(); + List responseTmpHeaders = responseInfo.getHeaders(); String responseHeaders = String.join("\n", responseTmpHeaders); - int responseBodyOffset = helpers.analyzeResponse(content).getBodyOffset(); + int responseBodyOffset = responseInfo.getBodyOffset(); byte[] responseBody = Arrays.copyOfRange(content, responseBodyOffset, content.length); obj = dataProcessingUnit.matchContentByRegex(content, responseHeaders, responseBody, "response", host); } diff --git a/src/main/java/burp/ui/board/Databoard.java b/src/main/java/burp/ui/board/Databoard.java index a4c572c..4f9a8de 100644 --- a/src/main/java/burp/ui/board/Databoard.java +++ b/src/main/java/burp/ui/board/Databoard.java @@ -7,7 +7,6 @@ import burp.ui.board.MessagePanel.Table; import java.util.*; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; -import javax.swing.table.DefaultTableModel; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import javax.swing.table.TableModel; @@ -27,14 +26,28 @@ public class Databoard extends JPanel { private static Boolean isMatchHost = false; private JLabel hostLabel; private JTextField hostTextField; - private JTabbedPane dataTabbedPaneA; - private JTabbedPane dataTabbedPaneB; + private JTabbedPane dataTabbedPane; private JButton clearButton; private JSplitPane splitPane; private MessagePanel messagePanel; private Table table; - DefaultComboBoxModel comboBoxModel = new DefaultComboBoxModel(); - JComboBox hostComboBox = new JComboBox(comboBoxModel); + private SwingWorker currentWorker; + private DefaultComboBoxModel comboBoxModel = new DefaultComboBoxModel(); + private JComboBox hostComboBox = new JComboBox(comboBoxModel); + private ChangeListener changeListenerInstance = new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + int selectedIndex = dataTabbedPane.getSelectedIndex(); + String selectedTitle = ""; + + if (selectedIndex != -1) { + selectedTitle = dataTabbedPane.getTitleAt(selectedIndex); + } + + applyHostFilter(selectedTitle); + } + }; + public Databoard(MessagePanel messagePanel) { this.messagePanel = messagePanel; @@ -42,8 +55,7 @@ public class Databoard extends JPanel { } private void cleanUI() { - dataTabbedPaneA.removeAll(); - dataTabbedPaneB.removeAll(); + dataTabbedPane.removeAll(); splitPane.setVisible(false); } @@ -70,8 +82,7 @@ public class Databoard extends JPanel { // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents hostLabel = new JLabel(); hostTextField = new JTextField(); - dataTabbedPaneA = new JTabbedPane(JTabbedPane.TOP); - dataTabbedPaneB = new JTabbedPane(JTabbedPane.TOP); + dataTabbedPane = new JTabbedPane(JTabbedPane.TOP); clearButton = new JButton(); //======== this ======== @@ -130,102 +141,120 @@ public class Databoard extends JPanel { * 设置输入自动匹配 */ private void setAutoMatch() { - isMatchHost = false; - - for (String host : getHostByList()) { - comboBoxModel.addElement(host); - } + populateComboBoxModel(); hostComboBox.setSelectedItem(null); + hostComboBox.addActionListener(this::handleComboBoxAction); - hostComboBox.addActionListener(e -> { - if (!isMatchHost) { - if (hostComboBox.getSelectedItem() != null) { - String selectedHost = hostComboBox.getSelectedItem().toString(); - hostTextField.setText(selectedHost); - populateTabbedPaneByHost(selectedHost); - } - } - }); - - // 事件监听 hostTextField.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { - isMatchHost = true; - int keyCode = e.getKeyCode(); - - if (keyCode == KeyEvent.VK_SPACE && hostComboBox.isPopupVisible()) { - e.setKeyCode(KeyEvent.VK_ENTER); - } - - if (keyCode == KeyEvent.VK_ENTER || keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_DOWN) { - e.setSource(hostComboBox); - hostComboBox.dispatchEvent(e); - - if (keyCode == KeyEvent.VK_ENTER) { - String selectedItem = hostComboBox.getSelectedItem().toString(); - hostTextField.setText(selectedItem); - populateTabbedPaneByHost(selectedItem); - hostComboBox.setPopupVisible(false); - return; - } - } - - if (keyCode == KeyEvent.VK_ESCAPE) { - hostComboBox.setPopupVisible(false); - } - - isMatchHost = false; + handleKeyEvents(e); } }); hostTextField.getDocument().addDocumentListener(new DocumentListener() { @Override public void insertUpdate(DocumentEvent e) { - updateList(); + update(e); } @Override public void removeUpdate(DocumentEvent e) { - updateList(); + update(e); } @Override public void changedUpdate(DocumentEvent e) { - updateList(); + update(e); } - private void updateList() { - 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; + public void update(DocumentEvent e) { + filterComboBoxList(); } }); } + private void populateComboBoxModel() { + for (String host : getHostByList()) { + comboBoxModel.addElement(host); + } + } + + 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_ENTER, KeyEvent.VK_UP, KeyEvent.VK_DOWN).contains(keyCode)) { + e.setSource(hostComboBox); + hostComboBox.dispatchEvent(e); + if (keyCode == KeyEvent.VK_ENTER) { + updateTextFieldFromComboBox(); + hostComboBox.setPopupVisible(false); + e.consume(); + } + } + + if (keyCode == KeyEvent.VK_ESCAPE) { + hostComboBox.setPopupVisible(false); + } + + isMatchHost = false; + } + + private void updateTextFieldFromComboBox() { + Object selectedItem = hostComboBox.getSelectedItem(); + if (selectedItem != null) { + String selectedHost = selectedItem.toString(); + hostTextField.setText(selectedHost); + populateTabbedPaneByHost(selectedHost); + } + } + + 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 applyHostFilter(String filterText) { TableRowSorter sorter = (TableRowSorter) table.getRowSorter(); + if (filterText.contains("*.")) { filterText = StringHelper.replaceFirstOccurrence(filterText, "*.", ""); } else if (filterText.contains("*")) { filterText = ""; } + RowFilter filter = RowFilter.regexFilter(filterText, 1); sorter.setRowFilter(filter); filterText = filterText.isEmpty() ? "*" : filterText; @@ -262,51 +291,58 @@ public class Databoard extends JPanel { selectedDataMap = dataMap.get(selectedHost); } - // 由于removeChangeListener不知什么原因不生效,因此建立两个tabbedPane - dataTabbedPaneA.removeAll(); - dataTabbedPaneB.removeAll(); + dataTabbedPane.removeAll(); - ChangeListener changeListenerInstance = new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - int selectedIndex = dataTabbedPaneA.getSelectedIndex(); - String selectedTitle = ""; - if (selectedIndex != -1) { - selectedTitle = dataTabbedPaneA.getTitleAt(selectedIndex); - } - applyHostFilter(selectedTitle); - } - }; + dataTabbedPane.setPreferredSize(new Dimension(500,0)); + dataTabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); + splitPane.setLeftComponent(dataTabbedPane); if (selectedHost.equals("**")) { - dataTabbedPaneA.setPreferredSize(new Dimension(500,0)); - dataTabbedPaneA.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); - splitPane.setLeftComponent(dataTabbedPaneA); for (Map.Entry>> entry : dataMap.entrySet()) { JTabbedPane newTabbedPane = new JTabbedPane(); newTabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); + if (currentWorker != null && !currentWorker.isDone()) { + currentWorker.cancel(true); + } + for (Map.Entry> entrySet : entry.getValue().entrySet()) { - Thread t = new Thread(() -> { - String tabTitle = String.format("%s (%s)", entrySet.getKey(), entrySet.getValue().size()); - newTabbedPane.addTab(tabTitle, new DataTable(entrySet.getKey(), entrySet.getValue())); - dataTabbedPaneA.addTab(entry.getKey(), newTabbedPane); - }); - t.start(); - try { - t.join(); - } catch (Exception e) { - e.printStackTrace(); - } + currentWorker = new SwingWorker() { + @Override + protected Object[] doInBackground() throws Exception { + String tabTitle = String.format("%s (%s)", entrySet.getKey(), + entrySet.getValue().size()); + DatatablePanel datatablePanel = new DatatablePanel(entrySet.getKey(), + entrySet.getValue()); + datatablePanel.setTableListener(messagePanel); + return new Object[] {tabTitle, datatablePanel}; + } + + @Override + protected void done() { + if (!isCancelled()) { + try { + Object[] result = (Object[]) get(); + newTabbedPane.addTab(result[0].toString(), (DatatablePanel) result[1]); + dataTabbedPane.addTab(entry.getKey(), newTabbedPane); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + }; + currentWorker.execute(); } } - dataTabbedPaneA.addChangeListener(changeListenerInstance); + + dataTabbedPane.addChangeListener(changeListenerInstance); } else { - dataTabbedPaneB.setPreferredSize(new Dimension(500,0)); - dataTabbedPaneB.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); - splitPane.setLeftComponent(dataTabbedPaneB); + dataTabbedPane.removeChangeListener(changeListenerInstance); + for (Map.Entry> entry : selectedDataMap.entrySet()) { String tabTitle = String.format("%s (%s)", entry.getKey(), entry.getValue().size()); - dataTabbedPaneB.addTab(tabTitle, new DataTable(entry.getKey(), entry.getValue())); + DatatablePanel datatablePanel = new DatatablePanel(entry.getKey(), entry.getValue()); + datatablePanel.setTableListener(messagePanel); + dataTabbedPane.addTab(tabTitle, datatablePanel); } } @@ -332,103 +368,21 @@ public class Databoard extends JPanel { } hostTextField.setText(selectedHost); - } - } - class DataTable extends JPanel { - private final JTable table; - private final DefaultTableModel model; - private final JTextField searchField; - private TableRowSorter sorter; - - - public DataTable(String tableName, List list) { - model = new DefaultTableModel(); - table = new JTable(model); - sorter = new TableRowSorter<>(model); - - table.setRowSorter(sorter); - table.setDefaultEditor(Object.class, null); - - // 表格内容双击事件 - table.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - if (e.getClickCount() == 2) { - int selectedRow = table.getSelectedRow(); - if (selectedRow != -1) { - String rowData = table.getValueAt(selectedRow, 0).toString(); - messagePanel.applyMessageFilter(tableName, rowData); + ChangeListener changeListener = new ChangeListener() { + public void stateChanged(ChangeEvent e) { + JTabbedPane tabSource = (JTabbedPane) e.getSource(); + int index = tabSource.getSelectedIndex(); + if (index != -1) { + Component selectedComponent = tabSource.getComponentAt(index); + if (selectedComponent instanceof DatatablePanel) { + ((DatatablePanel) selectedComponent).updatePageSize(); } } } - }); + }; - model.addColumn("Information"); - for (String item : list) { - model.addRow(new Object[]{item}); - } - - String defaultText = "Search"; - - searchField = new JTextField(defaultText); - // 设置灰色默认文本Search - searchField.setForeground(Color.GRAY); - searchField.addFocusListener(new FocusListener() { - @Override - public void focusGained(FocusEvent e) { - if (searchField.getText().equals(defaultText)) { - searchField.setText(""); - searchField.setForeground(Color.BLACK); - } - } - - @Override - public void focusLost(FocusEvent e) { - if (searchField.getText().isEmpty()) { - searchField.setForeground(Color.GRAY); - searchField.setText(defaultText); - } - } - }); - - // 监听输入框内容输入、更新、删除 - 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(); - } - - private void performSearch() { - // 通过字体颜色来判断是否可以进行过滤 - if (searchField.getForeground() == Color.BLACK) { - String searchText = searchField.getText(); - if (sorter == null) { - sorter = new TableRowSorter<>(model); - table.setRowSorter(sorter); - } - RowFilter rowFilter = RowFilter.regexFilter(String.format("%s%s", "(?i)", searchText), 0); - sorter.setRowFilter(rowFilter); - } - } - }); - - // 设置布局 - JScrollPane scrollPane = new JScrollPane(table); - - setLayout(new BorderLayout(0, 5)); - add(scrollPane, BorderLayout.CENTER); - add(searchField, BorderLayout.SOUTH); + dataTabbedPane.addChangeListener(changeListener); } } } diff --git a/src/main/java/burp/ui/board/DatatablePanel.java b/src/main/java/burp/ui/board/DatatablePanel.java new file mode 100644 index 0000000..2fad8fe --- /dev/null +++ b/src/main/java/burp/ui/board/DatatablePanel.java @@ -0,0 +1,193 @@ +package burp.ui.board; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.event.AdjustmentEvent; +import java.awt.event.AdjustmentListener; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.List; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.JTextField; +import javax.swing.RowFilter; +import javax.swing.ScrollPaneConstants; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.table.DefaultTableModel; +import javax.swing.table.TableRowSorter; + +public class DatatablePanel extends JPanel { + private final JTable table; + private final DefaultTableModel model; + private final JTextField searchField; + private TableRowSorter sorter; + private int pageSize; // 动态计算的,每页显示多少条记录 + private int currentPage; // 当前页码 + private List fullList; // 假设这是一个包含所有数据的列表 + private JScrollPane scrollPane; + private String tableName; + private final int SHOW_LENGTH = 3000; + + public DatatablePanel(String tableName, List list) { + fullList = list; + currentPage = 0; + pageSize = 10; + this.tableName = tableName; + + model = new DefaultTableModel(); + table = new JTable(model); + sorter = new TableRowSorter<>(model); + + table.setRowSorter(sorter); + model.addColumn("Information"); + + String defaultText = "Search"; + + searchField = new JTextField(defaultText); + // 设置灰色默认文本Search + searchField.setForeground(Color.GRAY); + searchField.addFocusListener(new FocusListener() { + @Override + public void focusGained(FocusEvent e) { + if (searchField.getText().equals(defaultText)) { + searchField.setText(""); + searchField.setForeground(Color.BLACK); + } + } + + @Override + public void focusLost(FocusEvent e) { + if (searchField.getText().isEmpty()) { + searchField.setForeground(Color.GRAY); + searchField.setText(defaultText); + } + } + }); + + // 监听输入框内容输入、更新、删除 + 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(); + } + + private void performSearch() { + // 通过字体颜色来判断是否可以进行过滤 + if (searchField.getForeground() == Color.BLACK) { + String searchText = searchField.getText(); + if (sorter == null) { + sorter = new TableRowSorter<>(model); + table.setRowSorter(sorter); + } + RowFilter rowFilter = RowFilter.regexFilter(String.format("%s%s", "(?i)", searchText), 0); + sorter.setRowFilter(rowFilter); + } + } + }); + + // 设置布局 + scrollPane = new JScrollPane(table); + scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); + scrollPane.addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + updatePageSize(); + } + }); + + // 添加滚动监听器,以加载更多数据 + scrollPane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() { + @Override + public void adjustmentValueChanged(AdjustmentEvent e) { + if (fullList.size() > SHOW_LENGTH) { + if (!e.getValueIsAdjusting() && !scrollPane.getVerticalScrollBar().getValueIsAdjusting()) { + if (scrollPane.getVerticalScrollBar().getValue() == scrollPane.getVerticalScrollBar().getMaximum() - scrollPane.getVerticalScrollBar().getVisibleAmount()) { + if ((currentPage + 1) * pageSize < fullList.size()) { + currentPage++; + loadPageData(); + } + } + } + } + } + }); + + setLayout(new BorderLayout(0, 5)); + add(scrollPane, BorderLayout.CENTER); + add(searchField, BorderLayout.SOUTH); + loadPageData(); + } + + // 加载指定页的数据 + private void loadPageData() { + if (fullList.size() > SHOW_LENGTH) { + int start = currentPage * pageSize; + int end = Math.min((currentPage + 1) * pageSize, fullList.size()); + int lastRow = model.getRowCount(); + start = Math.max(start, lastRow); + + for (int i = start; i < end; i++) { + model.addRow(new Object[]{fullList.get(i)}); + } + } else { + for (String item : fullList) { + model.addRow(new Object[]{item}); + } + } + } + + public void updatePageSize() { + if (fullList.size() > SHOW_LENGTH && isShowing()) { + int oldPageSize = pageSize; + pageSize = getDynamicSize(); + if (oldPageSize != pageSize) { + currentPage = 0; + loadPageData(); + } + } + } + + private int getDynamicSize() { + int visibleHeight = scrollPane.getViewport().getViewRect().height; + int rowHeight = table.getRowHeight(); + return Math.max(1, visibleHeight / rowHeight + 2); + } + + public void setTableListener(MessagePanel messagePanel) { + table.setDefaultEditor(Object.class, null); + + // 表格内容双击事件 + table.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + int selectedRow = table.getSelectedRow(); + if (selectedRow != -1) { + String rowData = table.getValueAt(selectedRow, 0).toString(); + messagePanel.applyMessageFilter(tableName, rowData); + } + } + } + }); + } + + public JTable getTable() { + return this.table; + } +} diff --git a/src/main/java/burp/ui/board/MessagePanel.java b/src/main/java/burp/ui/board/MessagePanel.java index fc1175e..65f52b3 100644 --- a/src/main/java/burp/ui/board/MessagePanel.java +++ b/src/main/java/burp/ui/board/MessagePanel.java @@ -21,12 +21,10 @@ import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.Map; -import java.util.Objects; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTabbedPane; import javax.swing.JTable; -import javax.swing.SwingUtilities; import javax.swing.SwingWorker; import javax.swing.table.AbstractTableModel; import javax.swing.table.DefaultTableModel; @@ -158,6 +156,9 @@ public class MessagePanel extends AbstractTableModel implements IMessageEditorCo @Override public Object getValueAt(int rowIndex, int columnIndex) { + if (filteredLog.isEmpty()) { + return ""; + } LogEntry logEntry = filteredLog.get(rowIndex); switch (columnIndex) { @@ -264,6 +265,7 @@ public class MessagePanel extends AbstractTableModel implements IMessageEditorCo } } fireTableDataChanged(); + logTable.lastSelectedIndex = -1; } public void deleteByHost(String filterText) { @@ -355,6 +357,9 @@ public class MessagePanel extends AbstractTableModel implements IMessageEditorCo } private boolean areMapsEqual(Map> map1, Map> map2) { + if (map1 == null || map2 == null) { + return false; + } if (map1.size() != map2.size()) { return false; } @@ -398,7 +403,10 @@ public class MessagePanel extends AbstractTableModel implements IMessageEditorCo public class Table extends JTable { LogEntry logEntry; - private SwingWorker currentWorker; + private SwingWorker currentWorker; + // 设置响应报文返回的最大长度为3MB + private final int MAX_LENGTH = 3145728; + private int lastSelectedIndex = -1; public Table(TableModel tableModel) { super(tableModel); @@ -407,35 +415,50 @@ public class MessagePanel extends AbstractTableModel implements IMessageEditorCo @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; + logEntry = filteredLog.get(selectedIndex); - logEntry = filteredLog.get(convertRowIndexToModel(row)); - requestViewer.setMessage("Loading...".getBytes(), true); - responseViewer.setMessage("Loading...".getBytes(), false); - currentlyDisplayedItem = logEntry.getRequestResponse(); + requestViewer.setMessage("Loading...".getBytes(), true); + responseViewer.setMessage("Loading...".getBytes(), false); + currentlyDisplayedItem = logEntry.getRequestResponse(); - // 取消之前的后台任务 - if (currentWorker != null && !currentWorker.isDone()) { - currentWorker.cancel(true); - } - // 在后台线程中执行耗时操作 - SwingWorker worker = new SwingWorker() { - @Override - protected Void doInBackground() throws Exception { - refreshMessage(); - return null; + if (currentWorker != null && !currentWorker.isDone()) { + currentWorker.cancel(true); } - }; - // 设置当前后台任务 - currentWorker = worker; - // 启动后台线程 - worker.execute(); - } - private synchronized void refreshMessage() { - SwingUtilities.invokeLater(() -> { - requestViewer.setMessage(logEntry.getRequestResponse().getRequest(), true); - responseViewer.setMessage(logEntry.getRequestResponse().getResponse(), false); - }); + currentWorker = new SwingWorker() { + @Override + protected byte[][] doInBackground() throws Exception { + byte[] requestByte = logEntry.getRequestResponse().getRequest(); + byte[] responseByte = logEntry.getRequestResponse().getResponse(); + + if (responseByte.length > MAX_LENGTH) { + String ellipsis = "\r\n......"; + responseByte = Arrays.copyOf(responseByte, MAX_LENGTH + ellipsis.length()); + byte[] ellipsisBytes = ellipsis.getBytes(); + System.arraycopy(ellipsisBytes, 0, responseByte, MAX_LENGTH, ellipsisBytes.length); + } + + return new byte[][] {requestByte, responseByte}; + } + + @Override + protected void done() { + if (!isCancelled()) { + try { + byte[][] result = (byte[][]) get(); + requestViewer.setMessage(result[0], true); + responseViewer.setMessage(result[1], false); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + }; + currentWorker.execute(); + } } }