firegex 3.0.0__tar.gz → 3.1.0__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.
Files changed (26) hide show
  1. {firegex-3.0.0/firegex.egg-info → firegex-3.1.0}/PKG-INFO +1 -3
  2. {firegex-3.0.0 → firegex-3.1.0}/README.md +0 -2
  3. firegex-3.1.0/firegex/__init__.py +5 -0
  4. {firegex-3.0.0 → firegex-3.1.0}/firegex/nfproxy/internals/__init__.py +34 -25
  5. {firegex-3.0.0 → firegex-3.1.0}/firegex/nfproxy/internals/data.py +19 -3
  6. {firegex-3.0.0 → firegex-3.1.0}/firegex/nfproxy/internals/exceptions.py +1 -0
  7. {firegex-3.0.0 → firegex-3.1.0}/firegex/nfproxy/internals/models.py +7 -2
  8. firegex-3.1.0/firegex/nfproxy/models/http.py +422 -0
  9. {firegex-3.0.0 → firegex-3.1.0}/firegex/nfproxy/models/tcp.py +1 -1
  10. {firegex-3.0.0 → firegex-3.1.0/firegex.egg-info}/PKG-INFO +1 -3
  11. {firegex-3.0.0 → firegex-3.1.0}/setup.py +1 -1
  12. firegex-3.0.0/firegex/__init__.py +0 -5
  13. firegex-3.0.0/firegex/nfproxy/models/http.py +0 -357
  14. {firegex-3.0.0 → firegex-3.1.0}/MANIFEST.in +0 -0
  15. {firegex-3.0.0 → firegex-3.1.0}/fgex +0 -0
  16. {firegex-3.0.0 → firegex-3.1.0}/firegex/__main__.py +0 -0
  17. {firegex-3.0.0 → firegex-3.1.0}/firegex/cli.py +0 -0
  18. {firegex-3.0.0 → firegex-3.1.0}/firegex/nfproxy/__init__.py +0 -0
  19. {firegex-3.0.0 → firegex-3.1.0}/firegex/nfproxy/models/__init__.py +0 -0
  20. {firegex-3.0.0 → firegex-3.1.0}/firegex/nfproxy/proxysim/__init__.py +0 -0
  21. {firegex-3.0.0 → firegex-3.1.0}/firegex.egg-info/SOURCES.txt +0 -0
  22. {firegex-3.0.0 → firegex-3.1.0}/firegex.egg-info/dependency_links.txt +0 -0
  23. {firegex-3.0.0 → firegex-3.1.0}/firegex.egg-info/requires.txt +0 -0
  24. {firegex-3.0.0 → firegex-3.1.0}/firegex.egg-info/top_level.txt +0 -0
  25. {firegex-3.0.0 → firegex-3.1.0}/requirements.txt +0 -0
  26. {firegex-3.0.0 → firegex-3.1.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: firegex
3
- Version: 3.0.0
3
+ Version: 3.1.0
4
4
  Summary: Firegex client
5
5
  Home-page: https://github.com/pwnzer0tt1/firegex
6
6
  Author: Pwnzer0tt1
@@ -122,7 +122,6 @@ This handler will be called twice: one for the request headers and one for the r
122
122
  - headers: dict - The headers of the request
123
123
  - user_agent: str - The user agent of the request
124
124
  - content_encoding: str - The content encoding of the request
125
- - has_begun: bool - It's true if the request has begun
126
125
  - body: bytes - The body of the request
127
126
  - headers_complete: bool - It's true if the headers are complete
128
127
  - message_complete: bool - It's true if the message is complete
@@ -150,7 +149,6 @@ This handler will be called twice: one for the response headers and one for the
150
149
  - headers: dict - The headers of the response
151
150
  - user_agent: str - The user agent of the response
152
151
  - content_encoding: str - The content encoding of the response
153
- - has_begun: bool - It's true if the response has begun
154
152
  - body: bytes - The body of the response
155
153
  - headers_complete: bool - It's true if the headers are complete
156
154
  - message_complete: bool - It's true if the message is complete
@@ -94,7 +94,6 @@ This handler will be called twice: one for the request headers and one for the r
94
94
  - headers: dict - The headers of the request
95
95
  - user_agent: str - The user agent of the request
96
96
  - content_encoding: str - The content encoding of the request
97
- - has_begun: bool - It's true if the request has begun
98
97
  - body: bytes - The body of the request
99
98
  - headers_complete: bool - It's true if the headers are complete
100
99
  - message_complete: bool - It's true if the message is complete
@@ -122,7 +121,6 @@ This handler will be called twice: one for the response headers and one for the
122
121
  - headers: dict - The headers of the response
123
122
  - user_agent: str - The user agent of the response
124
123
  - content_encoding: str - The content encoding of the response
125
- - has_begun: bool - It's true if the response has begun
126
124
  - body: bytes - The body of the response
127
125
  - headers_complete: bool - It's true if the headers are complete
128
126
  - message_complete: bool - It's true if the message is complete
@@ -0,0 +1,5 @@
1
+
2
+ __version__ = "3.1.0" if "{" not in "3.1.0" else "0.0.0"
3
+
4
+ #Exported functions
5
+ __all__ = []
@@ -75,12 +75,9 @@ def handle_packet(glob: dict) -> None:
75
75
 
76
76
  cache_call = {} # Cache of the data handler calls
77
77
  cache_call[RawPacket] = internal_data.current_pkt
78
-
79
- final_result = Action.ACCEPT
78
+
80
79
  result = PacketHandlerResult(glob)
81
80
 
82
- func_name = None
83
- mangled_packet = None
84
81
  for filter in internal_data.filter_call_info:
85
82
  final_params = []
86
83
  skip_call = False
@@ -116,24 +113,37 @@ def handle_packet(glob: dict) -> None:
116
113
  if skip_call:
117
114
  continue
118
115
 
119
- res = context_call(glob, filter.func, *final_params)
120
-
121
- if res is None:
122
- continue #ACCEPTED
123
- if not isinstance(res, Action):
124
- raise Exception(f"Invalid return type {type(res)} for function {filter.name}")
125
- if res == Action.MANGLE:
126
- mangled_packet = internal_data.current_pkt.raw_packet
127
- if res != Action.ACCEPT:
128
- func_name = filter.name
129
- final_result = res
130
- break
131
-
132
- result.action = final_result
133
- result.matched_by = func_name
134
- result.mangled_packet = mangled_packet
116
+ # Create an iterator with all the calls to be done
117
+ def try_to_call(params:list):
118
+ is_base_call = True
119
+ for i in range(len(params)):
120
+ if isinstance(params[i], list):
121
+ new_params = params.copy()
122
+ for ele in params[i]:
123
+ new_params[i] = ele
124
+ for ele in try_to_call(new_params):
125
+ yield ele
126
+ is_base_call = False
127
+ break
128
+ if is_base_call:
129
+ yield context_call(glob, filter.func, *params)
130
+
131
+ for res in try_to_call(final_params):
132
+ if res is None:
133
+ continue #ACCEPTED
134
+ if not isinstance(res, Action):
135
+ raise Exception(f"Invalid return type {type(res)} for function {filter.name}")
136
+ if res == Action.MANGLE:
137
+ result.matched_by = filter.name
138
+ result.mangled_packet = internal_data.current_pkt.raw_packet
139
+ result.action = Action.MANGLE
140
+ elif res != Action.ACCEPT:
141
+ result.matched_by = filter.name
142
+ result.action = res
143
+ result.mangled_packet = None
144
+ return result.set_result()
135
145
 
136
- return result.set_result()
146
+ return result.set_result() # Will be MANGLE or ACCEPT
137
147
 
138
148
 
139
149
  def compile(glob:dict) -> None:
@@ -148,13 +158,12 @@ def compile(glob:dict) -> None:
148
158
 
149
159
  if "FGEX_STREAM_MAX_SIZE" in glob and int(glob["FGEX_STREAM_MAX_SIZE"]) > 0:
150
160
  internal_data.stream_max_size = int(glob["FGEX_STREAM_MAX_SIZE"])
151
- else:
152
- internal_data.stream_max_size = 1*8e20 # 1MB default value
153
161
 
154
162
  if "FGEX_FULL_STREAM_ACTION" in glob and isinstance(glob["FGEX_FULL_STREAM_ACTION"], FullStreamAction):
155
163
  internal_data.full_stream_action = glob["FGEX_FULL_STREAM_ACTION"]
156
- else:
157
- internal_data.full_stream_action = FullStreamAction.FLUSH
164
+
165
+ if "FGEX_INVALID_ENCODING_ACTION" in glob and isinstance(glob["FGEX_INVALID_ENCODING_ACTION"], Action):
166
+ internal_data.invalid_encoding_action = glob["FGEX_INVALID_ENCODING_ACTION"]
158
167
 
159
168
  PacketHandlerResult(glob).reset_result()
160
169
 
@@ -1,5 +1,5 @@
1
1
  from firegex.nfproxy.internals.models import FilterHandler
2
- from firegex.nfproxy.internals.models import FullStreamAction
2
+ from firegex.nfproxy.internals.models import FullStreamAction, ExceptionAction
3
3
 
4
4
  class RawPacket:
5
5
  "class rapresentation of the nfqueue packet sent in python context by the c++ core"
@@ -120,23 +120,39 @@ class DataStreamCtx:
120
120
  @property
121
121
  def stream_max_size(self) -> int:
122
122
  if "stream_max_size" not in self.__data.keys():
123
- self.__data["stream_max_size"] = 1*8e20
123
+ self.__data["stream_max_size"] = 1*8e20 # 1MB default value
124
124
  return self.__data.get("stream_max_size")
125
125
 
126
126
  @stream_max_size.setter
127
127
  def stream_max_size(self, v: int):
128
+ if not isinstance(v, int):
129
+ raise Exception("Invalid data type, data MUST be of type int")
128
130
  self.__data["stream_max_size"] = v
129
131
 
130
132
  @property
131
133
  def full_stream_action(self) -> FullStreamAction:
132
134
  if "full_stream_action" not in self.__data.keys():
133
- self.__data["full_stream_action"] = "flush"
135
+ self.__data["full_stream_action"] = FullStreamAction.FLUSH
134
136
  return self.__data.get("full_stream_action")
135
137
 
136
138
  @full_stream_action.setter
137
139
  def full_stream_action(self, v: FullStreamAction):
140
+ if not isinstance(v, FullStreamAction):
141
+ raise Exception("Invalid data type, data MUST be of type FullStreamAction")
138
142
  self.__data["full_stream_action"] = v
139
143
 
144
+ @property
145
+ def invalid_encoding_action(self) -> ExceptionAction:
146
+ if "invalid_encoding_action" not in self.__data.keys():
147
+ self.__data["invalid_encoding_action"] = ExceptionAction.REJECT
148
+ return self.__data.get("invalid_encoding_action")
149
+
150
+ @invalid_encoding_action.setter
151
+ def invalid_encoding_action(self, v: ExceptionAction):
152
+ if not isinstance(v, ExceptionAction):
153
+ raise Exception("Invalid data type, data MUST be of type ExceptionAction")
154
+ self.__data["invalid_encoding_action"] = v
155
+
140
156
  @property
141
157
  def data_handler_context(self) -> dict:
142
158
  if "data_handler_context" not in self.__data.keys():
@@ -13,3 +13,4 @@ class RejectConnection(Exception):
13
13
 
14
14
  class StreamFullReject(Exception):
15
15
  "raise this exception if you want to reject the connection due to full stream"
16
+
@@ -8,6 +8,13 @@ class Action(Enum):
8
8
  REJECT = 2
9
9
  MANGLE = 3
10
10
 
11
+ class ExceptionAction(Enum):
12
+ """Action to be taken by the filter when an exception occurs (used in some cases)"""
13
+ ACCEPT = 0
14
+ DROP = 1
15
+ REJECT = 2
16
+ NOACTION = 3
17
+
11
18
  class FullStreamAction(Enum):
12
19
  """Action to be taken by the filter when the stream is full"""
13
20
  FLUSH = 0
@@ -40,5 +47,3 @@ class PacketHandlerResult:
40
47
 
41
48
  def reset_result(self) -> None:
42
49
  self.glob["__firegex_pyfilter_result"] = None
43
-
44
-
@@ -0,0 +1,422 @@
1
+ import pyllhttp
2
+ from firegex.nfproxy.internals.exceptions import NotReadyToRun
3
+ from firegex.nfproxy.internals.data import DataStreamCtx
4
+ from firegex.nfproxy.internals.exceptions import StreamFullDrop, StreamFullReject, RejectConnection, DropPacket
5
+ from firegex.nfproxy.internals.models import FullStreamAction, ExceptionAction
6
+ from dataclasses import dataclass, field
7
+ from collections import deque
8
+ from typing import Type
9
+
10
+ @dataclass
11
+ class InternalHTTPMessage:
12
+ """Internal class to handle HTTP messages"""
13
+ url: str|None = field(default=None)
14
+ headers: dict[str, str] = field(default_factory=dict)
15
+ lheaders: dict[str, str] = field(default_factory=dict) # lowercase copy of the headers
16
+ body: bytes|None = field(default=None)
17
+ headers_complete: bool = field(default=False)
18
+ message_complete: bool = field(default=False)
19
+ status: str|None = field(default=None)
20
+ total_size: int = field(default=0)
21
+ user_agent: str = field(default_factory=str)
22
+ content_encoding: str = field(default=str)
23
+ content_type: str = field(default=str)
24
+ keep_alive: bool = field(default=False)
25
+ should_upgrade: bool = field(default=False)
26
+ http_version: str = field(default=str)
27
+ method: str = field(default=str)
28
+ content_length: int = field(default=0)
29
+ stream: bytes = field(default_factory=bytes)
30
+
31
+ @dataclass
32
+ class InternalHttpBuffer:
33
+ """Internal class to handle HTTP messages"""
34
+ _url_buffer: bytes = field(default_factory=bytes)
35
+ _header_fields: dict[bytes, bytes] = field(default_factory=dict)
36
+ _body_buffer: bytes = field(default_factory=bytes)
37
+ _status_buffer: bytes = field(default_factory=bytes)
38
+ _current_header_field: bytes = field(default_factory=bytes)
39
+ _current_header_value: bytes = field(default_factory=bytes)
40
+
41
+ class InternalCallbackHandler():
42
+
43
+ buffers = InternalHttpBuffer()
44
+ msg = InternalHTTPMessage()
45
+ save_body = True
46
+ raised_error = False
47
+ has_begun = False
48
+ messages: deque[InternalHTTPMessage] = deque()
49
+
50
+ def reset_data(self):
51
+ self.msg = InternalHTTPMessage()
52
+ self.buffers = InternalHttpBuffer()
53
+ self.messages.clear()
54
+
55
+ def on_message_begin(self):
56
+ self.buffers = InternalHttpBuffer()
57
+ self.msg = InternalHTTPMessage()
58
+ self.has_begun = True
59
+
60
+ def on_url(self, url):
61
+ self.buffers._url_buffer += url
62
+ self.msg.total_size += len(url)
63
+
64
+ def on_url_complete(self):
65
+ self.msg.url = self.buffers._url_buffer.decode(errors="ignore")
66
+ self.buffers._url_buffer = b""
67
+
68
+ def on_status(self, status: bytes):
69
+ self.msg.total_size += len(status)
70
+ self.buffers._status_buffer += status
71
+
72
+ def on_status_complete(self):
73
+ self.msg.status = self.buffers._status_buffer.decode(errors="ignore")
74
+ self.buffers._status_buffer = b""
75
+
76
+ def on_header_field(self, field):
77
+ self.msg.total_size += len(field)
78
+ self.buffers._current_header_field += field
79
+
80
+ def on_header_field_complete(self):
81
+ pass # Nothing to do
82
+
83
+ def on_header_value(self, value):
84
+ self.msg.total_size += len(value)
85
+ self.buffers._current_header_value += value
86
+
87
+ def on_header_value_complete(self):
88
+ if self.buffers._current_header_field:
89
+ self.buffers._header_fields[self.buffers._current_header_field.decode(errors="ignore")] = self.buffers._current_header_value.decode(errors="ignore")
90
+ self.buffers._current_header_field = b""
91
+ self.buffers._current_header_value = b""
92
+
93
+ def on_headers_complete(self):
94
+ self.msg.headers = self.buffers._header_fields
95
+ self.msg.lheaders = {k.lower(): v for k, v in self.buffers._header_fields.items()}
96
+ self.buffers._header_fields = {}
97
+ self.buffers._current_header_field = b""
98
+ self.buffers._current_header_value = b""
99
+ self.msg.headers_complete = True
100
+ self.msg.method = self.method_parsed
101
+ self.msg.content_length = self.content_length_parsed
102
+ self.msg.should_upgrade = self.should_upgrade
103
+ self.msg.keep_alive = self.keep_alive
104
+ self.msg.http_version = self.http_version
105
+ self.msg.content_type = self.content_type
106
+ self.msg.content_encoding = self.content_encoding
107
+ self.msg.user_agent = self.user_agent
108
+
109
+ def on_body(self, body: bytes):
110
+ if self.save_body:
111
+ self.msg.total_size += len(body)
112
+ self.buffers._body_buffer += body
113
+
114
+ def on_message_complete(self):
115
+ self.msg.body = self.buffers._body_buffer
116
+ self.buffers._body_buffer = b""
117
+ try:
118
+ if "gzip" in self.content_encoding.lower():
119
+ import gzip
120
+ import io
121
+ with gzip.GzipFile(fileobj=io.BytesIO(self.msg.body)) as f:
122
+ self.msg.body = f.read()
123
+ except Exception as e:
124
+ print(f"Error decompressing gzip: {e}: skipping", flush=True)
125
+ self.msg.message_complete = True
126
+ self.has_begun = False
127
+ if not self._packet_to_stream():
128
+ self.messages.append(self.msg)
129
+
130
+ @property
131
+ def user_agent(self) -> str:
132
+ return self.msg.lheaders.get("user-agent", "")
133
+
134
+ @property
135
+ def content_encoding(self) -> str:
136
+ return self.msg.lheaders.get("content-encoding", "")
137
+
138
+ @property
139
+ def content_type(self) -> str:
140
+ return self.msg.lheaders.get("content-type", "")
141
+
142
+ @property
143
+ def keep_alive(self) -> bool:
144
+ return self.should_keep_alive
145
+
146
+ @property
147
+ def should_upgrade(self) -> bool:
148
+ return self.is_upgrading
149
+
150
+ @property
151
+ def http_version(self) -> str:
152
+ if self.major and self.minor:
153
+ return f"{self.major}.{self.minor}"
154
+ else:
155
+ return ""
156
+
157
+ @property
158
+ def method_parsed(self) -> str:
159
+ return self.method
160
+
161
+ @property
162
+ def total_size(self) -> int:
163
+ """Total size used by the parser"""
164
+ tot = self.msg.total_size
165
+ for msg in self.messages:
166
+ tot += msg.total_size
167
+ return tot
168
+
169
+ @property
170
+ def content_length_parsed(self) -> int:
171
+ return self.content_length
172
+
173
+ def _packet_to_stream(self):
174
+ return self.should_upgrade and self.save_body
175
+
176
+ def parse_data(self, data: bytes):
177
+ if self._packet_to_stream(): # This is a websocket upgrade!
178
+ self.msg.message_complete = True # The message is complete but becomed a stream, so need to be called every time a new packet is received
179
+ self.msg.total_size += len(data)
180
+ self.msg.stream += data #buffering stream
181
+ else:
182
+ try:
183
+ self.execute(data)
184
+ except Exception as e:
185
+ self.raised_error = True
186
+ print(f"Error parsing HTTP packet: {e} with data {data}", flush=True)
187
+ raise e
188
+
189
+ def pop_message(self):
190
+ return self.messages.popleft()
191
+
192
+ def __repr__(self):
193
+ 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}>"
194
+
195
+
196
+ class InternalHttpRequest(InternalCallbackHandler, pyllhttp.Request):
197
+ def __init__(self):
198
+ super(InternalCallbackHandler, self).__init__()
199
+ super(pyllhttp.Request, self).__init__()
200
+
201
+ class InternalHttpResponse(InternalCallbackHandler, pyllhttp.Response):
202
+ def __init__(self):
203
+ super(InternalCallbackHandler, self).__init__()
204
+ super(pyllhttp.Response, self).__init__()
205
+
206
+ class InternalBasicHttpMetaClass:
207
+ """Internal class to handle HTTP requests and responses"""
208
+
209
+ def __init__(self, parser: InternalHttpRequest|InternalHttpResponse, msg: InternalHTTPMessage):
210
+ self._parser = parser
211
+ self.stream = b""
212
+ self.raised_error = False
213
+ self._message: InternalHTTPMessage|None = msg
214
+ self._contructor_hook()
215
+
216
+ def _contructor_hook(self):
217
+ pass
218
+
219
+ @property
220
+ def total_size(self) -> int:
221
+ """Total size of the stream"""
222
+ return self._parser.total_size
223
+
224
+ @property
225
+ def url(self) -> str|None:
226
+ """URL of the message"""
227
+ return self._message.url
228
+
229
+ @property
230
+ def headers(self) -> dict[str, str]:
231
+ """Headers of the message"""
232
+ return self._message.headers
233
+
234
+ @property
235
+ def user_agent(self) -> str:
236
+ """User agent of the message"""
237
+ return self._message.user_agent
238
+
239
+ @property
240
+ def content_encoding(self) -> str:
241
+ """Content encoding of the message"""
242
+ return self._message.content_encoding
243
+
244
+ @property
245
+ def body(self) -> bytes:
246
+ """Body of the message"""
247
+ return self._message.body
248
+
249
+ @property
250
+ def headers_complete(self) -> bool:
251
+ """If the headers are complete"""
252
+ return self._message.headers_complete
253
+
254
+ @property
255
+ def message_complete(self) -> bool:
256
+ """If the message is complete"""
257
+ return self._message.message_complete
258
+
259
+ @property
260
+ def http_version(self) -> str:
261
+ """HTTP version of the message"""
262
+ return self._message.http_version
263
+
264
+ @property
265
+ def keep_alive(self) -> bool:
266
+ """If the message should keep alive"""
267
+ return self._message.keep_alive
268
+
269
+ @property
270
+ def should_upgrade(self) -> bool:
271
+ """If the message should upgrade"""
272
+ return self._message.should_upgrade
273
+
274
+ @property
275
+ def content_length(self) -> int|None:
276
+ """Content length of the message"""
277
+ return self._message.content_length
278
+
279
+ def get_header(self, header: str, default=None) -> str:
280
+ """Get a header from the message without caring about the case"""
281
+ return self._message.lheaders.get(header.lower(), default)
282
+
283
+ @staticmethod
284
+ def _associated_parser_class() -> Type[InternalHttpRequest]|Type[InternalHttpResponse]:
285
+ raise NotImplementedError()
286
+
287
+ @staticmethod
288
+ def _before_fetch_callable_checks(internal_data: DataStreamCtx):
289
+ return True
290
+
291
+ @classmethod
292
+ def _fetch_packet(cls, internal_data: DataStreamCtx):
293
+ if internal_data.current_pkt is None or internal_data.current_pkt.is_tcp is False:
294
+ raise NotReadyToRun()
295
+
296
+ ParserType = cls._associated_parser_class()
297
+
298
+ parser = internal_data.data_handler_context.get(cls, None)
299
+ if parser is None or parser.raised_error:
300
+ parser: InternalHttpRequest|InternalHttpResponse = ParserType()
301
+ internal_data.data_handler_context[cls] = parser
302
+
303
+ if not cls._before_fetch_callable_checks(internal_data):
304
+ raise NotReadyToRun()
305
+
306
+ # Memory size managment
307
+ if parser.total_size+len(internal_data.current_pkt.data) > internal_data.stream_max_size:
308
+ match internal_data.full_stream_action:
309
+ case FullStreamAction.FLUSH:
310
+ # Deleting parser and re-creating it
311
+ parser.messages.clear()
312
+ parser.msg.total_size -= len(parser.msg.stream)
313
+ parser.msg.stream = b""
314
+ parser.msg.total_size -= len(parser.msg.body)
315
+ parser.msg.body = b""
316
+ print("[WARNING] Flushing stream", flush=True)
317
+ if parser.total_size+len(internal_data.current_pkt.data) > internal_data.stream_max_size:
318
+ parser.reset_data()
319
+ case FullStreamAction.REJECT:
320
+ raise StreamFullReject()
321
+ case FullStreamAction.DROP:
322
+ raise StreamFullDrop()
323
+ case FullStreamAction.ACCEPT:
324
+ raise NotReadyToRun()
325
+
326
+ headers_were_set = parser.msg.headers_complete
327
+ try:
328
+ parser.parse_data(internal_data.current_pkt.data)
329
+ except Exception as e:
330
+ match internal_data.invalid_encoding_action:
331
+ case ExceptionAction.REJECT:
332
+ raise RejectConnection()
333
+ case ExceptionAction.DROP:
334
+ raise DropPacket()
335
+ case ExceptionAction.NOACTION:
336
+ raise e
337
+ case ExceptionAction.ACCEPT:
338
+ raise NotReadyToRun()
339
+
340
+ messages_tosend:list[InternalHTTPMessage] = []
341
+ for i in range(len(parser.messages)):
342
+ messages_tosend.append(parser.pop_message())
343
+
344
+ if len(messages_tosend) > 0:
345
+ headers_were_set = False # New messages completed so the current message headers were not set in this case
346
+
347
+ if not headers_were_set and parser.msg.headers_complete:
348
+ messages_tosend.append(parser.msg) # Also the current message needs to be sent due to complete headers
349
+
350
+ if headers_were_set and parser.msg.message_complete and parser.msg.should_upgrade and parser.save_body:
351
+ messages_tosend.append(parser.msg) # Also the current message needs to beacase a websocket stream is going on
352
+
353
+ messages_to_call = len(messages_tosend)
354
+
355
+ if messages_to_call == 0:
356
+ raise NotReadyToRun()
357
+ elif messages_to_call == 1:
358
+ return cls(parser, messages_tosend[0])
359
+
360
+ return [cls(parser, ele) for ele in messages_tosend]
361
+
362
+ class HttpRequest(InternalBasicHttpMetaClass):
363
+ """
364
+ HTTP Request handler
365
+ This data handler will be called twice, first with the headers complete, and second with the body complete
366
+ """
367
+
368
+ @staticmethod
369
+ def _associated_parser_class() -> Type[InternalHttpRequest]:
370
+ return InternalHttpRequest
371
+
372
+ @staticmethod
373
+ def _before_fetch_callable_checks(internal_data: DataStreamCtx):
374
+ return internal_data.current_pkt.is_input
375
+
376
+ @property
377
+ def method(self) -> bytes:
378
+ """Method of the request"""
379
+ return self._parser.msg.method
380
+
381
+ def __repr__(self):
382
+ return f"<HttpRequest method={self.method} url={self.url} headers={self.headers} body={self.body} 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}>"
383
+
384
+ class HttpResponse(InternalBasicHttpMetaClass):
385
+ """
386
+ HTTP Response handler
387
+ This data handler will be called twice, first with the headers complete, and second with the body complete
388
+ """
389
+
390
+ @staticmethod
391
+ def _associated_parser_class() -> Type[InternalHttpResponse]:
392
+ return InternalHttpResponse
393
+
394
+ @staticmethod
395
+ def _before_fetch_callable_checks(internal_data: DataStreamCtx):
396
+ return not internal_data.current_pkt.is_input
397
+
398
+ @property
399
+ def status_code(self) -> int:
400
+ """Status code of the response"""
401
+ return self._parser.msg.status
402
+
403
+ def __repr__(self):
404
+ return f"<HttpResponse status_code={self.status_code} url={self.url} headers={self.headers} body={self.body} 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}>"
405
+
406
+ class HttpRequestHeader(HttpRequest):
407
+ """
408
+ HTTP Request Header handler
409
+ This data handler will be called only once, the headers are complete, the body will be empty and not buffered
410
+ """
411
+
412
+ def _contructor_hook(self):
413
+ self._parser.save_body = False
414
+
415
+ class HttpResponseHeader(HttpResponse):
416
+ """
417
+ HTTP Response Header handler
418
+ This data handler will be called only once, the headers are complete, the body will be empty and not buffered
419
+ """
420
+
421
+ def _contructor_hook(self):
422
+ self._parser.save_body = False
@@ -71,7 +71,7 @@ class TCPInputStream(InternalTCPStream):
71
71
 
72
72
  TCPClientStream = TCPInputStream
73
73
 
74
- class TCPOutputStream:
74
+ class TCPOutputStream(InternalTCPStream):
75
75
  """
76
76
  This datamodel will assemble the TCP output stream from the server sent data.
77
77
  The function that use this data model will be handled when:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: firegex
3
- Version: 3.0.0
3
+ Version: 3.1.0
4
4
  Summary: Firegex client
5
5
  Home-page: https://github.com/pwnzer0tt1/firegex
6
6
  Author: Pwnzer0tt1
@@ -122,7 +122,6 @@ This handler will be called twice: one for the request headers and one for the r
122
122
  - headers: dict - The headers of the request
123
123
  - user_agent: str - The user agent of the request
124
124
  - content_encoding: str - The content encoding of the request
125
- - has_begun: bool - It's true if the request has begun
126
125
  - body: bytes - The body of the request
127
126
  - headers_complete: bool - It's true if the headers are complete
128
127
  - message_complete: bool - It's true if the message is complete
@@ -150,7 +149,6 @@ This handler will be called twice: one for the response headers and one for the
150
149
  - headers: dict - The headers of the response
151
150
  - user_agent: str - The user agent of the response
152
151
  - content_encoding: str - The content encoding of the response
153
- - has_begun: bool - It's true if the response has begun
154
152
  - body: bytes - The body of the response
155
153
  - headers_complete: bool - It's true if the headers are complete
156
154
  - message_complete: bool - It's true if the message is complete
@@ -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.0.0"
9
+ VERSION = "3.1.0"
10
10
 
11
11
  setuptools.setup(
12
12
  name="firegex",
@@ -1,5 +0,0 @@
1
-
2
- __version__ = "3.0.0" if "{" not in "3.0.0" else "0.0.0"
3
-
4
- #Exported functions
5
- __all__ = []
@@ -1,357 +0,0 @@
1
- import pyllhttp
2
- from firegex.nfproxy.internals.exceptions import NotReadyToRun
3
- from firegex.nfproxy.internals.data import DataStreamCtx
4
- from firegex.nfproxy.internals.exceptions import StreamFullDrop, StreamFullReject
5
- from firegex.nfproxy.internals.models import FullStreamAction
6
-
7
- class InternalCallbackHandler():
8
-
9
- url: str|None = None
10
- _url_buffer: bytes = b""
11
- headers: dict[str, str] = {}
12
- lheaders: dict[str, str] = {} # Lowercase headers
13
- _header_fields: dict[bytes, bytes] = {}
14
- has_begun: bool = False
15
- body: bytes = None
16
- _body_buffer: bytes = b""
17
- headers_complete: bool = False
18
- message_complete: bool = False
19
- status: str|None = None
20
- _status_buffer: bytes = b""
21
- _current_header_field = b""
22
- _current_header_value = b""
23
- _save_body = True
24
- total_size = 0
25
-
26
- def on_message_begin(self):
27
- self.has_begun = True
28
-
29
- def on_url(self, url):
30
- self.total_size += len(url)
31
- self._url_buffer += url
32
-
33
- def on_url_complete(self):
34
- self.url = self._url_buffer.decode(errors="ignore")
35
- self._url_buffer = None
36
-
37
- def on_header_field(self, field):
38
- self.total_size += len(field)
39
- self._current_header_field += field
40
-
41
- def on_header_field_complete(self):
42
- self._current_header_field = self._current_header_field
43
-
44
- def on_header_value(self, value):
45
- self.total_size += len(value)
46
- self._current_header_value += value
47
-
48
- def on_header_value_complete(self):
49
- if self._current_header_value is not None and self._current_header_field is not None:
50
- self._header_fields[self._current_header_field.decode(errors="ignore")] = self._current_header_value.decode(errors="ignore")
51
- self._current_header_field = b""
52
- self._current_header_value = b""
53
-
54
- def on_headers_complete(self):
55
- self.headers_complete = True
56
- self.headers = self._header_fields
57
- self.lheaders = {k.lower(): v for k, v in self._header_fields.items()}
58
- self._header_fields = {}
59
- self._current_header_field = b""
60
- self._current_header_value = b""
61
-
62
- def on_body(self, body: bytes):
63
- if self._save_body:
64
- self.total_size += len(body)
65
- self._body_buffer += body
66
-
67
- def on_message_complete(self):
68
- self.body = self._body_buffer
69
- self._body_buffer = b""
70
- try:
71
- if "gzip" in self.content_encoding.lower():
72
- import gzip
73
- import io
74
- with gzip.GzipFile(fileobj=io.BytesIO(self.body)) as f:
75
- self.body = f.read()
76
- except Exception as e:
77
- print(f"Error decompressing gzip: {e}: skipping", flush=True)
78
- self.message_complete = True
79
-
80
- def on_status(self, status: bytes):
81
- self.total_size += len(status)
82
- self._status_buffer += status
83
-
84
- def on_status_complete(self):
85
- self.status = self._status_buffer.decode(errors="ignore")
86
- self._status_buffer = b""
87
-
88
- @property
89
- def user_agent(self) -> str:
90
- return self.lheaders.get("user-agent", "")
91
-
92
- @property
93
- def content_encoding(self) -> str:
94
- return self.lheaders.get("content-encoding", "")
95
-
96
- @property
97
- def content_type(self) -> str:
98
- return self.lheaders.get("content-type", "")
99
-
100
- @property
101
- def keep_alive(self) -> bool:
102
- return self.should_keep_alive
103
-
104
- @property
105
- def should_upgrade(self) -> bool:
106
- return self.is_upgrading
107
-
108
- @property
109
- def http_version(self) -> str:
110
- return f"{self.major}.{self.minor}"
111
-
112
- @property
113
- def method_parsed(self) -> str:
114
- return self.method.decode(errors="ignore")
115
-
116
- @property
117
- def content_length_parsed(self) -> int:
118
- return self.content_length
119
-
120
-
121
- class InternalHttpRequest(InternalCallbackHandler, pyllhttp.Request):
122
- def __init__(self):
123
- super(InternalCallbackHandler, self).__init__()
124
- super(pyllhttp.Request, self).__init__()
125
-
126
- class InternalHttpResponse(InternalCallbackHandler, pyllhttp.Response):
127
- def __init__(self):
128
- super(InternalCallbackHandler, self).__init__()
129
- super(pyllhttp.Response, self).__init__()
130
-
131
- class InternalBasicHttpMetaClass:
132
- """Internal class to handle HTTP requests and responses"""
133
-
134
- def __init__(self):
135
- self._parser: InternalHttpRequest|InternalHttpResponse
136
- self._headers_were_set = False
137
- self.stream = b""
138
- self.raised_error = False
139
-
140
- @property
141
- def total_size(self) -> int:
142
- """Total size of the stream"""
143
- return self._parser.total_size
144
-
145
- @property
146
- def url(self) -> str|None:
147
- """URL of the message"""
148
- return self._parser.url
149
-
150
- @property
151
- def headers(self) -> dict[str, str]:
152
- """Headers of the message"""
153
- return self._parser.headers
154
-
155
- @property
156
- def user_agent(self) -> str:
157
- """User agent of the message"""
158
- return self._parser.user_agent
159
-
160
- @property
161
- def content_encoding(self) -> str:
162
- """Content encoding of the message"""
163
- return self._parser.content_encoding
164
-
165
- @property
166
- def has_begun(self) -> bool:
167
- """If the message has begun"""
168
- return self._parser.has_begun
169
-
170
- @property
171
- def body(self) -> bytes:
172
- """Body of the message"""
173
- return self._parser.body
174
-
175
- @property
176
- def headers_complete(self) -> bool:
177
- """If the headers are complete"""
178
- return self._parser.headers_complete
179
-
180
- @property
181
- def message_complete(self) -> bool:
182
- """If the message is complete"""
183
- return self._parser.message_complete
184
-
185
- @property
186
- def http_version(self) -> str:
187
- """HTTP version of the message"""
188
- return self._parser.http_version
189
-
190
- @property
191
- def keep_alive(self) -> bool:
192
- """If the message should keep alive"""
193
- return self._parser.keep_alive
194
-
195
- @property
196
- def should_upgrade(self) -> bool:
197
- """If the message should upgrade"""
198
- return self._parser.should_upgrade
199
-
200
- @property
201
- def content_length(self) -> int|None:
202
- """Content length of the message"""
203
- return self._parser.content_length_parsed
204
-
205
- def get_header(self, header: str, default=None) -> str:
206
- """Get a header from the message without caring about the case"""
207
- return self._parser.lheaders.get(header.lower(), default)
208
-
209
- def _packet_to_stream(self, internal_data: DataStreamCtx):
210
- return self.should_upgrade and self._parser._save_body
211
-
212
- def _fetch_current_packet(self, internal_data: DataStreamCtx):
213
- if self._packet_to_stream(internal_data): # This is a websocket upgrade!
214
- self._parser.total_size += len(internal_data.current_pkt.data)
215
- self.stream += internal_data.current_pkt.data
216
- else:
217
- try:
218
- self._parser.execute(internal_data.current_pkt.data)
219
- if not self._parser.message_complete and self._parser.headers_complete and len(self._parser._body_buffer) == self._parser.content_length_parsed:
220
- self._parser.on_message_complete()
221
- except Exception as e:
222
- self.raised_error = True
223
- print(f"Error parsing HTTP packet: {e} {internal_data.current_pkt}", self, flush=True)
224
- raise e
225
-
226
- #It's called the first time if the headers are complete, and second time with body complete
227
- def _after_fetch_callable_checks(self, internal_data: DataStreamCtx):
228
- if self._parser.headers_complete and not self._headers_were_set:
229
- self._headers_were_set = True
230
- return True
231
- return self._parser.message_complete or self.should_upgrade
232
-
233
- def _before_fetch_callable_checks(self, internal_data: DataStreamCtx):
234
- return True
235
-
236
- def _trigger_remove_data(self, internal_data: DataStreamCtx):
237
- return self.message_complete and not self.should_upgrade
238
-
239
- @classmethod
240
- def _fetch_packet(cls, internal_data: DataStreamCtx):
241
- if internal_data.current_pkt is None or internal_data.current_pkt.is_tcp is False:
242
- raise NotReadyToRun()
243
-
244
- datahandler:InternalBasicHttpMetaClass = internal_data.data_handler_context.get(cls, None)
245
- if datahandler is None or datahandler.raised_error:
246
- datahandler = cls()
247
- internal_data.data_handler_context[cls] = datahandler
248
-
249
- if not datahandler._before_fetch_callable_checks(internal_data):
250
- raise NotReadyToRun()
251
-
252
- # Memory size managment
253
- if datahandler.total_size+len(internal_data.current_pkt.data) > internal_data.stream_max_size:
254
- match internal_data.full_stream_action:
255
- case FullStreamAction.FLUSH:
256
- datahandler = cls()
257
- internal_data.data_handler_context[cls] = datahandler
258
- case FullStreamAction.REJECT:
259
- raise StreamFullReject()
260
- case FullStreamAction.DROP:
261
- raise StreamFullDrop()
262
- case FullStreamAction.ACCEPT:
263
- raise NotReadyToRun()
264
-
265
- datahandler._fetch_current_packet(internal_data)
266
-
267
- if not datahandler._after_fetch_callable_checks(internal_data):
268
- raise NotReadyToRun()
269
-
270
- if datahandler._trigger_remove_data(internal_data):
271
- if internal_data.data_handler_context.get(cls):
272
- del internal_data.data_handler_context[cls]
273
-
274
- return datahandler
275
-
276
- class HttpRequest(InternalBasicHttpMetaClass):
277
- """
278
- HTTP Request handler
279
- This data handler will be called twice, first with the headers complete, and second with the body complete
280
- """
281
-
282
- def __init__(self):
283
- super().__init__()
284
- # These will be used in the metaclass
285
- self._parser: InternalHttpRequest = InternalHttpRequest()
286
- self._headers_were_set = False
287
-
288
- @property
289
- def method(self) -> bytes:
290
- """Method of the request"""
291
- return self._parser.method_parsed
292
-
293
- def _before_fetch_callable_checks(self, internal_data: DataStreamCtx):
294
- return internal_data.current_pkt.is_input
295
-
296
- def __repr__(self):
297
- return f"<HttpRequest method={self.method} url={self.url} headers={self.headers} body={self.body} 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} has_begun={self.has_begun} content_length={self.content_length} stream={self.stream}>"
298
-
299
- class HttpResponse(InternalBasicHttpMetaClass):
300
- """
301
- HTTP Response handler
302
- This data handler will be called twice, first with the headers complete, and second with the body complete
303
- """
304
-
305
- def __init__(self):
306
- super().__init__()
307
- self._parser: InternalHttpResponse = InternalHttpResponse()
308
- self._headers_were_set = False
309
-
310
- @property
311
- def status_code(self) -> int:
312
- """Status code of the response"""
313
- return self._parser.status
314
-
315
- def _before_fetch_callable_checks(self, internal_data: DataStreamCtx):
316
- return not internal_data.current_pkt.is_input
317
-
318
- def __repr__(self):
319
- return f"<HttpResponse status_code={self.status_code} url={self.url} headers={self.headers} body={self.body} 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} has_begun={self.has_begun} content_length={self.content_length} stream={self.stream}>"
320
-
321
- class HttpRequestHeader(HttpRequest):
322
- """
323
- HTTP Request Header handler
324
- This data handler will be called only once, the headers are complete, the body will be empty and not buffered
325
- """
326
-
327
- def __init__(self):
328
- super().__init__()
329
- self._parser._save_body = False
330
-
331
- def _before_fetch_callable_checks(self, internal_data: DataStreamCtx):
332
- return internal_data.current_pkt.is_input and not self._headers_were_set
333
-
334
- def _after_fetch_callable_checks(self, internal_data: DataStreamCtx):
335
- if self._parser.headers_complete and not self._headers_were_set:
336
- self._headers_were_set = True
337
- return True
338
- return False
339
-
340
- class HttpResponseHeader(HttpResponse):
341
- """
342
- HTTP Response Header handler
343
- This data handler will be called only once, the headers are complete, the body will be empty and not buffered
344
- """
345
-
346
- def __init__(self):
347
- super().__init__()
348
- self._parser._save_body = False
349
-
350
- def _before_fetch_callable_checks(self, internal_data: DataStreamCtx):
351
- return not internal_data.current_pkt.is_input and not self._headers_were_set
352
-
353
- def _after_fetch_callable_checks(self, internal_data: DataStreamCtx):
354
- if self._parser.headers_complete and not self._headers_were_set:
355
- self._headers_were_set = True
356
- return True
357
- return False
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes