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.
- {firegex-3.0.0/firegex.egg-info → firegex-3.1.0}/PKG-INFO +1 -3
- {firegex-3.0.0 → firegex-3.1.0}/README.md +0 -2
- firegex-3.1.0/firegex/__init__.py +5 -0
- {firegex-3.0.0 → firegex-3.1.0}/firegex/nfproxy/internals/__init__.py +34 -25
- {firegex-3.0.0 → firegex-3.1.0}/firegex/nfproxy/internals/data.py +19 -3
- {firegex-3.0.0 → firegex-3.1.0}/firegex/nfproxy/internals/exceptions.py +1 -0
- {firegex-3.0.0 → firegex-3.1.0}/firegex/nfproxy/internals/models.py +7 -2
- firegex-3.1.0/firegex/nfproxy/models/http.py +422 -0
- {firegex-3.0.0 → firegex-3.1.0}/firegex/nfproxy/models/tcp.py +1 -1
- {firegex-3.0.0 → firegex-3.1.0/firegex.egg-info}/PKG-INFO +1 -3
- {firegex-3.0.0 → firegex-3.1.0}/setup.py +1 -1
- firegex-3.0.0/firegex/__init__.py +0 -5
- firegex-3.0.0/firegex/nfproxy/models/http.py +0 -357
- {firegex-3.0.0 → firegex-3.1.0}/MANIFEST.in +0 -0
- {firegex-3.0.0 → firegex-3.1.0}/fgex +0 -0
- {firegex-3.0.0 → firegex-3.1.0}/firegex/__main__.py +0 -0
- {firegex-3.0.0 → firegex-3.1.0}/firegex/cli.py +0 -0
- {firegex-3.0.0 → firegex-3.1.0}/firegex/nfproxy/__init__.py +0 -0
- {firegex-3.0.0 → firegex-3.1.0}/firegex/nfproxy/models/__init__.py +0 -0
- {firegex-3.0.0 → firegex-3.1.0}/firegex/nfproxy/proxysim/__init__.py +0 -0
- {firegex-3.0.0 → firegex-3.1.0}/firegex.egg-info/SOURCES.txt +0 -0
- {firegex-3.0.0 → firegex-3.1.0}/firegex.egg-info/dependency_links.txt +0 -0
- {firegex-3.0.0 → firegex-3.1.0}/firegex.egg-info/requires.txt +0 -0
- {firegex-3.0.0 → firegex-3.1.0}/firegex.egg-info/top_level.txt +0 -0
- {firegex-3.0.0 → firegex-3.1.0}/requirements.txt +0 -0
- {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.
|
|
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
|
|
@@ -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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
157
|
-
|
|
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"] =
|
|
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():
|
|
@@ -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.
|
|
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.
|
|
9
|
+
VERSION = "3.1.0"
|
|
10
10
|
|
|
11
11
|
setuptools.setup(
|
|
12
12
|
name="firegex",
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|