firegex 3.0.0__tar.gz → 3.2.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 (27) hide show
  1. {firegex-3.0.0/firegex.egg-info → firegex-3.2.0}/PKG-INFO +4 -3
  2. {firegex-3.0.0 → firegex-3.2.0}/README.md +0 -2
  3. firegex-3.2.0/firegex/__init__.py +5 -0
  4. {firegex-3.0.0 → firegex-3.2.0}/firegex/nfproxy/internals/__init__.py +38 -25
  5. {firegex-3.0.0 → firegex-3.2.0}/firegex/nfproxy/internals/data.py +19 -3
  6. {firegex-3.0.0 → firegex-3.2.0}/firegex/nfproxy/internals/exceptions.py +1 -0
  7. {firegex-3.0.0 → firegex-3.2.0}/firegex/nfproxy/internals/models.py +7 -2
  8. firegex-3.2.0/firegex/nfproxy/models/http.py +591 -0
  9. {firegex-3.0.0 → firegex-3.2.0}/firegex/nfproxy/models/tcp.py +1 -1
  10. {firegex-3.0.0 → firegex-3.2.0/firegex.egg-info}/PKG-INFO +4 -3
  11. {firegex-3.0.0 → firegex-3.2.0}/firegex.egg-info/requires.txt +3 -0
  12. firegex-3.2.0/requirements.txt +9 -0
  13. {firegex-3.0.0 → firegex-3.2.0}/setup.py +1 -1
  14. firegex-3.0.0/firegex/__init__.py +0 -5
  15. firegex-3.0.0/firegex/nfproxy/models/http.py +0 -357
  16. firegex-3.0.0/requirements.txt +0 -6
  17. {firegex-3.0.0 → firegex-3.2.0}/MANIFEST.in +0 -0
  18. {firegex-3.0.0 → firegex-3.2.0}/fgex +0 -0
  19. {firegex-3.0.0 → firegex-3.2.0}/firegex/__main__.py +0 -0
  20. {firegex-3.0.0 → firegex-3.2.0}/firegex/cli.py +0 -0
  21. {firegex-3.0.0 → firegex-3.2.0}/firegex/nfproxy/__init__.py +0 -0
  22. {firegex-3.0.0 → firegex-3.2.0}/firegex/nfproxy/models/__init__.py +0 -0
  23. {firegex-3.0.0 → firegex-3.2.0}/firegex/nfproxy/proxysim/__init__.py +0 -0
  24. {firegex-3.0.0 → firegex-3.2.0}/firegex.egg-info/SOURCES.txt +0 -0
  25. {firegex-3.0.0 → firegex-3.2.0}/firegex.egg-info/dependency_links.txt +0 -0
  26. {firegex-3.0.0 → firegex-3.2.0}/firegex.egg-info/top_level.txt +0 -0
  27. {firegex-3.0.0 → firegex-3.2.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.2.0
4
4
  Summary: Firegex client
5
5
  Home-page: https://github.com/pwnzer0tt1/firegex
6
6
  Author: Pwnzer0tt1
@@ -13,8 +13,11 @@ Description-Content-Type: text/markdown
13
13
  Requires-Dist: typer==0.15.2
14
14
  Requires-Dist: pydantic>=2
15
15
  Requires-Dist: typing-extensions>=4.7.1
16
+ Requires-Dist: zstd
17
+ Requires-Dist: brotli
16
18
  Requires-Dist: watchfiles
17
19
  Requires-Dist: fgex
20
+ Requires-Dist: websockets
18
21
  Requires-Dist: pyllhttp
19
22
  Dynamic: author
20
23
  Dynamic: author-email
@@ -122,7 +125,6 @@ This handler will be called twice: one for the request headers and one for the r
122
125
  - headers: dict - The headers of the request
123
126
  - user_agent: str - The user agent of the request
124
127
  - content_encoding: str - The content encoding of the request
125
- - has_begun: bool - It's true if the request has begun
126
128
  - body: bytes - The body of the request
127
129
  - headers_complete: bool - It's true if the headers are complete
128
130
  - message_complete: bool - It's true if the message is complete
@@ -150,7 +152,6 @@ This handler will be called twice: one for the response headers and one for the
150
152
  - headers: dict - The headers of the response
151
153
  - user_agent: str - The user agent of the response
152
154
  - content_encoding: str - The content encoding of the response
153
- - has_begun: bool - It's true if the response has begun
154
155
  - body: bytes - The body of the response
155
156
  - headers_complete: bool - It's true if the headers are complete
156
157
  - 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.2.0" if "{" not in "3.2.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,36 @@ 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
+ yield from try_to_call(new_params)
125
+ is_base_call = False
126
+ break
127
+ if is_base_call:
128
+ yield context_call(glob, filter.func, *params)
129
+
130
+ for res in try_to_call(final_params):
131
+ if res is None:
132
+ continue #ACCEPTED
133
+ if not isinstance(res, Action):
134
+ raise Exception(f"Invalid return type {type(res)} for function {filter.name}")
135
+ if res == Action.MANGLE:
136
+ result.matched_by = filter.name
137
+ result.mangled_packet = internal_data.current_pkt.raw_packet
138
+ result.action = Action.MANGLE
139
+ elif res != Action.ACCEPT:
140
+ result.matched_by = filter.name
141
+ result.action = res
142
+ result.mangled_packet = None
143
+ return result.set_result()
135
144
 
136
- return result.set_result()
145
+ return result.set_result() # Will be MANGLE or ACCEPT
137
146
 
138
147
 
139
148
  def compile(glob:dict) -> None:
@@ -148,13 +157,17 @@ def compile(glob:dict) -> None:
148
157
 
149
158
  if "FGEX_STREAM_MAX_SIZE" in glob and int(glob["FGEX_STREAM_MAX_SIZE"]) > 0:
150
159
  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
160
 
154
161
  if "FGEX_FULL_STREAM_ACTION" in glob and isinstance(glob["FGEX_FULL_STREAM_ACTION"], FullStreamAction):
155
162
  internal_data.full_stream_action = glob["FGEX_FULL_STREAM_ACTION"]
156
- else:
157
- internal_data.full_stream_action = FullStreamAction.FLUSH
163
+
164
+ if "FGEX_INVALID_ENCODING_ACTION" in glob and isinstance(glob["FGEX_INVALID_ENCODING_ACTION"], Action):
165
+ internal_data.invalid_encoding_action = glob["FGEX_INVALID_ENCODING_ACTION"]
158
166
 
159
167
  PacketHandlerResult(glob).reset_result()
168
+
169
+ def fake_exit(*_a, **_k):
170
+ print("WARNING: This function should not be called", flush=True)
171
+
172
+ glob["exit"] = fake_exit
160
173
 
@@ -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,591 @@
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
+ from zstd import ZSTD_uncompress
10
+ import gzip
11
+ import io
12
+ import zlib
13
+ import brotli
14
+ from websockets.frames import Frame
15
+ from websockets.extensions.permessage_deflate import PerMessageDeflate
16
+ from pyllhttp import PAUSED_H2_UPGRADE, PAUSED_UPGRADE
17
+
18
+ @dataclass
19
+ class InternalHTTPMessage:
20
+ """Internal class to handle HTTP messages"""
21
+ url: str|None = field(default=None)
22
+ headers: dict[str, str] = field(default_factory=dict)
23
+ lheaders: dict[str, str] = field(default_factory=dict) # lowercase copy of the headers
24
+ body: bytes|None = field(default=None)
25
+ body_decoded: bool = field(default=False)
26
+ headers_complete: bool = field(default=False)
27
+ message_complete: bool = field(default=False)
28
+ status: str|None = field(default=None)
29
+ total_size: int = field(default=0)
30
+ user_agent: str = field(default_factory=str)
31
+ content_encoding: str = field(default=str)
32
+ content_type: str = field(default=str)
33
+ keep_alive: bool = field(default=False)
34
+ should_upgrade: bool = field(default=False)
35
+ http_version: str = field(default=str)
36
+ method: str = field(default=str)
37
+ content_length: int = field(default=0)
38
+ stream: bytes = field(default_factory=bytes)
39
+ ws_stream: list[Frame] = field(default_factory=list) # Decoded websocket stream
40
+ upgrading_to_h2: bool = field(default=False)
41
+ upgrading_to_ws: bool = field(default=False)
42
+
43
+ @dataclass
44
+ class InternalHttpBuffer:
45
+ """Internal class to handle HTTP messages"""
46
+ _url_buffer: bytes = field(default_factory=bytes)
47
+ _raw_header_fields: dict[str, str|list[str]] = field(default_factory=dict)
48
+ _header_fields: dict[str, str] = field(default_factory=dict)
49
+ _body_buffer: bytes = field(default_factory=bytes)
50
+ _status_buffer: bytes = field(default_factory=bytes)
51
+ _current_header_field: bytes = field(default_factory=bytes)
52
+ _current_header_value: bytes = field(default_factory=bytes)
53
+ _ws_packet_stream: bytes = field(default_factory=bytes)
54
+
55
+ class InternalCallbackHandler():
56
+
57
+ buffers = InternalHttpBuffer()
58
+ msg = InternalHTTPMessage()
59
+ save_body = True
60
+ raised_error = False
61
+ has_begun = False
62
+ messages: deque[InternalHTTPMessage] = deque()
63
+ _ws_extentions = None
64
+ _ws_raised_error = False
65
+
66
+ def reset_data(self):
67
+ self.msg = InternalHTTPMessage()
68
+ self.buffers = InternalHttpBuffer()
69
+ self.messages.clear()
70
+
71
+ def on_message_begin(self):
72
+ self.buffers = InternalHttpBuffer()
73
+ self.msg = InternalHTTPMessage()
74
+ self.has_begun = True
75
+
76
+ def on_url(self, url):
77
+ self.buffers._url_buffer += url
78
+ self.msg.total_size += len(url)
79
+
80
+ def on_url_complete(self):
81
+ self.msg.url = self.buffers._url_buffer.decode(errors="ignore")
82
+ self.buffers._url_buffer = b""
83
+
84
+ def on_status(self, status: bytes):
85
+ self.msg.total_size += len(status)
86
+ self.buffers._status_buffer += status
87
+
88
+ def on_status_complete(self):
89
+ self.msg.status = self.buffers._status_buffer.decode(errors="ignore")
90
+ self.buffers._status_buffer = b""
91
+
92
+ def on_header_field(self, field):
93
+ self.msg.total_size += len(field)
94
+ self.buffers._current_header_field += field
95
+
96
+ def on_header_field_complete(self):
97
+ pass # Nothing to do
98
+
99
+ def on_header_value(self, value):
100
+ self.msg.total_size += len(value)
101
+ self.buffers._current_header_value += value
102
+
103
+ def on_header_value_complete(self):
104
+ if self.buffers._current_header_field:
105
+ k, v = self.buffers._current_header_field.decode(errors="ignore"), self.buffers._current_header_value.decode(errors="ignore")
106
+ old_value = self.buffers._raw_header_fields.get(k, None)
107
+
108
+ # raw headers are stored as thay were, considering to check changes between headers encoding
109
+ if isinstance(old_value, list):
110
+ old_value.append(v)
111
+ elif isinstance(old_value, str):
112
+ self.buffers._raw_header_fields[k] = [old_value, v]
113
+ else:
114
+ self.buffers._raw_header_fields[k] = v
115
+
116
+ # Decoding headers normally
117
+ kl = k.lower()
118
+ if kl in self.buffers._header_fields:
119
+ self.buffers._header_fields[kl] += f", {v}" # Should be considered as a single list separated by commas as said in the RFC
120
+ else:
121
+ self.buffers._header_fields[kl] = v
122
+
123
+ self.buffers._current_header_field = b""
124
+ self.buffers._current_header_value = b""
125
+
126
+ def on_headers_complete(self):
127
+ self.msg.headers = self.buffers._raw_header_fields
128
+ self.msg.lheaders = self.buffers._header_fields
129
+ self.buffers._raw_header_fields = {}
130
+ self.buffers._current_header_field = b""
131
+ self.buffers._current_header_value = b""
132
+ self.msg.headers_complete = True
133
+ self.msg.method = self.method_parsed
134
+ self.msg.content_length = self.content_length_parsed
135
+ self.msg.should_upgrade = self.should_upgrade
136
+ self.msg.keep_alive = self.keep_alive
137
+ self.msg.http_version = self.http_version
138
+ self.msg.content_type = self.content_type
139
+ self.msg.content_encoding = self.content_encoding
140
+ self.msg.user_agent = self.user_agent
141
+
142
+ def on_body(self, body: bytes):
143
+ if self.save_body:
144
+ self.msg.total_size += len(body)
145
+ self.buffers._body_buffer += body
146
+
147
+ def on_message_complete(self):
148
+ self.msg.body = self.buffers._body_buffer
149
+ self.msg.should_upgrade = self.should_upgrade
150
+ self.buffers._body_buffer = b""
151
+ encodings = [ele.strip() for ele in self.content_encoding.lower().split(",")]
152
+ decode_success = True
153
+ decoding_body = self.msg.body
154
+ for enc in reversed(encodings):
155
+ if not enc:
156
+ continue
157
+ if enc == "deflate":
158
+ try:
159
+ decompress = zlib.decompressobj(-zlib.MAX_WBITS)
160
+ decoding_body = decompress.decompress(decoding_body)
161
+ decoding_body += decompress.flush()
162
+ except Exception as e:
163
+ print(f"Error decompressing deflate: {e}: skipping", flush=True)
164
+ decode_success = False
165
+ break
166
+ elif enc == "br":
167
+ try:
168
+ decoding_body = brotli.decompress(decoding_body)
169
+ except Exception as e:
170
+ print(f"Error decompressing brotli: {e}: skipping", flush=True)
171
+ decode_success = False
172
+ break
173
+ elif enc == "gzip" or enc == "x-gzip": #https://datatracker.ietf.org/doc/html/rfc2616#section-3.5
174
+ try:
175
+ if "gzip" in self.content_encoding.lower():
176
+ with gzip.GzipFile(fileobj=io.BytesIO(decoding_body)) as f:
177
+ decoding_body = f.read()
178
+ except Exception as e:
179
+ print(f"Error decompressing gzip: {e}: skipping", flush=True)
180
+ decode_success = False
181
+ break
182
+ elif enc == "zstd":
183
+ try:
184
+ decoding_body = ZSTD_uncompress(decoding_body)
185
+ except Exception as e:
186
+ print(f"Error decompressing zstd: {e}: skipping", flush=True)
187
+ decode_success = False
188
+ break
189
+ elif enc == "identity":
190
+ 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
+ else:
192
+ decode_success = False
193
+ break
194
+
195
+ if decode_success:
196
+ self.msg.body = decoding_body
197
+ self.msg.body_decoded = True
198
+
199
+ self.msg.message_complete = True
200
+ self.has_begun = False
201
+ if not self._packet_to_stream():
202
+ self.messages.append(self.msg)
203
+
204
+ @property
205
+ def user_agent(self) -> str:
206
+ return self.msg.lheaders.get("user-agent", "")
207
+
208
+ @property
209
+ def content_encoding(self) -> str:
210
+ return self.msg.lheaders.get("content-encoding", "")
211
+
212
+ @property
213
+ def content_type(self) -> str:
214
+ return self.msg.lheaders.get("content-type", "")
215
+
216
+ @property
217
+ def keep_alive(self) -> bool:
218
+ return self.should_keep_alive
219
+
220
+ @property
221
+ def should_upgrade(self) -> bool:
222
+ return self.is_upgrading
223
+
224
+ @property
225
+ def http_version(self) -> str:
226
+ if self.major and self.minor:
227
+ return f"{self.major}.{self.minor}"
228
+ else:
229
+ return ""
230
+
231
+ @property
232
+ def method_parsed(self) -> str:
233
+ return self.method
234
+
235
+ @property
236
+ def total_size(self) -> int:
237
+ """Total size used by the parser"""
238
+ tot = self.msg.total_size
239
+ for msg in self.messages:
240
+ tot += msg.total_size
241
+ return tot
242
+
243
+ @property
244
+ def content_length_parsed(self) -> int:
245
+ return self.content_length
246
+
247
+ def _is_input(self) -> bool:
248
+ raise NotImplementedError()
249
+
250
+ def _packet_to_stream(self):
251
+ return self.should_upgrade and self.save_body
252
+
253
+ def _stream_parser(self, data: bytes):
254
+ if self.msg.upgrading_to_ws:
255
+ if self._ws_raised_error:
256
+ self.msg.stream += data
257
+ self.msg.total_size += len(data)
258
+ return
259
+ self.buffers._ws_packet_stream += data
260
+ while True:
261
+ try:
262
+ new_frame, self.buffers._ws_packet_stream = self._parse_websocket_frame(self.buffers._ws_packet_stream)
263
+ except Exception as e:
264
+ self._ws_raised_error = True
265
+ self.msg.stream += self.buffers._ws_packet_stream
266
+ self.buffers._ws_packet_stream = b""
267
+ self.msg.total_size += len(data)
268
+ return
269
+ if new_frame is None:
270
+ break
271
+ self.msg.ws_stream.append(new_frame)
272
+ self.msg.total_size += len(new_frame.data)
273
+ if self.msg.upgrading_to_h2:
274
+ self.msg.total_size += len(data)
275
+ self.msg.stream += data
276
+
277
+ def _parse_websocket_ext(self):
278
+ ext_ws = []
279
+ req_ext = []
280
+ for ele in self.msg.lheaders.get("sec-websocket-extensions", "").split(","):
281
+ for xt in ele.split(";"):
282
+ req_ext.append(xt.strip().lower())
283
+
284
+ for ele in req_ext:
285
+ if ele == "permessage-deflate":
286
+ ext_ws.append(PerMessageDeflate(False, False, 15, 15))
287
+ return ext_ws
288
+
289
+ def _parse_websocket_frame(self, data: bytes) -> tuple[Frame|None, bytes]:
290
+ # mask = is_input
291
+ if self._ws_extentions is None:
292
+ self._ws_extentions = self._parse_websocket_ext()
293
+ read_buffering = bytearray()
294
+ def read_exact(n: int):
295
+ nonlocal read_buffering
296
+ buffer = bytearray(read_buffering)
297
+ while len(buffer) < n:
298
+ data = yield
299
+ if data is None:
300
+ raise RuntimeError("Should not send None to this generator")
301
+ buffer.extend(data)
302
+ new_data = bytes(buffer[:n])
303
+ read_buffering = buffer[n:]
304
+ return new_data
305
+
306
+ parsing = Frame.parse(read_exact, extensions=self._ws_extentions, mask=self._is_input())
307
+ parsing.send(None)
308
+ try:
309
+ parsing.send(bytearray(data))
310
+ except StopIteration as e:
311
+ return e.value, read_buffering
312
+
313
+ return None, read_buffering
314
+
315
+ def parse_data(self, data: bytes):
316
+ if self._packet_to_stream(): # This is a websocket upgrade!
317
+ self._stream_parser(data)
318
+ else:
319
+ try:
320
+ reason, consumed = self.execute(data)
321
+ if reason == PAUSED_UPGRADE:
322
+ self.msg.upgrading_to_ws = True
323
+ self.msg.message_complete = True
324
+ self._stream_parser(data[consumed:])
325
+ elif reason == PAUSED_H2_UPGRADE:
326
+ self.msg.upgrading_to_h2 = True
327
+ self.msg.message_complete = True
328
+ self._stream_parser(data[consumed:])
329
+ except Exception as e:
330
+ self.raised_error = True
331
+ raise e
332
+
333
+ def pop_message(self):
334
+ return self.messages.popleft()
335
+
336
+ def __repr__(self):
337
+ 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}>"
338
+
339
+
340
+ class InternalHttpRequest(InternalCallbackHandler, pyllhttp.Request):
341
+ def __init__(self):
342
+ super(InternalCallbackHandler, self).__init__()
343
+ super(pyllhttp.Request, self).__init__()
344
+
345
+ def _is_input(self):
346
+ return True
347
+
348
+ class InternalHttpResponse(InternalCallbackHandler, pyllhttp.Response):
349
+ def __init__(self):
350
+ super(InternalCallbackHandler, self).__init__()
351
+ super(pyllhttp.Response, self).__init__()
352
+
353
+ def _is_input(self):
354
+ return False
355
+
356
+ class InternalBasicHttpMetaClass:
357
+ """Internal class to handle HTTP requests and responses"""
358
+
359
+ def __init__(self, parser: InternalHttpRequest|InternalHttpResponse, msg: InternalHTTPMessage):
360
+ self._parser = parser
361
+ self.raised_error = False
362
+ self._message: InternalHTTPMessage|None = msg
363
+ self._contructor_hook()
364
+
365
+ def _contructor_hook(self):
366
+ pass
367
+
368
+ @property
369
+ def total_size(self) -> int:
370
+ """Total size of the stream"""
371
+ return self._parser.total_size
372
+
373
+ @property
374
+ def url(self) -> str|None:
375
+ """URL of the message"""
376
+ return self._message.url
377
+
378
+ @property
379
+ def headers(self) -> dict[str, str]:
380
+ """Headers of the message"""
381
+ return self._message.headers
382
+
383
+ @property
384
+ def user_agent(self) -> str:
385
+ """User agent of the message"""
386
+ return self._message.user_agent
387
+
388
+ @property
389
+ def content_encoding(self) -> str:
390
+ """Content encoding of the message"""
391
+ return self._message.content_encoding
392
+
393
+ @property
394
+ def body(self) -> bytes:
395
+ """Body of the message"""
396
+ return self._message.body
397
+
398
+ @property
399
+ def headers_complete(self) -> bool:
400
+ """If the headers are complete"""
401
+ return self._message.headers_complete
402
+
403
+ @property
404
+ def message_complete(self) -> bool:
405
+ """If the message is complete"""
406
+ return self._message.message_complete
407
+
408
+ @property
409
+ def http_version(self) -> str:
410
+ """HTTP version of the message"""
411
+ return self._message.http_version
412
+
413
+ @property
414
+ def keep_alive(self) -> bool:
415
+ """If the message should keep alive"""
416
+ return self._message.keep_alive
417
+
418
+ @property
419
+ def should_upgrade(self) -> bool:
420
+ """If the message should upgrade"""
421
+ return self._parser.should_upgrade
422
+
423
+ @property
424
+ def content_length(self) -> int|None:
425
+ """Content length of the message"""
426
+ return self._message.content_length
427
+
428
+ @property
429
+ def upgrading_to_h2(self) -> bool:
430
+ """If the message is upgrading to HTTP/2"""
431
+ return self._message.upgrading_to_h2
432
+
433
+ @property
434
+ def upgrading_to_ws(self) -> bool:
435
+ """If the message is upgrading to Websocket"""
436
+ return self._message.upgrading_to_ws
437
+
438
+ @property
439
+ def ws_stream(self) -> list[Frame]:
440
+ """Websocket stream"""
441
+ return self._message.ws_stream
442
+
443
+ @property
444
+ def stream(self) -> bytes:
445
+ """Stream of the message"""
446
+ return self._message.stream
447
+
448
+ def get_header(self, header: str, default=None) -> str:
449
+ """Get a header from the message without caring about the case"""
450
+ return self._message.lheaders.get(header.lower(), default)
451
+
452
+ @staticmethod
453
+ def _associated_parser_class() -> Type[InternalHttpRequest]|Type[InternalHttpResponse]:
454
+ raise NotImplementedError()
455
+
456
+ @staticmethod
457
+ def _before_fetch_callable_checks(internal_data: DataStreamCtx):
458
+ return True
459
+
460
+ @classmethod
461
+ def _fetch_packet(cls, internal_data: DataStreamCtx):
462
+ if internal_data.current_pkt is None or internal_data.current_pkt.is_tcp is False:
463
+ raise NotReadyToRun()
464
+
465
+ ParserType = cls._associated_parser_class()
466
+
467
+ parser = internal_data.data_handler_context.get(cls, None)
468
+ if parser is None or parser.raised_error:
469
+ parser: InternalHttpRequest|InternalHttpResponse = ParserType()
470
+ internal_data.data_handler_context[cls] = parser
471
+
472
+ if not cls._before_fetch_callable_checks(internal_data):
473
+ raise NotReadyToRun()
474
+
475
+ # Memory size managment
476
+ if parser.total_size+len(internal_data.current_pkt.data) > internal_data.stream_max_size:
477
+ match internal_data.full_stream_action:
478
+ case FullStreamAction.FLUSH:
479
+ # Deleting parser and re-creating it
480
+ parser.messages.clear()
481
+ parser.msg.total_size -= len(parser.msg.stream)
482
+ parser.msg.stream = b""
483
+ parser.msg.total_size -= len(parser.msg.body)
484
+ parser.msg.body = b""
485
+ print("[WARNING] Flushing stream", flush=True)
486
+ if parser.total_size+len(internal_data.current_pkt.data) > internal_data.stream_max_size:
487
+ parser.reset_data()
488
+ case FullStreamAction.REJECT:
489
+ raise StreamFullReject()
490
+ case FullStreamAction.DROP:
491
+ raise StreamFullDrop()
492
+ case FullStreamAction.ACCEPT:
493
+ raise NotReadyToRun()
494
+
495
+ headers_were_set = parser.msg.headers_complete
496
+ try:
497
+ parser.parse_data(internal_data.current_pkt.data)
498
+ except Exception as e:
499
+ match internal_data.invalid_encoding_action:
500
+ case ExceptionAction.REJECT:
501
+ raise RejectConnection()
502
+ case ExceptionAction.DROP:
503
+ raise DropPacket()
504
+ case ExceptionAction.NOACTION:
505
+ raise e
506
+ case ExceptionAction.ACCEPT:
507
+ raise NotReadyToRun()
508
+
509
+ messages_tosend:list[InternalHTTPMessage] = []
510
+ for i in range(len(parser.messages)):
511
+ messages_tosend.append(parser.pop_message())
512
+
513
+ if len(messages_tosend) > 0:
514
+ headers_were_set = False # New messages completed so the current message headers were not set in this case
515
+
516
+ if not headers_were_set and parser.msg.headers_complete:
517
+ messages_tosend.append(parser.msg) # Also the current message needs to be sent due to complete headers
518
+
519
+ if parser._packet_to_stream():
520
+ messages_tosend.append(parser.msg) # Also the current message needs to beacase a stream is going on
521
+
522
+ messages_to_call = len(messages_tosend)
523
+
524
+ if messages_to_call == 0:
525
+ raise NotReadyToRun()
526
+ elif messages_to_call == 1:
527
+ return cls(parser, messages_tosend[0])
528
+
529
+ return [cls(parser, ele) for ele in messages_tosend]
530
+
531
+ class HttpRequest(InternalBasicHttpMetaClass):
532
+ """
533
+ HTTP Request handler
534
+ This data handler will be called twice, first with the headers complete, and second with the body complete
535
+ """
536
+
537
+ @staticmethod
538
+ def _associated_parser_class() -> Type[InternalHttpRequest]:
539
+ return InternalHttpRequest
540
+
541
+ @staticmethod
542
+ def _before_fetch_callable_checks(internal_data: DataStreamCtx):
543
+ return internal_data.current_pkt.is_input
544
+
545
+ @property
546
+ def method(self) -> bytes:
547
+ """Method of the request"""
548
+ return self._parser.msg.method
549
+
550
+ def __repr__(self):
551
+ 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}>"
552
+
553
+ class HttpResponse(InternalBasicHttpMetaClass):
554
+ """
555
+ HTTP Response handler
556
+ This data handler will be called twice, first with the headers complete, and second with the body complete
557
+ """
558
+
559
+ @staticmethod
560
+ def _associated_parser_class() -> Type[InternalHttpResponse]:
561
+ return InternalHttpResponse
562
+
563
+ @staticmethod
564
+ def _before_fetch_callable_checks(internal_data: DataStreamCtx):
565
+ return not internal_data.current_pkt.is_input
566
+
567
+ @property
568
+ def status_code(self) -> int:
569
+ """Status code of the response"""
570
+ return self._parser.msg.status
571
+
572
+ def __repr__(self):
573
+ 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}>"
574
+
575
+ class HttpRequestHeader(HttpRequest):
576
+ """
577
+ HTTP Request Header handler
578
+ This data handler will be called only once, the headers are complete, the body will be empty and not buffered
579
+ """
580
+
581
+ def _contructor_hook(self):
582
+ self._parser.save_body = False
583
+
584
+ class HttpResponseHeader(HttpResponse):
585
+ """
586
+ HTTP Response Header handler
587
+ This data handler will be called only once, the headers are complete, the body will be empty and not buffered
588
+ """
589
+
590
+ def _contructor_hook(self):
591
+ 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.2.0
4
4
  Summary: Firegex client
5
5
  Home-page: https://github.com/pwnzer0tt1/firegex
6
6
  Author: Pwnzer0tt1
@@ -13,8 +13,11 @@ Description-Content-Type: text/markdown
13
13
  Requires-Dist: typer==0.15.2
14
14
  Requires-Dist: pydantic>=2
15
15
  Requires-Dist: typing-extensions>=4.7.1
16
+ Requires-Dist: zstd
17
+ Requires-Dist: brotli
16
18
  Requires-Dist: watchfiles
17
19
  Requires-Dist: fgex
20
+ Requires-Dist: websockets
18
21
  Requires-Dist: pyllhttp
19
22
  Dynamic: author
20
23
  Dynamic: author-email
@@ -122,7 +125,6 @@ This handler will be called twice: one for the request headers and one for the r
122
125
  - headers: dict - The headers of the request
123
126
  - user_agent: str - The user agent of the request
124
127
  - content_encoding: str - The content encoding of the request
125
- - has_begun: bool - It's true if the request has begun
126
128
  - body: bytes - The body of the request
127
129
  - headers_complete: bool - It's true if the headers are complete
128
130
  - message_complete: bool - It's true if the message is complete
@@ -150,7 +152,6 @@ This handler will be called twice: one for the response headers and one for the
150
152
  - headers: dict - The headers of the response
151
153
  - user_agent: str - The user agent of the response
152
154
  - content_encoding: str - The content encoding of the response
153
- - has_begun: bool - It's true if the response has begun
154
155
  - body: bytes - The body of the response
155
156
  - headers_complete: bool - It's true if the headers are complete
156
157
  - message_complete: bool - It's true if the message is complete
@@ -1,6 +1,9 @@
1
1
  typer==0.15.2
2
2
  pydantic>=2
3
3
  typing-extensions>=4.7.1
4
+ zstd
5
+ brotli
4
6
  watchfiles
5
7
  fgex
8
+ websockets
6
9
  pyllhttp
@@ -0,0 +1,9 @@
1
+ typer==0.15.2
2
+ pydantic>=2
3
+ typing-extensions>=4.7.1
4
+ zstd # waiting for pull request to be merged
5
+ brotli # waiting for pull request to be merged
6
+ watchfiles
7
+ fgex
8
+ websockets
9
+ pyllhttp
@@ -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.2.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
@@ -1,6 +0,0 @@
1
- typer==0.15.2
2
- pydantic>=2
3
- typing-extensions>=4.7.1
4
- watchfiles
5
- fgex
6
- pyllhttp
File without changes
File without changes
File without changes
File without changes
File without changes