# SPDX-FileCopyrightText: 2026 Dennis Fink # # SPDX-License-Identifier: BSD-3-Clause from urllib.parse import urlsplit from flask import current_app from wtforms import Field, ValidationError from wtforms.form import BaseForm from ..url_security import AddressResolutionError, is_public_url class NotEqualTo: """ Validate that a field does not equal another field. :param fieldname: Name of the field to compare against. :param message: Optional validation error message. """ def __init__(self, fieldname: str, message: str | None = None) -> None: """ Initialize the validator. :param fieldname: Name of the field to compare against. :param message: Optional validation error message. """ self.fieldname = fieldname self.message = message def __call__(self, form: BaseForm, field: Field) -> None: """ Validate that two fields do not contain equal values. :param form: Form containing the fields to compare. :param field: Field being validated. :raises ValidationError: If the comparison field does not exist or both fields contain equal values. """ try: other = form[self.fieldname] except KeyError as exc: raise ValidationError( field.gettext("Invalid field name '%s'.") % self.fieldname ) from exc if field.data != other.data: return d = { "other_label": hasattr(other, "label") and other.label.text or self.fieldname, "other_name": self.fieldname, } message = self.message or field.gettext( "Field must not be equal to %(other_name)s." ) raise ValidationError(message % d) class AllowedHostname: """ Validate that a URL uses an allowed hostname. :param message: Optional validation error message. """ def __init__(self, message: str | None = None) -> None: """ Initialize the validator. :param message: Optional validation error message. """ self.message = message def __call__(self, form: BaseForm, field: Field) -> None: """ Validate that a URL uses an allowed hostname. :param form: Form containing the field. :param field: Field containing the URL to validate. :raises ValidationError: If the URL hostname is not configured as allowed. """ if ( urlsplit(field.data).hostname in current_app.config["WEBMENTIONS_SSG_ALLOWED_HOSTNAMES"] ): return raise ValidationError(self.message or field.gettext("Invalid input.")) class PublicURL: """ Validate that a URL resolves exclusively to public IP addresses. :param message: Optional validation error message. """ def __init__(self, message: str | None = None) -> None: """ Initialize the validator. :param message: Optional validation error message. """ self.message = message def __call__(self, form: BaseForm, field: Field) -> None: """ Validate that a URL resolves exclusively to public IP addresses. :param form: Form containing the field. :param field: Field containing the URL to validate. :raises ValidationError: If the URL cannot be resolved, has no hostname, or resolves to a non-public address. """ message = self.message if message is None: message = field.gettext("URL must resolve to a public address.") try: if not is_public_url(field.data): raise ValidationError(message) except (AddressResolutionError, ValueError) as exc: raise ValidationError(message) from exc