FlowAnalyzer 0.1.4__tar.gz → 0.1.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,179 @@
1
+ import os
2
+ import re
3
+ import json
4
+ import gzip
5
+ import contextlib
6
+ import subprocess
7
+ from typing import Tuple
8
+
9
+
10
+ class FlowAnalyzer:
11
+ """FlowAnalyzer是一个流量分析器,用于解析和处理tshark导出的JSON数据文件"""
12
+
13
+ def __init__(self, jsonPath: str):
14
+ """初始化FlowAnalyzer对象
15
+
16
+ Parameters
17
+ ----------
18
+ jsonPath : str
19
+ tshark导出的JSON文件路径
20
+ """
21
+ self.jsonPath = jsonPath
22
+ self.check_json_file()
23
+
24
+ def check_json_file(self):
25
+ # sourcery skip: remove-redundant-fstring, replace-interpolation-with-fstring
26
+ """检查JSON文件是否存在并非空
27
+
28
+ Raises
29
+ ------
30
+ FileNotFoundError
31
+ 当JSON文件不存在时抛出异常
32
+ ValueError
33
+ 当JSON文件内容为空时抛出异常
34
+ """
35
+ if not os.path.exists(self.jsonPath):
36
+ raise FileNotFoundError(
37
+ f"您的tshark导出的JSON文件没有找到!JSON路径:%s" % self.jsonPath)
38
+
39
+ if os.path.getsize(self.jsonPath) == 0:
40
+ raise ValueError("您的tshark导出的JSON文件内容为空!JSON路径:%s" % self.jsonPath)
41
+
42
+ def parse_http_json(self) -> Tuple[dict, list]:
43
+ # sourcery skip: use-named-expression
44
+ """解析JSON数据文件中的HTTP请求和响应信息
45
+
46
+ Returns
47
+ -------
48
+ tuple
49
+ 包含请求字典和响应列表的元组
50
+ """
51
+ with open(self.jsonPath, "r") as f:
52
+ data = json.load(f)
53
+
54
+ request, response = {}, []
55
+ for packet in data:
56
+ packet = packet["_source"]["layers"]
57
+ time_epoch = float(packet["frame.time_epoch"][0]) if packet.get("frame.time_epoch") else None
58
+ full_request = packet["tcp.reassembled.data"][0] if packet.get("tcp.reassembled.data") else packet["tcp.payload"][0]
59
+ response_num = int(packet["frame.number"][0]) if packet.get("frame.number") else None
60
+ request_num = int(packet["http.request_in"][0]) if packet.get("http.request_in") else None
61
+
62
+ if request_num:
63
+ response.append({"response_num": response_num, "request_in": request_num, "full_request": full_request, "time_epoch": time_epoch})
64
+ else:
65
+ request[response_num] = {"full_request": full_request, "time_epoch": time_epoch}
66
+ return request, response
67
+
68
+ def generate_http_dict_pairs(self, preserve_http_headers=False, preserve_start_time=False):
69
+ """生成HTTP请求和响应信息的字典对
70
+
71
+ Parameters
72
+ ----------
73
+ preserve_http_headers : bool, optional
74
+ 指是否保留HTTP除了请求体/返回体以外的所有HTTP头部信息,默认为False,只会返回HTTP请求体或者返回体
75
+
76
+ 如下结构:
77
+ 请求行/返回行:
78
+ 请求行:指的是HTTP请求中的第一行,包含了HTTP方法、请求的资源路径和HTTP协议版本。例如:GET /index.html HTTP/1.1
79
+ 返回行:指的是HTTP响应中的第一行,包含了HTTP协议版本、响应状态码和相应的状态描述。例如:HTTP/1.1 200 OK
80
+ 请求头/返回头:
81
+ 请求头:包含了HTTP请求的相关信息,以键值对的形式出现,每个键值对占据一行。常见的请求头包括Host、User-Agent、Content-Type等。例如:Host: example.com
82
+ 返回头:包含了HTTP响应的相关信息,也是以键值对的形式出现,每个键值对占据一行。常见的返回头包括Content-Type、Content-Length、Server等。例如:Content-Type: text/html
83
+ 空行:用于分隔请求头和请求体,由两个连续的回车换行符组成
84
+ 请求体/返回体:
85
+ 请求体:包含了HTTP请求中的实际数据部分,通常在POST请求中使用。请求体可以是表单数据、JSON数据等,格式取决于请求头中的Content-Type字段
86
+ 返回体:包含了HTTP响应中的实际数据部分,通常是服务器返回给客户端的内容。返回体的格式和内容取决于具体的请求和服务器的处理逻辑
87
+
88
+ preserve_start_time : bool, optional
89
+ 指是保留HTTP请求的时间,指的是开始时间
90
+
91
+ Yields
92
+ ------
93
+ Iterator[dict]
94
+ 包含请求和响应信息的字典迭代器
95
+ """
96
+ request, response = self.parse_http_json()
97
+ for resp in response:
98
+ frame_num = resp["response_num"]
99
+ request_num = resp["request_in"]
100
+ requ = request.get(request_num)
101
+
102
+ dic = {"response": [frame_num, self.extract_http_file_data(resp['full_request'], preserve_http_headers)]}
103
+ dic["request"] = [request_num, self.extract_http_file_data(requ["full_request"], preserve_http_headers)] if requ else None
104
+
105
+ if preserve_start_time:
106
+ dic["response"].append(resp["time_epoch"])
107
+ dic["request"].append(requ["time_epoch"]) if requ else None
108
+ yield dic
109
+
110
+ @staticmethod
111
+ def get_json_data(filePath, display_filter):
112
+ """获取JSON数据并保存至文件,保存目录是当前工作目录,也就是您运行脚本所在目录
113
+
114
+ Parameters
115
+ ----------
116
+ filePath : str
117
+ 待处理的数据文件路径
118
+ display_filter : str
119
+ Tshark的过滤器表达式
120
+
121
+ Returns
122
+ -------
123
+ str
124
+ 保存JSON数据的文件路径
125
+ """
126
+ # sourcery skip: use-fstring-for-formatting
127
+ # jsonPath = os.path.join(os.path.dirname(filePath), "output.json")
128
+ jsonPath = os.path.join(os.getcwd(), "output.json")
129
+ command = 'tshark -r {} -Y "{}" -T json -e http.request_in -e tcp.reassembled.data -e frame.number -e tcp.payload -e frame.time_epoch > {}'.format(
130
+ filePath, display_filter, jsonPath)
131
+ proc = subprocess.Popen(command, shell=True,
132
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
133
+ proc.communicate()
134
+ return jsonPath
135
+
136
+ @staticmethod
137
+ def extract_http_file_data(full_request, preserve_http_headers=False):
138
+ # sourcery skip: merge-else-if-into-elif, swap-if-else-branches
139
+ """提取HTTP请求或响应中的文件数据
140
+
141
+ Parameters
142
+ ----------
143
+ full_request : str
144
+ HTTP请求或响应的消息体
145
+ preserve_http_headers : bool, optional
146
+ 指是否保留HTTP除了请求体/返回体以外的所有HTTP头部信息,默认为False,只会返回HTTP请求体或者返回体
147
+
148
+ 如下结构:
149
+ 请求行/返回行:
150
+ 请求行:指的是HTTP请求中的第一行,包含了HTTP方法、请求的资源路径和HTTP协议版本。例如:GET /index.html HTTP/1.1
151
+ 返回行:指的是HTTP响应中的第一行,包含了HTTP协议版本、响应状态码和相应的状态描述。例如:HTTP/1.1 200 OK
152
+ 请求头/返回头:
153
+ 请求头:包含了HTTP请求的相关信息,以键值对的形式出现,每个键值对占据一行。常见的请求头包括Host、User-Agent、Content-Type等。例如:Host: example.com
154
+ 返回头:包含了HTTP响应的相关信息,也是以键值对的形式出现,每个键值对占据一行。常见的返回头包括Content-Type、Content-Length、Server等。例如:Content-Type: text/html
155
+ 空行:用于分隔请求头和请求体,由两个连续的回车换行符组成
156
+ 请求体/返回体:
157
+ 请求体:包含了HTTP请求中的实际数据部分,通常在POST请求中使用。请求体可以是表单数据、JSON数据等,格式取决于请求头中的Content-Type字段
158
+ 返回体:包含了HTTP响应中的实际数据部分,通常是服务器返回给客户端的内容。返回体的格式和内容取决于具体的请求和服务器的处理逻辑
159
+
160
+ Returns
161
+ -------
162
+ bytes
163
+ HTTP的bytes类型,如果有Gzip数据会自动解压缩
164
+ """
165
+ full_request = bytes.fromhex(full_request)
166
+ num = full_request.find(b"\r\n\r\n")
167
+ header = full_request[:num]
168
+
169
+ if full_request.endswith(b"\r\n\r\n"):
170
+ # 判断是否有file_data,没有的话就为b""空字符串
171
+ # 由于是多个tcp所以需要去除应该是长度的字节 不确定是不是4个字节 后期可能出现bug
172
+ ret = re.findall(b'^\r\n\r\n[0-9a-f]{1,}\r\n(.*)\r\n\x30\r\n\r\n$', full_request[num:], flags=re.DOTALL)
173
+ file_data = re.sub(b"\r\n[0-9a-f]{1,}\r\n", b"", ret[0]) if ret != [] else b""
174
+ else:
175
+ file_data = full_request[num+4:]
176
+
177
+ with contextlib.suppress(Exception):
178
+ file_data = gzip.decompress(file_data)
179
+ return header + b"\r\n\r\n" + file_data if preserve_http_headers else file_data
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: FlowAnalyzer
3
- Version: 0.1.4
3
+ Version: 0.1.6
4
4
  Summary: FlowAnalyzer是一个流量分析器,用于解析和处理tshark导出的JSON数据文件
5
5
  Home-page: https://github.com/Byxs20/FlowAnalyzer
6
6
  Author: Byxs20
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: FlowAnalyzer
3
- Version: 0.1.4
3
+ Version: 0.1.6
4
4
  Summary: FlowAnalyzer是一个流量分析器,用于解析和处理tshark导出的JSON数据文件
5
5
  Home-page: https://github.com/Byxs20/FlowAnalyzer
6
6
  Author: Byxs20
@@ -6,7 +6,7 @@ with open(os.path.join(os.path.dirname(__file__), "README.md"), encoding="utf-8"
6
6
 
7
7
  setup(
8
8
  name="FlowAnalyzer",
9
- version="0.1.4",
9
+ version="0.1.6",
10
10
  description="FlowAnalyzer是一个流量分析器,用于解析和处理tshark导出的JSON数据文件",
11
11
  author="Byxs20",
12
12
  author_email="97766819@qq.com",
@@ -1,162 +0,0 @@
1
- import os
2
- import re
3
- import json
4
- import gzip
5
- import contextlib
6
- import subprocess
7
- from typing import Tuple
8
-
9
-
10
- class FlowAnalyzer:
11
- """FlowAnalyzer是一个流量分析器,用于解析和处理tshark导出的JSON数据文件"""
12
-
13
- def __init__(self, jsonPath: str):
14
- """初始化FlowAnalyzer对象
15
-
16
- Parameters
17
- ----------
18
- jsonPath : str
19
- tshark导出的JSON文件路径
20
- """
21
- self.jsonPath = jsonPath
22
- self.check_json_file()
23
-
24
- def check_json_file(self):
25
- # sourcery skip: remove-redundant-fstring, replace-interpolation-with-fstring
26
- """检查JSON文件是否存在并非空
27
-
28
- Raises
29
- ------
30
- FileNotFoundError
31
- 当JSON文件不存在时抛出异常
32
- ValueError
33
- 当JSON文件内容为空时抛出异常
34
- """
35
- if not os.path.exists(self.jsonPath):
36
- raise FileNotFoundError(
37
- f"您的tshark导出的JSON文件没有找到!JSON路径:%s" % self.jsonPath)
38
-
39
- if os.path.getsize(self.jsonPath) == 0:
40
- raise ValueError("您的tshark导出的JSON文件内容为空!JSON路径:%s" % self.jsonPath)
41
-
42
- def parse_http_json(self) -> Tuple[dict, list]:
43
- """解析JSON数据文件中的HTTP请求和响应信息
44
-
45
- Returns
46
- -------
47
- tuple
48
- 包含请求字典和响应列表的元组
49
- """
50
- with open(self.jsonPath, "r") as f:
51
- data = json.load(f)
52
-
53
- request, response = {}, []
54
- for packet in data:
55
- packet = packet["_source"]["layers"]
56
- time_epoch = packet.get("frame.time_epoch")
57
- full_request = packet.get(
58
- "tcp.reassembled.data") or packet["tcp.payload"]
59
-
60
- if packet.get("http.request_in"):
61
- response.append({"response_num": packet["frame.number"][0], "request_in": packet["http.request_in"]
62
- [0], "full_request": full_request[0], "time_epoch": float(time_epoch[0])})
63
- else:
64
- request[packet["frame.number"][0]] = {
65
- "full_request": full_request[0], "time_epoch": float(time_epoch[0])}
66
- return request, response
67
-
68
- def generate_http_dict_pairs(self, http_header=False, time_epoch=False):
69
- """生成HTTP请求和响应信息的字典对
70
-
71
- Parameters
72
- ----------
73
- http_header : bool, optional
74
- 指是否提取HTTP请求的全部,默认为False,表示只提取请求体,否则返回整个HTTP请求,如下结构:
75
- 请求行:请求方法、请求目标和 HTTP 协议版本组成,例如 POST /login.php HTTP/1.1。
76
- 请求头:包含了各种请求的元数据,比如 Host、Content-Length、User-Agent 等。每个请求头都由一个字段名和对应的值组成,以冒号分隔,例如 Host: 192.168.52.176。
77
- 空行:用于分隔请求头和请求体,由两个连续的回车换行符组成。
78
- 请求体:包含了请求的实际数据,对于 POST 请求来说,请求体通常包含表单数据或其他数据,以便发送给服务器进行处理。
79
- time_epoch : bool, optional
80
- 指是否提取HTTP请求的时间,指的是开始时间
81
-
82
- Yields
83
- ------
84
- Iterator[dict]
85
- 包含请求和响应信息的字典迭代器
86
- """
87
- request, response = self.parse_http_json()
88
- for resp in response:
89
- frame_num = resp["response_num"]
90
- request_num = resp["request_in"]
91
- requ = request.get(request_num)
92
-
93
- dic = {"response": [frame_num, self.extract_http_file_data(resp['full_request'], http_header)]}
94
- dic["request"] = [request_num, self.extract_http_file_data(requ["full_request"], http_header)] if requ else None
95
-
96
- if time_epoch:
97
- dic["response"].append(resp["time_epoch"])
98
- dic["request"].append(requ["time_epoch"]) if requ else None
99
- yield dic
100
-
101
- @staticmethod
102
- def get_json_data(filePath, display_filter):
103
- """获取JSON数据并保存至文件,保存目录是当前工作目录,也就是您运行脚本所在目录
104
-
105
- Parameters
106
- ----------
107
- filePath : str
108
- 待处理的数据文件路径
109
- display_filter : str
110
- Tshark的过滤器表达式
111
-
112
- Returns
113
- -------
114
- str
115
- 保存JSON数据的文件路径
116
- """
117
- # sourcery skip: use-fstring-for-formatting
118
- # jsonPath = os.path.join(os.path.dirname(filePath), "output.json")
119
- jsonPath = os.path.join(os.getcwd(), "output.json")
120
- command = 'tshark -r {} -Y "{}" -T json -e http.request_in -e tcp.reassembled.data -e frame.number -e tcp.payload -e frame.time_epoch > {}'.format(
121
- filePath, display_filter, jsonPath)
122
- proc = subprocess.Popen(command, shell=True,
123
- stdout=subprocess.PIPE, stderr=subprocess.PIPE)
124
- proc.communicate()
125
- return jsonPath
126
-
127
- @staticmethod
128
- def extract_http_file_data(full_request, http_header=False):
129
- # sourcery skip: merge-else-if-into-elif, swap-if-else-branches
130
- """提取HTTP请求或响应中的文件数据
131
-
132
- Parameters
133
- ----------
134
- full_request : str
135
- HTTP请求或响应的消息体
136
- http_header : bool, optional
137
- 指是否提取HTTP请求的全部,默认为False,表示只提取请求体,否则返回整个HTTP请求,如下结构:
138
- 请求行:请求方法、请求目标和 HTTP 协议版本组成,例如 POST /login.php HTTP/1.1。
139
- 请求头:包含了各种请求的元数据,比如 Host、Content-Length、User-Agent 等。每个请求头都由一个字段名和对应的值组成,以冒号分隔,例如 Host: 192.168.52.176。
140
- 空行:用于分隔请求头和请求体,由两个连续的回车换行符组成。
141
- 请求体:包含了请求的实际数据,对于 POST 请求来说,请求体通常包含表单数据或其他数据,以便发送给服务器进行处理。
142
-
143
- Returns
144
- -------
145
- bytes
146
- HTTP的bytes类型,如果有Gzip数据会自动解压缩
147
- """
148
- full_request = bytes.fromhex(full_request)
149
- num = full_request.find(b"\r\n\r\n")
150
- header = full_request[:num]
151
-
152
- if full_request.endswith(b"\r\n\r\n"):
153
- ret = re.findall(b'^\r\n\r\n.*?\r\n(.*)\r\n.*?\r\n\r\n$', full_request[num:], flags=re.DOTALL)
154
- # 判断是否有file_data,没有的话就为b""空字符串
155
- # 由于是多个tcp所以需要去除应该是长度的字节 不确定是不是4个字节 后期可能出现bug
156
- file_data = re.sub(b"\r\n.{4}\r\n", b"", ret[0]) if ret != [] else b""
157
- else:
158
- file_data = full_request[num+4:]
159
-
160
- with contextlib.suppress(Exception):
161
- file_data = gzip.decompress(file_data)
162
- return header + b"\r\n\r\n" + file_data if http_header else file_data
File without changes
File without changes
File without changes