# Location: features.py import re from urllib.parse import urlparse def extract_url_features(url): """ Transforms a raw URL string into a fixed 14-dimensional numerical vector. Bakes protocol layout logic directly into the metrics—no hardcoded strings. """ url_str = str(url).strip() # Normalize scheme purely for structural parsing accuracy if not url_str.lower().startswith(('http://', 'https://')): parse_target = 'http://' + url_str else: parse_target = url_str try: parsed = urlparse(parse_target) # FIX: Extract the true domain name using parsed.hostname instead of parsed.netloc. # parsed.netloc returns 'youtube@evil-site.com', which contaminates the host measurements. # parsed.hostname correctly isolates and returns 'evil-site.com'. host = parsed.hostname if parsed.hostname else url_str path = parsed.path if parsed.path else "" # Ensure the anomaly flag catches userinfo components reliably across both parsers has_userinfo_anomaly = 1.0 if (parsed.username or parsed.password or '@' in parsed.netloc) else 0.0 except Exception: host = url_str path = "" has_userinfo_anomaly = 0.0 # Feature engineering pipeline (Maintained at exactly 14 Dimensions) features = [ len(url_str), # 1. Total length len(host), # 2. Hostname layout length (Corrected to true domain length) url_str.count('.'), # 3. Subdomain count url_str.count('-'), # 4. Hyphen count has_userinfo_anomaly, # 5. Structural Credential Injection Anomaly Flag url_str.count('?'), # 6. Query parameters url_str.count('='), # 7. Variable assignments url_str.count('_'), # 8. Underscores 1 if re.search(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', host) else 0, # 9. IP address in hostname host.count('.'), # 10. Dots in hostname specifically path.count('/'), # 11. Directory depth sum(1 for c in url_str if c.isdigit()), # 12. Total numbers in URL 1 if parsed.scheme == "https" else 0, # 13. Uses HTTPS 1 if url_str.lower().startswith(('bit.ly', 'tinyurl')) else 0 # 14. Shortener ] return features