(scan, host: str, port: int)
| 642 | def _normalize_base_url(target: str) -> str: |
| 643 | if "://" in target: |
| 644 | parsed = urllib.parse.urlparse(target) |
| 645 | return f"{parsed.scheme}://{parsed.netloc}".rstrip("/") |
| 646 | return f"https://{target}".rstrip("/") |
| 647 | |
| 648 | |
| 649 | def _registrable_domain(host: str) -> Optional[str]: |
| 650 | if not host: |
| 651 | return None |
| 652 | try: |
| 653 | import tldextract |
| 654 | ext = tldextract.extract(host) |
| 655 | if ext.domain and ext.suffix: |
| 656 | return f"{ext.domain}.{ext.suffix}" |
| 657 | return None |
| 658 | except ImportError: |
| 659 | parts = host.rsplit(".", 2) |
| 660 | if len(parts) >= 2: |
| 661 | return ".".join(parts[-2:]) |
| 662 | return host |
| 663 | |
| 664 | |
| 665 | def _query_crtsh(domain: str) -> List[str]: |
| 666 | url = CRTSH_URL_TEMPLATE.format(domain=urllib.parse.quote(domain)) |
| 667 | req = urllib.request.Request(url, headers={"User-Agent": "Ragnar-Recon/1.0"}) |
| 668 | with urllib.request.urlopen(req, timeout=CRTSH_TIMEOUT) as resp: |
| 669 | body = resp.read() |
| 670 | data = json.loads(body) |
| 671 | names = set() |
| 672 | for entry in data: |
| 673 | for raw in (entry.get("name_value", ""), entry.get("common_name", "")): |
| 674 | for line in raw.split("\n"): |
| 675 | name = line.strip().lower().lstrip("*.") |
| 676 | if not name or name.startswith(".") or " " in name: |
| 677 | continue |
| 678 | if name.endswith(domain): |
| 679 | names.add(name) |
| 680 | return sorted(names) |
| 681 | |
| 682 | |
| 683 | def _safe_resolve(resolver, name: str, rdtype: str) -> List[str]: |
| 684 | try: |
| 685 | import dns.resolver |
| 686 | answers = resolver.resolve(name, rdtype) |
| 687 | return [str(rdata).strip('"') for rdata in answers] |
| 688 | except Exception: |
| 689 | return [] |
| 690 | |
| 691 | |
| 692 | def _probe_liveness(host: str) -> bool: |
| 693 | for scheme in ("https", "http"): |
| 694 | try: |
| 695 | req = urllib.request.Request(f"{scheme}://{host}", method="HEAD") |
| 696 | with urllib.request.urlopen(req, timeout=LIVENESS_TIMEOUT) as resp: |
| 697 | if 200 <= resp.status < 400: |
| 698 | return True |
| 699 | except Exception: |
| 700 | continue |
| 701 | return False |
no test coverage detected