forked from ungleich-public/dynamicweb
clone cms with menus, make menus standalone app
This commit is contained in:
parent
1ece5ce7c7
commit
afb096b7a3
1153 changed files with 318897 additions and 37 deletions
117
cms/forms/validators.py
Normal file
117
cms/forms/validators.py
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
from typing import TYPE_CHECKING, Optional
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import RegexValidator, URLValidator
|
||||
from django.utils.html import escape
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import gettext
|
||||
|
||||
from cms.utils.urlutils import admin_reverse, path_segment_regex, relative_url_regex
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# Only needed for type hinting - avoid circular import
|
||||
from cms.models.pagemodel import Page
|
||||
|
||||
|
||||
def validate_relative_url(value):
|
||||
RegexValidator(regex=relative_url_regex)(value)
|
||||
|
||||
|
||||
def validate_url(value):
|
||||
if "<" in value or ">" in value:
|
||||
# ``relative_url_regex`` excludes angle brackets, but the absolute fallback below
|
||||
# is Django's ``URLValidator``, whose path pattern accepts them as long as the
|
||||
# value carries no space (``https://example.com/<script>alert(1)</script>``).
|
||||
# Reject them before either branch runs, so markup cannot enter through a scheme.
|
||||
raise ValidationError(
|
||||
gettext("Enter a valid relative or absolute URL."),
|
||||
code="invalid",
|
||||
)
|
||||
|
||||
try:
|
||||
# Validate relative urls first
|
||||
validate_relative_url(value)
|
||||
except ValidationError:
|
||||
# Fallback to absolute urls
|
||||
URLValidator()(value)
|
||||
else:
|
||||
# ``relative_url_regex`` only constrains the *characters* of a relative
|
||||
# path, not the part before the first slash. Values such as
|
||||
# ``javascript:alert(1)/x``, ``data:text/html,...`` or ``vbscript:.../x``
|
||||
# therefore match the relative branch while still carrying a scheme that
|
||||
# executes script when the value is later rendered into an ``href``
|
||||
# (CWE-79). A genuine relative URL has no scheme, so reject any value
|
||||
# that does. (Off-site targets such as ``https://example.com`` or
|
||||
# ``//example.com`` are intentionally permitted -- this field allows
|
||||
# absolute redirects -- and navigate rather than execute script.)
|
||||
if urlsplit(value).scheme:
|
||||
raise ValidationError(
|
||||
gettext("Enter a valid relative or absolute URL."),
|
||||
code="invalid",
|
||||
)
|
||||
|
||||
|
||||
def validate_path_segment(value):
|
||||
"""Validate a single path segment, i.e. a path that contains no "/"."""
|
||||
RegexValidator(
|
||||
regex=path_segment_regex,
|
||||
message=gettext("Enter a valid relative or absolute URL."),
|
||||
code="invalid",
|
||||
)(value)
|
||||
|
||||
|
||||
def validate_path(value):
|
||||
"""Validate a page path.
|
||||
|
||||
Unlike :func:`validate_url` this also accepts a single path segment such as ``contact``,
|
||||
which ``relative_url_regex`` -- and therefore :func:`validate_url` -- never matches.
|
||||
"""
|
||||
if "/" in value:
|
||||
validate_url(value)
|
||||
else:
|
||||
validate_path_segment(value)
|
||||
|
||||
|
||||
def validate_url_uniqueness(
|
||||
site, path: str, language: str, user_language: str | None = None, exclude_page: Optional["Page"] = None
|
||||
):
|
||||
"""Checks for conflicting urls"""
|
||||
from cms.models.pagemodel import PageUrl
|
||||
|
||||
# Single-segment paths used to skip validation entirely, so an "Overwrite URL" without a
|
||||
# "/" could hold arbitrary characters.
|
||||
validate_path(path)
|
||||
|
||||
path = path.strip("/")
|
||||
page_urls = PageUrl.objects.get_for_site(site, language=language).filter(path=path)
|
||||
|
||||
if exclude_page:
|
||||
page_urls = page_urls.exclude(page=exclude_page.pk)
|
||||
|
||||
try:
|
||||
conflict_page = page_urls[0].page
|
||||
except IndexError:
|
||||
return True
|
||||
|
||||
conflict_translation = conflict_page.get_content_obj(language, fallback=False)
|
||||
|
||||
if conflict_translation: # No empty page content
|
||||
change_url = admin_reverse("cms_pagecontent_change", args=[conflict_translation.pk])
|
||||
else:
|
||||
change_url = "" # Empty page has no slug
|
||||
if user_language:
|
||||
change_url += f"?language={user_language}"
|
||||
|
||||
conflict_url = f'<a href="{change_url}" target="_blank">{escape(str(conflict_translation.title))}</a>'
|
||||
|
||||
if exclude_page:
|
||||
message = gettext("Page %(conflict_page)s has the same url '%(url)s' as current page \"%(instance)s\".")
|
||||
else:
|
||||
message = gettext("Page %(conflict_page)s has the same url '%(url)s' as current page.")
|
||||
message = message % {
|
||||
"conflict_page": conflict_url,
|
||||
"url": escape(path),
|
||||
"instance": escape(exclude_page.get_title(language)) if exclude_page else "",
|
||||
}
|
||||
raise ValidationError(mark_safe(message))
|
||||
Loading…
Add table
Add a link
Reference in a new issue