mirror of
https://github.com/yogeshojha/rengine.git
synced 2026-10-20 07:11:28 +02:00
urllib.parse.SplitResult.port raises ValueError when the authority carries a non-numeric port; nine call sites read it unguarded, so one malformed URL (a stray quote, a client typo) turned into an unhandled exception in the middle of a scan, a report, an interest rule or a proxy dial. shared/utils/net.py:url_port makes the read itself the failure boundary; unreadable_port names the bogus text so the naabu and banner proxy paths can report what they refused rather than retrying against a host string that dropped its port silently. shared/definitions/ports.py:SCHEME_PORTS replaces four private copies of the same http/https default-port map (endpoints.py, endpoint_judge, scan_surface/normalize, nuclei's parser, dalfox's parser). Also renames live_hosts to answered_hosts in the report source and the interest context, and lifts the asset query's inline is:web predicate into predicates.answered() — the existing distinction between is:live (2xx) and is:web (answered at all) was already the rule; live_hosts was the one name still claiming the stronger one. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VckaDx6mBB4TsG76u61gvv
59 lines
1.8 KiB
Python
59 lines
1.8 KiB
Python
"""One spelling per target: nuclei deduplicates its input byte for byte."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from urllib.parse import urlsplit
|
|
|
|
from shared.definitions.ports import SCHEME_PORTS as DEFAULT_PORTS
|
|
from shared.utils.net import bracketed, host_port, url_port
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Root:
|
|
scheme: str
|
|
host: str
|
|
port: int
|
|
|
|
@property
|
|
def value(self) -> str:
|
|
return root_value(self.scheme, self.host, self.port)
|
|
|
|
|
|
def root_value(scheme: str, host: str, port: int | None) -> str:
|
|
"""`scheme://host[:port]`, lowercase, default port omitted, no path."""
|
|
scheme = (scheme or "https").lower()
|
|
host = (host or "").lower().strip("[]")
|
|
literal = bracketed(host)
|
|
if port and port != DEFAULT_PORTS.get(scheme):
|
|
return f"{scheme}://{literal}:{port}"
|
|
return f"{scheme}://{literal}"
|
|
|
|
|
|
def parse_root(
|
|
url: str, *, scheme: str | None = None, port: int | None = None
|
|
) -> Root | None:
|
|
"""The origin a URL names, or None when it names none."""
|
|
text = (url or "").strip()
|
|
if not text:
|
|
return None
|
|
if "://" not in text:
|
|
text = f"{scheme or 'https'}://{text}"
|
|
parsed = urlsplit(text)
|
|
host = (parsed.hostname or "").lower()
|
|
if not host:
|
|
return None
|
|
scheme_value = (parsed.scheme or scheme or "https").lower()
|
|
port_value = url_port(parsed) or port or DEFAULT_PORTS.get(scheme_value)
|
|
if not port_value:
|
|
return None
|
|
return Root(scheme=scheme_value, host=host, port=int(port_value))
|
|
|
|
|
|
def service_value(host: str, port: int) -> str:
|
|
"""`host:port`, the form ssl and network checks take."""
|
|
return host_port((host or "").lower(), int(port))
|
|
|
|
|
|
__all__ = ["DEFAULT_PORTS", "Root", "parse_root", "root_value", "service_value"]
|