From 3094bc393602c056f659e04e642addebb5ff95b4 Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Tue, 18 Jun 2024 20:24:12 +0200 Subject: [PATCH 01/19] fix version for release --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f5d906a2..4e0a30b1 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ from setuptools import find_packages, setup # When the version is updated, a new section in the CHANGELOG.md file must be # added too. -VERSION = "3.dev" +VERSION = "3.2.2" with open("README.md", encoding="utf-8") as f: long_description = f.read() From dd8b62eef548f98942ddd07a4bf51c8ccbd07e28 Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Tue, 18 Jun 2024 20:24:59 +0200 Subject: [PATCH 02/19] continue with dev --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4e0a30b1..f5d906a2 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ from setuptools import find_packages, setup # When the version is updated, a new section in the CHANGELOG.md file must be # added too. -VERSION = "3.2.2" +VERSION = "3.dev" with open("README.md", encoding="utf-8") as f: long_description = f.read() From fe33d79eb145911013a440f5420c74fef56c4300 Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Thu, 4 Jul 2024 09:37:25 +0200 Subject: [PATCH 03/19] fix item level --- DOCUMENTATION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index bace5789..7a459602 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -865,7 +865,7 @@ Delete sync-token that are older than the specified time. (seconds) Default: `2592000` -#### skip_broken_item +##### skip_broken_item Skip broken item instead of triggering an exception From f1d84cea358fbeb31753834c3cd816a2225ff292 Mon Sep 17 00:00:00 2001 From: Will Sowerbutts Date: Thu, 11 Jul 2024 13:38:48 +0100 Subject: [PATCH 04/19] Remove unexpected control codes from ICS files --- radicale/item/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/radicale/item/__init__.py b/radicale/item/__init__.py index c5a32690..c10824a6 100644 --- a/radicale/item/__init__.py +++ b/radicale/item/__init__.py @@ -49,6 +49,12 @@ def read_components(s: str) -> List[vobject.base.Component]: s = re.sub(r"^(PHOTO(?:;[^:\r\n]*)?;ENCODING=b(?:;[^:\r\n]*)?:)" r"data:[^;,\r\n]*;base64,", r"\1", s, flags=re.MULTILINE | re.IGNORECASE) + # Workaround for bug with malformed ICS files containing control codes + # Filter out all control codes except those we expect to find: + # * 0x09 Horizontal Tab + # * 0x0A Line Feed + # * 0x0D Carriage Return + s = re.sub(r'[\x00-\x08\x0B\x0C\x0E-\x1F]', '', s) return list(vobject.readComponents(s, allowQP=True)) From fe3d9d3f482e6da836b2c567e2db47455c52a276 Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Fri, 12 Jul 2024 05:22:13 +0200 Subject: [PATCH 05/19] update --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38ffafa3..beb6ea72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## 3.dev +* Enhancement: remove unexpected control codes from uploaded items ## 3.2.2 * Enhancement: add support for auth.type=denyall (will be default for security reasons in upcoming releases) From 9809fbcba4e2a70a072712fc397cff904e935d5f Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Fri, 12 Jul 2024 05:41:13 +0200 Subject: [PATCH 06/19] pin workflow to python 3.12.3 as 3.12.4 causes issues --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 12f28633..03467b85 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,7 +6,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', pypy-3.8, pypy-3.9] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12.3', pypy-3.8, pypy-3.9] exclude: - os: windows-latest python-version: pypy-3.8 From 53befe72dbe3e865149f1f07f51d14d6b9e26801 Mon Sep 17 00:00:00 2001 From: mldytech Date: Fri, 12 Jul 2024 10:29:35 +0200 Subject: [PATCH 07/19] Modified Reverse-Proxy Examples for Caddy: - Moved existing Caddy example with basicauth to the right section - Added basic Caddy example without using basicauth --- DOCUMENTATION.md | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 7a459602..cdae9f65 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -350,16 +350,13 @@ location /radicale/ { # The trailing / is important! } ``` -Example **Caddy** configuration with basicauth from Caddy: +Example **Caddy** configuration: -```Caddy -handle_path /radicale* { - basicauth { - user hash - } +``` +handle_path /radicale/* { + uri strip_prefix /radicale reverse_proxy localhost:5232 { - header_up +X-Script-Name "/radicale" - header_up +X-remote-user "{http.auth.user.id}" + header_up X-Script-Name /radicale } } ``` @@ -440,6 +437,21 @@ location /radicale/ { } ``` +Example **Caddy** configuration: + +``` +handle_path /radicale/* { + uri strip_prefix /radicale + basicauth { + USER HASH + } + reverse_proxy localhost:5232 { + header_up X-Script-Name /radicale + header_up X-remote-user {http.auth.user.id} + } +} +``` + Example **Apache** configuration: ```apache From f117fd06af21307b134c5dc60ad02808c6b35212 Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Thu, 18 Jul 2024 06:49:10 +0200 Subject: [PATCH 08/19] add missing test for auth/lc_username --- radicale/tests/test_auth.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/radicale/tests/test_auth.py b/radicale/tests/test_auth.py index 858e0827..bb298f1a 100644 --- a/radicale/tests/test_auth.py +++ b/radicale/tests/test_auth.py @@ -115,6 +115,11 @@ class TestBaseAuthRequests(BaseTest): def test_htpasswd_comment(self) -> None: self._test_htpasswd("plain", "#comment\n #comment\n \ntmp:bepo\n\n") + def test_htpasswd_lc_username(self) -> None: + self.configure({"auth": {"lc_username": "True"}}) + self._test_htpasswd("plain", "tmp:bepo", ( + ("tmp", "bepo", True), ("TMP", "bepo", True), ("tmp1", "bepo", False))) + def test_remote_user(self) -> None: self.configure({"auth": {"type": "remote_user"}}) _, responses = self.propfind("/", """\ From 13b1aaed39b70bed8901a20be0c173bf25e33c68 Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Thu, 18 Jul 2024 06:50:29 +0200 Subject: [PATCH 09/19] add auth/strip_domain option --- DOCUMENTATION.md | 6 ++++++ config | 2 ++ radicale/auth/__init__.py | 8 +++++++- radicale/config.py | 4 ++++ radicale/tests/test_auth.py | 5 +++++ 5 files changed, 24 insertions(+), 1 deletion(-) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 7a459602..b232f511 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -795,6 +795,12 @@ providers like ldap, kerberos Default: `False` +##### strip_domain + +Strip domain from username + +Default: `False` + #### rights ##### type diff --git a/config b/config index 114820ad..a9fe9da7 100644 --- a/config +++ b/config @@ -73,6 +73,8 @@ # Convert username to lowercase, must be true for case-insensitive auth providers #lc_username = False +# Strip domain name from username +#strip_domain = False [rights] diff --git a/radicale/auth/__init__.py b/radicale/auth/__init__.py index 15d91ec5..296dbfa8 100644 --- a/radicale/auth/__init__.py +++ b/radicale/auth/__init__.py @@ -52,6 +52,7 @@ def load(configuration: "config.Configuration") -> "BaseAuth": class BaseAuth: _lc_username: bool + _strip_domain: bool def __init__(self, configuration: "config.Configuration") -> None: """Initialize BaseAuth. @@ -63,6 +64,7 @@ class BaseAuth: """ self.configuration = configuration self._lc_username = configuration.get("auth", "lc_username") + self._strip_domain = configuration.get("auth", "strip_domain") def get_external_login(self, environ: types.WSGIEnviron) -> Union[ Tuple[()], Tuple[str, str]]: @@ -91,4 +93,8 @@ class BaseAuth: raise NotImplementedError def login(self, login: str, password: str) -> str: - return self._login(login, password).lower() if self._lc_username else self._login(login, password) + if self._lc_username: + login = login.lower() + if self._strip_domain: + login = login.split('@')[0] + return self._login(login, password) diff --git a/radicale/config.py b/radicale/config.py index 967580cb..d1b66251 100644 --- a/radicale/config.py +++ b/radicale/config.py @@ -191,6 +191,10 @@ DEFAULT_CONFIG_SCHEMA: types.CONFIG_SCHEMA = OrderedDict([ "value": "1", "help": "incorrect authentication delay", "type": positive_float}), + ("strip_domain", { + "value": "False", + "help": "strip domain from username", + "type": bool}), ("lc_username", { "value": "False", "help": "convert username to lowercase, must be true for case-insensitive auth providers", diff --git a/radicale/tests/test_auth.py b/radicale/tests/test_auth.py index bb298f1a..3604e2f9 100644 --- a/radicale/tests/test_auth.py +++ b/radicale/tests/test_auth.py @@ -120,6 +120,11 @@ class TestBaseAuthRequests(BaseTest): self._test_htpasswd("plain", "tmp:bepo", ( ("tmp", "bepo", True), ("TMP", "bepo", True), ("tmp1", "bepo", False))) + def test_htpasswd_strip_domain(self) -> None: + self.configure({"auth": {"strip_domain": "True"}}) + self._test_htpasswd("plain", "tmp:bepo", ( + ("tmp", "bepo", True), ("tmp@domain.example", "bepo", True), ("tmp1", "bepo", False))) + def test_remote_user(self) -> None: self.configure({"auth": {"type": "remote_user"}}) _, responses = self.propfind("/", """\ From e5096d31afeb3507a9a2e787e677bbc6f47c6945 Mon Sep 17 00:00:00 2001 From: Mathieu Dupuy Date: Tue, 23 Jul 2024 17:40:07 +0200 Subject: [PATCH 10/19] remove setuptools from runtime dependencies --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f5d906a2..4096685c 100644 --- a/setup.py +++ b/setup.py @@ -38,7 +38,7 @@ web_files = ["web/internal_data/css/icon.png", install_requires = ["defusedxml", "passlib", "vobject>=0.9.6", "python-dateutil>=2.7.3", "pika>=1.1.0", - "setuptools; python_version<'3.9'"] + ] bcrypt_requires = ["bcrypt"] test_requires = ["pytest>=7", "typeguard<4.3", "waitress", *bcrypt_requires] From 47bc966a13d438b94f0e923f93ee3b7ba64dabc1 Mon Sep 17 00:00:00 2001 From: Mathieu Dupuy Date: Wed, 24 Jul 2024 11:22:49 +0200 Subject: [PATCH 11/19] fix misspellings --- radicale/__main__.py | 2 +- radicale/app/__init__.py | 2 +- radicale/app/propfind.py | 6 +++--- radicale/app/report.py | 2 +- radicale/auth/htpasswd.py | 2 +- radicale/item/__init__.py | 2 +- radicale/item/filter.py | 2 +- radicale/rights/from_file.py | 2 +- radicale/storage/multifilesystem/get.py | 6 +++--- radicale/tests/__init__.py | 10 +++++----- radicale/tests/test_base.py | 10 +++++----- 11 files changed, 23 insertions(+), 23 deletions(-) diff --git a/radicale/__main__.py b/radicale/__main__.py index f2f157b9..a58b8c94 100644 --- a/radicale/__main__.py +++ b/radicale/__main__.py @@ -183,7 +183,7 @@ def run() -> None: storage_ = storage.load(configuration) with storage_.acquire_lock("r"): if not storage_.verify(): - logger.critical("Storage verifcation failed") + logger.critical("Storage verification failed") sys.exit(1) except Exception as e: logger.critical("An exception occurred during storage " diff --git a/radicale/app/__init__.py b/radicale/app/__init__.py index 3b8d1800..5fe71d30 100644 --- a/radicale/app/__init__.py +++ b/radicale/app/__init__.py @@ -232,7 +232,7 @@ class Application(ApplicationPartDelete, ApplicationPartHead, path.rstrip("/").endswith("/.well-known/carddav")): return response(*httputils.redirect( base_prefix + "/", client.MOVED_PERMANENTLY)) - # Return NOT FOUND for all other paths containing ".well-knwon" + # Return NOT FOUND for all other paths containing ".well-known" if path.endswith("/.well-known") or "/.well-known/" in path: return response(*httputils.NOT_FOUND) diff --git a/radicale/app/propfind.py b/radicale/app/propfind.py index b1cfc197..009c61dc 100644 --- a/radicale/app/propfind.py +++ b/radicale/app/propfind.py @@ -322,13 +322,13 @@ def xml_propfind_response( responses[404 if is404 else 200].append(element) - for status_code, childs in responses.items(): - if not childs: + for status_code, children in responses.items(): + if not children: continue propstat = ET.Element(xmlutils.make_clark("D:propstat")) response.append(propstat) prop = ET.Element(xmlutils.make_clark("D:prop")) - prop.extend(childs) + prop.extend(children) propstat.append(prop) status = ET.Element(xmlutils.make_clark("D:status")) status.text = xmlutils.make_response(status_code) diff --git a/radicale/app/report.py b/radicale/app/report.py index 8a947432..f0c50214 100644 --- a/radicale/app/report.py +++ b/radicale/app/report.py @@ -271,7 +271,7 @@ def _make_vobject_expanded_item( if hasattr(item.vobject_item.vevent, 'rrule'): rruleset = vevent.getrruleset() - # There is something strage behavour during serialization native datetime, so converting manualy + # There is something strange behaviour during serialization native datetime, so converting manually vevent.dtstart.value = vevent.dtstart.value.strftime(dt_format) if dt_end is not None: vevent.dtend.value = vevent.dtend.value.strftime(dt_format) diff --git a/radicale/auth/htpasswd.py b/radicale/auth/htpasswd.py index 689bb0fb..7422e16d 100644 --- a/radicale/auth/htpasswd.py +++ b/radicale/auth/htpasswd.py @@ -36,7 +36,7 @@ pointed to by the ``htpasswd_filename`` configuration value while assuming the password encryption method specified via the ``htpasswd_encryption`` configuration value. -The following htpasswd password encrpytion methods are supported by Radicale +The following htpasswd password encryption methods are supported by Radicale out-of-the-box: - plain-text (created by htpasswd -p ...) -- INSECURE - MD5-APR1 (htpasswd -m ...) -- htpasswd's default method, INSECURE diff --git a/radicale/item/__init__.py b/radicale/item/__init__.py index c10824a6..a05304ff 100644 --- a/radicale/item/__init__.py +++ b/radicale/item/__init__.py @@ -304,7 +304,7 @@ def find_time_range(vobject_item: vobject.base.Component, tag: str Returns a tuple (``start``, ``end``) where ``start`` and ``end`` are POSIX timestamps. - This is intened to be used for matching against simplified prefilters. + This is intended to be used for matching against simplified prefilters. """ if not tag: diff --git a/radicale/item/filter.py b/radicale/item/filter.py index 0b3d01ef..141845ce 100644 --- a/radicale/item/filter.py +++ b/radicale/item/filter.py @@ -199,7 +199,7 @@ def visit_time_ranges(vobject_item: vobject.base.Component, child_name: str, """ - # HACK: According to rfc5545-3.8.4.4 an recurrance that is resheduled + # HACK: According to rfc5545-3.8.4.4 a recurrence that is rescheduled # with Recurrence ID affects the recurrence itself and all following # recurrences too. This is not respected and client don't seem to bother # either. diff --git a/radicale/rights/from_file.py b/radicale/rights/from_file.py index 3b8ede05..d766d1dd 100644 --- a/radicale/rights/from_file.py +++ b/radicale/rights/from_file.py @@ -22,7 +22,7 @@ config (section "rights", key "file"). The login is matched against the "user" key, and the collection path is matched against the "collection" key. In the "collection" regex you can use `{user}` and get groups from the "user" regex with `{0}`, `{1}`, etc. -In consequence of the parameter subsitution you have to write `{{` and `}}` +In consequence of the parameter substitution you have to write `{{` and `}}` if you want to use regular curly braces in the "user" and "collection" regexes. For example, for the "user" key, ".+" means "authenticated user" and ".*" diff --git a/radicale/storage/multifilesystem/get.py b/radicale/storage/multifilesystem/get.py index c52c1f7e..f5d25816 100644 --- a/radicale/storage/multifilesystem/get.py +++ b/radicale/storage/multifilesystem/get.py @@ -84,7 +84,7 @@ class CollectionPartGet(CollectionPartCache, CollectionPartLock, cache_content = self._load_item_cache(href, cache_hash) if cache_content is None: with self._acquire_cache_lock("item"): - # Lock the item cache to prevent multpile processes from + # Lock the item cache to prevent multiple processes from # generating the same data in parallel. # This improves the performance for multiple requests. if self._storage._lock.locked == "r": @@ -127,7 +127,7 @@ class CollectionPartGet(CollectionPartCache, CollectionPartLock, def get_multi(self, hrefs: Iterable[str] ) -> Iterator[Tuple[str, Optional[radicale_item.Item]]]: - # It's faster to check for file name collissions here, because + # It's faster to check for file name collisions here, because # we only need to call os.listdir once. files = None for href in hrefs: @@ -146,7 +146,7 @@ class CollectionPartGet(CollectionPartCache, CollectionPartLock, def get_all(self) -> Iterator[radicale_item.Item]: for href in self._list(): - # We don't need to check for collissions, because the file names + # We don't need to check for collisions, because the file names # are from os.listdir. item = self._get(href, verify_href=False) if item is not None: diff --git a/radicale/tests/__init__.py b/radicale/tests/__init__.py index 63cfda04..528a98b2 100644 --- a/radicale/tests/__init__.py +++ b/radicale/tests/__init__.py @@ -112,7 +112,7 @@ class BaseTest: for response in xml.findall(xmlutils.make_clark("D:response")): href = response.find(xmlutils.make_clark("D:href")) assert href.text not in path_responses - prop_respones: Dict[str, Tuple[int, ET.Element]] = {} + prop_responses: Dict[str, Tuple[int, ET.Element]] = {} for propstat in response.findall( xmlutils.make_clark("D:propstat")): status = propstat.find(xmlutils.make_clark("D:status")) @@ -121,16 +121,16 @@ class BaseTest: for element in propstat.findall( "./%s/*" % xmlutils.make_clark("D:prop")): human_tag = xmlutils.make_human_tag(element.tag) - assert human_tag not in prop_respones - prop_respones[human_tag] = (status_code, element) + assert human_tag not in prop_responses + prop_responses[human_tag] = (status_code, element) status = response.find(xmlutils.make_clark("D:status")) if status is not None: - assert not prop_respones + assert not prop_responses assert status.text.startswith("HTTP/1.1 ") status_code = int(status.text.split(" ")[1]) path_responses[href.text] = status_code else: - path_responses[href.text] = prop_respones + path_responses[href.text] = prop_responses return path_responses def get(self, path: str, check: Optional[int] = 200, **kwargs diff --git a/radicale/tests/test_base.py b/radicale/tests/test_base.py index d591773d..dd2a2534 100644 --- a/radicale/tests/test_base.py +++ b/radicale/tests/test_base.py @@ -359,7 +359,7 @@ permissions: RrWw""") self.get(path1, check=404) self.get(path2) - def test_move_between_colections(self) -> None: + def test_move_between_collections(self) -> None: """Move a item.""" self.mkcalendar("/calendar1.ics/") self.mkcalendar("/calendar2.ics/") @@ -372,7 +372,7 @@ permissions: RrWw""") self.get(path1, check=404) self.get(path2) - def test_move_between_colections_duplicate_uid(self) -> None: + def test_move_between_collections_duplicate_uid(self) -> None: """Move a item to a collection which already contains the UID.""" self.mkcalendar("/calendar1.ics/") self.mkcalendar("/calendar2.ics/") @@ -388,7 +388,7 @@ permissions: RrWw""") assert xml.tag == xmlutils.make_clark("D:error") assert xml.find(xmlutils.make_clark("C:no-uid-conflict")) is not None - def test_move_between_colections_overwrite(self) -> None: + def test_move_between_collections_overwrite(self) -> None: """Move a item to a collection which already contains the item.""" self.mkcalendar("/calendar1.ics/") self.mkcalendar("/calendar2.ics/") @@ -402,8 +402,8 @@ permissions: RrWw""") self.request("MOVE", path1, check=204, HTTP_OVERWRITE="T", HTTP_DESTINATION="http://127.0.0.1/"+path2) - def test_move_between_colections_overwrite_uid_conflict(self) -> None: - """Move a item to a collection which already contains the item with + def test_move_between_collections_overwrite_uid_conflict(self) -> None: + """Move an item to a collection which already contains the item with a different UID.""" self.mkcalendar("/calendar1.ics/") self.mkcalendar("/calendar2.ics/") From c499c313c2280db2964348be3bf6aca2fd691f8c Mon Sep 17 00:00:00 2001 From: Mathieu Dupuy Date: Thu, 25 Jul 2024 00:08:46 +0200 Subject: [PATCH 12/19] split tox jobs --- .github/workflows/test.yml | 14 +++++++++++++- setup.cfg | 36 +++++++++++++++++++++--------------- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 03467b85..f93922f8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,7 +21,7 @@ jobs: - name: Install Test dependencies run: pip install tox - name: Test - run: tox + run: tox -e py - name: Install Coveralls if: github.event_name == 'push' run: pip install coveralls @@ -46,3 +46,15 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: coveralls --service=github --finish + + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Install tox + run: pip install tox + - name: Lint + run: tox -e flake8,mypy,isort diff --git a/setup.cfg b/setup.cfg index 4326cd97..551890be 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,25 +2,31 @@ addopts = --typeguard-packages=radicale [tox:tox] +min_version = 4.0 +envlist = py, flake8, isort, mypy [testenv] -extras = test +extras = + test deps = - flake8 - isort - # mypy installation fails with pypy<3.9 - mypy; implementation_name!='pypy' or python_version>='3.9' - types-setuptools + pytest pytest-cov -commands = - flake8 . - isort --check --diff . - # Run mypy if it's installed - python -c 'import importlib.util, subprocess, sys; \ - importlib.util.find_spec("mypy") \ - and sys.exit(subprocess.run(["mypy", "."]).returncode) \ - or print("Skipped: mypy is not installed")' - pytest -r s --cov --cov-report=term --cov-report=xml . +commands = pytest -r s --cov --cov-report=term --cov-report=xml . + +[testenv:flake8] +deps = flake8 +commands = flake8 . +skip_install = True + +[testenv:isort] +deps = isort +commands = isort --check --diff . +skip_install = True + +[testenv:mypy] +deps = mypy +commands = mypy . +skip_install = True [tool:isort] known_standard_library = _dummy_thread,_thread,abc,aifc,argparse,array,ast,asynchat,asyncio,asyncore,atexit,audioop,base64,bdb,binascii,binhex,bisect,builtins,bz2,cProfile,calendar,cgi,cgitb,chunk,cmath,cmd,code,codecs,codeop,collections,colorsys,compileall,concurrent,configparser,contextlib,contextvars,copy,copyreg,crypt,csv,ctypes,curses,dataclasses,datetime,dbm,decimal,difflib,dis,distutils,doctest,dummy_threading,email,encodings,ensurepip,enum,errno,faulthandler,fcntl,filecmp,fileinput,fnmatch,formatter,fpectl,fractions,ftplib,functools,gc,getopt,getpass,gettext,glob,grp,gzip,hashlib,heapq,hmac,html,http,imaplib,imghdr,imp,importlib,inspect,io,ipaddress,itertools,json,keyword,lib2to3,linecache,locale,logging,lzma,macpath,mailbox,mailcap,marshal,math,mimetypes,mmap,modulefinder,msilib,msvcrt,multiprocessing,netrc,nis,nntplib,ntpath,numbers,operator,optparse,os,ossaudiodev,parser,pathlib,pdb,pickle,pickletools,pipes,pkgutil,platform,plistlib,poplib,posix,posixpath,pprint,profile,pstats,pty,pwd,py_compile,pyclbr,pydoc,queue,quopri,random,re,readline,reprlib,resource,rlcompleter,runpy,sched,secrets,select,selectors,shelve,shlex,shutil,signal,site,smtpd,smtplib,sndhdr,socket,socketserver,spwd,sqlite3,sre,sre_compile,sre_constants,sre_parse,ssl,stat,statistics,string,stringprep,struct,subprocess,sunau,symbol,symtable,sys,sysconfig,syslog,tabnanny,tarfile,telnetlib,tempfile,termios,test,textwrap,threading,time,timeit,tkinter,token,tokenize,trace,traceback,tracemalloc,tty,turtle,turtledemo,types,typing,unicodedata,unittest,urllib,uu,uuid,venv,warnings,wave,weakref,webbrowser,winreg,winsound,wsgiref,xdrlib,xml,xmlrpc,zipapp,zipfile,zipimport,zlib From b47a253ccbb86b31ea2042a78db118c04cd0e00d Mon Sep 17 00:00:00 2001 From: Mathieu Dupuy Date: Thu, 25 Jul 2024 14:00:22 +0200 Subject: [PATCH 13/19] pin flake, isort and mypy versions --- setup.cfg | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index 551890be..9a5d3d82 100644 --- a/setup.cfg +++ b/setup.cfg @@ -14,17 +14,17 @@ deps = commands = pytest -r s --cov --cov-report=term --cov-report=xml . [testenv:flake8] -deps = flake8 +deps = flake8==7.1.0 commands = flake8 . skip_install = True [testenv:isort] -deps = isort +deps = isort==5.13.2 commands = isort --check --diff . skip_install = True [testenv:mypy] -deps = mypy +deps = mypy==1.11.0 commands = mypy . skip_install = True From c046c6ae346fcf52b431c810132cd184aabb04ce Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Thu, 25 Jul 2024 15:48:24 +0200 Subject: [PATCH 14/19] fix logger-warn-leftovers --- radicale/__init__.py | 2 +- radicale/__main__.py | 2 +- radicale/server.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/radicale/__init__.py b/radicale/__init__.py index 2ce5d4b7..2554e5b2 100644 --- a/radicale/__init__.py +++ b/radicale/__init__.py @@ -61,7 +61,7 @@ def _get_application_instance(config_path: str, wsgi_errors: types.ErrorStream if not miss and source != "default config": default_config_active = False if default_config_active: - logger.warn("%s", "No config file found/readable - only default config is active") + logger.warning("%s", "No config file found/readable - only default config is active") _application_instance = Application(configuration) if _application_config_path != config_path: raise ValueError("RADICALE_CONFIG must not change: %r != %r" % diff --git a/radicale/__main__.py b/radicale/__main__.py index a58b8c94..25d2b853 100644 --- a/radicale/__main__.py +++ b/radicale/__main__.py @@ -175,7 +175,7 @@ def run() -> None: default_config_active = False if default_config_active: - logger.warn("%s", "No config file found/readable - only default config is active") + logger.warning("%s", "No config file found/readable - only default config is active") if args_ns.verify_storage: logger.info("Verifying storage") diff --git a/radicale/server.py b/radicale/server.py index 82e5a0b7..600a31ac 100644 --- a/radicale/server.py +++ b/radicale/server.py @@ -291,7 +291,7 @@ def serve(configuration: config.Configuration, try: getaddrinfo = socket.getaddrinfo(address_port[0], address_port[1], 0, socket.SOCK_STREAM, socket.IPPROTO_TCP) except OSError as e: - logger.warn("cannot retrieve IPv4 or IPv6 address of '%s': %s" % (format_address(address_port), e)) + logger.warning("cannot retrieve IPv4 or IPv6 address of '%s': %s" % (format_address(address_port), e)) continue logger.debug("getaddrinfo of '%s': %s" % (format_address(address_port), getaddrinfo)) for (address_family, socket_kind, socket_proto, socket_flags, socket_address) in getaddrinfo: @@ -299,7 +299,7 @@ def serve(configuration: config.Configuration, try: server = server_class(configuration, address_family, (socket_address[0], socket_address[1]), RequestHandler) except OSError as e: - logger.warn("cannot create server socket on '%s': %s" % (format_address(socket_address), e)) + logger.warning("cannot create server socket on '%s': %s" % (format_address(socket_address), e)) continue servers[server.socket] = server server.set_app(application) From 01d48515815b1cbedb6c7d7b72926795b043a16a Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Sun, 9 Jun 2024 08:32:22 +0200 Subject: [PATCH 15/19] add Python 3.13 --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 4096685c..76449378 100644 --- a/setup.py +++ b/setup.py @@ -75,6 +75,7 @@ setup( "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Office/Business :: Groupware"]) From b1cf1f2e28997769998d7c2a78b81ae0a99602d7 Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Thu, 25 Jul 2024 16:04:11 +0200 Subject: [PATCH 16/19] add Python 3.13 --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f93922f8..32961e86 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,7 +6,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12.3', pypy-3.8, pypy-3.9] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12.3', '3.13.0-beta.4', pypy-3.8, pypy-3.9] exclude: - os: windows-latest python-version: pypy-3.8 From 0f505222d93caa6e04382d5f3a5e53b7fb75c70b Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Sun, 4 Aug 2024 08:23:11 +0200 Subject: [PATCH 17/19] remove unused requirement "typeguard" --- CHANGELOG.md | 1 + setup.cfg | 1 - setup.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index beb6ea72..a5c5a1de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## 3.dev * Enhancement: remove unexpected control codes from uploaded items +* Drop: remove unused requirement "typeguard" ## 3.2.2 * Enhancement: add support for auth.type=denyall (will be default for security reasons in upcoming releases) diff --git a/setup.cfg b/setup.cfg index 9a5d3d82..94a39915 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,4 @@ [tool:pytest] -addopts = --typeguard-packages=radicale [tox:tox] min_version = 4.0 diff --git a/setup.py b/setup.py index 4096685c..9bda116f 100644 --- a/setup.py +++ b/setup.py @@ -40,7 +40,7 @@ install_requires = ["defusedxml", "passlib", "vobject>=0.9.6", "pika>=1.1.0", ] bcrypt_requires = ["bcrypt"] -test_requires = ["pytest>=7", "typeguard<4.3", "waitress", *bcrypt_requires] +test_requires = ["pytest>=7", "waitress", *bcrypt_requires] setup( name="Radicale", From 7388a095f5a014d608eb01794988adbc187894ce Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Sun, 4 Aug 2024 08:23:11 +0200 Subject: [PATCH 18/19] remove unused requirement "typeguard" --- CHANGELOG.md | 1 + setup.cfg | 1 - setup.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index beb6ea72..a5c5a1de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## 3.dev * Enhancement: remove unexpected control codes from uploaded items +* Drop: remove unused requirement "typeguard" ## 3.2.2 * Enhancement: add support for auth.type=denyall (will be default for security reasons in upcoming releases) diff --git a/setup.cfg b/setup.cfg index 9a5d3d82..94a39915 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,4 @@ [tool:pytest] -addopts = --typeguard-packages=radicale [tox:tox] min_version = 4.0 diff --git a/setup.py b/setup.py index 76449378..68e36398 100644 --- a/setup.py +++ b/setup.py @@ -40,7 +40,7 @@ install_requires = ["defusedxml", "passlib", "vobject>=0.9.6", "pika>=1.1.0", ] bcrypt_requires = ["bcrypt"] -test_requires = ["pytest>=7", "typeguard<4.3", "waitress", *bcrypt_requires] +test_requires = ["pytest>=7", "waitress", *bcrypt_requires] setup( name="Radicale", From 773f09fe74aea0d3b7101c806571d4e0438f653e Mon Sep 17 00:00:00 2001 From: Henning Schild Date: Tue, 6 Aug 2024 19:39:37 +0200 Subject: [PATCH 19/19] hook: gracefully ignore non functional hooks and fall back to none In case a hook fails to load for some reason, fall back to the default hook "none" and treat errors as warnings in the log. This will gracefully ignore typos in hook names without crashing the server, and it will also allow configuration of "rabbitmq" where i.e. "pika" is missing. Closes: #1490 Signed-off-by: Henning Schild --- radicale/hook/__init__.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/radicale/hook/__init__.py b/radicale/hook/__init__.py index dc6b74c5..e31befc1 100644 --- a/radicale/hook/__init__.py +++ b/radicale/hook/__init__.py @@ -3,14 +3,23 @@ from enum import Enum from typing import Sequence from radicale import pathutils, utils +from radicale.log import logger INTERNAL_TYPES: Sequence[str] = ("none", "rabbitmq") def load(configuration): """Load the storage module chosen in configuration.""" - return utils.load_plugin( - INTERNAL_TYPES, "hook", "Hook", BaseHook, configuration) + try: + return utils.load_plugin( + INTERNAL_TYPES, "hook", "Hook", BaseHook, configuration) + except Exception as e: + logger.warn(e) + logger.warn("Hook \"%s\" failed to load, falling back to \"none\"." % configuration.get("hook", "type")) + configuration = configuration.copy() + configuration.update({"hook": {"type": "none"}}, "hook", privileged=True) + return utils.load_plugin( + INTERNAL_TYPES, "hook", "Hook", BaseHook, configuration) class BaseHook: