firegex 3.5.2__tar.gz → 4.0.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 (28) hide show
  1. firegex-4.0.0/PKG-INFO +57 -0
  2. firegex-4.0.0/README.md +26 -0
  3. firegex-4.0.0/firegex/__init__.py +69 -0
  4. {firegex-3.5.2 → firegex-4.0.0}/firegex/nfproxy/__init__.py +14 -5
  5. {firegex-3.5.2 → firegex-4.0.0}/firegex/nfproxy/models/__init__.py +7 -0
  6. {firegex-3.5.2 → firegex-4.0.0}/firegex/nfproxy/models/http.py +134 -8
  7. firegex-4.0.0/firegex.egg-info/PKG-INFO +57 -0
  8. {firegex-3.5.2 → firegex-4.0.0}/setup.py +1 -1
  9. firegex-3.5.2/PKG-INFO +0 -182
  10. firegex-3.5.2/README.md +0 -151
  11. firegex-3.5.2/firegex/__init__.py +0 -5
  12. firegex-3.5.2/firegex.egg-info/PKG-INFO +0 -182
  13. {firegex-3.5.2 → firegex-4.0.0}/MANIFEST.in +0 -0
  14. {firegex-3.5.2 → firegex-4.0.0}/fgex +0 -0
  15. {firegex-3.5.2 → firegex-4.0.0}/firegex/__main__.py +0 -0
  16. {firegex-3.5.2 → firegex-4.0.0}/firegex/cli.py +0 -0
  17. {firegex-3.5.2 → firegex-4.0.0}/firegex/nfproxy/internals/__init__.py +0 -0
  18. {firegex-3.5.2 → firegex-4.0.0}/firegex/nfproxy/internals/data.py +0 -0
  19. {firegex-3.5.2 → firegex-4.0.0}/firegex/nfproxy/internals/exceptions.py +0 -0
  20. {firegex-3.5.2 → firegex-4.0.0}/firegex/nfproxy/internals/models.py +0 -0
  21. {firegex-3.5.2 → firegex-4.0.0}/firegex/nfproxy/models/tcp.py +0 -0
  22. {firegex-3.5.2 → firegex-4.0.0}/firegex/nfproxy/proxysim/__init__.py +0 -0
  23. {firegex-3.5.2 → firegex-4.0.0}/firegex.egg-info/SOURCES.txt +0 -0
  24. {firegex-3.5.2 → firegex-4.0.0}/firegex.egg-info/dependency_links.txt +0 -0
  25. {firegex-3.5.2 → firegex-4.0.0}/firegex.egg-info/requires.txt +0 -0
  26. {firegex-3.5.2 → firegex-4.0.0}/firegex.egg-info/top_level.txt +0 -0
  27. {firegex-3.5.2 → firegex-4.0.0}/requirements.txt +0 -0
  28. {firegex-3.5.2 → firegex-4.0.0}/setup.cfg +0 -0
firegex-4.0.0/PKG-INFO ADDED
@@ -0,0 +1,57 @@
1
+ Metadata-Version: 2.4
2
+ Name: firegex
3
+ Version: 4.0.0
4
+ Summary: Firegex client
5
+ Home-page: https://github.com/pwnzer0tt1/firegex
6
+ Author: Pwnzer0tt1
7
+ Author-email: pwnzer0tt1@poliba.it
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: typer
14
+ Requires-Dist: pydantic>=2
15
+ Requires-Dist: typing-extensions>=4.7.1
16
+ Requires-Dist: pycryptodome
17
+ Requires-Dist: watchfiles
18
+ Requires-Dist: fgex
19
+ Requires-Dist: websockets
20
+ Requires-Dist: brotli
21
+ Requires-Dist: pyllhttp
22
+ Dynamic: author
23
+ Dynamic: author-email
24
+ Dynamic: classifier
25
+ Dynamic: description
26
+ Dynamic: description-content-type
27
+ Dynamic: home-page
28
+ Dynamic: requires-dist
29
+ Dynamic: requires-python
30
+ Dynamic: summary
31
+
32
+ # Firegex Python Library and CLI
33
+
34
+ This is the Python library for [Firegex](https://github.com/Pwnzer0tt1/firegex), a firewall built for CTF Attack-Defense competitions. It provides the `firegex.nfproxy` package used to write [Netfilter Proxy](../docs/nfproxy.md) Python packet filters, the `fgex` CLI, and a local proxy simulator (`proxysim`) for testing filters without a running Firegex instance.
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ pip install -U firegex
40
+ ```
41
+
42
+ `fgex` is an alias package for `firegex`: installing either one gives you the same `firegex` module and the `fgex`/`firegex` CLI commands.
43
+
44
+ ## Documentation
45
+
46
+ **➡️ [Netfilter Proxy: writing and testing `@pyfilter` scripts](../docs/nfproxy.md)** — the `pyfilter` decorator, every data structure (`RawPacket`, `TCPInputStream`/`TCPOutputStream`, `HttpRequest`/`HttpResponse` and their variants, `HttpHistory`), packet statements, stream limits, the `fgex nfproxy` CLI simulator, and a full worked example.
47
+
48
+ That page is also what's shown in the Firegex web UI itself (the docs button on the nfproxy pages) — both are generated from that single file, so they can never drift out of sync.
49
+
50
+ ## Firegex's other filtering modules
51
+
52
+ This library only covers Netfilter Proxy. Firegex ships several other independent modules, each with its own guide:
53
+
54
+ - **[Netfilter Regex](../docs/nfregex.md)** — fast, PCRE2/hyperscan-based regex matching against raw traffic.
55
+ - **[Hijack Port to Proxy](../docs/porthijack.md)** — redirect traffic to your own external proxy without touching the target service.
56
+ - **[Firewall Rules](../docs/firewall.md)** — plain nftables allow/drop/reject rules.
57
+ - **[TLS Decryption](../docs/tls.md)** — decrypt-and-reinspect bridge for services that speak TLS natively.
@@ -0,0 +1,26 @@
1
+ # Firegex Python Library and CLI
2
+
3
+ This is the Python library for [Firegex](https://github.com/Pwnzer0tt1/firegex), a firewall built for CTF Attack-Defense competitions. It provides the `firegex.nfproxy` package used to write [Netfilter Proxy](../docs/nfproxy.md) Python packet filters, the `fgex` CLI, and a local proxy simulator (`proxysim`) for testing filters without a running Firegex instance.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install -U firegex
9
+ ```
10
+
11
+ `fgex` is an alias package for `firegex`: installing either one gives you the same `firegex` module and the `fgex`/`firegex` CLI commands.
12
+
13
+ ## Documentation
14
+
15
+ **➡️ [Netfilter Proxy: writing and testing `@pyfilter` scripts](../docs/nfproxy.md)** — the `pyfilter` decorator, every data structure (`RawPacket`, `TCPInputStream`/`TCPOutputStream`, `HttpRequest`/`HttpResponse` and their variants, `HttpHistory`), packet statements, stream limits, the `fgex nfproxy` CLI simulator, and a full worked example.
16
+
17
+ That page is also what's shown in the Firegex web UI itself (the docs button on the nfproxy pages) — both are generated from that single file, so they can never drift out of sync.
18
+
19
+ ## Firegex's other filtering modules
20
+
21
+ This library only covers Netfilter Proxy. Firegex ships several other independent modules, each with its own guide:
22
+
23
+ - **[Netfilter Regex](../docs/nfregex.md)** — fast, PCRE2/hyperscan-based regex matching against raw traffic.
24
+ - **[Hijack Port to Proxy](../docs/porthijack.md)** — redirect traffic to your own external proxy without touching the target service.
25
+ - **[Firewall Rules](../docs/firewall.md)** — plain nftables allow/drop/reject rules.
26
+ - **[TLS Decryption](../docs/tls.md)** — decrypt-and-reinspect bridge for services that speak TLS natively.
@@ -0,0 +1,69 @@
1
+
2
+ def _get_version():
3
+ v = "4.0.0" if "{" not in "4.0.0" else "0.0.0"
4
+ if v == "0.0.0":
5
+ import os
6
+ env_v = os.getenv("FIREGEX_VERSION")
7
+ if env_v:
8
+ return env_v
9
+ try:
10
+ import re
11
+
12
+ def parse_version(ver):
13
+ ver = ver.lstrip("v")
14
+ parts = re.split(r'[^0-9]+', ver)
15
+ return tuple(int(p) if p.isdigit() else 0 for p in parts if p)
16
+
17
+ def find_git_dir(start_path):
18
+ current = start_path
19
+ while current and current != "/" and current != "":
20
+ gd = os.path.join(current, ".git")
21
+ if os.path.isdir(gd):
22
+ return gd
23
+ parent = os.path.dirname(current)
24
+ if parent == current:
25
+ break
26
+ current = parent
27
+ return None
28
+
29
+ git_dir = find_git_dir(os.path.abspath(os.path.dirname(__file__)))
30
+ if not git_dir:
31
+ return v
32
+
33
+ branch = None
34
+ with open(os.path.join(git_dir, "HEAD"), "r") as f:
35
+ head = f.read().strip()
36
+ if head.startswith("ref: refs/heads/"):
37
+ branch = head.split("ref: refs/heads/")[1]
38
+
39
+ if branch != "main":
40
+ return v
41
+
42
+ tags = []
43
+ try:
44
+ with open(os.path.join(git_dir, "packed-refs"), "r") as f:
45
+ for line in f:
46
+ if " refs/tags/" in line:
47
+ tags.append(line.strip().split(" refs/tags/")[1])
48
+ except Exception:
49
+ pass
50
+
51
+ tags_dir = os.path.join(git_dir, "refs", "tags")
52
+ try:
53
+ for root, _, files in os.walk(tags_dir):
54
+ for file in files:
55
+ tags.append(os.path.relpath(os.path.join(root, file), tags_dir).replace(os.path.sep, "/"))
56
+ except Exception:
57
+ pass
58
+
59
+ version_tags = [t for t in set(tags) if any(c.isdigit() for c in t)]
60
+ if version_tags:
61
+ return max(version_tags, key=parse_version)
62
+ except Exception:
63
+ pass
64
+ return v
65
+
66
+ __version__ = _get_version()
67
+
68
+ #Exported functions
69
+ __all__ = []
@@ -1,6 +1,14 @@
1
1
  import functools
2
- from firegex.nfproxy.models import RawPacket, TCPInputStream, TCPOutputStream, TCPClientStream, TCPServerStream
3
- from firegex.nfproxy.internals.models import Action, FullStreamAction
2
+ from firegex.nfproxy.models import (
3
+ RawPacket,
4
+ TCPInputStream,
5
+ TCPOutputStream,
6
+ TCPClientStream,
7
+ TCPServerStream,
8
+ HttpHistory,
9
+ HttpStreamHistory,
10
+ )
11
+ from firegex.nfproxy.internals.models import Action, FullStreamAction, ExceptionAction
4
12
 
5
13
  ACCEPT = Action.ACCEPT
6
14
  DROP = Action.DROP
@@ -35,7 +43,8 @@ def clear_pyfilter_registry():
35
43
  pyfilter.registry.clear()
36
44
 
37
45
  __all__ = [
38
- "ACCEPT", "DROP", "REJECT", "UNSTABLE_MANGLE"
39
- "Action", "FullStreamAction", "pyfilter",
40
- "RawPacket", "TCPInputStream", "TCPOutputStream", "TCPClientStream", "TCPServerStream"
46
+ "ACCEPT", "DROP", "REJECT", "UNSTABLE_MANGLE",
47
+ "Action", "FullStreamAction", "ExceptionAction", "pyfilter",
48
+ "RawPacket", "TCPInputStream", "TCPOutputStream", "TCPClientStream", "TCPServerStream",
49
+ "HttpHistory", "HttpStreamHistory"
41
50
  ]
@@ -11,6 +11,8 @@ from firegex.nfproxy.models.http import (
11
11
  HttpResponseHeader,
12
12
  HttpFullRequest,
13
13
  HttpFullResponse,
14
+ HttpHistory,
15
+ HttpStreamHistory,
14
16
  )
15
17
  from firegex.nfproxy.internals.data import RawPacket
16
18
  from enum import Enum
@@ -31,6 +33,8 @@ type_annotations_associations = {
31
33
  HttpResponseHeader: HttpResponseHeader._fetch_packet,
32
34
  HttpFullRequest: HttpFullRequest._fetch_packet,
33
35
  HttpFullResponse: HttpFullResponse._fetch_packet,
36
+ HttpHistory: HttpHistory._fetch_packet,
37
+ HttpStreamHistory: HttpStreamHistory._fetch_packet,
34
38
  },
35
39
  }
36
40
 
@@ -52,5 +56,8 @@ __all__ = [
52
56
  "HttpResponseHeader",
53
57
  "HttpFullRequest",
54
58
  "HttpFullResponse",
59
+ "HttpHistory",
60
+ "HttpStreamHistory",
55
61
  "Protocols",
56
62
  ]
63
+
@@ -48,6 +48,7 @@ class InternalHTTPMessage:
48
48
  ws_stream: list[Frame] = field(default_factory=list) # Decoded websocket stream
49
49
  upgrading_to_h2: bool = field(default=False)
50
50
  upgrading_to_ws: bool = field(default=False)
51
+ added_to_history: bool = field(default=False)
51
52
 
52
53
 
53
54
  @dataclass
@@ -393,6 +394,61 @@ class InternalHttpResponse(InternalCallbackHandler, pyllhttp.Response):
393
394
  return False
394
395
 
395
396
 
397
+ class HttpHistory:
398
+ """
399
+ HTTP History handler for pyfilters.
400
+ Provides access to completed previous requests and responses in the current TCP stream.
401
+ """
402
+
403
+ def __init__(
404
+ self,
405
+ requests: list["HttpFullRequest"] | None = None,
406
+ responses: list["HttpFullResponse"] | None = None,
407
+ ):
408
+ self._requests = list(requests) if requests is not None else []
409
+ self._responses = list(responses) if responses is not None else []
410
+
411
+ @property
412
+ def requests(self) -> list["HttpFullRequest"]:
413
+ """List of previous completed HTTP requests"""
414
+ return self._requests.copy()
415
+
416
+ @property
417
+ def responses(self) -> list["HttpFullResponse"]:
418
+ """List of previous completed HTTP responses"""
419
+ return self._responses.copy()
420
+
421
+ @classmethod
422
+ def _fetch_packet(cls, internal_data: DataStreamCtx):
423
+ if (
424
+ internal_data.current_pkt is None
425
+ or internal_data.current_pkt.is_tcp is False
426
+ ):
427
+ raise NotReadyToRun()
428
+
429
+ for key in ("http_module", "http_full", "http_header"):
430
+ obj = internal_data.call_mem.get(f"_fetched_obj_{key}")
431
+ if obj is not None:
432
+ if isinstance(obj, list) and obj:
433
+ return [item.history for item in obj]
434
+ elif hasattr(obj, "history"):
435
+ return obj.history
436
+
437
+ req_history_deque = internal_data.data_handler_context.get(
438
+ "http_history_requests", deque()
439
+ )
440
+ resp_history_deque = internal_data.data_handler_context.get(
441
+ "http_history_responses", deque()
442
+ )
443
+ return HttpHistory(list(req_history_deque), list(resp_history_deque))
444
+
445
+ def __repr__(self):
446
+ return f"<HttpHistory requests={len(self._requests)} responses={len(self._responses)}>"
447
+
448
+
449
+ HttpStreamHistory = HttpHistory
450
+
451
+
396
452
  class InternalBasicHttpMetaClass:
397
453
  """Internal class to handle HTTP requests and responses"""
398
454
 
@@ -404,15 +460,23 @@ class InternalBasicHttpMetaClass:
404
460
  self._parser = parser
405
461
  self.raised_error = False
406
462
  self._message: InternalHTTPMessage | None = msg
463
+ self._history: HttpHistory | None = None
407
464
  self._contructor_hook()
408
465
 
409
466
  def _contructor_hook(self):
410
467
  pass
411
468
 
469
+ @property
470
+ def history(self) -> HttpHistory:
471
+ """HTTP History for the current stream connection"""
472
+ if self._history is None:
473
+ return HttpHistory([], [])
474
+ return self._history
475
+
412
476
  @property
413
477
  def total_size(self) -> int:
414
- """Total size of the stream"""
415
- return self._parser.total_size
478
+ """Total size of the message"""
479
+ return self._message.total_size
416
480
 
417
481
  @property
418
482
  def url(self) -> str | None:
@@ -462,7 +526,7 @@ class InternalBasicHttpMetaClass:
462
526
  @property
463
527
  def should_upgrade(self) -> bool:
464
528
  """If the message should upgrade"""
465
- return self._parser.should_upgrade
529
+ return self._message.should_upgrade
466
530
 
467
531
  @property
468
532
  def content_length(self) -> int | None:
@@ -493,6 +557,10 @@ class InternalBasicHttpMetaClass:
493
557
  """Get a header from the message without caring about the case"""
494
558
  return self._message.lheaders.get(header.lower(), default)
495
559
 
560
+ @classmethod
561
+ def _should_release_message_headers(cls) -> bool:
562
+ return True
563
+
496
564
  @staticmethod
497
565
  def _before_fetch_callable_checks(internal_data: DataStreamCtx) -> bool:
498
566
  raise NotImplementedError()
@@ -521,6 +589,9 @@ class InternalBasicHttpMetaClass:
521
589
  parser: InternalHttpRequest | InternalHttpResponse = ParserType()
522
590
  internal_data.data_handler_context[parser_key] = parser
523
591
 
592
+ parser.release_message_headers = cls._should_release_message_headers()
593
+
594
+
524
595
  if not internal_data.call_mem.get(
525
596
  cls._parser_class(), False
526
597
  ): # Need to parse HTTP
@@ -620,10 +691,55 @@ class InternalBasicHttpMetaClass:
620
691
 
621
692
  if messages_to_call == 0:
622
693
  raise NotReadyToRun()
623
- elif messages_to_call == 1:
624
- return cls(parser, messages_tosend[0])
625
694
 
626
- return [cls(parser, ele) for ele in messages_tosend]
695
+ raw_max = internal_data.filter_glob.get("FGEX_MAX_HISTORY_SIZE", 100)
696
+ try:
697
+ max_history = max(0, int(raw_max))
698
+ except (ValueError, TypeError):
699
+ max_history = 100
700
+
701
+ req_history_deque: deque = (
702
+ internal_data.data_handler_context.setdefault(
703
+ "http_history_requests", deque(maxlen=max_history)
704
+ )
705
+ )
706
+ if req_history_deque.maxlen != max_history:
707
+ req_history_deque = deque(req_history_deque, maxlen=max_history)
708
+ internal_data.data_handler_context["http_history_requests"] = req_history_deque
709
+
710
+ resp_history_deque: deque = (
711
+ internal_data.data_handler_context.setdefault(
712
+ "http_history_responses", deque(maxlen=max_history)
713
+ )
714
+ )
715
+ if resp_history_deque.maxlen != max_history:
716
+ resp_history_deque = deque(resp_history_deque, maxlen=max_history)
717
+ internal_data.data_handler_context["http_history_responses"] = resp_history_deque
718
+
719
+ built_instances = []
720
+ for msg in messages_tosend:
721
+ history_snapshot = HttpHistory(
722
+ list(req_history_deque), list(resp_history_deque)
723
+ )
724
+ instance = cls(parser, msg)
725
+ instance._history = history_snapshot
726
+ built_instances.append(instance)
727
+
728
+ if msg.message_complete and not msg.added_to_history:
729
+ msg.added_to_history = True
730
+ if internal_data.current_pkt.is_input:
731
+ req_history_deque.append(HttpFullRequest(parser, msg))
732
+ else:
733
+ resp_history_deque.append(HttpFullResponse(parser, msg))
734
+
735
+ if len(built_instances) == 1:
736
+ res = built_instances[0]
737
+ internal_data.call_mem[f"_fetched_obj_{cls._parser_class()}"] = res
738
+ return res
739
+
740
+ internal_data.call_mem[f"_fetched_obj_{cls._parser_class()}"] = built_instances
741
+ return built_instances
742
+
627
743
 
628
744
 
629
745
  class HttpRequest(InternalBasicHttpMetaClass):
@@ -639,7 +755,7 @@ class HttpRequest(InternalBasicHttpMetaClass):
639
755
  @property
640
756
  def method(self) -> bytes:
641
757
  """Method of the request"""
642
- return self._parser.msg.method
758
+ return self._message.method
643
759
 
644
760
  @staticmethod
645
761
  def _parser_class() -> str:
@@ -662,7 +778,8 @@ class HttpResponse(InternalBasicHttpMetaClass):
662
778
  @property
663
779
  def status_code(self) -> int:
664
780
  """Status code of the response"""
665
- return self._parser.msg.status
781
+ return self._message.status
782
+
666
783
 
667
784
  @staticmethod
668
785
  def _parser_class() -> str:
@@ -678,6 +795,10 @@ class HttpFullRequest(HttpRequest):
678
795
  This data handler will be called when the request data is complete
679
796
  """
680
797
 
798
+ @classmethod
799
+ def _should_release_message_headers(cls) -> bool:
800
+ return False
801
+
681
802
  def _contructor_hook(self):
682
803
  self._parser.release_message_headers = False
683
804
 
@@ -695,6 +816,10 @@ class HttpFullResponse(HttpResponse):
695
816
  This data handler will be called when the response data is complete
696
817
  """
697
818
 
819
+ @classmethod
820
+ def _should_release_message_headers(cls) -> bool:
821
+ return False
822
+
698
823
  def _contructor_hook(self):
699
824
  self._parser.release_message_headers = False
700
825
 
@@ -706,6 +831,7 @@ class HttpFullResponse(HttpResponse):
706
831
  return f"<HttpFullResponse 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}>"
707
832
 
708
833
 
834
+
709
835
  class HttpRequestHeader(HttpRequest):
710
836
  """
711
837
  HTTP Request Header handler
@@ -0,0 +1,57 @@
1
+ Metadata-Version: 2.4
2
+ Name: firegex
3
+ Version: 4.0.0
4
+ Summary: Firegex client
5
+ Home-page: https://github.com/pwnzer0tt1/firegex
6
+ Author: Pwnzer0tt1
7
+ Author-email: pwnzer0tt1@poliba.it
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: typer
14
+ Requires-Dist: pydantic>=2
15
+ Requires-Dist: typing-extensions>=4.7.1
16
+ Requires-Dist: pycryptodome
17
+ Requires-Dist: watchfiles
18
+ Requires-Dist: fgex
19
+ Requires-Dist: websockets
20
+ Requires-Dist: brotli
21
+ Requires-Dist: pyllhttp
22
+ Dynamic: author
23
+ Dynamic: author-email
24
+ Dynamic: classifier
25
+ Dynamic: description
26
+ Dynamic: description-content-type
27
+ Dynamic: home-page
28
+ Dynamic: requires-dist
29
+ Dynamic: requires-python
30
+ Dynamic: summary
31
+
32
+ # Firegex Python Library and CLI
33
+
34
+ This is the Python library for [Firegex](https://github.com/Pwnzer0tt1/firegex), a firewall built for CTF Attack-Defense competitions. It provides the `firegex.nfproxy` package used to write [Netfilter Proxy](../docs/nfproxy.md) Python packet filters, the `fgex` CLI, and a local proxy simulator (`proxysim`) for testing filters without a running Firegex instance.
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ pip install -U firegex
40
+ ```
41
+
42
+ `fgex` is an alias package for `firegex`: installing either one gives you the same `firegex` module and the `fgex`/`firegex` CLI commands.
43
+
44
+ ## Documentation
45
+
46
+ **➡️ [Netfilter Proxy: writing and testing `@pyfilter` scripts](../docs/nfproxy.md)** — the `pyfilter` decorator, every data structure (`RawPacket`, `TCPInputStream`/`TCPOutputStream`, `HttpRequest`/`HttpResponse` and their variants, `HttpHistory`), packet statements, stream limits, the `fgex nfproxy` CLI simulator, and a full worked example.
47
+
48
+ That page is also what's shown in the Firegex web UI itself (the docs button on the nfproxy pages) — both are generated from that single file, so they can never drift out of sync.
49
+
50
+ ## Firegex's other filtering modules
51
+
52
+ This library only covers Netfilter Proxy. Firegex ships several other independent modules, each with its own guide:
53
+
54
+ - **[Netfilter Regex](../docs/nfregex.md)** — fast, PCRE2/hyperscan-based regex matching against raw traffic.
55
+ - **[Hijack Port to Proxy](../docs/porthijack.md)** — redirect traffic to your own external proxy without touching the target service.
56
+ - **[Firewall Rules](../docs/firewall.md)** — plain nftables allow/drop/reject rules.
57
+ - **[TLS Decryption](../docs/tls.md)** — decrypt-and-reinspect bridge for services that speak TLS natively.
@@ -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.5.2"
9
+ VERSION = "4.0.0"
10
10
 
11
11
  setuptools.setup(
12
12
  name="firegex",
firegex-3.5.2/PKG-INFO DELETED
@@ -1,182 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: firegex
3
- Version: 3.5.2
4
- Summary: Firegex client
5
- Home-page: https://github.com/pwnzer0tt1/firegex
6
- Author: Pwnzer0tt1
7
- Author-email: pwnzer0tt1@poliba.it
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
10
- Classifier: Operating System :: OS Independent
11
- Requires-Python: >=3.10
12
- Description-Content-Type: text/markdown
13
- Requires-Dist: typer
14
- Requires-Dist: pydantic>=2
15
- Requires-Dist: typing-extensions>=4.7.1
16
- Requires-Dist: pycryptodome
17
- Requires-Dist: watchfiles
18
- Requires-Dist: fgex
19
- Requires-Dist: websockets
20
- Requires-Dist: brotli
21
- Requires-Dist: pyllhttp
22
- Dynamic: author
23
- Dynamic: author-email
24
- Dynamic: classifier
25
- Dynamic: description
26
- Dynamic: description-content-type
27
- Dynamic: home-page
28
- Dynamic: requires-dist
29
- Dynamic: requires-python
30
- Dynamic: summary
31
-
32
- # Firegex Python Library and CLI
33
-
34
- This is the Python library for Firegex. It is used to get additional features of Firegex and use the feature of the command `fgex`.
35
-
36
- ## Installation
37
-
38
- ```bash
39
- pip install -U firegex
40
- ```
41
-
42
- fgex is an alias of firegex. You can use `fgex` instead of `firegex`.
43
-
44
- ## Command line usage:
45
-
46
- ```text
47
- ➤ fgex nfproxy -h
48
-
49
- Usage: fgex nfproxy [OPTIONS] FILTER_FILE ADDRESS PORT
50
-
51
- Run an nfproxy simulation
52
-
53
- ╭─ Arguments ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
54
- │ * filter_file TEXT The path to the filter file [default: None] [required] │
55
- │ * address TEXT The address of the target to proxy [default: None] [required] │
56
- │ * port INTEGER The port of the target to proxy [default: None] [required] │
57
- ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
58
- ╭─ Options ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
59
- │ --proto [tcp|http] The protocol to proxy [default: tcp] │
60
- │ --from-address TEXT The address of the local server [default: None] │
61
- │ --from-port INTEGER The port of the local server [default: 7474] │
62
- │ -6 Use IPv6 for the connection │
63
- │ --help -h Show this message and exit. │
64
- ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
65
- ```
66
-
67
- ## Library usage:
68
-
69
- ## NfProxy decorator
70
-
71
- ```python
72
- from firegex.nfproxy import pyfilter
73
- ```
74
- This decorator is used to create a filter for the nfproxy.
75
- Example:
76
- ```python
77
- @pyfilter
78
- def my_filter(raw_packet: RawPacket): #Logging filter
79
- print(raw_packet.data)
80
- ```
81
-
82
- ## Data handlers
83
-
84
- ### RawPacket
85
- ```python
86
- from firegex.nfproxy import RawPacket
87
- ```
88
- This handler will be called every time arrives a packet from the network. It will receive a RawPacket object with the following properties:
89
- - is_input: bool - It's true if the packet is an input packet, false if it's an output packet
90
- - is_ipv6: bool - It's true if the packet is an ipv6 packet, false if it's an ipv4 packet
91
- - is_tcp: bool - It's true if the packet is a tcp packet, false if it's an udp packet
92
- - data: bytes - The data of the packet assembled and sorted from TCP
93
- - l4_size: int - The size of the layer 4 data
94
- - raw_packet_header_len: int - The size of the original packet header
95
- - l4_data: bytes - The layer 4 payload of the packet
96
- - raw_packet: bytes - The raw packet with IP and TCP headers
97
-
98
- ### TCPInputStream
99
- Alias: TCPClientStream
100
- ```python
101
- from firegex.nfproxy import TCPInputStream
102
- ```
103
- This handler will be called every time a TCP stream is assembled in input. It will receive a TCPInputStream object with the following properties:
104
- - data: bytes - The data of the packets assembled and sorted from TCP
105
- - is_ipv6: bool - It's true if the packet is an ipv6 packet, false if it's an ipv4 packet
106
- - total_stream_size: int - The size of the stream
107
-
108
- ### TCPOutputStream
109
- Alias: TCPServerStream
110
- ```python
111
- from firegex.nfproxy import TCPOutputStream
112
- ```
113
- This handler will be called every time a TCP stream is assembled in output. It will receive a TCPOutputStream object with the following properties:
114
- - data: bytes - The data of the packets assembled and sorted from TCP
115
- - is_ipv6: bool - It's true if the packet is an ipv6 packet, false if it's an ipv4 packet
116
- - total_stream_size: int - The size of the stream
117
-
118
- ### HttpRequest
119
- ```python
120
- from firegex.nfproxy import HttpRequest
121
- ```
122
- This handler will be called twice: one for the request headers and one for the request body. It will receive a HttpRequest object with the following properties:
123
- - method: bytes - The method of the request
124
- - url: str - The url of the request
125
- - headers: dict - The headers of the request
126
- - user_agent: str - The user agent of the request
127
- - content_encoding: str - The content encoding of the request
128
- - body: bytes - The body of the request
129
- - headers_complete: bool - It's true if the headers are complete
130
- - message_complete: bool - It's true if the message is complete
131
- - http_version: str - The http version of the request
132
- - keep_alive: bool - It's true if the request should keep alive
133
- - should_upgrade: bool - It's true if the request should upgrade
134
- - content_length: int - The content length of the request
135
- - get_header(header: str, default=None): str - Get a header from the request without caring about the case
136
- - total_size: int - The total size of the stream
137
- - stream: bytes - The stream of the request
138
-
139
- ### HttpRequestHeader
140
- ```python
141
- from firegex.nfproxy import HttpRequestHeader
142
- ```
143
- This handler will be called only when the request headers are complete. It will receive a HttpRequestHeader object with the same properties as HttpRequest.
144
-
145
- ### HttpFullRequest
146
- ```python
147
- from firegex.nfproxy import HttpFullRequest
148
- ```
149
- This handler will be called only when the request is complete. It will receive a HttpFullRequest object with the same properties as HttpRequest.
150
-
151
- ### HttpResponse
152
- ```python
153
- from firegex.nfproxy import HttpResponse
154
- ```
155
- This handler will be called twice: one for the response headers and one for the response body. It will receive a HttpResponse object with the following properties:
156
- - status_code: int - The status code of the response
157
- - url: str - The url of the response
158
- - headers: dict - The headers of the response
159
- - user_agent: str - The user agent of the response
160
- - content_encoding: str - The content encoding of the response
161
- - body: bytes - The body of the response
162
- - headers_complete: bool - It's true if the headers are complete
163
- - message_complete: bool - It's true if the message is complete
164
- - http_version: str - The http version of the response
165
- - keep_alive: bool - It's true if the response should keep alive
166
- - should_upgrade: bool - It's true if the response should upgrade
167
- - content_length: int - The content length of the response
168
- - get_header(header: str, default=None): str - Get a header from the response without caring about the case
169
- - total_size: int - The total size of the stream
170
- - stream: bytes - The stream of the response
171
-
172
- ### HttpResponseHeader
173
- ```python
174
- from firegex.nfproxy import HttpResponseHeader
175
- ```
176
- This handler will be called only when the response headers are complete. It will receive a HttpResponseHeader object with the same properties as HttpResponse.
177
-
178
- ### HttpFullResponse
179
- ```python
180
- from firegex.nfproxy import HttpFullResponse
181
- ```
182
- This handler will be called only when the response is complete. It will receive a HttpFullResponse object with the same properties as HttpResponse.
firegex-3.5.2/README.md DELETED
@@ -1,151 +0,0 @@
1
- # Firegex Python Library and CLI
2
-
3
- This is the Python library for Firegex. It is used to get additional features of Firegex and use the feature of the command `fgex`.
4
-
5
- ## Installation
6
-
7
- ```bash
8
- pip install -U firegex
9
- ```
10
-
11
- fgex is an alias of firegex. You can use `fgex` instead of `firegex`.
12
-
13
- ## Command line usage:
14
-
15
- ```text
16
- ➤ fgex nfproxy -h
17
-
18
- Usage: fgex nfproxy [OPTIONS] FILTER_FILE ADDRESS PORT
19
-
20
- Run an nfproxy simulation
21
-
22
- ╭─ Arguments ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
23
- │ * filter_file TEXT The path to the filter file [default: None] [required] │
24
- │ * address TEXT The address of the target to proxy [default: None] [required] │
25
- │ * port INTEGER The port of the target to proxy [default: None] [required] │
26
- ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
27
- ╭─ Options ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
28
- │ --proto [tcp|http] The protocol to proxy [default: tcp] │
29
- │ --from-address TEXT The address of the local server [default: None] │
30
- │ --from-port INTEGER The port of the local server [default: 7474] │
31
- │ -6 Use IPv6 for the connection │
32
- │ --help -h Show this message and exit. │
33
- ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
34
- ```
35
-
36
- ## Library usage:
37
-
38
- ## NfProxy decorator
39
-
40
- ```python
41
- from firegex.nfproxy import pyfilter
42
- ```
43
- This decorator is used to create a filter for the nfproxy.
44
- Example:
45
- ```python
46
- @pyfilter
47
- def my_filter(raw_packet: RawPacket): #Logging filter
48
- print(raw_packet.data)
49
- ```
50
-
51
- ## Data handlers
52
-
53
- ### RawPacket
54
- ```python
55
- from firegex.nfproxy import RawPacket
56
- ```
57
- This handler will be called every time arrives a packet from the network. It will receive a RawPacket object with the following properties:
58
- - is_input: bool - It's true if the packet is an input packet, false if it's an output packet
59
- - is_ipv6: bool - It's true if the packet is an ipv6 packet, false if it's an ipv4 packet
60
- - is_tcp: bool - It's true if the packet is a tcp packet, false if it's an udp packet
61
- - data: bytes - The data of the packet assembled and sorted from TCP
62
- - l4_size: int - The size of the layer 4 data
63
- - raw_packet_header_len: int - The size of the original packet header
64
- - l4_data: bytes - The layer 4 payload of the packet
65
- - raw_packet: bytes - The raw packet with IP and TCP headers
66
-
67
- ### TCPInputStream
68
- Alias: TCPClientStream
69
- ```python
70
- from firegex.nfproxy import TCPInputStream
71
- ```
72
- This handler will be called every time a TCP stream is assembled in input. It will receive a TCPInputStream object with the following properties:
73
- - data: bytes - The data of the packets assembled and sorted from TCP
74
- - is_ipv6: bool - It's true if the packet is an ipv6 packet, false if it's an ipv4 packet
75
- - total_stream_size: int - The size of the stream
76
-
77
- ### TCPOutputStream
78
- Alias: TCPServerStream
79
- ```python
80
- from firegex.nfproxy import TCPOutputStream
81
- ```
82
- This handler will be called every time a TCP stream is assembled in output. It will receive a TCPOutputStream object with the following properties:
83
- - data: bytes - The data of the packets assembled and sorted from TCP
84
- - is_ipv6: bool - It's true if the packet is an ipv6 packet, false if it's an ipv4 packet
85
- - total_stream_size: int - The size of the stream
86
-
87
- ### HttpRequest
88
- ```python
89
- from firegex.nfproxy import HttpRequest
90
- ```
91
- This handler will be called twice: one for the request headers and one for the request body. It will receive a HttpRequest object with the following properties:
92
- - method: bytes - The method of the request
93
- - url: str - The url of the request
94
- - headers: dict - The headers of the request
95
- - user_agent: str - The user agent of the request
96
- - content_encoding: str - The content encoding of the request
97
- - body: bytes - The body of the request
98
- - headers_complete: bool - It's true if the headers are complete
99
- - message_complete: bool - It's true if the message is complete
100
- - http_version: str - The http version of the request
101
- - keep_alive: bool - It's true if the request should keep alive
102
- - should_upgrade: bool - It's true if the request should upgrade
103
- - content_length: int - The content length of the request
104
- - get_header(header: str, default=None): str - Get a header from the request without caring about the case
105
- - total_size: int - The total size of the stream
106
- - stream: bytes - The stream of the request
107
-
108
- ### HttpRequestHeader
109
- ```python
110
- from firegex.nfproxy import HttpRequestHeader
111
- ```
112
- This handler will be called only when the request headers are complete. It will receive a HttpRequestHeader object with the same properties as HttpRequest.
113
-
114
- ### HttpFullRequest
115
- ```python
116
- from firegex.nfproxy import HttpFullRequest
117
- ```
118
- This handler will be called only when the request is complete. It will receive a HttpFullRequest object with the same properties as HttpRequest.
119
-
120
- ### HttpResponse
121
- ```python
122
- from firegex.nfproxy import HttpResponse
123
- ```
124
- This handler will be called twice: one for the response headers and one for the response body. It will receive a HttpResponse object with the following properties:
125
- - status_code: int - The status code of the response
126
- - url: str - The url of the response
127
- - headers: dict - The headers of the response
128
- - user_agent: str - The user agent of the response
129
- - content_encoding: str - The content encoding of the response
130
- - body: bytes - The body of the response
131
- - headers_complete: bool - It's true if the headers are complete
132
- - message_complete: bool - It's true if the message is complete
133
- - http_version: str - The http version of the response
134
- - keep_alive: bool - It's true if the response should keep alive
135
- - should_upgrade: bool - It's true if the response should upgrade
136
- - content_length: int - The content length of the response
137
- - get_header(header: str, default=None): str - Get a header from the response without caring about the case
138
- - total_size: int - The total size of the stream
139
- - stream: bytes - The stream of the response
140
-
141
- ### HttpResponseHeader
142
- ```python
143
- from firegex.nfproxy import HttpResponseHeader
144
- ```
145
- This handler will be called only when the response headers are complete. It will receive a HttpResponseHeader object with the same properties as HttpResponse.
146
-
147
- ### HttpFullResponse
148
- ```python
149
- from firegex.nfproxy import HttpFullResponse
150
- ```
151
- This handler will be called only when the response is complete. It will receive a HttpFullResponse object with the same properties as HttpResponse.
@@ -1,5 +0,0 @@
1
-
2
- __version__ = "3.5.2" if "{" not in "3.5.2" else "0.0.0"
3
-
4
- #Exported functions
5
- __all__ = []
@@ -1,182 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: firegex
3
- Version: 3.5.2
4
- Summary: Firegex client
5
- Home-page: https://github.com/pwnzer0tt1/firegex
6
- Author: Pwnzer0tt1
7
- Author-email: pwnzer0tt1@poliba.it
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
10
- Classifier: Operating System :: OS Independent
11
- Requires-Python: >=3.10
12
- Description-Content-Type: text/markdown
13
- Requires-Dist: typer
14
- Requires-Dist: pydantic>=2
15
- Requires-Dist: typing-extensions>=4.7.1
16
- Requires-Dist: pycryptodome
17
- Requires-Dist: watchfiles
18
- Requires-Dist: fgex
19
- Requires-Dist: websockets
20
- Requires-Dist: brotli
21
- Requires-Dist: pyllhttp
22
- Dynamic: author
23
- Dynamic: author-email
24
- Dynamic: classifier
25
- Dynamic: description
26
- Dynamic: description-content-type
27
- Dynamic: home-page
28
- Dynamic: requires-dist
29
- Dynamic: requires-python
30
- Dynamic: summary
31
-
32
- # Firegex Python Library and CLI
33
-
34
- This is the Python library for Firegex. It is used to get additional features of Firegex and use the feature of the command `fgex`.
35
-
36
- ## Installation
37
-
38
- ```bash
39
- pip install -U firegex
40
- ```
41
-
42
- fgex is an alias of firegex. You can use `fgex` instead of `firegex`.
43
-
44
- ## Command line usage:
45
-
46
- ```text
47
- ➤ fgex nfproxy -h
48
-
49
- Usage: fgex nfproxy [OPTIONS] FILTER_FILE ADDRESS PORT
50
-
51
- Run an nfproxy simulation
52
-
53
- ╭─ Arguments ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
54
- │ * filter_file TEXT The path to the filter file [default: None] [required] │
55
- │ * address TEXT The address of the target to proxy [default: None] [required] │
56
- │ * port INTEGER The port of the target to proxy [default: None] [required] │
57
- ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
58
- ╭─ Options ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
59
- │ --proto [tcp|http] The protocol to proxy [default: tcp] │
60
- │ --from-address TEXT The address of the local server [default: None] │
61
- │ --from-port INTEGER The port of the local server [default: 7474] │
62
- │ -6 Use IPv6 for the connection │
63
- │ --help -h Show this message and exit. │
64
- ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
65
- ```
66
-
67
- ## Library usage:
68
-
69
- ## NfProxy decorator
70
-
71
- ```python
72
- from firegex.nfproxy import pyfilter
73
- ```
74
- This decorator is used to create a filter for the nfproxy.
75
- Example:
76
- ```python
77
- @pyfilter
78
- def my_filter(raw_packet: RawPacket): #Logging filter
79
- print(raw_packet.data)
80
- ```
81
-
82
- ## Data handlers
83
-
84
- ### RawPacket
85
- ```python
86
- from firegex.nfproxy import RawPacket
87
- ```
88
- This handler will be called every time arrives a packet from the network. It will receive a RawPacket object with the following properties:
89
- - is_input: bool - It's true if the packet is an input packet, false if it's an output packet
90
- - is_ipv6: bool - It's true if the packet is an ipv6 packet, false if it's an ipv4 packet
91
- - is_tcp: bool - It's true if the packet is a tcp packet, false if it's an udp packet
92
- - data: bytes - The data of the packet assembled and sorted from TCP
93
- - l4_size: int - The size of the layer 4 data
94
- - raw_packet_header_len: int - The size of the original packet header
95
- - l4_data: bytes - The layer 4 payload of the packet
96
- - raw_packet: bytes - The raw packet with IP and TCP headers
97
-
98
- ### TCPInputStream
99
- Alias: TCPClientStream
100
- ```python
101
- from firegex.nfproxy import TCPInputStream
102
- ```
103
- This handler will be called every time a TCP stream is assembled in input. It will receive a TCPInputStream object with the following properties:
104
- - data: bytes - The data of the packets assembled and sorted from TCP
105
- - is_ipv6: bool - It's true if the packet is an ipv6 packet, false if it's an ipv4 packet
106
- - total_stream_size: int - The size of the stream
107
-
108
- ### TCPOutputStream
109
- Alias: TCPServerStream
110
- ```python
111
- from firegex.nfproxy import TCPOutputStream
112
- ```
113
- This handler will be called every time a TCP stream is assembled in output. It will receive a TCPOutputStream object with the following properties:
114
- - data: bytes - The data of the packets assembled and sorted from TCP
115
- - is_ipv6: bool - It's true if the packet is an ipv6 packet, false if it's an ipv4 packet
116
- - total_stream_size: int - The size of the stream
117
-
118
- ### HttpRequest
119
- ```python
120
- from firegex.nfproxy import HttpRequest
121
- ```
122
- This handler will be called twice: one for the request headers and one for the request body. It will receive a HttpRequest object with the following properties:
123
- - method: bytes - The method of the request
124
- - url: str - The url of the request
125
- - headers: dict - The headers of the request
126
- - user_agent: str - The user agent of the request
127
- - content_encoding: str - The content encoding of the request
128
- - body: bytes - The body of the request
129
- - headers_complete: bool - It's true if the headers are complete
130
- - message_complete: bool - It's true if the message is complete
131
- - http_version: str - The http version of the request
132
- - keep_alive: bool - It's true if the request should keep alive
133
- - should_upgrade: bool - It's true if the request should upgrade
134
- - content_length: int - The content length of the request
135
- - get_header(header: str, default=None): str - Get a header from the request without caring about the case
136
- - total_size: int - The total size of the stream
137
- - stream: bytes - The stream of the request
138
-
139
- ### HttpRequestHeader
140
- ```python
141
- from firegex.nfproxy import HttpRequestHeader
142
- ```
143
- This handler will be called only when the request headers are complete. It will receive a HttpRequestHeader object with the same properties as HttpRequest.
144
-
145
- ### HttpFullRequest
146
- ```python
147
- from firegex.nfproxy import HttpFullRequest
148
- ```
149
- This handler will be called only when the request is complete. It will receive a HttpFullRequest object with the same properties as HttpRequest.
150
-
151
- ### HttpResponse
152
- ```python
153
- from firegex.nfproxy import HttpResponse
154
- ```
155
- This handler will be called twice: one for the response headers and one for the response body. It will receive a HttpResponse object with the following properties:
156
- - status_code: int - The status code of the response
157
- - url: str - The url of the response
158
- - headers: dict - The headers of the response
159
- - user_agent: str - The user agent of the response
160
- - content_encoding: str - The content encoding of the response
161
- - body: bytes - The body of the response
162
- - headers_complete: bool - It's true if the headers are complete
163
- - message_complete: bool - It's true if the message is complete
164
- - http_version: str - The http version of the response
165
- - keep_alive: bool - It's true if the response should keep alive
166
- - should_upgrade: bool - It's true if the response should upgrade
167
- - content_length: int - The content length of the response
168
- - get_header(header: str, default=None): str - Get a header from the response without caring about the case
169
- - total_size: int - The total size of the stream
170
- - stream: bytes - The stream of the response
171
-
172
- ### HttpResponseHeader
173
- ```python
174
- from firegex.nfproxy import HttpResponseHeader
175
- ```
176
- This handler will be called only when the response headers are complete. It will receive a HttpResponseHeader object with the same properties as HttpResponse.
177
-
178
- ### HttpFullResponse
179
- ```python
180
- from firegex.nfproxy import HttpFullResponse
181
- ```
182
- This handler will be called only when the response is complete. It will receive a HttpFullResponse object with the same properties as HttpResponse.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes