dynamicweb/cms/utils/urlutils.py

121 lines
3.5 KiB
Python

import re
from collections.abc import Sequence
from typing import Any
from urllib.parse import urlparse
from django.conf import settings
from django.urls import reverse
from django.utils.encoding import force_str
from django.utils.http import urlencode
import cms
from cms.utils.conf import get_cms_setting
# checks validity of absolute / relative url
any_path_re = re.compile('^/?[a-zA-Z0-9_.-]+(/[a-zA-Z0-9_.-]+)*/?$')
# checks validity of relative url
# matches the following:
# /test
# /test/
# ./test/
# ../test/
# ``<`` and ``>`` are excluded over the *whole* value, not just the leading segment: a
# trailing ``.*`` used to let markup through as soon as one safe character preceded it
# (e.g. ``foo/a<img src=x onerror=alert(1)>``).
relative_url_regex = re.compile(r'^[^/<>]+/[^/<>][^<>]*$|^/[^<>]*$', re.IGNORECASE)
# checks validity of a single path segment, e.g. an "Overwrite URL" of "contact".
# ``relative_url_regex`` only matches values containing a "/", so slug-like paths need
# their own check. The character class mirrors the one above; the empty path is allowed
# because that is the home page.
path_segment_regex = re.compile(r'^[^/<>]*$', re.IGNORECASE)
def levelize_path(path):
"""Splits given path to list of paths removing latest level in each step.
>>> path = '/application/item/new'
>>> levelize_path(path)
['/application/item/new', '/application/item', '/application']
"""
parts = tuple(filter(None, path.split('/')))
return ['/' + '/'.join(parts[:n]) for n in range(len(parts), 0, -1)]
def urljoin(*segments):
"""Joins url segments together and appends trailing slash if required.
>>> urljoin('a', 'b', 'c')
u'a/b/c/'
>>> urljoin('a', '//b//', 'c')
u'a/b/c/'
>>> urljoin('/a', '/b/', '/c/')
u'/a/b/c/'
>>> urljoin('/a', '')
u'/a/'
"""
url = '/' if segments[0].startswith('/') else ''
url += '/'.join(filter(None, (force_str(s).strip('/') for s in segments)))
return url + '/' if settings.APPEND_SLASH else url
def is_media_request(request):
"""
Check if a request is a media request.
"""
parsed_media_url = urlparse(settings.MEDIA_URL)
if request.path_info.startswith(parsed_media_url.path):
if parsed_media_url.netloc:
if request.get_host() == parsed_media_url.netloc:
return True
else:
return True
return False
def static_with_version(path):
"""
Changes provided path from `path/to/filename.ext` to `path/to/$CMS_VERSION/filename.ext`
"""
path_re = re.compile('(.*)/([^/]*$)')
return re.sub(path_re, r'\1/%s/\2' % (cms.__version__), path)
def add_url_parameters(url, *args, **params):
"""
adds parameters to an url -> url?p1=v1&p2=v2...
:param url: url without any parameters
:param args: one or more dictionaries containing url parameters
:param params: url parameters as keyword arguments
:return: url with parameters if any
"""
for arg in args:
params.update(arg)
if params:
return f"{url}?{urlencode(params)}"
return url
admin_namespace = get_cms_setting('ADMIN_NAMESPACE')
def admin_reverse(
viewname: str,
urlconf: str | None = None,
args: Sequence | None = None,
kwargs: dict[str, Any] | None = None,
prefix=None,
current_app: str | None = None
):
return reverse(
f"{admin_namespace}:{viewname}",
urlconf=urlconf,
args=args,
kwargs=kwargs,
current_app=current_app
)