parsing extentions from server response
This commit is contained in:
@@ -106,6 +106,7 @@ class DataStreamCtx:
|
|||||||
self.__data = glob["__firegex_pyfilter_ctx"]
|
self.__data = glob["__firegex_pyfilter_ctx"]
|
||||||
self.filter_glob = glob
|
self.filter_glob = glob
|
||||||
self.current_pkt = RawPacket._fetch_packet(self) if init_pkt else None
|
self.current_pkt = RawPacket._fetch_packet(self) if init_pkt else None
|
||||||
|
self.call_mem = {} #A memory space valid only for the current packet handler
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def filter_call_info(self) -> list[FilterHandler]:
|
def filter_call_info(self) -> list[FilterHandler]:
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ from firegex.nfproxy.internals.exceptions import StreamFullDrop, StreamFullRejec
|
|||||||
from firegex.nfproxy.internals.models import FullStreamAction, ExceptionAction
|
from firegex.nfproxy.internals.models import FullStreamAction, ExceptionAction
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from typing import Type
|
|
||||||
from zstd import ZSTD_uncompress
|
from zstd import ZSTD_uncompress
|
||||||
import gzip
|
import gzip
|
||||||
import io
|
import io
|
||||||
import zlib
|
import zlib
|
||||||
import brotli
|
import brotli
|
||||||
|
import traceback
|
||||||
from websockets.frames import Frame
|
from websockets.frames import Frame
|
||||||
from websockets.extensions.permessage_deflate import PerMessageDeflate
|
from websockets.extensions.permessage_deflate import PerMessageDeflate
|
||||||
from pyllhttp import PAUSED_H2_UPGRADE, PAUSED_UPGRADE
|
from pyllhttp import PAUSED_H2_UPGRADE, PAUSED_UPGRADE
|
||||||
@@ -260,7 +260,9 @@ class InternalCallbackHandler():
|
|||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
new_frame, self.buffers._ws_packet_stream = self._parse_websocket_frame(self.buffers._ws_packet_stream)
|
new_frame, self.buffers._ws_packet_stream = self._parse_websocket_frame(self.buffers._ws_packet_stream)
|
||||||
except Exception as e:
|
except Exception:
|
||||||
|
print("[WARNING] Websocket parsing failed, passing data to stream...", flush=True)
|
||||||
|
traceback.print_exc()
|
||||||
self._ws_raised_error = True
|
self._ws_raised_error = True
|
||||||
self.msg.stream += self.buffers._ws_packet_stream
|
self.msg.stream += self.buffers._ws_packet_stream
|
||||||
self.buffers._ws_packet_stream = b""
|
self.buffers._ws_packet_stream = b""
|
||||||
@@ -287,9 +289,11 @@ class InternalCallbackHandler():
|
|||||||
return ext_ws
|
return ext_ws
|
||||||
|
|
||||||
def _parse_websocket_frame(self, data: bytes) -> tuple[Frame|None, bytes]:
|
def _parse_websocket_frame(self, data: bytes) -> tuple[Frame|None, bytes]:
|
||||||
# mask = is_input
|
|
||||||
if self._ws_extentions is None:
|
if self._ws_extentions is None:
|
||||||
self._ws_extentions = self._parse_websocket_ext()
|
if self._is_input():
|
||||||
|
self._ws_extentions = [] # Fallback to no options
|
||||||
|
else:
|
||||||
|
self._ws_extentions = self._parse_websocket_ext() # Extentions used are choosen by the server response
|
||||||
read_buffering = bytearray()
|
read_buffering = bytearray()
|
||||||
def read_exact(n: int):
|
def read_exact(n: int):
|
||||||
nonlocal read_buffering
|
nonlocal read_buffering
|
||||||
@@ -450,70 +454,89 @@ class InternalBasicHttpMetaClass:
|
|||||||
return self._message.lheaders.get(header.lower(), default)
|
return self._message.lheaders.get(header.lower(), default)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _associated_parser_class() -> Type[InternalHttpRequest]|Type[InternalHttpResponse]:
|
def _before_fetch_callable_checks(internal_data: DataStreamCtx) -> bool:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _before_fetch_callable_checks(internal_data: DataStreamCtx):
|
def _parser_class() -> str:
|
||||||
return True
|
raise NotImplementedError()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _fetch_packet(cls, internal_data: DataStreamCtx):
|
def _fetch_packet(cls, internal_data: DataStreamCtx):
|
||||||
if internal_data.current_pkt is None or internal_data.current_pkt.is_tcp is False:
|
if internal_data.current_pkt is None or internal_data.current_pkt.is_tcp is False:
|
||||||
raise NotReadyToRun()
|
raise NotReadyToRun()
|
||||||
|
|
||||||
ParserType = cls._associated_parser_class()
|
ParserType = InternalHttpRequest if internal_data.current_pkt.is_input else InternalHttpResponse
|
||||||
|
parser_key = f"{cls._parser_class()}_{'in' if internal_data.current_pkt.is_input else 'out'}"
|
||||||
|
|
||||||
parser = internal_data.data_handler_context.get(cls, None)
|
parser = internal_data.data_handler_context.get(parser_key, None)
|
||||||
if parser is None or parser.raised_error:
|
if parser is None or parser.raised_error:
|
||||||
parser: InternalHttpRequest|InternalHttpResponse = ParserType()
|
parser: InternalHttpRequest|InternalHttpResponse = ParserType()
|
||||||
internal_data.data_handler_context[cls] = parser
|
internal_data.data_handler_context[parser_key] = parser
|
||||||
|
|
||||||
|
if not internal_data.call_mem.get(cls._parser_class(), False): #Need to parse HTTP
|
||||||
|
internal_data.call_mem[cls._parser_class()] = True
|
||||||
|
|
||||||
|
#Setting websocket options if needed to the client parser
|
||||||
|
if internal_data.current_pkt.is_input:
|
||||||
|
ext_opt = internal_data.data_handler_context.get(f"{cls._parser_class()}_ws_options_client")
|
||||||
|
if ext_opt is not None and parser._ws_extentions != ext_opt:
|
||||||
|
parser._ws_extentions = ext_opt
|
||||||
|
|
||||||
|
# Memory size managment
|
||||||
|
if parser.total_size+len(internal_data.current_pkt.data) > internal_data.stream_max_size:
|
||||||
|
match internal_data.full_stream_action:
|
||||||
|
case FullStreamAction.FLUSH:
|
||||||
|
# Deleting parser and re-creating it
|
||||||
|
parser.messages.clear()
|
||||||
|
parser.msg.total_size -= len(parser.msg.stream)
|
||||||
|
parser.msg.stream = b""
|
||||||
|
parser.msg.total_size -= len(parser.msg.body)
|
||||||
|
parser.msg.body = b""
|
||||||
|
print("[WARNING] Flushing stream", flush=True)
|
||||||
|
if parser.total_size+len(internal_data.current_pkt.data) > internal_data.stream_max_size:
|
||||||
|
parser.reset_data()
|
||||||
|
case FullStreamAction.REJECT:
|
||||||
|
raise StreamFullReject()
|
||||||
|
case FullStreamAction.DROP:
|
||||||
|
raise StreamFullDrop()
|
||||||
|
case FullStreamAction.ACCEPT:
|
||||||
|
raise NotReadyToRun()
|
||||||
|
|
||||||
|
internal_data.call_mem["headers_were_set"] = parser.msg.headers_complete #This information is usefull for building the real object
|
||||||
|
|
||||||
|
try:
|
||||||
|
parser.parse_data(internal_data.current_pkt.data)
|
||||||
|
except Exception as e:
|
||||||
|
traceback.print_exc()
|
||||||
|
match internal_data.invalid_encoding_action:
|
||||||
|
case ExceptionAction.REJECT:
|
||||||
|
raise RejectConnection()
|
||||||
|
case ExceptionAction.DROP:
|
||||||
|
raise DropPacket()
|
||||||
|
case ExceptionAction.NOACTION:
|
||||||
|
raise e
|
||||||
|
case ExceptionAction.ACCEPT:
|
||||||
|
raise NotReadyToRun()
|
||||||
|
|
||||||
|
if parser.should_upgrade and not internal_data.current_pkt.is_input:
|
||||||
|
#Creating ws_option for the client
|
||||||
|
if not internal_data.data_handler_context.get(f"{cls._parser_class()}_ws_options_client"):
|
||||||
|
ext = parser._parse_websocket_ext()
|
||||||
|
internal_data.data_handler_context[f"{cls._parser_class()}_ws_options_client"] = ext
|
||||||
|
|
||||||
|
#Once the parsers has been triggered, we can return the object if needed
|
||||||
if not cls._before_fetch_callable_checks(internal_data):
|
if not cls._before_fetch_callable_checks(internal_data):
|
||||||
raise NotReadyToRun()
|
raise NotReadyToRun()
|
||||||
|
|
||||||
# Memory size managment
|
|
||||||
if parser.total_size+len(internal_data.current_pkt.data) > internal_data.stream_max_size:
|
|
||||||
match internal_data.full_stream_action:
|
|
||||||
case FullStreamAction.FLUSH:
|
|
||||||
# Deleting parser and re-creating it
|
|
||||||
parser.messages.clear()
|
|
||||||
parser.msg.total_size -= len(parser.msg.stream)
|
|
||||||
parser.msg.stream = b""
|
|
||||||
parser.msg.total_size -= len(parser.msg.body)
|
|
||||||
parser.msg.body = b""
|
|
||||||
print("[WARNING] Flushing stream", flush=True)
|
|
||||||
if parser.total_size+len(internal_data.current_pkt.data) > internal_data.stream_max_size:
|
|
||||||
parser.reset_data()
|
|
||||||
case FullStreamAction.REJECT:
|
|
||||||
raise StreamFullReject()
|
|
||||||
case FullStreamAction.DROP:
|
|
||||||
raise StreamFullDrop()
|
|
||||||
case FullStreamAction.ACCEPT:
|
|
||||||
raise NotReadyToRun()
|
|
||||||
|
|
||||||
headers_were_set = parser.msg.headers_complete
|
|
||||||
try:
|
|
||||||
parser.parse_data(internal_data.current_pkt.data)
|
|
||||||
except Exception as e:
|
|
||||||
match internal_data.invalid_encoding_action:
|
|
||||||
case ExceptionAction.REJECT:
|
|
||||||
raise RejectConnection()
|
|
||||||
case ExceptionAction.DROP:
|
|
||||||
raise DropPacket()
|
|
||||||
case ExceptionAction.NOACTION:
|
|
||||||
raise e
|
|
||||||
case ExceptionAction.ACCEPT:
|
|
||||||
raise NotReadyToRun()
|
|
||||||
|
|
||||||
messages_tosend:list[InternalHTTPMessage] = []
|
messages_tosend:list[InternalHTTPMessage] = []
|
||||||
for i in range(len(parser.messages)):
|
for i in range(len(parser.messages)):
|
||||||
messages_tosend.append(parser.pop_message())
|
messages_tosend.append(parser.pop_message())
|
||||||
|
|
||||||
if len(messages_tosend) > 0:
|
if len(messages_tosend) > 0:
|
||||||
headers_were_set = False # New messages completed so the current message headers were not set in this case
|
internal_data.call_mem["headers_were_set"] = False # New messages completed so the current message headers were not set in this case
|
||||||
|
|
||||||
if not headers_were_set and parser.msg.headers_complete:
|
if not internal_data.call_mem["headers_were_set"] and parser.msg.headers_complete:
|
||||||
messages_tosend.append(parser.msg) # Also the current message needs to be sent due to complete headers
|
messages_tosend.append(parser.msg) # Also the current message needs to be sent due to complete headers
|
||||||
|
|
||||||
if parser._packet_to_stream():
|
if parser._packet_to_stream():
|
||||||
@@ -534,10 +557,6 @@ class HttpRequest(InternalBasicHttpMetaClass):
|
|||||||
This data handler will be called twice, first with the headers complete, and second with the body complete
|
This data handler will be called twice, first with the headers complete, and second with the body complete
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _associated_parser_class() -> Type[InternalHttpRequest]:
|
|
||||||
return InternalHttpRequest
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _before_fetch_callable_checks(internal_data: DataStreamCtx):
|
def _before_fetch_callable_checks(internal_data: DataStreamCtx):
|
||||||
return internal_data.current_pkt.is_input
|
return internal_data.current_pkt.is_input
|
||||||
@@ -547,6 +566,10 @@ class HttpRequest(InternalBasicHttpMetaClass):
|
|||||||
"""Method of the request"""
|
"""Method of the request"""
|
||||||
return self._parser.msg.method
|
return self._parser.msg.method
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _parser_class() -> str:
|
||||||
|
return "full_http"
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
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}>"
|
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}>"
|
||||||
|
|
||||||
@@ -556,10 +579,6 @@ class HttpResponse(InternalBasicHttpMetaClass):
|
|||||||
This data handler will be called twice, first with the headers complete, and second with the body complete
|
This data handler will be called twice, first with the headers complete, and second with the body complete
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _associated_parser_class() -> Type[InternalHttpResponse]:
|
|
||||||
return InternalHttpResponse
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _before_fetch_callable_checks(internal_data: DataStreamCtx):
|
def _before_fetch_callable_checks(internal_data: DataStreamCtx):
|
||||||
return not internal_data.current_pkt.is_input
|
return not internal_data.current_pkt.is_input
|
||||||
@@ -569,6 +588,10 @@ class HttpResponse(InternalBasicHttpMetaClass):
|
|||||||
"""Status code of the response"""
|
"""Status code of the response"""
|
||||||
return self._parser.msg.status
|
return self._parser.msg.status
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _parser_class() -> str:
|
||||||
|
return "full_http"
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
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}>"
|
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}>"
|
||||||
|
|
||||||
@@ -581,6 +604,10 @@ class HttpRequestHeader(HttpRequest):
|
|||||||
def _contructor_hook(self):
|
def _contructor_hook(self):
|
||||||
self._parser.save_body = False
|
self._parser.save_body = False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _parser_class() -> str:
|
||||||
|
return "header_http"
|
||||||
|
|
||||||
class HttpResponseHeader(HttpResponse):
|
class HttpResponseHeader(HttpResponse):
|
||||||
"""
|
"""
|
||||||
HTTP Response Header handler
|
HTTP Response Header handler
|
||||||
@@ -589,3 +616,7 @@ class HttpResponseHeader(HttpResponse):
|
|||||||
|
|
||||||
def _contructor_hook(self):
|
def _contructor_hook(self):
|
||||||
self._parser.save_body = False
|
self._parser.save_body = False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _parser_class() -> str:
|
||||||
|
return "header_http"
|
||||||
|
|||||||
Reference in New Issue
Block a user