"""Shared utilities for report template rendering.""" from datetime import UTC, datetime from pathlib import Path from typing import Any from jinja2 import Environment, FileSystemLoader, select_autoescape from .query import has_tls_support def format_tls_version(version: str) -> str: """Format TLS version string for display. Args: version: TLS version identifier (e.g., "1.2", "ssl_3.0") Returns: Formatted version string (e.g., "TLS 1.2", "SSL 3.0") """ version_map = { "ssl_3.0": "SSL 3.0", "1.0": "TLS 1.0", "1.1": "TLS 1.1", "1.2": "TLS 1.2", "1.3": "TLS 1.3", } return version_map.get(version, version) def create_jinja_env() -> Environment: """Create Jinja2 environment with standard configuration. Returns: Configured Jinja2 Environment with custom filters """ template_dir = Path(__file__).parent.parent / "templates" env = Environment( loader=FileSystemLoader(str(template_dir)), autoescape=select_autoescape(["html", "xml"]), trim_blocks=True, lstrip_blocks=True, ) env.filters["format_tls_version"] = format_tls_version return env def generate_report_id(metadata: dict[str, Any]) -> str: """Generate report ID from scan metadata. Args: metadata: Scan metadata dictionary containing timestamp Returns: Report ID in format YYYYMMDD_ """ try: dt = datetime.fromisoformat(metadata["timestamp"]) date_str = dt.strftime("%Y%m%d") except (ValueError, KeyError): date_str = datetime.now(UTC).strftime("%Y%m%d") return f"{date_str}_{metadata['scan_id']}" def build_template_context(data: dict[str, Any]) -> dict[str, Any]: """Build template context from scan data. Args: data: Scan data dictionary from get_scan_data() Returns: Dictionary with template context variables """ metadata = data["metadata"] duration = metadata.get("duration") if duration is not None: duration_str = ( f"{duration:.2f}" if isinstance(duration, (int, float)) else str(duration) ) else: duration_str = "N/A" # Format timestamp to minute precision (DD.MM.YYYY HH:MM) timestamp_str = metadata["timestamp"] try: dt = datetime.fromisoformat(timestamp_str) timestamp_str = dt.strftime("%d.%m.%Y %H:%M") except (ValueError, KeyError): pass # Filter ports with TLS support for port sections ports_with_tls = [] for port_data in data["ports_data"].values(): if has_tls_support(port_data): ports_with_tls.append(port_data) return { "scan_id": metadata["scan_id"], "hostname": metadata["hostname"], "fqdn": metadata["fqdn"], "ipv4": metadata["ipv4"], "ipv6": metadata["ipv6"], "timestamp": timestamp_str, "duration": duration_str, "ports": ", ".join(metadata["ports"]), "ports_without_tls": data.get("summary", {}).get("ports_without_tls", 0), "summary": data.get("summary", {}), "ports_data": sorted(ports_with_tls, key=lambda x: x["port"]), } def prepare_output_path( output_file: str | None, output_dir: str, default_filename: str, ) -> Path: """Prepare output file path and ensure parent directory exists. Args: output_file: Explicit output file path (optional) output_dir: Output directory for auto-generated files default_filename: Default filename if output_file is None Returns: Path object for output file """ if output_file: output_path = Path(output_file) else: output_path = Path(output_dir) / default_filename output_path.parent.mkdir(parents=True, exist_ok=True) return output_path def render_template_to_file( template_name: str, context: dict[str, Any], output_path: Path, ) -> str: """Render Jinja2 template and write to file. Args: template_name: Name of template file context: Template context variables output_path: Output file path Returns: String path of generated file """ env = create_jinja_env() template = env.get_template(template_name) content = template.render(**context) output_path.write_text(content, encoding="utf-8") return str(output_path)