Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package Radicale for openSUSE:Factory checked in at 2025-06-05 20:33:58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/Radicale (Old) and /work/SRC/openSUSE:Factory/.Radicale.new.19631 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "Radicale" Thu Jun 5 20:33:58 2025 rev:22 rq:1282912 version:3.5.4 Changes: -------- --- /work/SRC/openSUSE:Factory/Radicale/Radicale.changes 2025-05-30 17:19:48.329459133 +0200 +++ /work/SRC/openSUSE:Factory/.Radicale.new.19631/Radicale.changes 2025-06-05 20:36:34.682878745 +0200 @@ -1,0 +2,7 @@ +Wed May 28 20:02:32 UTC 2025 - Ákos Szőts <szots...@gmail.com> + +- Update to 3.5.4 + * item filter enhanced for 3rd level supporting VALARM and honoring TRIGGER (offset or absolute) + * add Caddy config file example (see contrib directory) + +------------------------------------------------------------------- Old: ---- v3.5.3.tar.gz New: ---- v3.5.4.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ Radicale.spec ++++++ --- /var/tmp/diff_new_pack.sjV8RO/_old 2025-06-05 20:36:35.330905690 +0200 +++ /var/tmp/diff_new_pack.sjV8RO/_new 2025-06-05 20:36:35.330905690 +0200 @@ -26,7 +26,7 @@ %define vo_min_ver 0.9.6 %define pk_min_ver 1.1.0 Name: Radicale -Version: 3.5.3 +Version: 3.5.4 Release: 0 Summary: A CalDAV calendar and CardDav contact server License: GPL-3.0-or-later ++++++ v3.5.3.tar.gz -> v3.5.4.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.3/CHANGELOG.md new/Radicale-3.5.4/CHANGELOG.md --- old/Radicale-3.5.3/CHANGELOG.md 2025-05-11 17:15:10.000000000 +0200 +++ new/Radicale-3.5.4/CHANGELOG.md 2025-05-25 08:50:29.000000000 +0200 @@ -1,5 +1,9 @@ # Changelog +## 3.5.4 +* Improve: item filter enhanced for 3rd level supporting VALARM and honoring TRIGGER (offset or absolute) +* Enhancement: add Caddy config file example (see contrib directory) + ## 3.5.3 * Add: [auth] htpasswd: support for Argon2 hashes * Improve: catch error on calendar collection upload and display problematic item content on debug level diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.3/DOCUMENTATION.md new/Radicale-3.5.4/DOCUMENTATION.md --- old/Radicale-3.5.3/DOCUMENTATION.md 2025-05-11 17:15:10.000000000 +0200 +++ new/Radicale-3.5.4/DOCUMENTATION.md 2025-05-25 08:50:29.000000000 +0200 @@ -262,6 +262,10 @@ `mkdir -p /var/lib/radicale/collections && chown -R radicale:radicale /var/lib/radicale/collections` as root.) +If a dedicated cache folder is configured (see option 'storage' -> 'filesystem_cache_folder'), it also must be also writable by **radicale**. (Run +`mkdir -p /var/cache/radicale && chown -R radicale:radicale /var/cache/radicale` +as root.) + > **Security:** The storage should not be readable by others. > (Run `chmod -R o= /var/lib/radicale/collections` as root.) @@ -288,7 +292,9 @@ ProtectKernelModules=true ProtectControlGroups=true NoNewPrivileges=true -ReadWritePaths=/var/lib/radicale/ /var/cache/radicale/ +ReadWritePaths=/var/lib/radicale/ +# Replace with following in case of dedicated cache folder should be used +#ReadWritePaths=/var/lib/radicale/ /var/cache/radicale/ [Install] WantedBy=multi-user.target @@ -350,7 +356,7 @@ * Service name: `Radicale` * Application * Path: `C:\Path\To\Python\python.exe` - * Arguments: `-m radicale --config C:\Path\To\Config` + * Arguments: `--config C:\Path\To\Config` * I/O redirection * Error: `C:\Path\To\Radicale.log` @@ -1459,6 +1465,8 @@ ##### rights_rule_doesnt_match_on_debug +_(>= 3.2.3)_ + Log rights rule which doesn't match on level=debug Default: `False` diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.3/contrib/caddy/radicale.caddyfile new/Radicale-3.5.4/contrib/caddy/radicale.caddyfile --- old/Radicale-3.5.3/contrib/caddy/radicale.caddyfile 1970-01-01 01:00:00.000000000 +0100 +++ new/Radicale-3.5.4/contrib/caddy/radicale.caddyfile 2025-05-25 08:50:29.000000000 +0200 @@ -0,0 +1,26 @@ +## example for a dedidcated FQDN and radicale is served on / there +## if you are serving on /radicale/ this needs to be prepended at the obvious places +# taken from https://github.com/Kozea/Radicale/discussions/1753 + +caldav.example.com { + # Append / if GETting /.web + @get-root { + method GET + path /.web + } + + redir @get-root /.web/ + + # Do not auth on /.web/* + @not-webui { + not path /.web/* + } + + basic_auth @not-webui { + USER HASH + } + + reverse_proxy localhost:5232 { + header_up X-Remote-User {http.auth.user.id} + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.3/pyproject.toml new/Radicale-3.5.4/pyproject.toml --- old/Radicale-3.5.3/pyproject.toml 2025-05-11 17:15:10.000000000 +0200 +++ new/Radicale-3.5.4/pyproject.toml 2025-05-25 08:50:29.000000000 +0200 @@ -3,7 +3,7 @@ # When the version is updated, a new section in the CHANGELOG.md file must be # added too. readme = "README.md" -version = "3.5.3" +version = "3.5.4" authors = [{name = "Guillaume Ayoub", email = "guillaume.ay...@kozea.fr"}, {name = "Unrud", email = "un...@outlook.com"}, {name = "Peter Bieringer", email = "p...@bieringer.de"}] license = {text = "GNU GPL v3"} description = "CalDAV and CardDAV Server" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.3/radicale/app/report.py new/Radicale-3.5.4/radicale/app/report.py --- old/Radicale-3.5.3/radicale/app/report.py 2025-05-11 17:15:10.000000000 +0200 +++ new/Radicale-3.5.4/radicale/app/report.py 2025-05-25 08:50:29.000000000 +0200 @@ -177,7 +177,7 @@ props: Union[ET.Element, List] if root.find(xmlutils.make_clark("D:prop")) is not None: - props = root.find(xmlutils.make_clark("D:prop")) # type: ignore[assignment] + props = root.find(xmlutils.make_clark("D:prop")) # type: ignore[assignment] else: props = [] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.3/radicale/auth/htpasswd.py new/Radicale-3.5.4/radicale/auth/htpasswd.py --- old/Radicale-3.5.3/radicale/auth/htpasswd.py 2025-05-11 17:15:10.000000000 +0200 +++ new/Radicale-3.5.4/radicale/auth/htpasswd.py 2025-05-25 08:50:29.000000000 +0200 @@ -96,7 +96,7 @@ self._has_bcrypt = False self._has_argon2 = False self._htpasswd_ok = False - self._htpasswd_not_ok_reminder_seconds = 60 # currently hardcoded + self._htpasswd_not_ok_reminder_seconds = 60 # currently hardcoded (self._htpasswd_ok, self._htpasswd_bcrypt_use, self._htpasswd_argon2_use, self._htpasswd, self._htpasswd_size, self._htpasswd_mtime_ns) = self._read_htpasswd(True, False) self._lock = threading.Lock() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.3/radicale/auth/imap.py new/Radicale-3.5.4/radicale/auth/imap.py --- old/Radicale-3.5.3/radicale/auth/imap.py 2025-05-11 17:15:10.000000000 +0200 +++ new/Radicale-3.5.4/radicale/auth/imap.py 2025-05-25 08:50:29.000000000 +0200 @@ -17,6 +17,8 @@ import imaplib import ssl +import sys +from typing import Union from radicale import auth from radicale.log import logger @@ -49,7 +51,10 @@ def _login(self, login, password) -> str: try: - connection: imaplib.IMAP4 | imaplib.IMAP4_SSL + if sys.version_info < (3, 10): + connection: Union[imaplib.IMAP4, imaplib.IMAP4_SSL] + else: + connection: imaplib.IMAP4 | imaplib.IMAP4_SSL if self._security == "tls": connection = imaplib.IMAP4_SSL( host=self._host, port=self._port, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.3/radicale/item/filter.py new/Radicale-3.5.4/radicale/item/filter.py --- old/Radicale-3.5.3/radicale/item/filter.py 2025-05-11 17:15:10.000000000 +0200 +++ new/Radicale-3.5.4/radicale/item/filter.py 2025-05-25 08:50:29.000000000 +0200 @@ -21,11 +21,12 @@ import math +import sys import xml.etree.ElementTree as ET from datetime import date, datetime, timedelta, timezone from itertools import chain from typing import (Callable, Iterable, Iterator, List, Optional, Sequence, - Tuple) + Tuple, Union) import vobject @@ -39,6 +40,11 @@ TIMESTAMP_MIN: int = math.floor(DATETIME_MIN.timestamp()) TIMESTAMP_MAX: int = math.ceil(DATETIME_MAX.timestamp()) +if sys.version_info < (3, 10): + TRIGGER = Union[datetime, None] +else: + TRIGGER = datetime | None + def date_to_datetime(d: date) -> datetime: """Transform any date to a UTC datetime. @@ -88,20 +94,23 @@ """ - # TODO: Filtering VALARM and VFREEBUSY is not implemented + # TODO: Filtering VFREEBUSY is not implemented # HACK: the filters are tested separately against all components + name = filter_.get("name", "").upper() + if level == 0: tag = item.name elif level == 1: tag = item.component_name + elif level == 2: + tag = item.component_name else: logger.warning( - "Filters with three levels of comp-filter are not supported") + "Filters with %d levels of comp-filter are not supported", level) return True if not tag: return False - name = filter_.get("name", "").upper() if len(filter_) == 0: # Point #1 of rfc4791-9.7.1 return name == tag @@ -109,23 +118,35 @@ if filter_[0].tag == xmlutils.make_clark("C:is-not-defined"): # Point #2 of rfc4791-9.7.1 return name != tag - if name != tag: + if (level < 2) and (name != tag): return False - if (level == 0 and name != "VCALENDAR" or - level == 1 and name not in ("VTODO", "VEVENT", "VJOURNAL")): + if ((level == 0 and name != "VCALENDAR") or + (level == 1 and name not in ("VTODO", "VEVENT", "VJOURNAL")) or + (level == 2 and name not in ("VALARM"))): logger.warning("Filtering %s is not supported", name) return True # Point #3 and #4 of rfc4791-9.7.1 - components = ([item.vobject_item] if level == 0 - else list(getattr(item.vobject_item, - "%s_list" % tag.lower()))) + trigger = None + if level == 0: + components = [item.vobject_item] + elif level == 1: + components = list(getattr(item.vobject_item, "%s_list" % tag.lower())) + elif level == 2: + components = list(getattr(item.vobject_item, "%s_list" % tag.lower())) + for comp in components: + subcomp = getattr(comp, name.lower(), None) + if not subcomp: + return False + if hasattr(subcomp, "trigger"): + # rfc4791-7.8.5: + trigger = subcomp.trigger.value for child in filter_: if child.tag == xmlutils.make_clark("C:prop-filter"): if not any(prop_match(comp, child, "C") for comp in components): return False elif child.tag == xmlutils.make_clark("C:time-range"): - if not time_range_match(item.vobject_item, filter_[0], tag): + if not time_range_match(item.vobject_item, filter_[0], tag, trigger): return False elif child.tag == xmlutils.make_clark("C:comp-filter"): if not comp_match(item, child, level=level + 1): @@ -155,7 +176,7 @@ # Point #3 and #4 of rfc4791-9.7.2 for child in filter_: if ns == "C" and child.tag == xmlutils.make_clark("C:time-range"): - if not time_range_match(vobject_item, child, name): + if not time_range_match(vobject_item, child, name, None): return False elif child.tag == xmlutils.make_clark("%s:text-match" % ns): if not text_match(vobject_item, child, name, ns): @@ -169,9 +190,10 @@ def time_range_match(vobject_item: vobject.base.Component, - filter_: ET.Element, child_name: str) -> bool: + filter_: ET.Element, child_name: str, trigger: TRIGGER) -> bool: """Check whether the component/property ``child_name`` of ``vobject_item`` matches the time-range ``filter_``.""" + # supporting since 3.5.4 now optional trigger (either absolute or relative offset) if not filter_.get("start") and not filter_.get("end"): return False @@ -182,6 +204,25 @@ def range_fn(range_start: datetime, range_end: datetime, is_recurrence: bool) -> bool: nonlocal matched + if trigger: + # if trigger is given, only check range_start + if isinstance(trigger, timedelta): + # trigger is a offset, apply to range_start + if start < range_start + trigger and range_start + trigger < end: + matched = True + return True + else: + return False + elif isinstance(trigger, datetime): + # trigger is absolute, use instead of range_start + if start < trigger and trigger < end: + matched = True + return True + else: + return False + else: + logger.warning("item/filter/time_range_match/range_fn: unsupported data format of provided trigger=%r", trigger) + return True if start < range_end and range_start < end: matched = True return True diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.3/radicale/tests/static/valarm1.ics new/Radicale-3.5.4/radicale/tests/static/valarm1.ics --- old/Radicale-3.5.3/radicale/tests/static/valarm1.ics 1970-01-01 01:00:00.000000000 +0100 +++ new/Radicale-3.5.4/radicale/tests/static/valarm1.ics 2025-05-25 08:50:29.000000000 +0200 @@ -0,0 +1,15 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//python-caldav//caldav//en_DK +BEGIN:VEVENT +SUMMARY:This is a test event +DTSTART:20151010T060000Z +DTEND:20161010T070000Z +DTSTAMP:20250515T073149Z +UID:a9cef952-315e-11f0-a30a-1c1bb5134174 +BEGIN:VALARM +ACTION:AUDIO +TRIGGER:-PT15M +END:VALARM +END:VEVENT +END:VCALENDAR diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.3/radicale/tests/static/valarm2.ics new/Radicale-3.5.4/radicale/tests/static/valarm2.ics --- old/Radicale-3.5.3/radicale/tests/static/valarm2.ics 1970-01-01 01:00:00.000000000 +0100 +++ new/Radicale-3.5.4/radicale/tests/static/valarm2.ics 2025-05-25 08:50:29.000000000 +0200 @@ -0,0 +1,15 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//python-caldav//caldav//en_DK +BEGIN:VEVENT +SUMMARY:This is a test event +DTSTART:20151010T060000Z +DTEND:20161010T070000Z +DTSTAMP:20250515T073149Z +UID:a9cef952-315e-11f0-a30a-1c1bb5134175 +BEGIN:VALARM +ACTION:AUDIO +TRIGGER;VALUE=DATE-TIME:20151010T033000Z +END:VALARM +END:VEVENT +END:VCALENDAR diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.3/radicale/tests/test_base.py new/Radicale-3.5.4/radicale/tests/test_base.py --- old/Radicale-3.5.3/radicale/tests/test_base.py 2025-05-11 17:15:10.000000000 +0200 +++ new/Radicale-3.5.4/radicale/tests/test_base.py 2025-05-25 08:50:29.000000000 +0200 @@ -21,6 +21,7 @@ """ +import logging import os import posixpath from typing import Any, Callable, ClassVar, Iterable, List, Optional, Tuple @@ -745,7 +746,7 @@ ) -> List[str]: filter_template = "<C:filter>%s</C:filter>" create_collection_fn: Callable[[str], Any] - if kind in ("event", "journal", "todo"): + if kind in ("event", "journal", "todo", "valarm"): create_collection_fn = self.mkcalendar path = "/calendar.ics/" filename_template = "%s%d.ics" @@ -764,10 +765,13 @@ status, _, = self.delete(path, check=None) assert status in (200, 404) create_collection_fn(path) + logging.warning("Upload items %r", items) for i in items: + logging.warning("Upload %d", i) filename = filename_template % (kind, i) event = get_file_content(filename) self.put(posixpath.join(path, filename), event) + logging.warning("Upload items finished") filters_text = "".join(filter_template % f for f in filters) _, responses = self.report(path, """\ <?xml version="1.0" encoding="utf-8" ?> @@ -1304,6 +1308,49 @@ </C:comp-filter>"""], "todo", items=range(1, 9)) assert "/calendar.ics/todo7.ics" in answer + def test_time_range_filter_events_valarm(self) -> None: + """Report request with time-range filter on events having absolute VALARM.""" + answer = self._test_filter(["""\ +<C:comp-filter name="VCALENDAR"> + <C:comp-filter name="VEVENT"> + <C:comp-filter name="VALARM"> + <C:time-range start="20151010T030000Z" end="20151010T040000Z"/> + </C:comp-filter> + </C:comp-filter> +</C:comp-filter>"""], "valarm", items=[1, 2]) + assert "/calendar.ics/valarm1.ics" not in answer + assert "/calendar.ics/valarm2.ics" in answer # absolute date + answer = self._test_filter(["""\ +<C:comp-filter name="VCALENDAR"> + <C:comp-filter name="VEVENT"> + <C:comp-filter name="VALARM"> + <C:time-range start="20151010T010000Z" end="20151010T020000Z"/> + </C:comp-filter> + </C:comp-filter> +</C:comp-filter>"""], "valarm", items=[1, 2]) + assert "/calendar.ics/valarm1.ics" not in answer + assert "/calendar.ics/valarm2.ics" not in answer + answer = self._test_filter(["""\ +<C:comp-filter name="VCALENDAR"> + <C:comp-filter name="VEVENT"> + <C:comp-filter name="VALARM"> + <C:time-range start="20151010T080000Z" end="20151010T090000Z"/> + </C:comp-filter> + </C:comp-filter> +</C:comp-filter>"""], "valarm", items=[1, 2]) + assert "/calendar.ics/valarm1.ics" not in answer + assert "/calendar.ics/valarm2.ics" not in answer + answer = self._test_filter(["""\ +<C:comp-filter name="VCALENDAR"> + <C:comp-filter name="VEVENT"> + <C:comp-filter name="VALARM"> + <C:time-range start="20151010T053000Z" end="20151010T055000Z"/> + </C:comp-filter> + </C:comp-filter> +</C:comp-filter>"""], "valarm", items=[1, 2]) + assert "/calendar.ics/valarm1.ics" in answer # -15 min offset + assert "/calendar.ics/valarm2.ics" not in answer + def test_time_range_filter_todos_completed(self) -> None: answer = self._test_filter(["""\ <C:comp-filter name="VCALENDAR"> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.3/radicale/utils.py new/Radicale-3.5.4/radicale/utils.py --- old/Radicale-3.5.3/radicale/utils.py 2025-05-11 17:15:10.000000000 +0200 +++ new/Radicale-3.5.4/radicale/utils.py 2025-05-25 08:50:29.000000000 +0200 @@ -102,7 +102,7 @@ ssl_context_options |= ssl.OP_NO_TLSv1_3 logger.debug("SSL cleared SSL context options: '0x%x'", ssl_context_options) for entry in protocol.split(): - entry = entry.strip('+') # remove trailing '+' + entry = entry.strip('+') # remove trailing '+' if entry == "ALL": logger.debug("SSL context options, enable ALL (some maybe not supported by underlying OpenSSL, SSLv2 not enabled at all)") ssl_context_options &= ~ssl.OP_NO_SSLv3 @@ -162,7 +162,7 @@ def ssl_context_minimum_version_by_options(ssl_context_options): logger.debug("SSL calculate minimum version by context options: '0x%x'", ssl_context_options) - ssl_context_minimum_version = ssl.TLSVersion.SSLv3 # default + ssl_context_minimum_version = ssl.TLSVersion.SSLv3 # default if ((ssl_context_options & ssl.OP_NO_SSLv3) and (ssl_context_minimum_version == ssl.TLSVersion.SSLv3)): ssl_context_minimum_version = ssl.TLSVersion.TLSv1 if ((ssl_context_options & ssl.OP_NO_TLSv1) and (ssl_context_minimum_version == ssl.TLSVersion.TLSv1)): @@ -172,7 +172,7 @@ if ((ssl_context_options & ssl.OP_NO_TLSv1_2) and (ssl_context_minimum_version == ssl.TLSVersion.TLSv1_2)): ssl_context_minimum_version = ssl.TLSVersion.TLSv1_3 if ((ssl_context_options & ssl.OP_NO_TLSv1_3) and (ssl_context_minimum_version == ssl.TLSVersion.TLSv1_3)): - ssl_context_minimum_version = 0 # all disabled + ssl_context_minimum_version = 0 # all disabled logger.debug("SSL context options: '0x%x' results in minimum version: %s", ssl_context_options, ssl_context_minimum_version) return ssl_context_minimum_version @@ -180,7 +180,7 @@ def ssl_context_maximum_version_by_options(ssl_context_options): logger.debug("SSL calculate maximum version by context options: '0x%x'", ssl_context_options) - ssl_context_maximum_version = ssl.TLSVersion.TLSv1_3 # default + ssl_context_maximum_version = ssl.TLSVersion.TLSv1_3 # default if ((ssl_context_options & ssl.OP_NO_TLSv1_3) and (ssl_context_maximum_version == ssl.TLSVersion.TLSv1_3)): ssl_context_maximum_version = ssl.TLSVersion.TLSv1_2 if ((ssl_context_options & ssl.OP_NO_TLSv1_2) and (ssl_context_maximum_version == ssl.TLSVersion.TLSv1_2)): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Radicale-3.5.3/setup.py.legacy new/Radicale-3.5.4/setup.py.legacy --- old/Radicale-3.5.3/setup.py.legacy 2025-05-11 17:15:10.000000000 +0200 +++ new/Radicale-3.5.4/setup.py.legacy 2025-05-25 08:50:29.000000000 +0200 @@ -20,7 +20,7 @@ # When the version is updated, a new section in the CHANGELOG.md file must be # added too. -VERSION = "3.5.3" +VERSION = "3.5.4" with open("README.md", encoding="utf-8") as f: long_description = f.read()