Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions doc/scapy/advanced_usage/fwdmachine.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions scapy/fwdmachine.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
28 changes: 22 additions & 6 deletions scapy/layers/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -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""
Expand All @@ -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:
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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,
Expand Down
8 changes: 8 additions & 0 deletions scapy/supersocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
11 changes: 11 additions & 0 deletions test/scapy/layers/http.uts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading