Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54973d9f4f | ||
|
|
fb347a8dc6 | ||
|
|
04b6652b03 | ||
|
|
6d4abae898 | ||
|
|
97172fab45 | ||
|
|
ba3b206acf | ||
|
|
99ed2cb2fd | ||
|
|
8a47f61caa | ||
|
|
ad323ba7a5 |
22
README.md
22
README.md
@@ -1,7 +1,7 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="images/logo.png" style="width: 20%" />
|
<img src="images/logo.png" style="width: 20%" />
|
||||||
<h4><a href="https://gh0st.cn/HaE/">赋能白帽,高效作战!</a></h4>
|
<h4><a href="https://gh0st.cn/HaE/">赋能白帽,高效作战!</a></h4>
|
||||||
<h5>第一作者: <a href="https://github.com/gh0stkey">EvilChen</a>(中孚信息元亨实验室), 第二作者: <a href="https://github.com/0chencc">0chencc</a>(米斯特安全团队)</h5>
|
<h5>第一作者: <a href="https://github.com/gh0stkey">EvilChen</a>(中孚信息元亨实验室)<br>第二作者: <a href="https://github.com/0chencc">0chencc</a>(米斯特安全团队)<br>第三作者: <a href="https://github.com/vaycore">vaycore</a>(独立安全研究员)</h5>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## 项目介绍
|
## 项目介绍
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
1. 由于HaE 3.0版本开始采用`Montoya API`进行开发,因此使用新版HaE需要升级你的BurpSuite版本(>=2023.12.1)。
|
1. 由于HaE 3.0版本开始采用`Montoya API`进行开发,因此使用新版HaE需要升级你的BurpSuite版本(>=2023.12.1)。
|
||||||
2. 由于HaE 2.6版本后对规则字段进行了更新,因此无法适配<=2.6版本的规则,请用户自行前往[规则转换页面](https://gh0st.cn/HaE/ConversionRule.html)进行转换。
|
2. 由于HaE 2.6版本后对规则字段进行了更新,因此无法适配<=2.6版本的规则,请用户自行前往[规则转换页面](https://gh0st.cn/HaE/ConversionRule.html)进行转换。
|
||||||
3. HaE官方规则库存放在[Github](https://raw.githubusercontent.com/gh0stkey/HaE/gh-pages/Rules.yml)上,因此默认加载HaE官方规则库需使用代理(BApp审核不允许使用CDN)。
|
3. HaE官方规则库存放在[Github](https://raw.githubusercontent.com/gh0stkey/HaE/gh-pages/Rules.yml)上,因此点击`Update`升级HaE官方规则库时需使用代理(BApp审核考虑安全性,不允许使用CDN)。
|
||||||
4. 自定义HaE规则必须用左右括号`()`将所需提取的表达式内容包含,例如你要匹配一个**Shiro应用**的响应报文,正常匹配规则为`rememberMe=delete`,在HaE的规则中就需要变成`(rememberMe=delete)`。
|
4. 自定义HaE规则必须用左右括号`()`将所需提取的表达式内容包含,例如你要匹配一个**Shiro应用**的响应报文,正常匹配规则为`rememberMe=delete`,在HaE的规则中就需要变成`(rememberMe=delete)`。
|
||||||
|
|
||||||
## 使用方法
|
## 使用方法
|
||||||
@@ -46,14 +46,12 @@ HaE目前的规则一共有8个字段,详细的含义如下所示:
|
|||||||
|
|
||||||
## 优势特点
|
## 优势特点
|
||||||
|
|
||||||
1. 精细配置:高度自由的配置选项,以满足各类精细化场景需求。
|
1. **功能**:通过对HTTP报文的颜色高亮、注释和提取,帮助使用者获取有意义的信息,**聚焦高价值报文**。
|
||||||
2. 分类标签:使用标签对规则进行分类,便于管理和组织规则。
|
2. **界面**:清晰可视的界面设计,以及**简洁的界面交互**,帮助使用者更轻松的了解和配置项目,**避免`多按钮`式的复杂体验**。
|
||||||
3. 高亮标记:在HTTP History页面,通过颜色高亮和注释判断请求的价值。
|
3. **查询**:将HTTP报文的高亮、注释和提取到的相关信息**集中在一个数据面板**,可以一键查询、提取信息,从而提高测试和梳理效率。
|
||||||
4. 易读配置:使用易读的YAML格式存储配置文件,方便阅读和修改。
|
4. **算法**:内置高亮颜色的升级算法,当出现相同颜色时**会自动向上升级一个颜色**进行标记,**避免`屠龙者终成恶龙`场景**。
|
||||||
5. 数据集合:将匹配到的数据、请求和响应集中在数据面板中,提高测试和梳理效率。
|
5. **管理**:支持对数据的一键导出、导入,以**自定义`.hae`文件的方式**进行项目数据存储,**便于存储和共享项目数据**。
|
||||||
6. 简洁可视:清晰可视的界面设计,更轻松地了解和配置HaE,操作简单、使用便捷。
|
6. **实战**:官方规则库和规则字段作用功能,都是**基于实战化场景总结输出**的,**以此提高数据的有效性、精准性发现**。
|
||||||
7. 颜色升级:内置颜色升级算法,避免“屠龙者终成恶龙”场景,突出最具价值的请求。
|
|
||||||
8. 实战规则:官方规则库是基于实战化场景总结输出,提升数据发现的有效性、精准性。
|
|
||||||
|
|
||||||
| 界面名称 | 界面展示 |
|
| 界面名称 | 界面展示 |
|
||||||
| ------------------------ | ---------------------------------------------------- |
|
| ------------------------ | ---------------------------------------------------- |
|
||||||
@@ -62,9 +60,7 @@ HaE目前的规则一共有8个字段,详细的含义如下所示:
|
|||||||
| Databoard(数据集合) | <img src="images/databoard.png" style="width: 80%" /> |
|
| Databoard(数据集合) | <img src="images/databoard.png" style="width: 80%" /> |
|
||||||
| MarkInfo(数据展示) | <img src="images/markinfo.png" style="width: 80%" /> |
|
| MarkInfo(数据展示) | <img src="images/markinfo.png" style="width: 80%" /> |
|
||||||
|
|
||||||
## 文末随笔
|
## 支持项目
|
||||||
|
|
||||||
正义感是一个不可丢失的东西。
|
|
||||||
|
|
||||||
如果你觉得HaE好用,可以打赏一下作者,给作者持续更新下去的动力!
|
如果你觉得HaE好用,可以打赏一下作者,给作者持续更新下去的动力!
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ sourceSets {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation 'net.portswigger.burp.extensions:montoya-api:2023.12.1'
|
implementation 'net.portswigger.burp.extensions:montoya-api:2023.12.1'
|
||||||
implementation 'org.yaml:snakeyaml:2.0'
|
implementation 'org.yaml:snakeyaml:2.0'
|
||||||
implementation 'net.sourceforge.jregex:jregex:1.2_01'
|
|
||||||
implementation 'dk.brics.automaton:automaton:1.11-8'
|
implementation 'dk.brics.automaton:automaton:1.11-8'
|
||||||
|
implementation 'com.github.ben-manes.caffeine:caffeine:3.1.8'
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
@@ -34,4 +34,4 @@ jar {
|
|||||||
from {
|
from {
|
||||||
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
|
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 106 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 328 KiB After Width: | Height: | Size: 853 KiB |
BIN
images/rules.png
BIN
images/rules.png
Binary file not shown.
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 123 KiB |
@@ -54,4 +54,6 @@ public class Config {
|
|||||||
public static Map<String, Object[][]> globalRules = new HashMap<>();
|
public static Map<String, Object[][]> globalRules = new HashMap<>();
|
||||||
|
|
||||||
public static ConcurrentHashMap<String, Map<String, List<String>>> globalDataMap = new ConcurrentHashMap<>();
|
public static ConcurrentHashMap<String, Map<String, List<String>>> globalDataMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public static ConcurrentHashMap<String, Map<String, Object>> globalHostHashMap = new ConcurrentHashMap<>();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,19 +10,19 @@ import hae.instances.editor.ResponseEditor;
|
|||||||
import hae.instances.editor.WebSocketEditor;
|
import hae.instances.editor.WebSocketEditor;
|
||||||
import hae.instances.http.HttpMessageHandler;
|
import hae.instances.http.HttpMessageHandler;
|
||||||
import hae.instances.websocket.WebSocketMessageHandler;
|
import hae.instances.websocket.WebSocketMessageHandler;
|
||||||
import hae.utils.config.ConfigLoader;
|
import hae.utils.ConfigLoader;
|
||||||
|
|
||||||
public class HaE implements BurpExtension {
|
public class HaE implements BurpExtension {
|
||||||
@Override
|
@Override
|
||||||
public void initialize(MontoyaApi api) {
|
public void initialize(MontoyaApi api) {
|
||||||
// 设置扩展名称
|
// 设置扩展名称
|
||||||
String version = "3.1";
|
String version = "3.2.1";
|
||||||
api.extension().setName(String.format("HaE (%s) - Highlighter and Extractor", version));
|
api.extension().setName(String.format("HaE (%s) - Highlighter and Extractor", version));
|
||||||
|
|
||||||
// 加载扩展后输出的项目信息
|
// 加载扩展后输出的项目信息
|
||||||
Logging logging = api.logging();
|
Logging logging = api.logging();
|
||||||
logging.logToOutput("[ HACK THE WORLD - TO DO IT ]");
|
logging.logToOutput("[ HACK THE WORLD - TO DO IT ]");
|
||||||
logging.logToOutput("[#] Author: EvilChen && 0chencc");
|
logging.logToOutput("[#] Author: EvilChen && 0chencc && vaycore");
|
||||||
logging.logToOutput("[#] Github: https://github.com/gh0stkey/HaE");
|
logging.logToOutput("[#] Github: https://github.com/gh0stkey/HaE");
|
||||||
|
|
||||||
// 配置文件加载
|
// 配置文件加载
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import hae.component.board.Databoard;
|
|||||||
import hae.component.board.message.MessageTableModel;
|
import hae.component.board.message.MessageTableModel;
|
||||||
import hae.component.config.Config;
|
import hae.component.config.Config;
|
||||||
import hae.component.rule.Rules;
|
import hae.component.rule.Rules;
|
||||||
import hae.utils.config.ConfigLoader;
|
import hae.utils.ConfigLoader;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
|||||||
@@ -2,39 +2,53 @@ package hae.component.board;
|
|||||||
|
|
||||||
import burp.api.montoya.MontoyaApi;
|
import burp.api.montoya.MontoyaApi;
|
||||||
import hae.Config;
|
import hae.Config;
|
||||||
|
import hae.component.board.message.MessageEntry;
|
||||||
import hae.component.board.message.MessageTableModel;
|
import hae.component.board.message.MessageTableModel;
|
||||||
import hae.component.board.message.MessageTableModel.MessageTable;
|
import hae.component.board.message.MessageTableModel.MessageTable;
|
||||||
import hae.utils.config.ConfigLoader;
|
import hae.instances.http.utils.RegularMatcher;
|
||||||
|
import hae.utils.ConfigLoader;
|
||||||
|
import hae.utils.project.ProjectProcessor;
|
||||||
|
import hae.utils.project.model.HaeFileContent;
|
||||||
import hae.utils.string.StringProcessor;
|
import hae.utils.string.StringProcessor;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.event.DocumentEvent;
|
import javax.swing.event.DocumentEvent;
|
||||||
import javax.swing.event.DocumentListener;
|
import javax.swing.event.DocumentListener;
|
||||||
|
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||||
import javax.swing.table.TableColumnModel;
|
import javax.swing.table.TableColumnModel;
|
||||||
import javax.swing.table.TableModel;
|
import javax.swing.table.TableModel;
|
||||||
import javax.swing.table.TableRowSorter;
|
import javax.swing.table.TableRowSorter;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.*;
|
import java.awt.event.*;
|
||||||
|
import java.io.File;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.*;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class Databoard extends JPanel {
|
public class Databoard extends JPanel {
|
||||||
private final MontoyaApi api;
|
private final MontoyaApi api;
|
||||||
private final ConfigLoader configLoader;
|
private final ConfigLoader configLoader;
|
||||||
|
private final ProjectProcessor projectProcessor;
|
||||||
private final MessageTableModel messageTableModel;
|
private final MessageTableModel messageTableModel;
|
||||||
|
|
||||||
private JTextField hostTextField;
|
private JTextField hostTextField;
|
||||||
private JTabbedPane dataTabbedPane;
|
private JTabbedPane dataTabbedPane;
|
||||||
private JSplitPane splitPane;
|
private JSplitPane splitPane;
|
||||||
private MessageTable messageTable;
|
private MessageTable messageTable;
|
||||||
|
private JProgressBar progressBar;
|
||||||
|
|
||||||
private static Boolean isMatchHost = false;
|
private static Boolean isMatchHost = false;
|
||||||
private final DefaultComboBoxModel comboBoxModel = new DefaultComboBoxModel();
|
private final DefaultComboBoxModel comboBoxModel = new DefaultComboBoxModel();
|
||||||
private final JComboBox hostComboBox = new JComboBox(comboBoxModel);
|
private final JComboBox hostComboBox = new JComboBox(comboBoxModel);
|
||||||
|
|
||||||
|
private SwingWorker<Boolean, Void> currentWorker;
|
||||||
|
|
||||||
public Databoard(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) {
|
public Databoard(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) {
|
||||||
this.api = api;
|
this.api = api;
|
||||||
this.configLoader = configLoader;
|
this.configLoader = configLoader;
|
||||||
|
this.projectProcessor = new ProjectProcessor(api);
|
||||||
this.messageTableModel = messageTableModel;
|
this.messageTableModel = messageTableModel;
|
||||||
|
|
||||||
initComponents();
|
initComponents();
|
||||||
@@ -43,24 +57,32 @@ public class Databoard extends JPanel {
|
|||||||
private void initComponents() {
|
private void initComponents() {
|
||||||
setLayout(new GridBagLayout());
|
setLayout(new GridBagLayout());
|
||||||
((GridBagLayout) getLayout()).columnWidths = new int[]{25, 0, 0, 0, 20, 0};
|
((GridBagLayout) getLayout()).columnWidths = new int[]{25, 0, 0, 0, 20, 0};
|
||||||
((GridBagLayout) getLayout()).rowHeights = new int[]{0, 65, 20, 0};
|
((GridBagLayout) getLayout()).rowHeights = new int[]{0, 65, 20, 25, 0};
|
||||||
((GridBagLayout) getLayout()).columnWeights = new double[]{0.0, 0.0, 1.0, 0.0, 0.0, 1.0E-4};
|
((GridBagLayout) getLayout()).columnWeights = new double[]{0.0, 0.0, 1.0, 0.0, 0.0, 1.0E-4};
|
||||||
((GridBagLayout) getLayout()).rowWeights = new double[]{0.0, 1.0, 0.0, 1.0E-4};
|
((GridBagLayout) getLayout()).rowWeights = new double[]{0.0, 1.0, 0.0, 0.0, 1.0E-4};
|
||||||
|
|
||||||
JLabel hostLabel = new JLabel("Host:");
|
JLabel hostLabel = new JLabel("Host:");
|
||||||
|
|
||||||
JButton clearButton = new JButton("Clear");
|
JButton clearButton = new JButton("Clear");
|
||||||
|
JButton exportButton = new JButton("Export");
|
||||||
|
JButton importButton = new JButton("Import");
|
||||||
JButton actionButton = new JButton("Action");
|
JButton actionButton = new JButton("Action");
|
||||||
JPanel menuPanel = new JPanel(new GridLayout(1, 1));
|
JPanel menuPanel = new JPanel(new GridLayout(3, 1, 0, 5));
|
||||||
menuPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
|
menuPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
|
||||||
JPopupMenu menu = new JPopupMenu();
|
JPopupMenu menu = new JPopupMenu();
|
||||||
menuPanel.add(clearButton);
|
menuPanel.add(clearButton);
|
||||||
|
menuPanel.add(exportButton);
|
||||||
|
menuPanel.add(importButton);
|
||||||
menu.add(menuPanel);
|
menu.add(menuPanel);
|
||||||
|
|
||||||
hostTextField = new JTextField();
|
hostTextField = new JTextField();
|
||||||
splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
|
splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
|
||||||
dataTabbedPane = new JTabbedPane(JTabbedPane.TOP);
|
dataTabbedPane = new JTabbedPane(JTabbedPane.TOP);
|
||||||
|
|
||||||
|
dataTabbedPane.setPreferredSize(new Dimension(500, 0));
|
||||||
|
dataTabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
|
||||||
|
splitPane.setLeftComponent(dataTabbedPane);
|
||||||
|
|
||||||
actionButton.addActionListener(e -> {
|
actionButton.addActionListener(e -> {
|
||||||
int x = 0;
|
int x = 0;
|
||||||
int y = actionButton.getHeight();
|
int y = actionButton.getHeight();
|
||||||
@@ -68,6 +90,10 @@ public class Databoard extends JPanel {
|
|||||||
});
|
});
|
||||||
|
|
||||||
clearButton.addActionListener(this::clearActionPerformed);
|
clearButton.addActionListener(this::clearActionPerformed);
|
||||||
|
exportButton.addActionListener(this::exportActionPerformed);
|
||||||
|
importButton.addActionListener(this::importActionPerformed);
|
||||||
|
|
||||||
|
progressBar = new JProgressBar();
|
||||||
|
|
||||||
splitPane.addComponentListener(new ComponentAdapter() {
|
splitPane.addComponentListener(new ComponentAdapter() {
|
||||||
@Override
|
@Override
|
||||||
@@ -77,6 +103,7 @@ public class Databoard extends JPanel {
|
|||||||
});
|
});
|
||||||
|
|
||||||
splitPane.setVisible(false);
|
splitPane.setVisible(false);
|
||||||
|
progressBar.setVisible(false);
|
||||||
|
|
||||||
add(hostLabel, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
add(hostLabel, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||||
new Insets(8, 0, 5, 5), 0, 0));
|
new Insets(8, 0, 5, 5), 0, 0));
|
||||||
@@ -84,9 +111,13 @@ public class Databoard extends JPanel {
|
|||||||
new Insets(8, 0, 5, 5), 0, 0));
|
new Insets(8, 0, 5, 5), 0, 0));
|
||||||
add(actionButton, new GridBagConstraints(3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
add(actionButton, new GridBagConstraints(3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||||
new Insets(8, 0, 5, 5), 0, 0));
|
new Insets(8, 0, 5, 5), 0, 0));
|
||||||
add(splitPane, new GridBagConstraints(1, 1, 3, 3, 0.0, 0.0,
|
|
||||||
|
add(splitPane, new GridBagConstraints(1, 1, 3, 1, 0.0, 1.0,
|
||||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||||
new Insets(8, 0, 5, 5), 0, 0));
|
new Insets(0, 5, 0, 5), 0, 0));
|
||||||
|
add(progressBar, new GridBagConstraints(1, 3, 3, 1, 1.0, 0.0,
|
||||||
|
GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,
|
||||||
|
new Insets(0, 5, 0, 5), 0, 0));
|
||||||
hostComboBox.setMaximumRowCount(5);
|
hostComboBox.setMaximumRowCount(5);
|
||||||
add(hostComboBox, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
add(hostComboBox, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||||
new Insets(8, 0, 5, 5), 0, 0));
|
new Insets(8, 0, 5, 5), 0, 0));
|
||||||
@@ -106,6 +137,19 @@ public class Databoard extends JPanel {
|
|||||||
columnModel.getColumn(5).setPreferredWidth((int) (totalWidth * 0.1));
|
columnModel.getColumn(5).setPreferredWidth((int) (totalWidth * 0.1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setProgressBar(boolean status) {
|
||||||
|
progressBar.setIndeterminate(status);
|
||||||
|
if (!status) {
|
||||||
|
progressBar.setMaximum(100);
|
||||||
|
progressBar.setString("OK");
|
||||||
|
progressBar.setStringPainted(true);
|
||||||
|
progressBar.setValue(progressBar.getMaximum());
|
||||||
|
} else {
|
||||||
|
progressBar.setString("Loading...");
|
||||||
|
progressBar.setStringPainted(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void setAutoMatch() {
|
private void setAutoMatch() {
|
||||||
hostComboBox.setSelectedItem(null);
|
hostComboBox.setSelectedItem(null);
|
||||||
hostComboBox.addActionListener(this::handleComboBoxAction);
|
hostComboBox.addActionListener(this::handleComboBoxAction);
|
||||||
@@ -138,9 +182,45 @@ public class Databoard extends JPanel {
|
|||||||
|
|
||||||
private void handleComboBoxAction(ActionEvent e) {
|
private void handleComboBoxAction(ActionEvent e) {
|
||||||
if (!isMatchHost && hostComboBox.getSelectedItem() != null) {
|
if (!isMatchHost && hostComboBox.getSelectedItem() != null) {
|
||||||
|
progressBar.setVisible(true);
|
||||||
|
setProgressBar(true);
|
||||||
String selectedHost = hostComboBox.getSelectedItem().toString();
|
String selectedHost = hostComboBox.getSelectedItem().toString();
|
||||||
hostTextField.setText(selectedHost);
|
hostTextField.setText(selectedHost);
|
||||||
populateTabbedPaneByHost(selectedHost);
|
|
||||||
|
if (currentWorker != null && !currentWorker.isDone()) {
|
||||||
|
currentWorker.cancel(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentWorker = new SwingWorker<Boolean, Void>() {
|
||||||
|
@Override
|
||||||
|
protected Boolean doInBackground() {
|
||||||
|
return populateTabbedPaneByHost(selectedHost);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void done() {
|
||||||
|
if (!isCancelled()) {
|
||||||
|
try {
|
||||||
|
boolean status = get();
|
||||||
|
if (status) {
|
||||||
|
JSplitPane messageSplitPane = messageTableModel.getSplitPane();
|
||||||
|
splitPane.setRightComponent(messageSplitPane);
|
||||||
|
messageTable = messageTableModel.getMessageTable();
|
||||||
|
resizePanel();
|
||||||
|
|
||||||
|
splitPane.setVisible(true);
|
||||||
|
hostTextField.setText(selectedHost);
|
||||||
|
|
||||||
|
hostComboBox.setPopupVisible(false);
|
||||||
|
applyHostFilter(selectedHost);
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
currentWorker.execute();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +239,6 @@ public class Databoard extends JPanel {
|
|||||||
if (keyCode == KeyEvent.VK_ENTER) {
|
if (keyCode == KeyEvent.VK_ENTER) {
|
||||||
isMatchHost = false;
|
isMatchHost = false;
|
||||||
handleComboBoxAction(null);
|
handleComboBoxAction(null);
|
||||||
hostComboBox.setPopupVisible(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keyCode == KeyEvent.VK_ESCAPE) {
|
if (keyCode == KeyEvent.VK_ESCAPE) {
|
||||||
@@ -169,10 +248,52 @@ public class Databoard extends JPanel {
|
|||||||
isMatchHost = false;
|
isMatchHost = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean populateTabbedPaneByHost(String selectedHost) {
|
||||||
|
ConcurrentHashMap<String, Map<String, List<String>>> dataMap = Config.globalDataMap;
|
||||||
|
Map<String, List<String>> selectedDataMap;
|
||||||
|
|
||||||
|
if (selectedHost.contains("*")) {
|
||||||
|
selectedDataMap = new HashMap<>();
|
||||||
|
dataMap.keySet().parallelStream().forEach(key -> {
|
||||||
|
if ((StringProcessor.matchesHostPattern(key, selectedHost) || selectedHost.equals("*")) && !key.contains("*")) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selectedDataMap.isEmpty()) {
|
||||||
|
dataTabbedPane.removeAll();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private void filterComboBoxList() {
|
private void filterComboBoxList() {
|
||||||
isMatchHost = true;
|
isMatchHost = true;
|
||||||
comboBoxModel.removeAllElements();
|
comboBoxModel.removeAllElements();
|
||||||
String input = hostTextField.getText().toLowerCase();
|
String input = hostTextField.getText().toLowerCase();
|
||||||
|
|
||||||
if (!input.isEmpty()) {
|
if (!input.isEmpty()) {
|
||||||
for (String host : getHostByList()) {
|
for (String host : getHostByList()) {
|
||||||
String lowerCaseHost = host.toLowerCase();
|
String lowerCaseHost = host.toLowerCase();
|
||||||
@@ -191,102 +312,302 @@ public class Databoard extends JPanel {
|
|||||||
isMatchHost = false;
|
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) {
|
private void applyHostFilter(String filterText) {
|
||||||
TableRowSorter<TableModel> sorter = (TableRowSorter<TableModel>) messageTable.getRowSorter();
|
TableRowSorter<TableModel> sorter = (TableRowSorter<TableModel>) messageTable.getRowSorter();
|
||||||
|
|
||||||
String cleanedText = StringProcessor.replaceFirstOccurrence(filterText, "*.", "");
|
String cleanedText = StringProcessor.replaceFirstOccurrence(filterText, "*.", "");
|
||||||
|
|
||||||
RowFilter<Object, Object> rowFilter = new RowFilter<Object, Object>() {
|
new SwingWorker<Void, Void>() {
|
||||||
public boolean include(Entry<?, ?> entry) {
|
@Override
|
||||||
if (cleanedText.equals("*")) {
|
protected Void doInBackground() throws Exception {
|
||||||
return true;
|
RowFilter<Object, Object> rowFilter = new RowFilter<Object, Object>() {
|
||||||
} else {
|
public boolean include(Entry<?, ?> entry) {
|
||||||
String host = StringProcessor.getHostByUrl((String) entry.getValue(1));
|
if (cleanedText.equals("*")) {
|
||||||
return StringProcessor.matchFromEnd(host, cleanedText);
|
return true;
|
||||||
}
|
} else {
|
||||||
|
String host = StringProcessor.getHostByUrl((String) entry.getValue(1));
|
||||||
|
return StringProcessor.matchesHostPattern(host, filterText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
sorter.setRowFilter(rowFilter);
|
||||||
|
messageTableModel.applyHostFilter(filterText);
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
sorter.setRowFilter(rowFilter);
|
@Override
|
||||||
|
protected void done() {
|
||||||
|
setProgressBar(false);
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
|
||||||
messageTableModel.applyHostFilter(filterText);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> getHostByList() {
|
private List<String> getHostByList() {
|
||||||
return new ArrayList<>(Config.globalDataMap.keySet());
|
if (!Config.globalDataMap.keySet().isEmpty()) {
|
||||||
|
return new ArrayList<>(Config.globalDataMap.keySet());
|
||||||
|
}
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void exportActionPerformed(ActionEvent e) {
|
||||||
|
String selectedHost = hostTextField.getText().trim();
|
||||||
|
|
||||||
|
if (selectedHost.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String exportDir = selectDirectory(true);
|
||||||
|
|
||||||
|
if (exportDir.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
new SwingWorker<List<String>, Void>() {
|
||||||
|
@Override
|
||||||
|
protected List<String> doInBackground() {
|
||||||
|
ConcurrentHashMap<String, Map<String, List<String>>> dataMap = Config.globalDataMap;
|
||||||
|
return exportData(selectedHost, exportDir, dataMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void done() {
|
||||||
|
try {
|
||||||
|
List<String> taskStatusList = get();
|
||||||
|
if (!taskStatusList.isEmpty()) {
|
||||||
|
String exportStatusMessage = String.format("Exported File List Status:\n%s", String.join("\n", taskStatusList));
|
||||||
|
JOptionPane.showConfirmDialog(Databoard.this, exportStatusMessage, "Info", JOptionPane.YES_OPTION);
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> exportData(String selectedHost, String exportDir, Map<String, Map<String, List<String>>> dataMap) {
|
||||||
|
return dataMap.entrySet().stream()
|
||||||
|
.filter(entry -> selectedHost.equals("*") || StringProcessor.matchesHostPattern(entry.getKey(), selectedHost))
|
||||||
|
.filter(entry -> !entry.getKey().contains("*"))
|
||||||
|
.map(entry -> exportEntry(entry, exportDir))
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String exportEntry(Map.Entry<String, Map<String, List<String>>> entry, String exportDir) {
|
||||||
|
String key = entry.getKey();
|
||||||
|
Map<String, List<String>> ruleMap = entry.getValue();
|
||||||
|
|
||||||
|
if (ruleMap == null || ruleMap.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<MessageEntry> messageEntryList = messageTableModel.getLogs();
|
||||||
|
|
||||||
|
Map<MessageEntry, String> entryUUIDMap = messageEntryList.stream()
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
messageEntry -> messageEntry,
|
||||||
|
messageEntry -> StringProcessor.getRandomUUID(),
|
||||||
|
(existing, replacement) -> existing // 在冲突时保留现有的映射
|
||||||
|
));
|
||||||
|
|
||||||
|
Map<String, Map<String, Object>> httpMap = processEntries(
|
||||||
|
messageEntryList,
|
||||||
|
key,
|
||||||
|
entryUUIDMap,
|
||||||
|
this::createHttpItemMap
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, Map<String, Object>> urlMap = processEntries(
|
||||||
|
messageEntryList,
|
||||||
|
key,
|
||||||
|
entryUUIDMap,
|
||||||
|
this::creteUrlItemMap
|
||||||
|
);
|
||||||
|
|
||||||
|
String hostName = key.replace(":", "_");
|
||||||
|
String filename = String.format("%s/%s-%s.hae", exportDir, StringProcessor.getCurrentTime(), hostName);
|
||||||
|
boolean createdStatus = projectProcessor.createHaeFile(filename, key, ruleMap, urlMap, httpMap);
|
||||||
|
|
||||||
|
return String.format("Filename: %s, Status: %s", filename, createdStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Map<String, Map<String, Object>> processEntries(List<MessageEntry> messageEntryList, String key, Map<MessageEntry, String> entryUUIDMap, Function<MessageEntry, Map<String, Object>> mapFunction) {
|
||||||
|
return messageEntryList.stream()
|
||||||
|
.filter(messageEntry -> !StringProcessor.getHostByUrl(messageEntry.getUrl()).isEmpty())
|
||||||
|
.filter(messageEntry -> StringProcessor.getHostByUrl(messageEntry.getUrl()).equals(key))
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
entryUUIDMap::get,
|
||||||
|
mapFunction,
|
||||||
|
(existing, replacement) -> existing
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> creteUrlItemMap(MessageEntry entry) {
|
||||||
|
Map<String, Object> urlItemMap = new LinkedHashMap<>();
|
||||||
|
urlItemMap.put("url", entry.getUrl());
|
||||||
|
urlItemMap.put("method", entry.getMethod());
|
||||||
|
urlItemMap.put("status", entry.getStatus());
|
||||||
|
urlItemMap.put("length", entry.getLength());
|
||||||
|
urlItemMap.put("comment", entry.getComment());
|
||||||
|
urlItemMap.put("color", entry.getColor());
|
||||||
|
urlItemMap.put("size", String.valueOf(entry.getRequestResponse().request().toByteArray().length()));
|
||||||
|
return urlItemMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> createHttpItemMap(MessageEntry entry) {
|
||||||
|
Map<String, Object> httpItemMap = new LinkedHashMap<>();
|
||||||
|
httpItemMap.put("request", entry.getRequestResponse().request().toByteArray().getBytes());
|
||||||
|
httpItemMap.put("response", entry.getRequestResponse().response().toByteArray().getBytes());
|
||||||
|
return httpItemMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void importActionPerformed(ActionEvent e) {
|
||||||
|
String exportDir = selectDirectory(false);
|
||||||
|
if (exportDir.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
new SwingWorker<List<String>, Void>() {
|
||||||
|
@Override
|
||||||
|
protected List<String> doInBackground() {
|
||||||
|
List<String> filesWithExtension = findFilesWithExtension(new File(exportDir), ".hae");
|
||||||
|
return filesWithExtension.stream()
|
||||||
|
.map(Databoard.this::importData)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void done() {
|
||||||
|
try {
|
||||||
|
List<String> taskStatusList = get();
|
||||||
|
if (!taskStatusList.isEmpty()) {
|
||||||
|
String importStatusMessage = "Imported File List Status:\n" + String.join("\n", taskStatusList);
|
||||||
|
JOptionPane.showConfirmDialog(Databoard.this, importStatusMessage, "Info", JOptionPane.YES_OPTION);
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String importData(String filename) {
|
||||||
|
HaeFileContent haeFileContent = projectProcessor.readHaeFile(filename);
|
||||||
|
boolean readStatus = haeFileContent != null;
|
||||||
|
|
||||||
|
ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
|
||||||
|
List<Future<?>> futures = new ArrayList<>();
|
||||||
|
|
||||||
|
if (readStatus) {
|
||||||
|
try {
|
||||||
|
String host = haeFileContent.getHost();
|
||||||
|
haeFileContent.getDataMap().forEach((key, value) -> RegularMatcher.putDataToGlobalMap(host, key, value));
|
||||||
|
|
||||||
|
haeFileContent.getUrlMap().forEach((key, urlItemMap) -> {
|
||||||
|
Future<?> future = executor.submit(() -> {
|
||||||
|
String url = urlItemMap.get("url");
|
||||||
|
String comment = urlItemMap.get("comment");
|
||||||
|
String color = urlItemMap.get("color");
|
||||||
|
String length = urlItemMap.get("length");
|
||||||
|
String method = urlItemMap.get("method");
|
||||||
|
String status = urlItemMap.get("status");
|
||||||
|
String path = haeFileContent.getHttpPath();
|
||||||
|
|
||||||
|
messageTableModel.add(null, url, method, status, length, comment, color, key, path);
|
||||||
|
});
|
||||||
|
|
||||||
|
futures.add(future);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (Future<?> future : futures) {
|
||||||
|
try {
|
||||||
|
future.get();
|
||||||
|
} catch (InterruptedException | ExecutionException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
api.logging().logToError("importData: " + e.getMessage());
|
||||||
|
} finally {
|
||||||
|
executor.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return String.format("Filename: %s, Status: %s", filename, readStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> findFilesWithExtension(File directory, String extension) {
|
||||||
|
List<String> filePaths = new ArrayList<>();
|
||||||
|
if (directory.isDirectory()) {
|
||||||
|
File[] files = directory.listFiles();
|
||||||
|
if (files != null) {
|
||||||
|
for (File file : files) {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
filePaths.addAll(findFilesWithExtension(file, extension));
|
||||||
|
} else if (file.isFile() && file.getName().toLowerCase().endsWith(extension)) {
|
||||||
|
filePaths.add(file.getAbsolutePath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
filePaths.add(directory.getAbsolutePath());
|
||||||
|
}
|
||||||
|
return filePaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String selectDirectory(boolean forDirectories) {
|
||||||
|
JFileChooser chooser = new JFileChooser();
|
||||||
|
chooser.setCurrentDirectory(new java.io.File(configLoader.getRulesFilePath()));
|
||||||
|
chooser.setDialogTitle(String.format("Select a Directory%s", forDirectories ? "" : " or File"));
|
||||||
|
FileNameExtensionFilter filter = new FileNameExtensionFilter(".hae Files", "hae");
|
||||||
|
chooser.addChoosableFileFilter(filter);
|
||||||
|
chooser.setFileFilter(filter);
|
||||||
|
|
||||||
|
chooser.setFileSelectionMode(forDirectories ? JFileChooser.DIRECTORIES_ONLY : JFileChooser.FILES_AND_DIRECTORIES);
|
||||||
|
chooser.setAcceptAllFileFilterUsed(!forDirectories);
|
||||||
|
|
||||||
|
if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
|
||||||
|
File selectedDirectory = chooser.getSelectedFile();
|
||||||
|
return selectedDirectory.getAbsolutePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearActionPerformed(ActionEvent e) {
|
private void clearActionPerformed(ActionEvent e) {
|
||||||
int retCode = JOptionPane.showConfirmDialog(null, "Do you want to clear data?", "Info",
|
int retCode = JOptionPane.showConfirmDialog(this, "Do you want to clear data?", "Info",
|
||||||
JOptionPane.YES_NO_OPTION);
|
JOptionPane.YES_NO_OPTION);
|
||||||
String host = hostTextField.getText();
|
String host = hostTextField.getText();
|
||||||
if (retCode == JOptionPane.YES_OPTION && !host.isEmpty()) {
|
if (retCode == JOptionPane.YES_OPTION && !host.isEmpty()) {
|
||||||
dataTabbedPane.removeAll();
|
dataTabbedPane.removeAll();
|
||||||
splitPane.setVisible(false);
|
splitPane.setVisible(false);
|
||||||
|
progressBar.setVisible(false);
|
||||||
|
|
||||||
String cleanedHost = StringProcessor.replaceFirstOccurrence(host, "*.", "");
|
Config.globalDataMap.keySet().parallelStream().forEach(key -> {
|
||||||
|
if (StringProcessor.matchesHostPattern(key, host) || host.equals("*")) {
|
||||||
|
Config.globalDataMap.remove(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (host.contains("*")) {
|
if (!StringProcessor.matchHostIsIp(host) && !host.contains("*.")) {
|
||||||
Config.globalDataMap.keySet().removeIf(i -> i.contains(cleanedHost) || cleanedHost.contains("*"));
|
String baseDomain = StringProcessor.getBaseDomain(StringProcessor.extractHostname(host));
|
||||||
} else {
|
|
||||||
Config.globalDataMap.remove(host);
|
long count = Config.globalDataMap.keySet().stream()
|
||||||
|
.filter(k -> !k.equals("*." + baseDomain))
|
||||||
|
.filter(k -> StringProcessor.matchFromEnd(k, baseDomain))
|
||||||
|
.count();
|
||||||
|
|
||||||
|
if (count == 0) {
|
||||||
|
Config.globalDataMap.remove("*." + baseDomain);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
messageTableModel.deleteByHost(cleanedHost);
|
if (Config.globalDataMap.keySet().size() == 1 && Config.globalDataMap.keySet().stream().anyMatch(key -> key.contains("*"))) {
|
||||||
|
Config.globalDataMap.keySet().remove("*");
|
||||||
|
}
|
||||||
|
|
||||||
|
messageTableModel.deleteByHost(host);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package hae.component.board;
|
|||||||
|
|
||||||
import burp.api.montoya.MontoyaApi;
|
import burp.api.montoya.MontoyaApi;
|
||||||
import hae.component.board.message.MessageTableModel;
|
import hae.component.board.message.MessageTableModel;
|
||||||
import hae.utils.ui.UIEnhancer;
|
import hae.utils.UIEnhancer;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.event.DocumentEvent;
|
import javax.swing.event.DocumentEvent;
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ public class MessageEntry {
|
|||||||
private final String status;
|
private final String status;
|
||||||
private final String color;
|
private final String color;
|
||||||
private final String method;
|
private final String method;
|
||||||
|
private final String hash;
|
||||||
|
private final String path;
|
||||||
|
|
||||||
MessageEntry(HttpRequestResponse requestResponse, String method, String url, String comment, String length, String color, String status) {
|
MessageEntry(HttpRequestResponse requestResponse, String method, String url, String comment, String length, String color, String status, String hash, String path) {
|
||||||
this.requestResponse = requestResponse;
|
this.requestResponse = requestResponse;
|
||||||
this.method = method;
|
this.method = method;
|
||||||
this.url = url;
|
this.url = url;
|
||||||
@@ -20,6 +22,8 @@ public class MessageEntry {
|
|||||||
this.length = length;
|
this.length = length;
|
||||||
this.color = color;
|
this.color = color;
|
||||||
this.status = status;
|
this.status = status;
|
||||||
|
this.hash = hash;
|
||||||
|
this.path = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getColor() {
|
public String getColor() {
|
||||||
@@ -49,4 +53,12 @@ public class MessageEntry {
|
|||||||
public HttpRequestResponse getRequestResponse() {
|
public HttpRequestResponse getRequestResponse() {
|
||||||
return this.requestResponse;
|
return this.requestResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getHash() {
|
||||||
|
return this.hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPath() {
|
||||||
|
return this.path;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -11,6 +11,7 @@ import burp.api.montoya.ui.editor.HttpRequestEditor;
|
|||||||
import burp.api.montoya.ui.editor.HttpResponseEditor;
|
import burp.api.montoya.ui.editor.HttpResponseEditor;
|
||||||
import hae.Config;
|
import hae.Config;
|
||||||
import hae.cache.CachePool;
|
import hae.cache.CachePool;
|
||||||
|
import hae.utils.project.FileProcessor;
|
||||||
import hae.utils.string.HashCalculator;
|
import hae.utils.string.HashCalculator;
|
||||||
import hae.utils.string.StringProcessor;
|
import hae.utils.string.StringProcessor;
|
||||||
|
|
||||||
@@ -22,6 +23,10 @@ import javax.swing.table.TableRowSorter;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -32,7 +37,7 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
private final MessageTable messageTable;
|
private final MessageTable messageTable;
|
||||||
private final JTabbedPane messageTab;
|
private final JTabbedPane messageTab;
|
||||||
private final JSplitPane splitPane;
|
private final JSplitPane splitPane;
|
||||||
private final List<MessageEntry> log = new ArrayList<MessageEntry>();
|
private final LinkedList<MessageEntry> log = new LinkedList<>();
|
||||||
private final LinkedList<MessageEntry> filteredLog;
|
private final LinkedList<MessageEntry> filteredLog;
|
||||||
|
|
||||||
public MessageTableModel(MontoyaApi api) {
|
public MessageTableModel(MontoyaApi api) {
|
||||||
@@ -92,25 +97,25 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
splitPane.setRightComponent(messageTab);
|
splitPane.setRightComponent(messageTab);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add(HttpRequestResponse messageInfo, String comment, String color) {
|
public void add(HttpRequestResponse messageInfo, String url, String method, String status, String length, String comment, String color, String hash, String path) {
|
||||||
synchronized (log) {
|
synchronized (log) {
|
||||||
HttpRequest httpRequest = messageInfo.request();
|
boolean isDuplicate = false;
|
||||||
String url = httpRequest.url();
|
MessageEntry logEntry = new MessageEntry(messageInfo, method, url, comment, length, color, status, hash, path);
|
||||||
String method = httpRequest.method();
|
|
||||||
|
|
||||||
HttpResponse httpResponse = messageInfo.response();
|
byte[] reqByteA = new byte[0];
|
||||||
String status = String.valueOf(httpResponse.statusCode());
|
byte[] resByteA = new byte[0];
|
||||||
String length = String.valueOf(httpResponse.body().length());
|
|
||||||
|
|
||||||
MessageEntry logEntry = new MessageEntry(messageInfo, method, url, comment, length, color, status);
|
if (messageInfo != null) {
|
||||||
|
HttpRequest httpRequest = messageInfo.request();
|
||||||
|
HttpResponse httpResponse = messageInfo.response();
|
||||||
|
|
||||||
|
reqByteA = httpRequest.toByteArray().getBytes();
|
||||||
|
resByteA = httpResponse.toByteArray().getBytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 比较Hash,如若存在重复的请求或响应,则不放入消息内容里
|
||||||
try {
|
try {
|
||||||
// 比较Hash,如若存在重复的请求或响应,则不放入消息内容里
|
if (!log.isEmpty()) {
|
||||||
byte[] reqByteA = httpRequest.toByteArray().getBytes();
|
|
||||||
byte[] resByteA = httpResponse.toByteArray().getBytes();
|
|
||||||
boolean isDuplicate = false;
|
|
||||||
|
|
||||||
if (log.size() > 0) {
|
|
||||||
for (MessageEntry entry : log) {
|
for (MessageEntry entry : log) {
|
||||||
HttpRequestResponse reqResMessage = entry.getRequestResponse();
|
HttpRequestResponse reqResMessage = entry.getRequestResponse();
|
||||||
byte[] reqByteB = reqResMessage.request().toByteArray().getBytes();
|
byte[] reqByteB = reqResMessage.request().toByteArray().getBytes();
|
||||||
@@ -125,12 +130,12 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isDuplicate) {
|
|
||||||
log.add(logEntry);
|
|
||||||
}
|
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isDuplicate) {
|
||||||
|
log.add(logEntry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -138,52 +143,113 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
public void deleteByHost(String filterText) {
|
public void deleteByHost(String filterText) {
|
||||||
filteredLog.clear();
|
filteredLog.clear();
|
||||||
List<Integer> rowsToRemove = new ArrayList<>();
|
List<Integer> rowsToRemove = new ArrayList<>();
|
||||||
for (int i = 0; i < log.size(); i++) {
|
|
||||||
MessageEntry entry = log.get(i);
|
new SwingWorker<Void, Void>() {
|
||||||
String host = StringProcessor.getHostByUrl(entry.getUrl());
|
@Override
|
||||||
if (!host.isEmpty()) {
|
protected Void doInBackground() {
|
||||||
if (StringProcessor.matchFromEnd(host, filterText) || filterText.contains("*")) {
|
for (int i = 0; i < log.size(); i++) {
|
||||||
rowsToRemove.add(i);
|
MessageEntry entry = log.get(i);
|
||||||
|
String host = StringProcessor.getHostByUrl(entry.getUrl());
|
||||||
|
if (!host.isEmpty()) {
|
||||||
|
if (StringProcessor.matchesHostPattern(host, filterText) || filterText.equals("*")) {
|
||||||
|
rowsToRemove.add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = rowsToRemove.size() - 1; i >= 0; i--) {
|
||||||
|
int row = rowsToRemove.get(i);
|
||||||
|
log.remove(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void done() {
|
||||||
|
if (!rowsToRemove.isEmpty()) {
|
||||||
|
int[] rows = rowsToRemove.stream().mapToInt(Integer::intValue).toArray();
|
||||||
|
fireTableRowsDeleted(rows[0], rows[rows.length - 1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}.execute();
|
||||||
|
|
||||||
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) {
|
public void applyHostFilter(String filterText) {
|
||||||
filteredLog.clear();
|
filteredLog.clear();
|
||||||
fireTableDataChanged();
|
|
||||||
String cleanedText = StringProcessor.replaceFirstOccurrence(filterText, "*.", "");
|
|
||||||
|
|
||||||
for (MessageEntry entry : log) {
|
ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
|
||||||
String host = StringProcessor.getHostByUrl(entry.getUrl());
|
List<Future<?>> futures = new ArrayList<>();
|
||||||
if (!host.isEmpty()) {
|
try {
|
||||||
if (filterText.contains("*.") && StringProcessor.matchFromEnd(host, cleanedText)) {
|
log.parallelStream().forEach(entry -> {
|
||||||
filteredLog.add(entry);
|
Future<?> future = executor.submit(() -> {
|
||||||
} else if (host.equals(filterText) || filterText.contains("*")) {
|
MessageEntry finalEntry = getEntryByFile(entry);
|
||||||
filteredLog.add(entry);
|
String host = StringProcessor.getHostByUrl(finalEntry.getUrl());
|
||||||
|
if (!host.isEmpty()) {
|
||||||
|
synchronized (filteredLog) {
|
||||||
|
if (StringProcessor.matchesHostPattern(host, filterText) || filterText.contains("*")) {
|
||||||
|
filteredLog.add(finalEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
futures.add(future);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (Future<?> future : futures) {
|
||||||
|
try {
|
||||||
|
future.get();
|
||||||
|
} catch (InterruptedException | ExecutionException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
api.logging().logToError("applyHostFilter: " + e.getMessage());
|
||||||
|
} finally {
|
||||||
|
executor.shutdown();
|
||||||
|
fireTableDataChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MessageEntry getEntryByFile(MessageEntry entry) {
|
||||||
|
HttpRequestResponse requestResponse = entry.getRequestResponse();
|
||||||
|
if (requestResponse == null) {
|
||||||
|
String url = entry.getUrl();
|
||||||
|
String method = entry.getMethod();
|
||||||
|
String status = entry.getStatus();
|
||||||
|
String comment = entry.getComment();
|
||||||
|
String color = entry.getColor();
|
||||||
|
String path = entry.getPath();
|
||||||
|
String hash = entry.getHash();
|
||||||
|
int length = Integer.parseInt(entry.getLength());
|
||||||
|
|
||||||
|
byte[] contents = FileProcessor.readFileContent(path, hash);
|
||||||
|
|
||||||
|
if (contents.length > length) {
|
||||||
|
byte[] response = Arrays.copyOf(contents, length);
|
||||||
|
byte[] request = Arrays.copyOfRange(contents, length, contents.length);
|
||||||
|
requestResponse = StringProcessor.createHttpRequestResponse(url, request, response);
|
||||||
|
|
||||||
|
int index = log.indexOf(entry);
|
||||||
|
entry = new MessageEntry(requestResponse, method, url, comment, String.valueOf(length), color, status, "", "");
|
||||||
|
log.set(index, entry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fireTableDataChanged();
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void applyMessageFilter(String tableName, String filterText) {
|
public void applyMessageFilter(String tableName, String filterText) {
|
||||||
filteredLog.clear();
|
filteredLog.clear();
|
||||||
for (MessageEntry entry : log) {
|
for (MessageEntry entry : log) {
|
||||||
HttpRequestResponse requestResponse = entry.getRequestResponse();
|
HttpRequestResponse requestResponse = entry.getRequestResponse();
|
||||||
HttpRequest httpRequest = requestResponse.request();
|
// 标志变量,表示是否满足过滤条件
|
||||||
HttpResponse httpResponse = requestResponse.response();
|
AtomicBoolean isMatched = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
HttpRequest httpRequest = entry.getRequestResponse().request();
|
||||||
|
HttpResponse httpResponse = entry.getRequestResponse().response();
|
||||||
|
|
||||||
String requestString = new String(httpRequest.toByteArray().getBytes(), StandardCharsets.UTF_8);
|
String requestString = new String(httpRequest.toByteArray().getBytes(), StandardCharsets.UTF_8);
|
||||||
String requestBody = new String(httpRequest.body().getBytes(), StandardCharsets.UTF_8);
|
String requestBody = new String(httpRequest.body().getBytes(), StandardCharsets.UTF_8);
|
||||||
@@ -197,9 +263,7 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
.map(HttpHeader::toString)
|
.map(HttpHeader::toString)
|
||||||
.collect(Collectors.joining("\n"));
|
.collect(Collectors.joining("\n"));
|
||||||
|
|
||||||
// 标志变量,表示是否满足过滤条件
|
MessageEntry finalEntry = entry;
|
||||||
AtomicBoolean isMatched = new AtomicBoolean(false);
|
|
||||||
|
|
||||||
Config.globalRules.keySet().forEach(i -> {
|
Config.globalRules.keySet().forEach(i -> {
|
||||||
for (Object[] objects : Config.globalRules.get(i)) {
|
for (Object[] objects : Config.globalRules.get(i)) {
|
||||||
String name = objects[1].toString();
|
String name = objects[1].toString();
|
||||||
@@ -207,7 +271,7 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
String scope = objects[6].toString();
|
String scope = objects[6].toString();
|
||||||
|
|
||||||
// 从注释中查看是否包含当前规则名,包含的再进行查询,有效减少无意义的检索时间
|
// 从注释中查看是否包含当前规则名,包含的再进行查询,有效减少无意义的检索时间
|
||||||
if (entry.getComment().contains(name)) {
|
if (finalEntry.getComment().contains(name)) {
|
||||||
if (name.equals(tableName)) {
|
if (name.equals(tableName)) {
|
||||||
// 标志变量,表示当前规则是否匹配
|
// 标志变量,表示当前规则是否匹配
|
||||||
boolean isMatch = false;
|
boolean isMatch = false;
|
||||||
@@ -305,7 +369,7 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
if (!map2.containsKey(key)) {
|
if (!map2.containsKey(key)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!areInnerMapsEqual(map1.get(key), map2.get(key))) {
|
if (areInnerMapsEqual(map1.get(key), map2.get(key))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -315,30 +379,29 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
|
|
||||||
private boolean areInnerMapsEqual(Map<String, Object> innerMap1, Map<String, Object> innerMap2) {
|
private boolean areInnerMapsEqual(Map<String, Object> innerMap1, Map<String, Object> innerMap2) {
|
||||||
if (innerMap1.size() != innerMap2.size()) {
|
if (innerMap1.size() != innerMap2.size()) {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (String key : innerMap1.keySet()) {
|
for (String key : innerMap1.keySet()) {
|
||||||
if (!innerMap2.containsKey(key)) {
|
if (!innerMap2.containsKey(key)) {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
Object value1 = innerMap1.get(key);
|
Object value1 = innerMap1.get(key);
|
||||||
Object value2 = innerMap2.get(key);
|
Object value2 = innerMap2.get(key);
|
||||||
|
|
||||||
// 如果值是Map,则递归对比
|
// 如果值是Map,则递归对比
|
||||||
if (value1 instanceof Map && value2 instanceof Map) {
|
if (value1 instanceof Map && value2 instanceof Map) {
|
||||||
if (!areInnerMapsEqual((Map<String, Object>) value1, (Map<String, Object>) value2)) {
|
if (areInnerMapsEqual((Map<String, Object>) value1, (Map<String, Object>) value2)) {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (!value1.equals(value2)) {
|
} else if (!value1.equals(value2)) {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public JSplitPane getSplitPane() {
|
public JSplitPane getSplitPane() {
|
||||||
return splitPane;
|
return splitPane;
|
||||||
}
|
}
|
||||||
@@ -351,7 +414,6 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
return log;
|
return log;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getRowCount() {
|
public int getRowCount() {
|
||||||
return filteredLog.size();
|
return filteredLog.size();
|
||||||
@@ -367,6 +429,7 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
if (filteredLog.isEmpty()) {
|
if (filteredLog.isEmpty()) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageEntry messageEntry = filteredLog.get(rowIndex);
|
MessageEntry messageEntry = filteredLog.get(rowIndex);
|
||||||
|
|
||||||
return switch (columnIndex) {
|
return switch (columnIndex) {
|
||||||
@@ -394,7 +457,7 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public class MessageTable extends JTable {
|
public class MessageTable extends JTable {
|
||||||
private MessageEntry MessageEntry;
|
private MessageEntry messageEntry;
|
||||||
private SwingWorker<Object, Void> currentWorker;
|
private SwingWorker<Object, Void> currentWorker;
|
||||||
// 设置响应报文返回的最大长度
|
// 设置响应报文返回的最大长度
|
||||||
private final int MAX_LENGTH = 5242880;
|
private final int MAX_LENGTH = 5242880;
|
||||||
@@ -411,46 +474,41 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
@Override
|
@Override
|
||||||
public void changeSelection(int row, int col, boolean toggle, boolean extend) {
|
public void changeSelection(int row, int col, boolean toggle, boolean extend) {
|
||||||
super.changeSelection(row, col, toggle, 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..."));
|
requestEditor.setRequest(HttpRequest.httpRequest("Loading..."));
|
||||||
responseEditor.setResponse(HttpResponse.httpResponse("Loading..."));
|
responseEditor.setResponse(HttpResponse.httpResponse("Loading..."));
|
||||||
|
|
||||||
|
if (currentWorker != null && !currentWorker.isDone()) {
|
||||||
|
currentWorker.cancel(true);
|
||||||
|
}
|
||||||
|
|
||||||
if (currentWorker != null && !currentWorker.isDone()) {
|
currentWorker = new SwingWorker<>() {
|
||||||
currentWorker.cancel(true);
|
@Override
|
||||||
}
|
protected Void doInBackground() {
|
||||||
|
int selectedIndex = convertRowIndexToModel(row);
|
||||||
|
if (lastSelectedIndex != selectedIndex) {
|
||||||
|
lastSelectedIndex = selectedIndex;
|
||||||
|
messageEntry = filteredLog.get(selectedIndex);
|
||||||
|
|
||||||
currentWorker = new SwingWorker<>() {
|
HttpRequestResponse httpRequestResponse = messageEntry.getRequestResponse();
|
||||||
@Override
|
|
||||||
protected ByteArray[] doInBackground() {
|
ByteArray requestByte = httpRequestResponse.request().toByteArray();
|
||||||
ByteArray requestByte = MessageEntry.getRequestResponse().request().toByteArray();
|
ByteArray responseByte = httpRequestResponse.response().toByteArray();
|
||||||
ByteArray responseByte = MessageEntry.getRequestResponse().response().toByteArray();
|
|
||||||
|
|
||||||
if (responseByte.length() > MAX_LENGTH) {
|
if (responseByte.length() > MAX_LENGTH) {
|
||||||
String ellipsis = "\r\n......";
|
String ellipsis = "\r\n......";
|
||||||
responseByte = responseByte.subArray(0, MAX_LENGTH).withAppended(ellipsis);
|
responseByte = responseByte.subArray(0, MAX_LENGTH).withAppended(ellipsis);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ByteArray[]{requestByte, responseByte};
|
requestEditor.setRequest(HttpRequest.httpRequest(messageEntry.getRequestResponse().httpService(), requestByte));
|
||||||
|
responseEditor.setResponse(HttpResponse.httpResponse(responseByte));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
return null;
|
||||||
protected void done() {
|
}
|
||||||
if (!isCancelled()) {
|
};
|
||||||
try {
|
currentWorker.execute();
|
||||||
ByteArray[] result = (ByteArray[]) get();
|
|
||||||
requestEditor.setRequest(HttpRequest.httpRequest(MessageEntry.getRequestResponse().httpService(), result[0]));
|
|
||||||
responseEditor.setResponse(HttpResponse.httpResponse(result[1]));
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
currentWorker.execute();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ package hae.component.config;
|
|||||||
|
|
||||||
import burp.api.montoya.MontoyaApi;
|
import burp.api.montoya.MontoyaApi;
|
||||||
import hae.component.rule.Rules;
|
import hae.component.rule.Rules;
|
||||||
import hae.utils.config.ConfigLoader;
|
import hae.utils.ConfigLoader;
|
||||||
import hae.utils.ui.UIEnhancer;
|
import hae.utils.UIEnhancer;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.border.EmptyBorder;
|
import javax.swing.border.EmptyBorder;
|
||||||
@@ -14,6 +14,8 @@ import java.awt.*;
|
|||||||
import java.awt.datatransfer.Clipboard;
|
import java.awt.datatransfer.Clipboard;
|
||||||
import java.awt.datatransfer.DataFlavor;
|
import java.awt.datatransfer.DataFlavor;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.KeyAdapter;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -23,6 +25,8 @@ public class Config extends JPanel {
|
|||||||
private final MontoyaApi api;
|
private final MontoyaApi api;
|
||||||
private final ConfigLoader configLoader;
|
private final ConfigLoader configLoader;
|
||||||
private final Rules rules;
|
private final Rules rules;
|
||||||
|
private JTextField addTextField;
|
||||||
|
private final String defaultText = "Enter a new item";
|
||||||
|
|
||||||
public Config(MontoyaApi api, ConfigLoader configLoader, Rules rules) {
|
public Config(MontoyaApi api, ConfigLoader configLoader, Rules rules) {
|
||||||
this.api = api;
|
this.api = api;
|
||||||
@@ -40,7 +44,7 @@ public class Config extends JPanel {
|
|||||||
constraints.fill = GridBagConstraints.HORIZONTAL;
|
constraints.fill = GridBagConstraints.HORIZONTAL;
|
||||||
|
|
||||||
JPanel ruleInfoPanel = new JPanel(new GridBagLayout());
|
JPanel ruleInfoPanel = new JPanel(new GridBagLayout());
|
||||||
ruleInfoPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
|
ruleInfoPanel.setBorder(new EmptyBorder(10, 15, 5, 15));
|
||||||
|
|
||||||
JLabel ruleLabel = new JLabel("Path:");
|
JLabel ruleLabel = new JLabel("Path:");
|
||||||
JTextField pathTextField = new JTextField();
|
JTextField pathTextField = new JTextField();
|
||||||
@@ -112,7 +116,7 @@ public class Config extends JPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (selected.equals("Block host")) {
|
if (selected.equals("Block host")) {
|
||||||
if (!values.equals(configLoader.getExcludeSuffix()) && !values.isEmpty()) {
|
if (!values.equals(configLoader.getBlockHost()) && !values.isEmpty()) {
|
||||||
configLoader.setBlockHost(values);
|
configLoader.setBlockHost(values);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -131,8 +135,7 @@ public class Config extends JPanel {
|
|||||||
constraints.gridy = 4;
|
constraints.gridy = 4;
|
||||||
buttonPanel.add(clearButton, constraints);
|
buttonPanel.add(clearButton, constraints);
|
||||||
|
|
||||||
JTextField addTextField = new JTextField();
|
addTextField = new JTextField();
|
||||||
String defaultText = "Enter a new item";
|
|
||||||
UIEnhancer.setTextFieldPlaceholder(addTextField, defaultText);
|
UIEnhancer.setTextFieldPlaceholder(addTextField, defaultText);
|
||||||
|
|
||||||
inputPanelB.add(addTextField, BorderLayout.CENTER);
|
inputPanelB.add(addTextField, BorderLayout.CENTER);
|
||||||
@@ -142,13 +145,16 @@ public class Config extends JPanel {
|
|||||||
settingPanel.add(buttonPanel, BorderLayout.EAST);
|
settingPanel.add(buttonPanel, BorderLayout.EAST);
|
||||||
settingPanel.add(inputPanel, BorderLayout.CENTER);
|
settingPanel.add(inputPanel, BorderLayout.CENTER);
|
||||||
|
|
||||||
addButton.addActionListener(e -> {
|
|
||||||
String addTextFieldText = addTextField.getText();
|
addButton.addActionListener(e -> addActionPerformedAction(e, model));
|
||||||
if (!addTextFieldText.equals(defaultText)) {
|
|
||||||
addDataToTable(addTextFieldText, model);
|
addTextField.addKeyListener(new KeyAdapter() {
|
||||||
|
@Override
|
||||||
|
public void keyPressed(KeyEvent e) {
|
||||||
|
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||||
|
addActionPerformedAction(null, model);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
addTextField.setText("");
|
|
||||||
addTextField.requestFocusInWindow();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
pasteButton.addActionListener(e -> {
|
pasteButton.addActionListener(e -> {
|
||||||
@@ -176,7 +182,7 @@ public class Config extends JPanel {
|
|||||||
JLabel settingLabel = new JLabel("Setting:");
|
JLabel settingLabel = new JLabel("Setting:");
|
||||||
JPanel settingLabelPanel = new JPanel(new BorderLayout());
|
JPanel settingLabelPanel = new JPanel(new BorderLayout());
|
||||||
settingLabelPanel.add(settingLabel, BorderLayout.WEST);
|
settingLabelPanel.add(settingLabel, BorderLayout.WEST);
|
||||||
settingMainPanel.setBorder(new EmptyBorder(0, 5, 10, 5));
|
settingMainPanel.setBorder(new EmptyBorder(0, 15, 10, 15));
|
||||||
settingMainPanel.add(settingLabelPanel, BorderLayout.NORTH);
|
settingMainPanel.add(settingLabelPanel, BorderLayout.NORTH);
|
||||||
settingMainPanel.add(settingPanel, BorderLayout.CENTER);
|
settingMainPanel.add(settingPanel, BorderLayout.CENTER);
|
||||||
|
|
||||||
@@ -184,6 +190,7 @@ public class Config extends JPanel {
|
|||||||
add(settingMainPanel, BorderLayout.CENTER);
|
add(settingMainPanel, BorderLayout.CENTER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private String getFirstColumnDataAsString(DefaultTableModel model) {
|
private String getFirstColumnDataAsString(DefaultTableModel model) {
|
||||||
StringBuilder firstColumnData = new StringBuilder();
|
StringBuilder firstColumnData = new StringBuilder();
|
||||||
int numRows = model.getRowCount();
|
int numRows = model.getRowCount();
|
||||||
@@ -232,9 +239,18 @@ public class Config extends JPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addActionPerformedAction(ActionEvent e, DefaultTableModel model) {
|
||||||
|
String addTextFieldText = addTextField.getText();
|
||||||
|
if (!addTextFieldText.equals(defaultText)) {
|
||||||
|
addDataToTable(addTextFieldText, model);
|
||||||
|
}
|
||||||
|
addTextField.setText("");
|
||||||
|
addTextField.requestFocusInWindow();
|
||||||
|
}
|
||||||
|
|
||||||
private void onlineUpdateActionPerformed(ActionEvent e) {
|
private void onlineUpdateActionPerformed(ActionEvent e) {
|
||||||
// 添加提示框防止用户误触导致配置更新
|
// 添加提示框防止用户误触导致配置更新
|
||||||
int retCode = JOptionPane.showConfirmDialog(null, "Do you want to update rules?", "Info", JOptionPane.YES_NO_OPTION);
|
int retCode = JOptionPane.showConfirmDialog(this, "Do you want to update rules?", "Info", JOptionPane.YES_NO_OPTION);
|
||||||
if (retCode == JOptionPane.YES_OPTION) {
|
if (retCode == JOptionPane.YES_OPTION) {
|
||||||
configLoader.initRulesByNet();
|
configLoader.initRulesByNet();
|
||||||
reloadActionPerformed(null);
|
reloadActionPerformed(null);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package hae.component.rule;
|
|||||||
|
|
||||||
import burp.api.montoya.MontoyaApi;
|
import burp.api.montoya.MontoyaApi;
|
||||||
import hae.Config;
|
import hae.Config;
|
||||||
import hae.utils.config.ConfigLoader;
|
import hae.utils.ConfigLoader;
|
||||||
import hae.utils.rule.RuleProcessor;
|
import hae.utils.rule.RuleProcessor;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
@@ -97,7 +97,7 @@ public class Rule extends JPanel {
|
|||||||
Display ruleDisplay = new Display();
|
Display ruleDisplay = new Display();
|
||||||
ruleDisplay.formatTextField.setText("{0}");
|
ruleDisplay.formatTextField.setText("{0}");
|
||||||
|
|
||||||
int showState = JOptionPane.showConfirmDialog(null, ruleDisplay, "Add Rule", JOptionPane.OK_OPTION);
|
int showState = JOptionPane.showConfirmDialog(this, ruleDisplay, "Add Rule", JOptionPane.OK_OPTION);
|
||||||
if (showState == YES_OPTION) {
|
if (showState == YES_OPTION) {
|
||||||
Vector<Object> ruleData = new Vector<>();
|
Vector<Object> ruleData = new Vector<>();
|
||||||
ruleData.add(false);
|
ruleData.add(false);
|
||||||
@@ -132,7 +132,7 @@ public class Rule extends JPanel {
|
|||||||
|
|
||||||
ruleDisplay.formatTextField.setEnabled(ruleDisplay.engineComboBox.getSelectedItem().toString().equals("nfa"));
|
ruleDisplay.formatTextField.setEnabled(ruleDisplay.engineComboBox.getSelectedItem().toString().equals("nfa"));
|
||||||
|
|
||||||
int showState = JOptionPane.showConfirmDialog(null, ruleDisplay, "Edit Rule", JOptionPane.OK_OPTION);
|
int showState = JOptionPane.showConfirmDialog(this, ruleDisplay, "Edit Rule", JOptionPane.OK_OPTION);
|
||||||
if (showState == 0) {
|
if (showState == 0) {
|
||||||
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
|
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
|
||||||
model.setValueAt(ruleDisplay.ruleNameTextField.getText(), select, 1);
|
model.setValueAt(ruleDisplay.ruleNameTextField.getText(), select, 1);
|
||||||
@@ -151,7 +151,7 @@ public class Rule extends JPanel {
|
|||||||
|
|
||||||
private void ruleRemoveActionPerformed(ActionEvent e, JTable ruleTable, JTabbedPane tabbedPane) {
|
private void ruleRemoveActionPerformed(ActionEvent e, JTable ruleTable, JTabbedPane tabbedPane) {
|
||||||
if (ruleTable.getSelectedRowCount() >= 1) {
|
if (ruleTable.getSelectedRowCount() >= 1) {
|
||||||
if (JOptionPane.showConfirmDialog(null, "Are you sure you want to delete this rule?", "Info", JOptionPane.OK_OPTION) == 0) {
|
if (JOptionPane.showConfirmDialog(this, "Are you sure you want to remove this rule?", "Info", JOptionPane.YES_NO_OPTION) == 0) {
|
||||||
DefaultTableModel model = (DefaultTableModel) ruleTable.getModel();
|
DefaultTableModel model = (DefaultTableModel) ruleTable.getModel();
|
||||||
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
|
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package hae.component.rule;
|
|||||||
|
|
||||||
import burp.api.montoya.MontoyaApi;
|
import burp.api.montoya.MontoyaApi;
|
||||||
import hae.Config;
|
import hae.Config;
|
||||||
import hae.utils.config.ConfigLoader;
|
import hae.utils.ConfigLoader;
|
||||||
import hae.utils.rule.RuleProcessor;
|
import hae.utils.rule.RuleProcessor;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
@@ -109,7 +109,7 @@ public class Rules extends JTabbedPane {
|
|||||||
|
|
||||||
private void deleteRuleGroupActionPerformed(ActionEvent e) {
|
private void deleteRuleGroupActionPerformed(ActionEvent e) {
|
||||||
if (getTabCount() > 2) {
|
if (getTabCount() > 2) {
|
||||||
int retCode = JOptionPane.showConfirmDialog(null, "Do you want to delete this rule group?", "Info",
|
int retCode = JOptionPane.showConfirmDialog(this, "Do you want to delete this rule group?", "Info",
|
||||||
JOptionPane.YES_NO_OPTION);
|
JOptionPane.YES_NO_OPTION);
|
||||||
if (retCode == JOptionPane.YES_OPTION) {
|
if (retCode == JOptionPane.YES_OPTION) {
|
||||||
String title = getTitleAt(getSelectedIndex());
|
String title = getTitleAt(getSelectedIndex());
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import burp.api.montoya.ui.editor.extension.ExtensionProvidedHttpRequestEditor;
|
|||||||
import burp.api.montoya.ui.editor.extension.HttpRequestEditorProvider;
|
import burp.api.montoya.ui.editor.extension.HttpRequestEditorProvider;
|
||||||
import hae.component.board.Datatable;
|
import hae.component.board.Datatable;
|
||||||
import hae.instances.http.utils.MessageProcessor;
|
import hae.instances.http.utils.MessageProcessor;
|
||||||
import hae.utils.config.ConfigLoader;
|
import hae.utils.ConfigLoader;
|
||||||
import hae.utils.string.StringProcessor;
|
import hae.utils.string.StringProcessor;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
@@ -122,7 +122,9 @@ public class RequestEditor implements HttpRequestEditorProvider {
|
|||||||
boolean isBlockHost = false;
|
boolean isBlockHost = false;
|
||||||
for (String hostName : hostList) {
|
for (String hostName : hostList) {
|
||||||
String cleanedHost = StringProcessor.replaceFirstOccurrence(hostName, "*.", "");
|
String cleanedHost = StringProcessor.replaceFirstOccurrence(hostName, "*.", "");
|
||||||
if (StringProcessor.matchFromEnd(host, cleanedHost)) {
|
if (hostName.contains("*.") && StringProcessor.matchFromEnd(host, cleanedHost)) {
|
||||||
|
isBlockHost = true;
|
||||||
|
} else if (host.equals(hostName) || hostName.equals("*")) {
|
||||||
isBlockHost = true;
|
isBlockHost = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import burp.api.montoya.ui.editor.extension.ExtensionProvidedHttpResponseEditor;
|
|||||||
import burp.api.montoya.ui.editor.extension.HttpResponseEditorProvider;
|
import burp.api.montoya.ui.editor.extension.HttpResponseEditorProvider;
|
||||||
import hae.component.board.Datatable;
|
import hae.component.board.Datatable;
|
||||||
import hae.instances.http.utils.MessageProcessor;
|
import hae.instances.http.utils.MessageProcessor;
|
||||||
import hae.utils.config.ConfigLoader;
|
import hae.utils.ConfigLoader;
|
||||||
import hae.utils.string.StringProcessor;
|
import hae.utils.string.StringProcessor;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import burp.api.montoya.http.message.requests.HttpRequest;
|
|||||||
import hae.component.board.message.MessageTableModel;
|
import hae.component.board.message.MessageTableModel;
|
||||||
import hae.instances.editor.RequestEditor;
|
import hae.instances.editor.RequestEditor;
|
||||||
import hae.instances.http.utils.MessageProcessor;
|
import hae.instances.http.utils.MessageProcessor;
|
||||||
import hae.utils.config.ConfigLoader;
|
import hae.utils.ConfigLoader;
|
||||||
import hae.utils.string.StringProcessor;
|
import hae.utils.string.StringProcessor;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -80,7 +80,12 @@ public class HttpMessageHandler implements HttpHandler {
|
|||||||
HttpRequestResponse httpRequestResponse = HttpRequestResponse.httpRequestResponse(httpRequest.get(), httpResponseReceived);
|
HttpRequestResponse httpRequestResponse = HttpRequestResponse.httpRequestResponse(httpRequest.get(), httpResponseReceived);
|
||||||
|
|
||||||
// 添加到Databoard
|
// 添加到Databoard
|
||||||
messageTableModel.add(httpRequestResponse, comment, color);
|
String method = httpRequest.get().method();
|
||||||
|
String url = httpRequest.get().url();
|
||||||
|
String status = String.valueOf(httpResponseReceived.statusCode());
|
||||||
|
String length = String.valueOf(httpResponseReceived.toByteArray().length());
|
||||||
|
|
||||||
|
messageTableModel.add(httpRequestResponse, url, method, status, length, comment, color, "", "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -90,43 +90,15 @@ public class RegularMatcher {
|
|||||||
result.clear();
|
result.clear();
|
||||||
result.addAll(tmpList);
|
result.addAll(tmpList);
|
||||||
|
|
||||||
String nameAndSize = String.format("%s (%s)", name, result.size());
|
|
||||||
if (!result.isEmpty()) {
|
if (!result.isEmpty()) {
|
||||||
tmpMap.put("color", color);
|
tmpMap.put("color", color);
|
||||||
String dataStr = String.join("\n", result);
|
String dataStr = String.join("\n", result);
|
||||||
tmpMap.put("data", dataStr);
|
tmpMap.put("data", dataStr);
|
||||||
|
|
||||||
|
String nameAndSize = String.format("%s (%s)", name, result.size());
|
||||||
finalMap.put(nameAndSize, tmpMap);
|
finalMap.put(nameAndSize, tmpMap);
|
||||||
// 添加到全局变量中,便于Databoard检索
|
|
||||||
if (!Objects.equals(host, "") && host != null) {
|
|
||||||
List<String> dataList = Arrays.asList(dataStr.split("\n"));
|
|
||||||
|
|
||||||
Config.globalDataMap.compute(host, (existingHost, existingMap) -> {
|
putDataToGlobalMap(host, name, result);
|
||||||
Map<String, List<String>> gRuleMap = Optional.ofNullable(existingMap).orElse(new ConcurrentHashMap<>());
|
|
||||||
|
|
||||||
gRuleMap.merge(name, new ArrayList<>(dataList), (existingList, newList) -> {
|
|
||||||
Set<String> combinedSet = new LinkedHashSet<>(existingList);
|
|
||||||
combinedSet.addAll(newList);
|
|
||||||
return new ArrayList<>(combinedSet);
|
|
||||||
});
|
|
||||||
|
|
||||||
return gRuleMap;
|
|
||||||
});
|
|
||||||
|
|
||||||
String[] splitHost = host.split("\\.");
|
|
||||||
String onlyHost = host.split(":")[0];
|
|
||||||
|
|
||||||
String anyHost = (splitHost.length > 2 && !onlyHost.matches("\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b")) ? StringProcessor.replaceFirstOccurrence(onlyHost, splitHost[0], "*") : "";
|
|
||||||
|
|
||||||
if (!Config.globalDataMap.containsKey(anyHost) && anyHost.length() > 0) {
|
|
||||||
// 添加通配符Host,实际数据从查询哪里将所有数据提取
|
|
||||||
Config.globalDataMap.put(anyHost, new HashMap<>());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Config.globalDataMap.containsKey("*")) {
|
|
||||||
// 添加通配符全匹配,同上
|
|
||||||
Config.globalDataMap.put("*", new HashMap<>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,6 +108,38 @@ public class RegularMatcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void putDataToGlobalMap(String host, String name, List<String> dataList) {
|
||||||
|
// 添加到全局变量中,便于Databoard检索
|
||||||
|
if (!Objects.equals(host, "") && host != null) {
|
||||||
|
Config.globalDataMap.compute(host, (existingHost, existingMap) -> {
|
||||||
|
Map<String, List<String>> gRuleMap = Optional.ofNullable(existingMap).orElse(new ConcurrentHashMap<>());
|
||||||
|
|
||||||
|
gRuleMap.merge(name, new ArrayList<>(dataList), (existingList, newList) -> {
|
||||||
|
Set<String> combinedSet = new LinkedHashSet<>(existingList);
|
||||||
|
combinedSet.addAll(newList);
|
||||||
|
return new ArrayList<>(combinedSet);
|
||||||
|
});
|
||||||
|
|
||||||
|
return gRuleMap;
|
||||||
|
});
|
||||||
|
|
||||||
|
String[] splitHost = host.split("\\.");
|
||||||
|
String onlyHost = host.split(":")[0];
|
||||||
|
|
||||||
|
String anyHost = (splitHost.length > 2 && !StringProcessor.matchHostIsIp(onlyHost)) ? StringProcessor.replaceFirstOccurrence(onlyHost, splitHost[0], "*") : "";
|
||||||
|
|
||||||
|
if (!Config.globalDataMap.containsKey(anyHost) && anyHost.length() > 0) {
|
||||||
|
// 添加通配符Host,实际数据从查询哪里将所有数据提取
|
||||||
|
Config.globalDataMap.put(anyHost, new HashMap<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Config.globalDataMap.containsKey("*")) {
|
||||||
|
// 添加通配符全匹配,同上
|
||||||
|
Config.globalDataMap.put("*", new HashMap<>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private List<String> matchByRegex(String f_regex, String s_regex, String content, String format, String engine, boolean sensitive) {
|
private List<String> matchByRegex(String f_regex, String s_regex, String content, String format, String engine, boolean sensitive) {
|
||||||
List<String> retList = new ArrayList<>();
|
List<String> retList = new ArrayList<>();
|
||||||
if ("nfa".equals(engine)) {
|
if ("nfa".equals(engine)) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package hae.utils.config;
|
package hae.utils;
|
||||||
|
|
||||||
import burp.api.montoya.MontoyaApi;
|
import burp.api.montoya.MontoyaApi;
|
||||||
import burp.api.montoya.http.RequestOptions;
|
import burp.api.montoya.http.RequestOptions;
|
||||||
@@ -178,7 +178,7 @@ public class ConfigLoader {
|
|||||||
|
|
||||||
try (InputStream in = Files.newInputStream(path)) {
|
try (InputStream in = Files.newInputStream(path)) {
|
||||||
return yaml.load(in);
|
return yaml.load(in);
|
||||||
} catch (IOException e) {
|
} catch (Exception e) {
|
||||||
return new LinkedHashMap<>(); // 读取失败时也返回空的Map
|
return new LinkedHashMap<>(); // 读取失败时也返回空的Map
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -189,7 +189,7 @@ public class ConfigLoader {
|
|||||||
|
|
||||||
try (Writer ws = new OutputStreamWriter(Files.newOutputStream(Paths.get(configFilePath)), StandardCharsets.UTF_8)) {
|
try (Writer ws = new OutputStreamWriter(Files.newOutputStream(Paths.get(configFilePath)), StandardCharsets.UTF_8)) {
|
||||||
yaml.dump(currentConfig, ws);
|
yaml.dump(currentConfig, ws);
|
||||||
} catch (IOException ignored) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,7 +199,7 @@ public class ConfigLoader {
|
|||||||
|
|
||||||
try (Writer ws = new OutputStreamWriter(Files.newOutputStream(Paths.get(configFilePath)), StandardCharsets.UTF_8)) {
|
try (Writer ws = new OutputStreamWriter(Files.newOutputStream(Paths.get(configFilePath)), StandardCharsets.UTF_8)) {
|
||||||
yaml.dump(currentConfig, ws);
|
yaml.dump(currentConfig, ws);
|
||||||
} catch (IOException ignored) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,7 +225,7 @@ public class ConfigLoader {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} catch (IOException ignored) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package hae.utils.ui;
|
package hae.utils;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
47
src/main/java/hae/utils/project/FileProcessor.java
Normal file
47
src/main/java/hae/utils/project/FileProcessor.java
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package hae.utils.project;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.file.DirectoryStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
public class FileProcessor {
|
||||||
|
public static void deleteDirectoryWithContents(Path pathToBeDeleted) {
|
||||||
|
if (pathToBeDeleted != null) {
|
||||||
|
try {
|
||||||
|
Files.walk(pathToBeDeleted)
|
||||||
|
.sorted(Comparator.reverseOrder())
|
||||||
|
.map(Path::toFile)
|
||||||
|
.forEach(File::delete);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] readFileContent(String basePath, String fileName) {
|
||||||
|
Path filePath = Paths.get(basePath, fileName);
|
||||||
|
Path path = Paths.get(basePath);
|
||||||
|
try {
|
||||||
|
byte[] fileContent = Files.readAllBytes(filePath);
|
||||||
|
|
||||||
|
Files.deleteIfExists(filePath);
|
||||||
|
|
||||||
|
boolean isEmpty = isDirectoryEmpty(path);
|
||||||
|
if (isEmpty) {
|
||||||
|
Files.deleteIfExists(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileContent;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isDirectoryEmpty(Path directory) throws Exception {
|
||||||
|
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(directory)) {
|
||||||
|
return !dirStream.iterator().hasNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
194
src/main/java/hae/utils/project/ProjectProcessor.java
Normal file
194
src/main/java/hae/utils/project/ProjectProcessor.java
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
package hae.utils.project;
|
||||||
|
|
||||||
|
import burp.api.montoya.MontoyaApi;
|
||||||
|
import hae.utils.project.model.HaeFileContent;
|
||||||
|
import org.yaml.snakeyaml.LoaderOptions;
|
||||||
|
import org.yaml.snakeyaml.Yaml;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipFile;
|
||||||
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
|
public class ProjectProcessor {
|
||||||
|
private final MontoyaApi api;
|
||||||
|
|
||||||
|
public ProjectProcessor(MontoyaApi api) {
|
||||||
|
this.api = api;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean createHaeFile(String haeFilePath, String host, Map<String, List<String>> dataMap, Map<String, Map<String, Object>> urlMap, Map<String, Map<String, Object>> httpMap) {
|
||||||
|
ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
|
||||||
|
List<Future<?>> futures = new ArrayList<>();
|
||||||
|
|
||||||
|
ByteArrayOutputStream dataYamlStream = new ByteArrayOutputStream();
|
||||||
|
ByteArrayOutputStream urlYamlStream = new ByteArrayOutputStream();
|
||||||
|
Yaml yaml = new Yaml();
|
||||||
|
|
||||||
|
yaml.dump(dataMap, new OutputStreamWriter(dataYamlStream, StandardCharsets.UTF_8));
|
||||||
|
yaml.dump(urlMap, new OutputStreamWriter(urlYamlStream, StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
try (ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(haeFilePath))) {
|
||||||
|
zipOut.putNextEntry(new ZipEntry("info"));
|
||||||
|
zipOut.write(host.getBytes(StandardCharsets.UTF_8));
|
||||||
|
zipOut.closeEntry();
|
||||||
|
|
||||||
|
zipOut.putNextEntry(new ZipEntry("data"));
|
||||||
|
zipOut.write(dataYamlStream.toByteArray());
|
||||||
|
zipOut.closeEntry();
|
||||||
|
|
||||||
|
zipOut.putNextEntry(new ZipEntry("url"));
|
||||||
|
zipOut.write(urlYamlStream.toByteArray());
|
||||||
|
zipOut.closeEntry();
|
||||||
|
|
||||||
|
for (String httpHash : httpMap.keySet()) {
|
||||||
|
Map<String, Object> httpItem = httpMap.get(httpHash);
|
||||||
|
futures.add(executorService.submit(() -> {
|
||||||
|
try {
|
||||||
|
ByteArrayOutputStream httpOutStream = new ByteArrayOutputStream();
|
||||||
|
byte[] request = (byte[]) httpItem.get("request");
|
||||||
|
byte[] response = (byte[]) httpItem.get("response");
|
||||||
|
|
||||||
|
httpOutStream.write(response);
|
||||||
|
httpOutStream.write(request);
|
||||||
|
|
||||||
|
synchronized (zipOut) {
|
||||||
|
zipOut.putNextEntry(new ZipEntry(String.format("http/%s", httpHash)));
|
||||||
|
zipOut.write(httpOutStream.toByteArray());
|
||||||
|
zipOut.closeEntry();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
api.logging().logToError("createHaeFile: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Future<?> future : futures) {
|
||||||
|
try {
|
||||||
|
future.get();
|
||||||
|
} catch (InterruptedException | ExecutionException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
api.logging().logToError("createHaeFile: " + e.getMessage());
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
executorService.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HaeFileContent readHaeFile(String haeFilePath) {
|
||||||
|
ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
|
||||||
|
List<Future<?>> futures = new ArrayList<>();
|
||||||
|
|
||||||
|
HaeFileContent haeFileContent = new HaeFileContent(api);
|
||||||
|
LoaderOptions loaderOptions = new LoaderOptions();
|
||||||
|
loaderOptions.setMaxAliasesForCollections(Integer.MAX_VALUE);
|
||||||
|
loaderOptions.setCodePointLimit(Integer.MAX_VALUE);
|
||||||
|
Yaml yaml = new Yaml(loaderOptions);
|
||||||
|
Path tempDirectory = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (hasValidStructure(haeFilePath)) {
|
||||||
|
tempDirectory = Files.createTempDirectory("hae");
|
||||||
|
haeFileContent.setHttpPath(tempDirectory.toString());
|
||||||
|
|
||||||
|
try (ZipFile zipFile = new ZipFile(haeFilePath)) {
|
||||||
|
Enumeration<? extends ZipEntry> entries = zipFile.entries();
|
||||||
|
while (entries.hasMoreElements()) {
|
||||||
|
ZipEntry entry = entries.nextElement();
|
||||||
|
String fileName = entry.getName();
|
||||||
|
if (fileName.startsWith("http/")) {
|
||||||
|
Path filePath = tempDirectory.resolve(fileName.substring("http/".length()));
|
||||||
|
futures.add(executorService.submit(() -> {
|
||||||
|
try (InputStream in = zipFile.getInputStream(entry)) {
|
||||||
|
Files.copy(in, filePath, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
} catch (IOException e) {
|
||||||
|
api.logging().logToError("readHaeFile: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
try (InputStream in = zipFile.getInputStream(entry)) {
|
||||||
|
switch (fileName) {
|
||||||
|
case "info" ->
|
||||||
|
haeFileContent.setHost(new String(in.readAllBytes(), StandardCharsets.UTF_8));
|
||||||
|
case "data" ->
|
||||||
|
haeFileContent.setDataMap(yaml.load(new InputStreamReader(in, StandardCharsets.UTF_8)));
|
||||||
|
case "url" ->
|
||||||
|
haeFileContent.setUrlMap(yaml.load(new InputStreamReader(in, StandardCharsets.UTF_8)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Future<?> future : futures) {
|
||||||
|
try {
|
||||||
|
future.get();
|
||||||
|
} catch (InterruptedException | ExecutionException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
api.logging().logToError("readHaeFile: " + e.getMessage());
|
||||||
|
if (tempDirectory != null) {
|
||||||
|
FileProcessor.deleteDirectoryWithContents(tempDirectory);
|
||||||
|
}
|
||||||
|
haeFileContent = null;
|
||||||
|
} finally {
|
||||||
|
executorService.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
return haeFileContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasValidStructure(String zipFilePath) {
|
||||||
|
Set<String> requiredRootEntries = new HashSet<>();
|
||||||
|
requiredRootEntries.add("info");
|
||||||
|
requiredRootEntries.add("data");
|
||||||
|
requiredRootEntries.add("url");
|
||||||
|
|
||||||
|
boolean hasHttpDirectoryWithFiles = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
ZipFile zipFile = new ZipFile(zipFilePath);
|
||||||
|
Enumeration<? extends ZipEntry> entries = zipFile.entries();
|
||||||
|
|
||||||
|
while (entries.hasMoreElements()) {
|
||||||
|
ZipEntry entry = entries.nextElement();
|
||||||
|
String name = entry.getName();
|
||||||
|
|
||||||
|
if (!entry.isDirectory() && !name.contains("/")) {
|
||||||
|
requiredRootEntries.remove(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.startsWith("http/") && !entry.isDirectory()) {
|
||||||
|
hasHttpDirectoryWithFiles = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requiredRootEntries.isEmpty() && hasHttpDirectoryWithFiles) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
zipFile.close();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return requiredRootEntries.isEmpty() && hasHttpDirectoryWithFiles;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
76
src/main/java/hae/utils/project/model/HaeFileContent.java
Normal file
76
src/main/java/hae/utils/project/model/HaeFileContent.java
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package hae.utils.project.model;
|
||||||
|
|
||||||
|
import burp.api.montoya.MontoyaApi;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class HaeFileContent {
|
||||||
|
private final MontoyaApi api;
|
||||||
|
private String host;
|
||||||
|
private String httpPath;
|
||||||
|
private final Map<String, List<String>> dataMap;
|
||||||
|
private final Map<String, Map<String, String>> urlMap;
|
||||||
|
|
||||||
|
public HaeFileContent(MontoyaApi api) {
|
||||||
|
this.api = api;
|
||||||
|
this.dataMap = new HashMap<>();
|
||||||
|
this.urlMap = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHost() {
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, List<String>> getDataMap() {
|
||||||
|
return dataMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Map<String, String>> getUrlMap() {
|
||||||
|
return urlMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHttpPath() {
|
||||||
|
return httpPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHost(String host) {
|
||||||
|
this.host = host;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHttpPath(String path) {
|
||||||
|
this.httpPath = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDataMap(Map<String, List<Object>> dataMap) {
|
||||||
|
for (Map.Entry<String, List<Object>> entry : dataMap.entrySet()) {
|
||||||
|
List<String> values = new ArrayList<>();
|
||||||
|
for (Object value : entry.getValue()) {
|
||||||
|
try {
|
||||||
|
values.add(new String((byte[]) value, StandardCharsets.UTF_8));
|
||||||
|
} catch (Exception e) {
|
||||||
|
values.add(value.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.dataMap.put(entry.getKey(), values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUrlMap(Map<String, Map<String, Object>> urlMap) {
|
||||||
|
for (Map.Entry<String, Map<String, Object>> entry : urlMap.entrySet()) {
|
||||||
|
Map<String, String> newValues = new HashMap<>();
|
||||||
|
Map<String, Object> values = entry.getValue();
|
||||||
|
for (String key : values.keySet()) {
|
||||||
|
try {
|
||||||
|
newValues.put(key, new String((byte[]) values.get(key), StandardCharsets.UTF_8));
|
||||||
|
} catch (Exception e) {
|
||||||
|
newValues.put(key, values.get(key).toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.urlMap.put(entry.getKey(), newValues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ package hae.utils.rule;
|
|||||||
|
|
||||||
import burp.api.montoya.MontoyaApi;
|
import burp.api.montoya.MontoyaApi;
|
||||||
import hae.Config;
|
import hae.Config;
|
||||||
import hae.utils.config.ConfigLoader;
|
import hae.utils.ConfigLoader;
|
||||||
import hae.utils.rule.model.Group;
|
import hae.utils.rule.model.Group;
|
||||||
import hae.utils.rule.model.Info;
|
import hae.utils.rule.model.Info;
|
||||||
import org.yaml.snakeyaml.DumperOptions;
|
import org.yaml.snakeyaml.DumperOptions;
|
||||||
|
|||||||
@@ -1,8 +1,17 @@
|
|||||||
package hae.utils.string;
|
package hae.utils.string;
|
||||||
|
|
||||||
|
import burp.api.montoya.core.ByteArray;
|
||||||
|
import burp.api.montoya.http.HttpService;
|
||||||
|
import burp.api.montoya.http.message.HttpRequestResponse;
|
||||||
|
import burp.api.montoya.http.message.requests.HttpRequest;
|
||||||
|
import burp.api.montoya.http.message.responses.HttpResponse;
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
public class StringProcessor {
|
public class StringProcessor {
|
||||||
public static String replaceFirstOccurrence(String original, String find, String replace) {
|
public static String replaceFirstOccurrence(String original, String find, String replace) {
|
||||||
@@ -32,6 +41,47 @@ public class StringProcessor {
|
|||||||
return patternIndex == -1;
|
return patternIndex == -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String extractHostname(String hostWithPort) {
|
||||||
|
if (hostWithPort == null || hostWithPort.isEmpty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
int colonIndex = hostWithPort.indexOf(":");
|
||||||
|
if (colonIndex != -1) {
|
||||||
|
return hostWithPort.substring(0, colonIndex);
|
||||||
|
} else {
|
||||||
|
return hostWithPort;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean matchesHostPattern(String host, String selectedHost) {
|
||||||
|
String hostname = StringProcessor.extractHostname(host);
|
||||||
|
String hostPattern = selectedHost.replace("*.", "");
|
||||||
|
boolean matchesDirectly = selectedHost.equals("*") || host.equals(selectedHost);
|
||||||
|
boolean matchesPattern = !host.contains("*") &&
|
||||||
|
(hostPattern.equals(selectedHost) ?
|
||||||
|
StringProcessor.matchFromEnd(host, hostPattern) :
|
||||||
|
StringProcessor.matchFromEnd(hostname, hostPattern));
|
||||||
|
return matchesDirectly || matchesPattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HttpRequestResponse createHttpRequestResponse(String url, byte[] request, byte[] response) {
|
||||||
|
HttpService httpService = HttpService.httpService(url);
|
||||||
|
HttpRequest httpRequest = HttpRequest.httpRequest(httpService, ByteArray.byteArray(request));
|
||||||
|
HttpResponse httpResponse = HttpResponse.httpResponse(ByteArray.byteArray(response));
|
||||||
|
return HttpRequestResponse.httpRequestResponse(httpRequest, httpResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getCurrentTime() {
|
||||||
|
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss");
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
return now.format(formatter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getRandomUUID() {
|
||||||
|
UUID uuid = UUID.randomUUID();
|
||||||
|
return uuid.toString();
|
||||||
|
}
|
||||||
|
|
||||||
public static String mergeComment(String comment) {
|
public static String mergeComment(String comment) {
|
||||||
if (!comment.contains(",")) {
|
if (!comment.contains(",")) {
|
||||||
return comment;
|
return comment;
|
||||||
@@ -69,6 +119,21 @@ public class StringProcessor {
|
|||||||
return host;
|
return host;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getBaseDomain(String host) {
|
||||||
|
int lastIndex = host.lastIndexOf('.');
|
||||||
|
if (lastIndex > 0) {
|
||||||
|
int secondLastIndex = host.substring(0, lastIndex).lastIndexOf('.');
|
||||||
|
if (secondLastIndex >= 0) {
|
||||||
|
return host.substring(secondLastIndex + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean matchHostIsIp(String host) {
|
||||||
|
return host.matches("\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b");
|
||||||
|
}
|
||||||
|
|
||||||
private static Map<String, Integer> getStringIntegerMap(String comment) {
|
private static Map<String, Integer> getStringIntegerMap(String comment) {
|
||||||
Map<String, Integer> itemCounts = new HashMap<>();
|
Map<String, Integer> itemCounts = new HashMap<>();
|
||||||
String[] items = comment.split(", ");
|
String[] items = comment.split(", ");
|
||||||
|
|||||||
Reference in New Issue
Block a user