#!/usr/bin/python3
# SPDX-FileCopyrightText: 2008-2025 Univention GmbH
# SPDX-License-Identifier: AGPL-3.0-only
"""Univention Updater: UCR Repository Server URL"""
from __future__ import annotations
from copy import copy
from typing import TYPE_CHECKING, TypeVar, overload
from urllib.parse import quote, urlsplit
if TYPE_CHECKING:
from univention.config_registry import ConfigRegistry
_T = TypeVar("_T")
[docs]
class UcsRepoUrl: # noqa: PLW1641
"""UCS repository server base URL."""
DEFAULT = 'https://updates.software-univention.de/'
def __init__(self, ucr: ConfigRegistry, component: str, default: str | UcsRepoUrl | None = None) -> None:
"""
>>> UcsRepoUrl({'_/server': 'hostname'}, '_').path
''
>>> UcsRepoUrl({'_/server': 'hostname', '_/prefix': '/p'}, '_').path
'/p/'
>>> UcsRepoUrl({'_/server': 'hostname', '_/prefix': 'path'}, '_').path
'/path/'
>>> UcsRepoUrl({}, '', UcsRepoUrl({'_/server': 'https://hostname/'}, '_')).private()
'https://hostname/'
>>> UcsRepoUrl({'_/server': 'other'}, '_', UcsRepoUrl({'_/server': 'https://hostname:80/'}, '_')).private()
'http://other/'
>>> UcsRepoUrl({'_/server': 'other'}, '_', UcsRepoUrl.DEFAULT).private()
'http://other/'
>>> UcsRepoUrl({}, '').private() == UcsRepoUrl.DEFAULT
True
"""
@overload
def ucrv(key: str, default: _T) -> str | _T:
...
@overload
def ucrv(key: str) -> str | None:
...
def ucrv(key: str, default: _T | None = None) -> str | _T | None:
return ucr.get('%s/%s' % (component, key), default)
server = ucrv('server', '')
url = urlsplit(server)
if url.scheme:
self.scheme = url.scheme
self.username = url.username
self.password = url.password
self.hostname = url.hostname
port: int | str | None = url.port
prefix: str | None = url.path
else:
if default is None:
default = self.DEFAULT
elif isinstance(default, UcsRepoUrl):
default = default.private()
defaults = urlsplit(default)
self.username = ucrv('username', defaults.username)
self.password = ucrv('password', defaults.password)
if server:
self.hostname = server
port = ucrv('port', 80)
self.scheme = 'https' if port == 443 else 'http'
prefix = ucrv('prefix', None)
else:
self.hostname = defaults.hostname
port = ucrv('port', defaults.port)
self.scheme = defaults.scheme
prefix = ucrv('prefix', defaults.path)
self.port = int(port or (443 if self.scheme == 'https' else 80))
if prefix:
prefix = prefix.strip('/')
if prefix:
self.path = '/%s/' % (prefix,)
else:
self.path = '/'
else:
self.path = ''
@property
def cred(self) -> str:
if self.username:
# FIXME: http://bugs.debian.org/500560: [@:/] don't work
return '%s:%s@' % (quote(self.username), quote(self.password or ''))
return ''
@property
def _port(self) -> str:
return ':%d' % (self.port) if (self.scheme, self.port) not in (
('http', 80),
('https', 443),
) else ''
@property
def _path(self) -> str:
return quote('/%s' % (self.path.lstrip('/'),))
[docs]
def public(self) -> str:
"""
URI without credentials.
>>> UcsRepoUrl({'_/server': 'hostname'}, '_').public()
'http://hostname/'
>>> UcsRepoUrl({'_/server': 'hostname', '_/username': 'user', '_/password': 'pass'}, '_').public()
'http://hostname/'
>>> UcsRepoUrl({'_/server': 'https://hostname'}, '_').public()
'https://hostname/'
>>> UcsRepoUrl({'_/server': 'https://user:pass@hostname'}, '_').public()
'https://hostname/'
"""
return f'{self.scheme}://{self.hostname}{self._port}{self._path}'
[docs]
def private(self) -> str:
"""
URI with credentials.
>>> UcsRepoUrl({'_/server': 'hostname'}, '_').private()
'http://hostname/'
>>> UcsRepoUrl({'_/server': 'hostname', '_/username': 'user', '_/password': 'pass'}, '_').private()
'http://user:pass@hostname/'
>>> UcsRepoUrl({'_/server': 'https://hostname'}, '_').private()
'https://hostname/'
>>> UcsRepoUrl({'_/server': 'https://user:pass@hostname'}, '_').private()
'https://user:pass@hostname/'
"""
return f'{self.scheme}://{self.cred}{self.hostname}{self._port}{self._path}'
def __repr__(self) -> str:
"""
>>> repr(UcsRepoUrl({'_/server': 'hostname'}, '_'))
"UcsRepoUrl({}, '', 'http://hostname/')"
"""
return '%s(%r, %r, %r)' % (
self.__class__.__name__,
{},
'',
self.private(),
)
def __eq__(self, other: object) -> bool:
"""
>>> UcsRepoUrl({}, '') == UcsRepoUrl({}, '')
True
>>> UcsRepoUrl({}, '') == UcsRepoUrl({'_/server': 'other'}, '_')
False
"""
return isinstance(other, UcsRepoUrl) and self.private() == other.private()
def __add__(self, rel: str) -> UcsRepoUrl:
"""
Append relative path component.
>>> (UcsRepoUrl({'_/server': 'http://hostname'}, '_') + '/b').public()
'http://hostname/b'
>>> (UcsRepoUrl({'_/server': 'http://hostname/'}, '_') + '/b').public()
'http://hostname/b'
>>> (UcsRepoUrl({'_/server': 'http://hostname/a'}, '_') + '/b').public()
'http://hostname/a/b'
>>> (UcsRepoUrl({'_/server': 'http://hostname/a/'}, '_') + '/b').public()
'http://hostname/a/b'
>>> (UcsRepoUrl({'_/server': 'http://hostname/a/'}, '_') + '/b/').public()
'http://hostname/a/b/'
"""
cfg = copy(self)
cfg.path = '%s/%s' % (self._path.rstrip('/'), str(rel).lstrip('/'))
return cfg