firegex 3.2.8__tar.gz → 3.2.10__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.
- {firegex-3.2.8/firegex.egg-info → firegex-3.2.10}/PKG-INFO +2 -2
- firegex-3.2.10/firegex/__init__.py +5 -0
- {firegex-3.2.8 → firegex-3.2.10}/firegex/nfproxy/models/http.py +187 -111
- {firegex-3.2.8 → firegex-3.2.10/firegex.egg-info}/PKG-INFO +2 -2
- {firegex-3.2.8 → firegex-3.2.10}/firegex.egg-info/requires.txt +1 -1
- {firegex-3.2.8 → firegex-3.2.10}/requirements.txt +1 -1
- {firegex-3.2.8 → firegex-3.2.10}/setup.py +1 -1
- firegex-3.2.8/firegex/__init__.py +0 -5
- {firegex-3.2.8 → firegex-3.2.10}/MANIFEST.in +0 -0
- {firegex-3.2.8 → firegex-3.2.10}/README.md +0 -0
- {firegex-3.2.8 → firegex-3.2.10}/fgex +0 -0
- {firegex-3.2.8 → firegex-3.2.10}/firegex/__main__.py +0 -0
- {firegex-3.2.8 → firegex-3.2.10}/firegex/cli.py +0 -0
- {firegex-3.2.8 → firegex-3.2.10}/firegex/nfproxy/__init__.py +0 -0
- {firegex-3.2.8 → firegex-3.2.10}/firegex/nfproxy/internals/__init__.py +0 -0
- {firegex-3.2.8 → firegex-3.2.10}/firegex/nfproxy/internals/data.py +0 -0
- {firegex-3.2.8 → firegex-3.2.10}/firegex/nfproxy/internals/exceptions.py +0 -0
- {firegex-3.2.8 → firegex-3.2.10}/firegex/nfproxy/internals/models.py +0 -0
- {firegex-3.2.8 → firegex-3.2.10}/firegex/nfproxy/models/__init__.py +0 -0
- {firegex-3.2.8 → firegex-3.2.10}/firegex/nfproxy/models/tcp.py +0 -0
- {firegex-3.2.8 → firegex-3.2.10}/firegex/nfproxy/proxysim/__init__.py +0 -0
- {firegex-3.2.8 → firegex-3.2.10}/firegex.egg-info/SOURCES.txt +0 -0
- {firegex-3.2.8 → firegex-3.2.10}/firegex.egg-info/dependency_links.txt +0 -0
- {firegex-3.2.8 → firegex-3.2.10}/firegex.egg-info/top_level.txt +0 -0
- {firegex-3.2.8 → firegex-3.2.10}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: firegex
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.10
|
|
4
4
|
Summary: Firegex client
|
|
5
5
|
Home-page: https://github.com/pwnzer0tt1/firegex
|
|
6
6
|
Author: Pwnzer0tt1
|
|
@@ -10,7 +10,7 @@ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (G
|
|
|
10
10
|
Classifier: Operating System :: OS Independent
|
|
11
11
|
Requires-Python: >=3.10
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
|
-
Requires-Dist: typer
|
|
13
|
+
Requires-Dist: typer
|
|
14
14
|
Requires-Dist: pydantic>=2
|
|
15
15
|
Requires-Dist: typing-extensions>=4.7.1
|
|
16
16
|
Requires-Dist: pycryptodome
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import pyllhttp
|
|
1
|
+
import pyllhttp
|
|
2
2
|
from firegex.nfproxy.internals.exceptions import NotReadyToRun
|
|
3
3
|
from firegex.nfproxy.internals.data import DataStreamCtx
|
|
4
|
-
from firegex.nfproxy.internals.exceptions import
|
|
4
|
+
from firegex.nfproxy.internals.exceptions import (
|
|
5
|
+
StreamFullDrop,
|
|
6
|
+
StreamFullReject,
|
|
7
|
+
RejectConnection,
|
|
8
|
+
DropPacket,
|
|
9
|
+
)
|
|
5
10
|
from firegex.nfproxy.internals.models import FullStreamAction, ExceptionAction
|
|
6
11
|
from dataclasses import dataclass, field
|
|
7
12
|
from collections import deque
|
|
@@ -15,17 +20,21 @@ from websockets.frames import Frame
|
|
|
15
20
|
from websockets.extensions.permessage_deflate import PerMessageDeflate
|
|
16
21
|
from pyllhttp import PAUSED_H2_UPGRADE, PAUSED_UPGRADE
|
|
17
22
|
|
|
23
|
+
|
|
18
24
|
@dataclass
|
|
19
25
|
class InternalHTTPMessage:
|
|
20
26
|
"""Internal class to handle HTTP messages"""
|
|
21
|
-
|
|
27
|
+
|
|
28
|
+
url: str | None = field(default=None)
|
|
22
29
|
headers: dict[str, str] = field(default_factory=dict)
|
|
23
|
-
lheaders: dict[str, str] = field(
|
|
24
|
-
|
|
30
|
+
lheaders: dict[str, str] = field(
|
|
31
|
+
default_factory=dict
|
|
32
|
+
) # lowercase copy of the headers
|
|
33
|
+
body: bytes | None = field(default=None)
|
|
25
34
|
body_decoded: bool = field(default=False)
|
|
26
35
|
headers_complete: bool = field(default=False)
|
|
27
36
|
message_complete: bool = field(default=False)
|
|
28
|
-
status: str|None = field(default=None)
|
|
37
|
+
status: str | None = field(default=None)
|
|
29
38
|
total_size: int = field(default=0)
|
|
30
39
|
user_agent: str = field(default_factory=str)
|
|
31
40
|
content_encoding: str = field(default=str)
|
|
@@ -36,15 +45,17 @@ class InternalHTTPMessage:
|
|
|
36
45
|
method: str = field(default=str)
|
|
37
46
|
content_length: int = field(default=0)
|
|
38
47
|
stream: bytes = field(default_factory=bytes)
|
|
39
|
-
ws_stream: list[Frame] = field(default_factory=list)
|
|
48
|
+
ws_stream: list[Frame] = field(default_factory=list) # Decoded websocket stream
|
|
40
49
|
upgrading_to_h2: bool = field(default=False)
|
|
41
50
|
upgrading_to_ws: bool = field(default=False)
|
|
42
51
|
|
|
52
|
+
|
|
43
53
|
@dataclass
|
|
44
54
|
class InternalHttpBuffer:
|
|
45
55
|
"""Internal class to handle HTTP messages"""
|
|
56
|
+
|
|
46
57
|
_url_buffer: bytes = field(default_factory=bytes)
|
|
47
|
-
_raw_header_fields: dict[str, str|list[str]] = field(default_factory=dict)
|
|
58
|
+
_raw_header_fields: dict[str, str | list[str]] = field(default_factory=dict)
|
|
48
59
|
_header_fields: dict[str, str] = field(default_factory=dict)
|
|
49
60
|
_body_buffer: bytes = field(default_factory=bytes)
|
|
50
61
|
_status_buffer: bytes = field(default_factory=bytes)
|
|
@@ -52,8 +63,8 @@ class InternalHttpBuffer:
|
|
|
52
63
|
_current_header_value: bytes = field(default_factory=bytes)
|
|
53
64
|
_ws_packet_stream: bytes = field(default_factory=bytes)
|
|
54
65
|
|
|
55
|
-
|
|
56
|
-
|
|
66
|
+
|
|
67
|
+
class InternalCallbackHandler:
|
|
57
68
|
buffers = InternalHttpBuffer()
|
|
58
69
|
msg = InternalHTTPMessage()
|
|
59
70
|
save_body = True
|
|
@@ -72,11 +83,11 @@ class InternalCallbackHandler():
|
|
|
72
83
|
self.buffers = InternalHttpBuffer()
|
|
73
84
|
self.msg = InternalHTTPMessage()
|
|
74
85
|
self.has_begun = True
|
|
75
|
-
|
|
86
|
+
|
|
76
87
|
def on_url(self, url):
|
|
77
88
|
self.buffers._url_buffer += url
|
|
78
89
|
self.msg.total_size += len(url)
|
|
79
|
-
|
|
90
|
+
|
|
80
91
|
def on_url_complete(self):
|
|
81
92
|
self.msg.url = self.buffers._url_buffer.decode(errors="ignore")
|
|
82
93
|
self.buffers._url_buffer = b""
|
|
@@ -84,45 +95,50 @@ class InternalCallbackHandler():
|
|
|
84
95
|
def on_status(self, status: bytes):
|
|
85
96
|
self.msg.total_size += len(status)
|
|
86
97
|
self.buffers._status_buffer += status
|
|
87
|
-
|
|
98
|
+
|
|
88
99
|
def on_status_complete(self):
|
|
89
100
|
self.msg.status = self.buffers._status_buffer.decode(errors="ignore")
|
|
90
101
|
self.buffers._status_buffer = b""
|
|
91
|
-
|
|
102
|
+
|
|
92
103
|
def on_header_field(self, field):
|
|
93
104
|
self.msg.total_size += len(field)
|
|
94
105
|
self.buffers._current_header_field += field
|
|
95
106
|
|
|
96
107
|
def on_header_field_complete(self):
|
|
97
|
-
pass
|
|
98
|
-
|
|
108
|
+
pass # Nothing to do
|
|
109
|
+
|
|
99
110
|
def on_header_value(self, value):
|
|
100
111
|
self.msg.total_size += len(value)
|
|
101
112
|
self.buffers._current_header_value += value
|
|
102
113
|
|
|
103
114
|
def on_header_value_complete(self):
|
|
104
115
|
if self.buffers._current_header_field:
|
|
105
|
-
k, v =
|
|
116
|
+
k, v = (
|
|
117
|
+
self.buffers._current_header_field.decode(errors="ignore"),
|
|
118
|
+
self.buffers._current_header_value.decode(errors="ignore"),
|
|
119
|
+
)
|
|
106
120
|
old_value = self.buffers._raw_header_fields.get(k, None)
|
|
107
|
-
|
|
121
|
+
|
|
108
122
|
# raw headers are stored as thay were, considering to check changes between headers encoding
|
|
109
|
-
if isinstance(old_value, list):
|
|
123
|
+
if isinstance(old_value, list):
|
|
110
124
|
old_value.append(v)
|
|
111
125
|
elif isinstance(old_value, str):
|
|
112
126
|
self.buffers._raw_header_fields[k] = [old_value, v]
|
|
113
127
|
else:
|
|
114
128
|
self.buffers._raw_header_fields[k] = v
|
|
115
|
-
|
|
129
|
+
|
|
116
130
|
# Decoding headers normally
|
|
117
131
|
kl = k.lower()
|
|
118
132
|
if kl in self.buffers._header_fields:
|
|
119
|
-
self.buffers._header_fields[kl] +=
|
|
133
|
+
self.buffers._header_fields[kl] += (
|
|
134
|
+
f", {v}" # Should be considered as a single list separated by commas as said in the RFC
|
|
135
|
+
)
|
|
120
136
|
else:
|
|
121
137
|
self.buffers._header_fields[kl] = v
|
|
122
|
-
|
|
138
|
+
|
|
123
139
|
self.buffers._current_header_field = b""
|
|
124
140
|
self.buffers._current_header_value = b""
|
|
125
|
-
|
|
141
|
+
|
|
126
142
|
def on_headers_complete(self):
|
|
127
143
|
self.msg.headers = self.buffers._raw_header_fields
|
|
128
144
|
self.msg.lheaders = self.buffers._header_fields
|
|
@@ -170,7 +186,9 @@ class InternalCallbackHandler():
|
|
|
170
186
|
print(f"Error decompressing brotli: {e}: skipping", flush=True)
|
|
171
187
|
decode_success = False
|
|
172
188
|
break
|
|
173
|
-
elif
|
|
189
|
+
elif (
|
|
190
|
+
enc == "gzip" or enc == "x-gzip"
|
|
191
|
+
): # https://datatracker.ietf.org/doc/html/rfc2616#section-3.5
|
|
174
192
|
try:
|
|
175
193
|
if "gzip" in self.content_encoding.lower():
|
|
176
194
|
with gzip.GzipFile(fileobj=io.BytesIO(decoding_body)) as f:
|
|
@@ -187,11 +205,11 @@ class InternalCallbackHandler():
|
|
|
187
205
|
decode_success = False
|
|
188
206
|
break
|
|
189
207
|
elif enc == "identity":
|
|
190
|
-
pass
|
|
208
|
+
pass # No need to do anything https://datatracker.ietf.org/doc/html/rfc2616#section-3.5 (it's possible to be found also if it should't be used)
|
|
191
209
|
else:
|
|
192
210
|
decode_success = False
|
|
193
211
|
break
|
|
194
|
-
|
|
212
|
+
|
|
195
213
|
if decode_success:
|
|
196
214
|
self.msg.body = decoding_body
|
|
197
215
|
self.msg.body_decoded = True
|
|
@@ -200,15 +218,15 @@ class InternalCallbackHandler():
|
|
|
200
218
|
self.has_begun = False
|
|
201
219
|
if not self._packet_to_stream():
|
|
202
220
|
self.messages.append(self.msg)
|
|
203
|
-
|
|
221
|
+
|
|
204
222
|
@property
|
|
205
223
|
def user_agent(self) -> str:
|
|
206
224
|
return self.msg.lheaders.get("user-agent", "")
|
|
207
|
-
|
|
225
|
+
|
|
208
226
|
@property
|
|
209
227
|
def content_encoding(self) -> str:
|
|
210
228
|
return self.msg.lheaders.get("content-encoding", "")
|
|
211
|
-
|
|
229
|
+
|
|
212
230
|
@property
|
|
213
231
|
def content_type(self) -> str:
|
|
214
232
|
return self.msg.lheaders.get("content-type", "")
|
|
@@ -220,14 +238,14 @@ class InternalCallbackHandler():
|
|
|
220
238
|
@property
|
|
221
239
|
def should_upgrade(self) -> bool:
|
|
222
240
|
return self.is_upgrading
|
|
223
|
-
|
|
241
|
+
|
|
224
242
|
@property
|
|
225
243
|
def http_version(self) -> str:
|
|
226
244
|
if self.major and self.minor:
|
|
227
245
|
return f"{self.major}.{self.minor}"
|
|
228
246
|
else:
|
|
229
247
|
return ""
|
|
230
|
-
|
|
248
|
+
|
|
231
249
|
@property
|
|
232
250
|
def method_parsed(self) -> str:
|
|
233
251
|
return self.method
|
|
@@ -239,17 +257,17 @@ class InternalCallbackHandler():
|
|
|
239
257
|
for msg in self.messages:
|
|
240
258
|
tot += msg.total_size
|
|
241
259
|
return tot
|
|
242
|
-
|
|
260
|
+
|
|
243
261
|
@property
|
|
244
262
|
def content_length_parsed(self) -> int:
|
|
245
263
|
return self.content_length
|
|
246
|
-
|
|
264
|
+
|
|
247
265
|
def _is_input(self) -> bool:
|
|
248
266
|
raise NotImplementedError()
|
|
249
|
-
|
|
267
|
+
|
|
250
268
|
def _packet_to_stream(self):
|
|
251
269
|
return self.should_upgrade and self.save_body
|
|
252
|
-
|
|
270
|
+
|
|
253
271
|
def _stream_parser(self, data: bytes):
|
|
254
272
|
if self.msg.upgrading_to_ws:
|
|
255
273
|
if self._ws_raised_error:
|
|
@@ -259,9 +277,14 @@ class InternalCallbackHandler():
|
|
|
259
277
|
self.buffers._ws_packet_stream += data
|
|
260
278
|
while True:
|
|
261
279
|
try:
|
|
262
|
-
new_frame, self.buffers._ws_packet_stream =
|
|
280
|
+
new_frame, self.buffers._ws_packet_stream = (
|
|
281
|
+
self._parse_websocket_frame(self.buffers._ws_packet_stream)
|
|
282
|
+
)
|
|
263
283
|
except Exception:
|
|
264
|
-
print(
|
|
284
|
+
print(
|
|
285
|
+
"[WARNING] Websocket parsing failed, passing data to stream...",
|
|
286
|
+
flush=True,
|
|
287
|
+
)
|
|
265
288
|
traceback.print_exc()
|
|
266
289
|
self._ws_raised_error = True
|
|
267
290
|
self.msg.stream += self.buffers._ws_packet_stream
|
|
@@ -275,7 +298,7 @@ class InternalCallbackHandler():
|
|
|
275
298
|
if self.msg.upgrading_to_h2:
|
|
276
299
|
self.msg.total_size += len(data)
|
|
277
300
|
self.msg.stream += data
|
|
278
|
-
|
|
301
|
+
|
|
279
302
|
def _parse_websocket_ext(self):
|
|
280
303
|
ext_ws = []
|
|
281
304
|
req_ext = []
|
|
@@ -287,14 +310,17 @@ class InternalCallbackHandler():
|
|
|
287
310
|
if ele == "permessage-deflate":
|
|
288
311
|
ext_ws.append(PerMessageDeflate(False, False, 15, 15))
|
|
289
312
|
return ext_ws
|
|
290
|
-
|
|
291
|
-
def _parse_websocket_frame(self, data: bytes) -> tuple[Frame|None, bytes]:
|
|
313
|
+
|
|
314
|
+
def _parse_websocket_frame(self, data: bytes) -> tuple[Frame | None, bytes]:
|
|
292
315
|
if self._ws_extentions is None:
|
|
293
316
|
if self._is_input():
|
|
294
|
-
self._ws_extentions = []
|
|
317
|
+
self._ws_extentions = [] # Fallback to no options
|
|
295
318
|
else:
|
|
296
|
-
self._ws_extentions =
|
|
319
|
+
self._ws_extentions = (
|
|
320
|
+
self._parse_websocket_ext()
|
|
321
|
+
) # Extentions used are choosen by the server response
|
|
297
322
|
read_buffering = bytearray()
|
|
323
|
+
|
|
298
324
|
def read_exact(n: int):
|
|
299
325
|
nonlocal read_buffering
|
|
300
326
|
buffer = bytearray(read_buffering)
|
|
@@ -307,17 +333,19 @@ class InternalCallbackHandler():
|
|
|
307
333
|
read_buffering = buffer[n:]
|
|
308
334
|
return new_data
|
|
309
335
|
|
|
310
|
-
parsing = Frame.parse(
|
|
336
|
+
parsing = Frame.parse(
|
|
337
|
+
read_exact, extensions=self._ws_extentions, mask=self._is_input()
|
|
338
|
+
)
|
|
311
339
|
parsing.send(None)
|
|
312
340
|
try:
|
|
313
341
|
parsing.send(bytearray(data))
|
|
314
342
|
except StopIteration as e:
|
|
315
343
|
return e.value, read_buffering
|
|
316
|
-
|
|
344
|
+
|
|
317
345
|
return None, read_buffering
|
|
318
|
-
|
|
346
|
+
|
|
319
347
|
def parse_data(self, data: bytes):
|
|
320
|
-
if self._packet_to_stream():
|
|
348
|
+
if self._packet_to_stream(): # This is a websocket upgrade!
|
|
321
349
|
self._stream_parser(data)
|
|
322
350
|
else:
|
|
323
351
|
try:
|
|
@@ -333,82 +361,93 @@ class InternalCallbackHandler():
|
|
|
333
361
|
except Exception as e:
|
|
334
362
|
self.raised_error = True
|
|
335
363
|
raise e
|
|
336
|
-
|
|
364
|
+
|
|
337
365
|
def pop_message(self):
|
|
338
366
|
return self.messages.popleft()
|
|
339
|
-
|
|
367
|
+
|
|
368
|
+
def pop_all_messages(self):
|
|
369
|
+
tmp = self.messages
|
|
370
|
+
self.messages = deque()
|
|
371
|
+
return tmp
|
|
372
|
+
|
|
340
373
|
def __repr__(self):
|
|
341
374
|
return f"<InternalCallbackHandler msg={self.msg} buffers={self.buffers} save_body={self.save_body} raised_error={self.raised_error} has_begun={self.has_begun} messages={self.messages}>"
|
|
342
|
-
|
|
375
|
+
|
|
343
376
|
|
|
344
377
|
class InternalHttpRequest(InternalCallbackHandler, pyllhttp.Request):
|
|
345
378
|
def __init__(self):
|
|
346
379
|
super(InternalCallbackHandler, self).__init__()
|
|
347
380
|
super(pyllhttp.Request, self).__init__()
|
|
348
|
-
|
|
381
|
+
|
|
349
382
|
def _is_input(self):
|
|
350
383
|
return True
|
|
351
|
-
|
|
384
|
+
|
|
385
|
+
|
|
352
386
|
class InternalHttpResponse(InternalCallbackHandler, pyllhttp.Response):
|
|
353
387
|
def __init__(self):
|
|
354
388
|
super(InternalCallbackHandler, self).__init__()
|
|
355
389
|
super(pyllhttp.Response, self).__init__()
|
|
356
|
-
|
|
390
|
+
|
|
357
391
|
def _is_input(self):
|
|
358
392
|
return False
|
|
359
|
-
|
|
393
|
+
|
|
394
|
+
|
|
360
395
|
class InternalBasicHttpMetaClass:
|
|
361
396
|
"""Internal class to handle HTTP requests and responses"""
|
|
362
|
-
|
|
363
|
-
def __init__(
|
|
397
|
+
|
|
398
|
+
def __init__(
|
|
399
|
+
self,
|
|
400
|
+
parser: InternalHttpRequest | InternalHttpResponse,
|
|
401
|
+
msg: InternalHTTPMessage,
|
|
402
|
+
):
|
|
364
403
|
self._parser = parser
|
|
365
404
|
self.raised_error = False
|
|
366
|
-
self._message: InternalHTTPMessage|None = msg
|
|
405
|
+
self._message: InternalHTTPMessage | None = msg
|
|
367
406
|
self._contructor_hook()
|
|
368
|
-
|
|
407
|
+
|
|
369
408
|
def _contructor_hook(self):
|
|
370
409
|
pass
|
|
371
|
-
|
|
410
|
+
|
|
372
411
|
@property
|
|
373
412
|
def total_size(self) -> int:
|
|
374
413
|
"""Total size of the stream"""
|
|
375
414
|
return self._parser.total_size
|
|
376
|
-
|
|
415
|
+
|
|
377
416
|
@property
|
|
378
|
-
def url(self) -> str|None:
|
|
417
|
+
def url(self) -> str | None:
|
|
379
418
|
"""URL of the message"""
|
|
380
419
|
return self._message.url
|
|
381
|
-
|
|
420
|
+
|
|
382
421
|
@property
|
|
383
422
|
def headers(self) -> dict[str, str]:
|
|
384
423
|
"""Headers of the message"""
|
|
385
424
|
return self._message.headers
|
|
386
|
-
|
|
425
|
+
|
|
387
426
|
@property
|
|
388
427
|
def user_agent(self) -> str:
|
|
389
428
|
"""User agent of the message"""
|
|
390
429
|
return self._message.user_agent
|
|
391
|
-
|
|
430
|
+
|
|
392
431
|
@property
|
|
393
432
|
def content_encoding(self) -> str:
|
|
394
433
|
"""Content encoding of the message"""
|
|
395
434
|
return self._message.content_encoding
|
|
396
|
-
|
|
435
|
+
|
|
397
436
|
@property
|
|
398
437
|
def body(self) -> bytes:
|
|
399
438
|
"""Body of the message"""
|
|
400
439
|
return self._message.body
|
|
401
|
-
|
|
440
|
+
|
|
402
441
|
@property
|
|
403
442
|
def headers_complete(self) -> bool:
|
|
404
443
|
"""If the headers are complete"""
|
|
405
444
|
return self._message.headers_complete
|
|
406
|
-
|
|
445
|
+
|
|
407
446
|
@property
|
|
408
447
|
def message_complete(self) -> bool:
|
|
409
448
|
"""If the message is complete"""
|
|
410
449
|
return self._message.message_complete
|
|
411
|
-
|
|
450
|
+
|
|
412
451
|
@property
|
|
413
452
|
def http_version(self) -> str:
|
|
414
453
|
"""HTTP version of the message"""
|
|
@@ -425,7 +464,7 @@ class InternalBasicHttpMetaClass:
|
|
|
425
464
|
return self._parser.should_upgrade
|
|
426
465
|
|
|
427
466
|
@property
|
|
428
|
-
def content_length(self) -> int|None:
|
|
467
|
+
def content_length(self) -> int | None:
|
|
429
468
|
"""Content length of the message"""
|
|
430
469
|
return self._message.content_length
|
|
431
470
|
|
|
@@ -433,7 +472,7 @@ class InternalBasicHttpMetaClass:
|
|
|
433
472
|
def upgrading_to_h2(self) -> bool:
|
|
434
473
|
"""If the message is upgrading to HTTP/2"""
|
|
435
474
|
return self._message.upgrading_to_h2
|
|
436
|
-
|
|
475
|
+
|
|
437
476
|
@property
|
|
438
477
|
def upgrading_to_ws(self) -> bool:
|
|
439
478
|
"""If the message is upgrading to Websocket"""
|
|
@@ -443,48 +482,63 @@ class InternalBasicHttpMetaClass:
|
|
|
443
482
|
def ws_stream(self) -> list[Frame]:
|
|
444
483
|
"""Websocket stream"""
|
|
445
484
|
return self._message.ws_stream
|
|
446
|
-
|
|
485
|
+
|
|
447
486
|
@property
|
|
448
487
|
def stream(self) -> bytes:
|
|
449
488
|
"""Stream of the message"""
|
|
450
489
|
return self._message.stream
|
|
451
|
-
|
|
490
|
+
|
|
452
491
|
def get_header(self, header: str, default=None) -> str:
|
|
453
492
|
"""Get a header from the message without caring about the case"""
|
|
454
493
|
return self._message.lheaders.get(header.lower(), default)
|
|
455
|
-
|
|
494
|
+
|
|
456
495
|
@staticmethod
|
|
457
496
|
def _before_fetch_callable_checks(internal_data: DataStreamCtx) -> bool:
|
|
458
497
|
raise NotImplementedError()
|
|
459
|
-
|
|
498
|
+
|
|
460
499
|
@staticmethod
|
|
461
500
|
def _parser_class() -> str:
|
|
462
501
|
raise NotImplementedError()
|
|
463
|
-
|
|
502
|
+
|
|
464
503
|
@classmethod
|
|
465
504
|
def _fetch_packet(cls, internal_data: DataStreamCtx):
|
|
466
|
-
if
|
|
505
|
+
if (
|
|
506
|
+
internal_data.current_pkt is None
|
|
507
|
+
or internal_data.current_pkt.is_tcp is False
|
|
508
|
+
):
|
|
467
509
|
raise NotReadyToRun()
|
|
468
|
-
|
|
469
|
-
ParserType =
|
|
510
|
+
|
|
511
|
+
ParserType = (
|
|
512
|
+
InternalHttpRequest
|
|
513
|
+
if internal_data.current_pkt.is_input
|
|
514
|
+
else InternalHttpResponse
|
|
515
|
+
)
|
|
470
516
|
parser_key = f"{cls._parser_class()}_{'in' if internal_data.current_pkt.is_input else 'out'}"
|
|
471
|
-
|
|
517
|
+
|
|
472
518
|
parser = internal_data.data_handler_context.get(parser_key, None)
|
|
473
519
|
if parser is None or parser.raised_error:
|
|
474
|
-
parser: InternalHttpRequest|InternalHttpResponse = ParserType()
|
|
520
|
+
parser: InternalHttpRequest | InternalHttpResponse = ParserType()
|
|
475
521
|
internal_data.data_handler_context[parser_key] = parser
|
|
476
|
-
|
|
477
|
-
if not internal_data.call_mem.get(
|
|
522
|
+
|
|
523
|
+
if not internal_data.call_mem.get(
|
|
524
|
+
cls._parser_class(), False
|
|
525
|
+
): # Need to parse HTTP
|
|
478
526
|
internal_data.call_mem[cls._parser_class()] = True
|
|
479
|
-
|
|
480
|
-
|
|
527
|
+
parser.pop_all_messages() # Delete content on message deque
|
|
528
|
+
|
|
529
|
+
# Setting websocket options if needed to the client parser
|
|
481
530
|
if internal_data.current_pkt.is_input:
|
|
482
|
-
ext_opt = internal_data.data_handler_context.get(
|
|
531
|
+
ext_opt = internal_data.data_handler_context.get(
|
|
532
|
+
f"{cls._parser_class()}_ws_options_client"
|
|
533
|
+
)
|
|
483
534
|
if ext_opt is not None and parser._ws_extentions != ext_opt:
|
|
484
535
|
parser._ws_extentions = ext_opt
|
|
485
|
-
|
|
536
|
+
|
|
486
537
|
# Memory size managment
|
|
487
|
-
if
|
|
538
|
+
if (
|
|
539
|
+
parser.total_size + len(internal_data.current_pkt.data)
|
|
540
|
+
> internal_data.stream_max_size
|
|
541
|
+
):
|
|
488
542
|
match internal_data.full_stream_action:
|
|
489
543
|
case FullStreamAction.FLUSH:
|
|
490
544
|
# Deleting parser and re-creating it
|
|
@@ -494,7 +548,10 @@ class InternalBasicHttpMetaClass:
|
|
|
494
548
|
parser.msg.total_size -= len(parser.msg.body)
|
|
495
549
|
parser.msg.body = b""
|
|
496
550
|
print("[WARNING] Flushing stream", flush=True)
|
|
497
|
-
if
|
|
551
|
+
if (
|
|
552
|
+
parser.total_size + len(internal_data.current_pkt.data)
|
|
553
|
+
> internal_data.stream_max_size
|
|
554
|
+
):
|
|
498
555
|
parser.reset_data()
|
|
499
556
|
case FullStreamAction.REJECT:
|
|
500
557
|
raise StreamFullReject()
|
|
@@ -502,9 +559,11 @@ class InternalBasicHttpMetaClass:
|
|
|
502
559
|
raise StreamFullDrop()
|
|
503
560
|
case FullStreamAction.ACCEPT:
|
|
504
561
|
raise NotReadyToRun()
|
|
505
|
-
|
|
506
|
-
internal_data.call_mem["headers_were_set"] =
|
|
507
|
-
|
|
562
|
+
|
|
563
|
+
internal_data.call_mem["headers_were_set"] = (
|
|
564
|
+
parser.msg.headers_complete
|
|
565
|
+
) # This information is usefull for building the real object
|
|
566
|
+
|
|
508
567
|
try:
|
|
509
568
|
parser.parse_data(internal_data.current_pkt.data)
|
|
510
569
|
except Exception as e:
|
|
@@ -520,37 +579,51 @@ class InternalBasicHttpMetaClass:
|
|
|
520
579
|
raise NotReadyToRun()
|
|
521
580
|
|
|
522
581
|
if parser.should_upgrade and not internal_data.current_pkt.is_input:
|
|
523
|
-
#Creating ws_option for the client
|
|
524
|
-
if not internal_data.data_handler_context.get(
|
|
582
|
+
# Creating ws_option for the client
|
|
583
|
+
if not internal_data.data_handler_context.get(
|
|
584
|
+
f"{cls._parser_class()}_ws_options_client"
|
|
585
|
+
):
|
|
525
586
|
ext = parser._parse_websocket_ext()
|
|
526
|
-
internal_data.data_handler_context[
|
|
527
|
-
|
|
528
|
-
|
|
587
|
+
internal_data.data_handler_context[
|
|
588
|
+
f"{cls._parser_class()}_ws_options_client"
|
|
589
|
+
] = ext
|
|
590
|
+
|
|
591
|
+
# Once the parsers has been triggered, we can return the object if needed
|
|
529
592
|
if not cls._before_fetch_callable_checks(internal_data):
|
|
530
593
|
raise NotReadyToRun()
|
|
531
594
|
|
|
532
|
-
messages_tosend:list[InternalHTTPMessage] = []
|
|
595
|
+
messages_tosend: list[InternalHTTPMessage] = []
|
|
533
596
|
for i in range(len(parser.messages)):
|
|
534
597
|
messages_tosend.append(parser.pop_message())
|
|
535
|
-
|
|
598
|
+
|
|
536
599
|
if len(messages_tosend) > 0:
|
|
537
|
-
internal_data.call_mem["headers_were_set"] =
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
600
|
+
internal_data.call_mem["headers_were_set"] = (
|
|
601
|
+
False # New messages completed so the current message headers were not set in this case
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
if (
|
|
605
|
+
not internal_data.call_mem["headers_were_set"]
|
|
606
|
+
and parser.msg.headers_complete
|
|
607
|
+
):
|
|
608
|
+
messages_tosend.append(
|
|
609
|
+
parser.msg
|
|
610
|
+
) # Also the current message needs to be sent due to complete headers
|
|
611
|
+
|
|
542
612
|
if parser._packet_to_stream():
|
|
543
|
-
messages_tosend.append(
|
|
544
|
-
|
|
613
|
+
messages_tosend.append(
|
|
614
|
+
parser.msg
|
|
615
|
+
) # Also the current message needs to beacase a stream is going on
|
|
616
|
+
|
|
545
617
|
messages_to_call = len(messages_tosend)
|
|
546
|
-
|
|
618
|
+
|
|
547
619
|
if messages_to_call == 0:
|
|
548
620
|
raise NotReadyToRun()
|
|
549
621
|
elif messages_to_call == 1:
|
|
550
622
|
return cls(parser, messages_tosend[0])
|
|
551
|
-
|
|
623
|
+
|
|
552
624
|
return [cls(parser, ele) for ele in messages_tosend]
|
|
553
625
|
|
|
626
|
+
|
|
554
627
|
class HttpRequest(InternalBasicHttpMetaClass):
|
|
555
628
|
"""
|
|
556
629
|
HTTP Request handler
|
|
@@ -569,10 +642,11 @@ class HttpRequest(InternalBasicHttpMetaClass):
|
|
|
569
642
|
@staticmethod
|
|
570
643
|
def _parser_class() -> str:
|
|
571
644
|
return "full_http"
|
|
572
|
-
|
|
645
|
+
|
|
573
646
|
def __repr__(self):
|
|
574
647
|
return f"<HttpRequest method={self.method} url={self.url} headers={self.headers} body=[{0 if not self.body else len(self.body)} bytes] http_version={self.http_version} keep_alive={self.keep_alive} should_upgrade={self.should_upgrade} headers_complete={self.headers_complete} message_complete={self.message_complete} content_length={self.content_length} stream={self.stream} ws_stream={self.ws_stream}>"
|
|
575
648
|
|
|
649
|
+
|
|
576
650
|
class HttpResponse(InternalBasicHttpMetaClass):
|
|
577
651
|
"""
|
|
578
652
|
HTTP Response handler
|
|
@@ -591,29 +665,31 @@ class HttpResponse(InternalBasicHttpMetaClass):
|
|
|
591
665
|
@staticmethod
|
|
592
666
|
def _parser_class() -> str:
|
|
593
667
|
return "full_http"
|
|
594
|
-
|
|
668
|
+
|
|
595
669
|
def __repr__(self):
|
|
596
670
|
return f"<HttpResponse status_code={self.status_code} url={self.url} headers={self.headers} body=[{0 if not self.body else len(self.body)} bytes] http_version={self.http_version} keep_alive={self.keep_alive} should_upgrade={self.should_upgrade} headers_complete={self.headers_complete} message_complete={self.message_complete} content_length={self.content_length} stream={self.stream} ws_stream={self.ws_stream}>"
|
|
597
671
|
|
|
672
|
+
|
|
598
673
|
class HttpRequestHeader(HttpRequest):
|
|
599
674
|
"""
|
|
600
675
|
HTTP Request Header handler
|
|
601
676
|
This data handler will be called only once, the headers are complete, the body will be empty and not buffered
|
|
602
677
|
"""
|
|
603
|
-
|
|
678
|
+
|
|
604
679
|
def _contructor_hook(self):
|
|
605
680
|
self._parser.save_body = False
|
|
606
|
-
|
|
681
|
+
|
|
607
682
|
@staticmethod
|
|
608
683
|
def _parser_class() -> str:
|
|
609
684
|
return "header_http"
|
|
610
685
|
|
|
686
|
+
|
|
611
687
|
class HttpResponseHeader(HttpResponse):
|
|
612
688
|
"""
|
|
613
689
|
HTTP Response Header handler
|
|
614
690
|
This data handler will be called only once, the headers are complete, the body will be empty and not buffered
|
|
615
691
|
"""
|
|
616
|
-
|
|
692
|
+
|
|
617
693
|
def _contructor_hook(self):
|
|
618
694
|
self._parser.save_body = False
|
|
619
695
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: firegex
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.10
|
|
4
4
|
Summary: Firegex client
|
|
5
5
|
Home-page: https://github.com/pwnzer0tt1/firegex
|
|
6
6
|
Author: Pwnzer0tt1
|
|
@@ -10,7 +10,7 @@ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (G
|
|
|
10
10
|
Classifier: Operating System :: OS Independent
|
|
11
11
|
Requires-Python: >=3.10
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
|
-
Requires-Dist: typer
|
|
13
|
+
Requires-Dist: typer
|
|
14
14
|
Requires-Dist: pydantic>=2
|
|
15
15
|
Requires-Dist: typing-extensions>=4.7.1
|
|
16
16
|
Requires-Dist: pycryptodome
|
|
@@ -6,7 +6,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
|
|
|
6
6
|
with open('requirements.txt', 'r', encoding='utf-8') as f:
|
|
7
7
|
required = [ele.strip() for ele in f.read().splitlines() if not ele.strip().startswith("#") and ele.strip() != ""]
|
|
8
8
|
|
|
9
|
-
VERSION = "3.2.
|
|
9
|
+
VERSION = "3.2.10"
|
|
10
10
|
|
|
11
11
|
setuptools.setup(
|
|
12
12
|
name="firegex",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|