diff --git a/doc/scapy/advanced_usage/fwdmachine.rst b/doc/scapy/advanced_usage/fwdmachine.rst index 24b5bcd3b41..ba4b221893f 100644 --- a/doc/scapy/advanced_usage/fwdmachine.rst +++ b/doc/scapy/advanced_usage/fwdmachine.rst @@ -75,7 +75,7 @@ If we were to use this machine in SERVER mode, we would call it like: TLS support ___________ -``ForwardMachine`` has support for TLS through the ``ssl=True`` argument. When TLS is enabled, the SNI (Server Name Indication) is +``ForwardMachine`` has support for TLS through the ``tls=True`` argument. When TLS is enabled, the SNI (Server Name Indication) is properly forwarded to the remote peer, and can be accessed through the ``ctx.tls_sni_name`` attribute in the callbacks. **By default, a ForwardMachine generates self-signed certificates** that copy the attributes from the certificate of the remote @@ -90,7 +90,7 @@ We can run the same ForwardMachine as from the previous example, this time with mode=ForwardMachine.MODE.SERVER, port=443, cls=HTTP, - ssl=True, + tls=True, ).run() Configuring TPROXY diff --git a/scapy/fwdmachine.py b/scapy/fwdmachine.py index 3e75cef8e88..273f522df51 100644 --- a/scapy/fwdmachine.py +++ b/scapy/fwdmachine.py @@ -221,7 +221,7 @@ class CONTEXT: CONTEXT object kept during a session """ - def __init__(self, addr, dest): + def __init__(self, fwdm, addr, dest): self.addr = addr self.dest = dest self.tls_sni_name = None # Retrieved when receiving a connection @@ -335,7 +335,7 @@ def handler(self, sock, addr, dest): """ Handler of a client socket """ - ctx = self.CONTEXT(addr, dest) # we have a context object + ctx = self.CONTEXT(self, addr, dest) # we have a context object # Initialize peer socket ss = self._getpeersock(dest) # Wrap both server and peer sockets in SSL diff --git a/scapy/layers/http.py b/scapy/layers/http.py index 88930d569c4..367d0b9264f 100644 --- a/scapy/layers/http.py +++ b/scapy/layers/http.py @@ -315,9 +315,16 @@ def _get_encodings(self): def hashret(self): return b"HTTP1" - def post_dissect(self, s): + def do_dissect_payload(self, s): self._original_len = len(s) + _orig_s = s + + # Dissect normally + super(_HTTPContent, self).do_dissect_payload(s) + + # If enabled, perform some post-processing encodings = self._get_encodings() + # Un-chunkify if conf.contribs["http"]["auto_chunk"] and "chunked" in encodings: data = b"" @@ -338,7 +345,11 @@ def post_dissect(self, s): if not s: s = data if not conf.contribs["http"]["auto_compression"]: - return s + self.payload.load = s + self.payload.raw_packet_cache = _orig_s + self.payload.raw_packet_cache_fields = {} + return + # Decompress try: if "deflate" in encodings: @@ -374,10 +385,12 @@ def post_dissect(self, s): "Can't import zstandard. zstd decompression " "will be ignored !" ) + self.payload.load = s + self.payload.raw_packet_cache = _orig_s + self.payload.raw_packet_cache_fields = {} except Exception: # Cannot decompress - probably incomplete data pass - return s def post_build(self, pkt, pay): encodings = self._get_encodings() @@ -709,10 +722,13 @@ def tcp_reassemble(cls, data, metadata, session): and http_packet.Method == b"HEAD" ): session["head_request"] = True - elif is_response and http_packet.Status_Code == b"101": + elif is_response and ( + http_packet.Status_Code == b"101" + or session.pop("head_request", False) + ): # If it's an upgrade response, it may also hold a - # different protocol data. - # make sure all headers are present + # different protocol data. make sure all headers are present + # If header_request is set, this is an answer to a HEAD. detect_end = lambda dat: dat.find(b"\r\n\r\n") else: # If neither Content-Length nor chunked is specified, diff --git a/scapy/supersocket.py b/scapy/supersocket.py index 628920b8a62..a497eb4f1f0 100644 --- a/scapy/supersocket.py +++ b/scapy/supersocket.py @@ -545,6 +545,14 @@ def recv(self, x=None, **kwargs): return self.recv(x) return pkt + @property + def streamsession(self) -> Dict[Any, Any]: + return self.sess.session + + @streamsession.setter + def streamsession(self, session: Dict[Any, Any]) -> None: + self.sess.session = session + @staticmethod def select(sockets, remain=None): # type: (List[SuperSocket], Optional[float]) -> List[SuperSocket] diff --git a/test/scapy/layers/http.uts b/test/scapy/layers/http.uts index e090bf8b243..ffe0ab337a6 100644 --- a/test/scapy/layers/http.uts +++ b/test/scapy/layers/http.uts @@ -261,6 +261,17 @@ for i in range(3, 10): assert HTTP not in pkts[i] assert H2Frame in pkts[i] += Test chunked then rebuild + +data = b'HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=ISO-8859-1\r\nDate: Tue, 16 Jun 2026 17:47:46 GMT\r\nExpires: -1\r\nVary: Accept-Encoding\r\nTransfer-Encoding: chunked\r\n\r\nA\r\n0000000000\r\nA\r\n0000000000\r\n0\r\n\r\n' +pkt = HTTP(data) +assert pkt.load == b"00000000000000000000" +assert bytes(pkt) == data + +pkt.clear_cache() +bytes(pkt) +assert bytes(pkt) == b'HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=ISO-8859-1\r\nDate: Tue, 16 Jun 2026 17:47:46 GMT\r\nExpires: -1\r\nTransfer-Encoding: chunked\r\nVary: Accept-Encoding\r\n\r\n14\r\n00000000000000000000\r\n0\r\n\r\n' + = Test chunked with gzip conf.contribs["http"]["auto_compression"] = False