From dcaec206816b2e44f3a793bc3228cd139d56e250 Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Mon, 10 Feb 2025 19:33:28 +0100 Subject: [PATCH 01/18] extend copyright year --- radicale/app/put.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/radicale/app/put.py b/radicale/app/put.py index 6e1ba215..4e1e0c9b 100644 --- a/radicale/app/put.py +++ b/radicale/app/put.py @@ -4,7 +4,7 @@ # Copyright © 2008-2017 Guillaume Ayoub # Copyright © 2017-2020 Unrud # Copyright © 2020-2023 Tuna Celik -# Copyright © 2024-2024 Peter Bieringer +# Copyright © 2024-2025 Peter Bieringer # # This library is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by From b011fa4e61b3f8dfcd484b0af546da9059ea842c Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Mon, 10 Feb 2025 19:34:13 +0100 Subject: [PATCH 02/18] extend copyright year --- radicale/httputils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/radicale/httputils.py b/radicale/httputils.py index 3983d7eb..f3c53965 100644 --- a/radicale/httputils.py +++ b/radicale/httputils.py @@ -3,7 +3,7 @@ # Copyright © 2008 Pascal Halter # Copyright © 2008-2017 Guillaume Ayoub # Copyright © 2017-2022 Unrud -# Copyright © 2024-2024 Peter Bieringer +# Copyright © 2024-2025 Peter Bieringer # # This library is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by From 77f69f2b1e6f772e6811d12381b4699ffb360bbf Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Mon, 10 Feb 2025 19:34:29 +0100 Subject: [PATCH 03/18] add new error code --- radicale/httputils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/radicale/httputils.py b/radicale/httputils.py index f3c53965..23cc3677 100644 --- a/radicale/httputils.py +++ b/radicale/httputils.py @@ -79,6 +79,9 @@ REMOTE_DESTINATION: types.WSGIResponse = ( DIRECTORY_LISTING: types.WSGIResponse = ( client.FORBIDDEN, (("Content-Type", "text/plain"),), "Directory listings are not supported.") +INSUFFICIENT_STORAGE: types.WSGIResponse = ( + client.INSUFFICIENT_STORAGE, (("Content-Type", "text/plain"),), + "Insufficient Storage. Please contact the administrator.") INTERNAL_SERVER_ERROR: types.WSGIResponse = ( client.INTERNAL_SERVER_ERROR, (("Content-Type", "text/plain"),), "A server error occurred. Please contact the administrator.") From f0d06cbc7d8b1c133d84db6dc4b51febbdbe888c Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Mon, 10 Feb 2025 19:37:19 +0100 Subject: [PATCH 04/18] catch server errors on put --- radicale/app/put.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/radicale/app/put.py b/radicale/app/put.py index 4e1e0c9b..976b7bfd 100644 --- a/radicale/app/put.py +++ b/radicale/app/put.py @@ -19,8 +19,10 @@ # You should have received a copy of the GNU General Public License # along with Radicale. If not, see . +import errno import itertools import posixpath +import re import socket import sys from http import client @@ -264,9 +266,22 @@ class ApplicationPartPut(ApplicationBase): ) self._hook.notify(hook_notification_item) except ValueError as e: - logger.warning( - "Bad PUT request on %r (upload): %s", path, e, exc_info=True) - return httputils.BAD_REQUEST + # return better matching HTTP result in case errno is provided and catched + errno_match = re.search("\\[Errno ([0-9]+)\\]", str(e)) + if errno_match: + logger.warning( + "Failed PUT request on %r (upload): %s", path, e, exc_info=True) + errno_e = int(errno_match.group(1)) + if errno_e == errno.ENOSPC: + return httputils.INSUFFICIENT_STORAGE + elif (errno_e == errno.EPERM) or (errno_e == errno.EACCES): + return httputils.FORBIDDEN + else: + return httputils.INTERNAL_SERVER_ERROR + else: + logger.warning( + "Bad PUT request on %r (upload): %s", path, e, exc_info=True) + return httputils.BAD_REQUEST headers = {"ETag": etag} return client.CREATED, headers, None From 605fc655847efbd2be7f6d65c16581614825895d Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Tue, 11 Feb 2025 16:17:47 +0100 Subject: [PATCH 05/18] improve coding --- radicale/app/put.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/radicale/app/put.py b/radicale/app/put.py index 976b7bfd..7bd37035 100644 --- a/radicale/app/put.py +++ b/radicale/app/put.py @@ -274,7 +274,7 @@ class ApplicationPartPut(ApplicationBase): errno_e = int(errno_match.group(1)) if errno_e == errno.ENOSPC: return httputils.INSUFFICIENT_STORAGE - elif (errno_e == errno.EPERM) or (errno_e == errno.EACCES): + elif errno_e in [errno.EPERM, errno.EACCES]: return httputils.FORBIDDEN else: return httputils.INTERNAL_SERVER_ERROR From c157dd7d19f2101c957a4992394595359cb32020 Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Tue, 11 Feb 2025 16:18:45 +0100 Subject: [PATCH 06/18] extend copyright --- radicale/app/mkcol.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/radicale/app/mkcol.py b/radicale/app/mkcol.py index 5bccc50c..50c94dce 100644 --- a/radicale/app/mkcol.py +++ b/radicale/app/mkcol.py @@ -2,7 +2,8 @@ # Copyright © 2008 Nicolas Kandel # Copyright © 2008 Pascal Halter # Copyright © 2008-2017 Guillaume Ayoub -# Copyright © 2017-2018 Unrud +# Copyright © 2017-2021 Unrud +# Copyright © 2024-2025 Peter Bieringer # # This library is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by From 88accdb6723acda526d1bad30513ba6c826fea35 Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Tue, 11 Feb 2025 16:19:16 +0100 Subject: [PATCH 07/18] catch server errors and return proper message --- radicale/app/mkcol.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/radicale/app/mkcol.py b/radicale/app/mkcol.py index 50c94dce..953508ad 100644 --- a/radicale/app/mkcol.py +++ b/radicale/app/mkcol.py @@ -18,7 +18,9 @@ # You should have received a copy of the GNU General Public License # along with Radicale. If not, see . +import errno import posixpath +import re import socket from http import client @@ -75,8 +77,21 @@ class ApplicationPartMkcol(ApplicationBase): try: self._storage.create_collection(path, props=props) except ValueError as e: - logger.warning( - "Bad MKCOL request on %r (type:%s): %s", path, collection_type, e, exc_info=True) - return httputils.BAD_REQUEST + # return better matching HTTP result in case errno is provided and catched + errno_match = re.search("\\[Errno ([0-9]+)\\]", str(e)) + if errno_match: + logger.error( + "Failed MKCOL request on %r (type:%s): %s", path, collection_type, e, exc_info=True) + errno_e = int(errno_match.group(1)) + if errno_e == errno.ENOSPC: + return httputils.INSUFFICIENT_STORAGE + elif errno_e in [errno.EPERM, errno.EACCES]: + return httputils.FORBIDDEN + else: + return httputils.INTERNAL_SERVER_ERROR + else: + logger.warning( + "Bad MKCOL request on %r (type:%s): %s", path, collection_type, e, exc_info=True) + return httputils.BAD_REQUEST logger.info("MKCOL request %r (type:%s): %s", path, collection_type, "successful") return client.CREATED, {}, None From cd51581f389024dbec6b04d790ac1725cf085352 Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Tue, 11 Feb 2025 16:20:29 +0100 Subject: [PATCH 08/18] extend copyright --- radicale/storage/multifilesystem/create_collection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/radicale/storage/multifilesystem/create_collection.py b/radicale/storage/multifilesystem/create_collection.py index 2e6e9ce7..f300f59a 100644 --- a/radicale/storage/multifilesystem/create_collection.py +++ b/radicale/storage/multifilesystem/create_collection.py @@ -2,7 +2,7 @@ # Copyright © 2014 Jean-Marc Martins # Copyright © 2012-2017 Guillaume Ayoub # Copyright © 2017-2021 Unrud -# Copyright © 2024-2024 Peter Bieringer +# Copyright © 2024-2025 Peter Bieringer # # This library is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by From 37b18cf5a2c3c986e739eb973e79ec9ca46ab205 Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Tue, 11 Feb 2025 16:20:51 +0100 Subject: [PATCH 09/18] catch error during create_collection --- .../multifilesystem/create_collection.py | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/radicale/storage/multifilesystem/create_collection.py b/radicale/storage/multifilesystem/create_collection.py index f300f59a..cbbdee53 100644 --- a/radicale/storage/multifilesystem/create_collection.py +++ b/radicale/storage/multifilesystem/create_collection.py @@ -50,27 +50,31 @@ class StoragePartCreateCollection(StorageBase): self._makedirs_synced(parent_dir) # Create a temporary directory with an unsafe name - with TemporaryDirectory(prefix=".Radicale.tmp-", dir=parent_dir - ) as tmp_dir: - # The temporary directory itself can't be renamed - tmp_filesystem_path = os.path.join(tmp_dir, "collection") - os.makedirs(tmp_filesystem_path) - col = self._collection_class( - cast(multifilesystem.Storage, self), - pathutils.unstrip_path(sane_path, True), - filesystem_path=tmp_filesystem_path) - col.set_meta(props) - if items is not None: - if props.get("tag") == "VCALENDAR": - col._upload_all_nonatomic(items, suffix=".ics") - elif props.get("tag") == "VADDRESSBOOK": - col._upload_all_nonatomic(items, suffix=".vcf") + try: + with TemporaryDirectory(prefix=".Radicale.tmp-", dir=parent_dir + ) as tmp_dir: + # The temporary directory itself can't be renamed + tmp_filesystem_path = os.path.join(tmp_dir, "collection") + os.makedirs(tmp_filesystem_path) + col = self._collection_class( + cast(multifilesystem.Storage, self), + pathutils.unstrip_path(sane_path, True), + filesystem_path=tmp_filesystem_path) + col.set_meta(props) + if items is not None: + if props.get("tag") == "VCALENDAR": + col._upload_all_nonatomic(items, suffix=".ics") + elif props.get("tag") == "VADDRESSBOOK": + col._upload_all_nonatomic(items, suffix=".vcf") - if os.path.lexists(filesystem_path): - pathutils.rename_exchange(tmp_filesystem_path, filesystem_path) - else: - os.rename(tmp_filesystem_path, filesystem_path) - self._sync_directory(parent_dir) + if os.path.lexists(filesystem_path): + pathutils.rename_exchange(tmp_filesystem_path, filesystem_path) + else: + os.rename(tmp_filesystem_path, filesystem_path) + self._sync_directory(parent_dir) + except Exception as e: + raise ValueError("Failed to create collection %r as %r %s" % + (href, filesystem_path, e)) from e return self._collection_class( cast(multifilesystem.Storage, self), From 803763729a1a9310f32fdfb3126b540324d794b9 Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Tue, 11 Feb 2025 16:26:57 +0100 Subject: [PATCH 10/18] extend copyright --- radicale/app/mkcalendar.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/radicale/app/mkcalendar.py b/radicale/app/mkcalendar.py index c507ae44..20c3445f 100644 --- a/radicale/app/mkcalendar.py +++ b/radicale/app/mkcalendar.py @@ -2,7 +2,8 @@ # Copyright © 2008 Nicolas Kandel # Copyright © 2008 Pascal Halter # Copyright © 2008-2017 Guillaume Ayoub -# Copyright © 2017-2018 Unrud +# Copyright © 2017-2021 Unrud +# Copyright © 2024-2025 Peter Bieringer # # This library is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by From fde0ecb9b20b070a1c1205aa5b1d578b351bc218 Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Tue, 11 Feb 2025 16:29:33 +0100 Subject: [PATCH 11/18] change loglevel --- radicale/app/put.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/radicale/app/put.py b/radicale/app/put.py index 7bd37035..962bf756 100644 --- a/radicale/app/put.py +++ b/radicale/app/put.py @@ -269,7 +269,7 @@ class ApplicationPartPut(ApplicationBase): # return better matching HTTP result in case errno is provided and catched errno_match = re.search("\\[Errno ([0-9]+)\\]", str(e)) if errno_match: - logger.warning( + logger.error( "Failed PUT request on %r (upload): %s", path, e, exc_info=True) errno_e = int(errno_match.group(1)) if errno_e == errno.ENOSPC: From b078a8f00233ff4c19ae2b14dfbbd61949dc21d7 Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Tue, 11 Feb 2025 16:29:56 +0100 Subject: [PATCH 12/18] catch os errors --- radicale/app/mkcalendar.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/radicale/app/mkcalendar.py b/radicale/app/mkcalendar.py index 20c3445f..b9f2063a 100644 --- a/radicale/app/mkcalendar.py +++ b/radicale/app/mkcalendar.py @@ -18,7 +18,9 @@ # You should have received a copy of the GNU General Public License # along with Radicale. If not, see . +import errno import posixpath +import re import socket from http import client @@ -71,7 +73,20 @@ class ApplicationPartMkcalendar(ApplicationBase): try: self._storage.create_collection(path, props=props) except ValueError as e: - logger.warning( - "Bad MKCALENDAR request on %r: %s", path, e, exc_info=True) - return httputils.BAD_REQUEST + # return better matching HTTP result in case errno is provided and catched + errno_match = re.search("\\[Errno ([0-9]+)\\]", str(e)) + if errno_match: + logger.error( + "Failed MKCALENDAR request on %r: %s", path, e, exc_info=True) + errno_e = int(errno_match.group(1)) + if errno_e == errno.ENOSPC: + return httputils.INSUFFICIENT_STORAGE + elif errno_e in [errno.EPERM, errno.EACCES]: + return httputils.FORBIDDEN + else: + return httputils.INTERNAL_SERVER_ERROR + else: + logger.warning( + "Bad MKCALENDAR request on %r: %s", path, e, exc_info=True) + return httputils.BAD_REQUEST return client.CREATED, {}, None From 718089e3bfc512aa45479d62500216896439f5c6 Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Tue, 11 Feb 2025 16:34:46 +0100 Subject: [PATCH 13/18] extend copyright --- radicale/app/proppatch.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/radicale/app/proppatch.py b/radicale/app/proppatch.py index c15fddfe..1d2701a0 100644 --- a/radicale/app/proppatch.py +++ b/radicale/app/proppatch.py @@ -2,7 +2,9 @@ # Copyright © 2008 Nicolas Kandel # Copyright © 2008 Pascal Halter # Copyright © 2008-2017 Guillaume Ayoub -# Copyright © 2017-2018 Unrud +# Copyright © 2017-2020 Unrud +# Copyright © 2020-2020 Tuna Celik +# Copyright © 2025-2025 Peter Bieringer # # This library is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by From 484616f3631a24c5a8d3bcd6a87e8bc1a2b01a01 Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Tue, 11 Feb 2025 16:35:03 +0100 Subject: [PATCH 14/18] catch os error --- radicale/app/proppatch.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/radicale/app/proppatch.py b/radicale/app/proppatch.py index 1d2701a0..faa8c25b 100644 --- a/radicale/app/proppatch.py +++ b/radicale/app/proppatch.py @@ -19,6 +19,8 @@ # You should have received a copy of the GNU General Public License # along with Radicale. If not, see . +import errno +import re import socket import xml.etree.ElementTree as ET from http import client @@ -109,7 +111,20 @@ class ApplicationPartProppatch(ApplicationBase): ) self._hook.notify(hook_notification_item) except ValueError as e: - logger.warning( - "Bad PROPPATCH request on %r: %s", path, e, exc_info=True) - return httputils.BAD_REQUEST + # return better matching HTTP result in case errno is provided and catched + errno_match = re.search("\\[Errno ([0-9]+)\\]", str(e)) + if errno_match: + logger.warning( + "Failed PROPPATCH request on %r: %s", path, e, exc_info=True) + errno_e = int(errno_match.group(1)) + if errno_e == errno.ENOSPC: + return httputils.INSUFFICIENT_STORAGE + elif errno_e in [errno.EPERM, errno.EACCES]: + return httputils.FORBIDDEN + else: + return httputils.INTERNAL_SERVER_ERROR + else: + logger.warning( + "Bad PROPPATCH request on %r: %s", path, e, exc_info=True) + return httputils.BAD_REQUEST return client.MULTI_STATUS, headers, self._xml_response(xml_answer) From dc83c6d7d01d7e2fcfa97c648ce0de07073805ad Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Tue, 11 Feb 2025 16:39:40 +0100 Subject: [PATCH 15/18] extend copyright --- radicale/app/move.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/radicale/app/move.py b/radicale/app/move.py index 5bd8a579..b2909cd2 100644 --- a/radicale/app/move.py +++ b/radicale/app/move.py @@ -2,7 +2,8 @@ # Copyright © 2008 Nicolas Kandel # Copyright © 2008 Pascal Halter # Copyright © 2008-2017 Guillaume Ayoub -# Copyright © 2017-2018 Unrud +# Copyright © 2017-2023 Unrud +# Copyright © 2023-2025 Peter Bieringer # # This library is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by From 67bbc9a31b5334af64a25de0944bf055ea8d2e4c Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Tue, 11 Feb 2025 16:39:54 +0100 Subject: [PATCH 16/18] catch os error --- radicale/app/move.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/radicale/app/move.py b/radicale/app/move.py index b2909cd2..f555e871 100644 --- a/radicale/app/move.py +++ b/radicale/app/move.py @@ -18,6 +18,7 @@ # You should have received a copy of the GNU General Public License # along with Radicale. If not, see . +import errno import posixpath import re from http import client @@ -110,7 +111,20 @@ class ApplicationPartMove(ApplicationBase): try: self._storage.move(item, to_collection, to_href) except ValueError as e: - logger.warning( - "Bad MOVE request on %r: %s", path, e, exc_info=True) - return httputils.BAD_REQUEST + # return better matching HTTP result in case errno is provided and catched + errno_match = re.search("\\[Errno ([0-9]+)\\]", str(e)) + if errno_match: + logger.error( + "Failed MOVE request on %r: %s", path, e, exc_info=True) + errno_e = int(errno_match.group(1)) + if errno_e == errno.ENOSPC: + return httputils.INSUFFICIENT_STORAGE + elif errno_e in [errno.EPERM, errno.EACCES]: + return httputils.FORBIDDEN + else: + return httputils.INTERNAL_SERVER_ERROR + else: + logger.warning( + "Bad MOVE request on %r: %s", path, e, exc_info=True) + return httputils.BAD_REQUEST return client.NO_CONTENT if to_item else client.CREATED, {}, None From a62da71aa22077a7c3a9d2176d84bcba17956e0a Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Tue, 11 Feb 2025 16:44:30 +0100 Subject: [PATCH 17/18] fix loglevel --- radicale/app/proppatch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/radicale/app/proppatch.py b/radicale/app/proppatch.py index faa8c25b..76b4a1a1 100644 --- a/radicale/app/proppatch.py +++ b/radicale/app/proppatch.py @@ -114,7 +114,7 @@ class ApplicationPartProppatch(ApplicationBase): # return better matching HTTP result in case errno is provided and catched errno_match = re.search("\\[Errno ([0-9]+)\\]", str(e)) if errno_match: - logger.warning( + logger.error( "Failed PROPPATCH request on %r: %s", path, e, exc_info=True) errno_e = int(errno_match.group(1)) if errno_e == errno.ENOSPC: From 19a47158bdf982f5809df8f74fb36f8b6ff37841 Mon Sep 17 00:00:00 2001 From: Peter Bieringer Date: Tue, 11 Feb 2025 16:48:48 +0100 Subject: [PATCH 18/18] extend changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a1d896e..cd0ab4f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## 3.4.2.dev * Add: option [auth] type oauth2 by code migration from https://gitlab.mim-libre.fr/alphabet/radicale_oauth/-/blob/dev/oauth2/ +* Fix: catch OS errors on PUT MKCOL MKCALENDAR MOVE PROPPATCH (insufficient storage, access denied, internal server error) ## 3.4.1 * Add: option [auth] dovecot_connection_type / dovecot_host / dovecot_port