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-11-17 12:18:51
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/Radicale (Old)
 and      /work/SRC/openSUSE:Factory/.Radicale.new.2061 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "Radicale"

Mon Nov 17 12:18:51 2025 rev:26 rq:1318071 version:3.5.8

Changes:
--------
--- /work/SRC/openSUSE:Factory/Radicale/Radicale.changes        2025-10-05 
17:52:12.277982802 +0200
+++ /work/SRC/openSUSE:Factory/.Radicale.new.2061/Radicale.changes      
2025-11-17 12:24:05.551576792 +0100
@@ -1,0 +2,12 @@
+Sun Nov  9 11:44:40 UTC 2025 - Ákos Szőts <[email protected]>
+
+- Enable Argon2 support for testing and as a recommendation
+- Update to 3.5.8
+  * Extend [auth]: re-factor & overhaul LDAP authentication, especially for 
Python's ldap module
+  * Fix: out-of-range timestamp on 32-bit systems
+  * Fix: format_ut problem on 32-bit systems
+  * Feature: extend logging with response size in bytes and flag served as 
plain or gzip
+  * Feature: [storage] strict_preconditions: new config option to enforce 
strict preconditions check on PUT in case item already exists [RFC6352#9.2]
+  * Doc: Telugu translation
+
+-------------------------------------------------------------------

Old:
----
  v3.5.7.tar.gz

New:
----
  v3.5.8.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ Radicale.spec ++++++
--- /var/tmp/diff_new_pack.iMeBHQ/_old  2025-11-17 12:24:06.483616096 +0100
+++ /var/tmp/diff_new_pack.iMeBHQ/_new  2025-11-17 12:24:06.487616265 +0100
@@ -25,8 +25,9 @@
 %define py_min_ver 3.9
 %define vo_min_ver 0.9.6
 %define pk_min_ver 1.1.0
+%define pt_min_ver 7
 Name:           Radicale
-Version:        3.5.7
+Version:        3.5.8
 Release:        0
 Summary:        A CalDAV calendar and CardDav contact server
 License:        GPL-3.0-or-later
@@ -41,11 +42,12 @@
 BuildRequires:  firewall-macros
 BuildRequires:  pkgconfig
 BuildRequires:  python-rpm-macros
+BuildRequires:  python3-argon2-cffi
 BuildRequires:  python3-bcrypt
 BuildRequires:  python3-defusedxml
 BuildRequires:  python3-passlib
 BuildRequires:  python3-pika
-BuildRequires:  python3-pytest
+BuildRequires:  python3-pytest >= %{pt_min_ver}
 BuildRequires:  python3-setuptools
 BuildRequires:  python3-vobject >= %{vo_min_ver}
 BuildRequires:  python3-waitress
@@ -59,6 +61,7 @@
 Requires:       python3-requests
 Requires:       python3-vobject >= %{vo_min_ver}
 Recommends:     apache2-utils
+Recommends:     python3-argon2-cffi
 Recommends:     python3-bcrypt
 Recommends:     python3-ldap3
 BuildArch:      noarch

++++++ v3.5.7.tar.gz -> v3.5.8.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/Radicale-3.5.7/.github/workflows/docker-nightly-cleanup.yml 
new/Radicale-3.5.8/.github/workflows/docker-nightly-cleanup.yml
--- old/Radicale-3.5.7/.github/workflows/docker-nightly-cleanup.yml     
1970-01-01 01:00:00.000000000 +0100
+++ new/Radicale-3.5.8/.github/workflows/docker-nightly-cleanup.yml     
2025-11-06 06:29:21.000000000 +0100
@@ -0,0 +1,41 @@
+name: Cleanup old nightly docker images
+
+on:
+  schedule:
+    - cron: '10 0 * * *'
+  workflow_dispatch:
+
+
+jobs:
+  delete-package-versions:
+    name: Cleanup old nightly docker images
+    runs-on: ubuntu-latest
+    steps:
+      - name: Get list of all docker image versions in registry
+        env:
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        run: |
+          gh api --paginate -X GET 
"/orgs/Kozea/packages/container/Radicale/versions" -F package_type=container -F 
per_page=200 > data.json
+          
+      - name: Delete each nightly image older than cutoff date
+        env:
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        run: |
+          cutoff_date=$(date --date="30 days ago" --iso-8601)
+          echo "Cutoff date is: $cutoff_date"
+          
+          # Loop through each nightly image version (tag) older than the 
cutoff date
+          jq --arg cutoff_date "$cutoff_date" -r '.[] | 
select((.metadata.container.tags | any(. | contains("nightly"))) and 
(.created_at < $cutoff_date)) | [.metadata.container.tags[], .id] | @tsv' 
data.json | while IFS=$'\t' read -r tag nightly_image_id ; do
+            echo "Tag - $tag"
+
+            # Because of multi-platform, manifest for each tag would contain 
more than 1 image. Loop through all
+            all_digests=$(docker manifest inspect 
"ghcr.io/kozea/radicale:${tag}" | jq -r 'if .manifests then 
.manifests[]?.digest else empty end')
+            for digest in $all_digests; do
+              image_id=$(jq -r --arg digest "$digest" '.[] | select(.name == 
$digest) | .id' data.json)
+              echo "Deleting $image_id"
+              gh api -X DELETE 
"/orgs/Kozea/packages/container/Radicale/versions/$image_id"
+            done
+            # Now that all dependents are deleted, delete this tag
+            echo "Deleting $tag with ID: $nightly_image_id"
+            gh api -X DELETE 
"/orgs/Kozea/packages/container/Radicale/versions/$nightly_image_id"
+          done
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Radicale-3.5.7/.github/workflows/docker-publish.yml 
new/Radicale-3.5.8/.github/workflows/docker-publish.yml
--- old/Radicale-3.5.7/.github/workflows/docker-publish.yml     2025-09-27 
08:16:00.000000000 +0200
+++ new/Radicale-3.5.8/.github/workflows/docker-publish.yml     2025-11-06 
06:29:21.000000000 +0100
@@ -2,7 +2,7 @@
 
 on:
   release:
-    types: [published]
+    types: [released]
   schedule:
     - cron: '0 0 * * *'
   workflow_dispatch:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Radicale-3.5.7/.github/workflows/pypi-publish.yml 
new/Radicale-3.5.8/.github/workflows/pypi-publish.yml
--- old/Radicale-3.5.7/.github/workflows/pypi-publish.yml       2025-09-27 
08:16:00.000000000 +0200
+++ new/Radicale-3.5.8/.github/workflows/pypi-publish.yml       2025-11-06 
06:29:21.000000000 +0100
@@ -1,7 +1,7 @@
 name: PyPI publish
 on:
   release:
-    types: [published]
+    types: [released]
 
 jobs:
   publish:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Radicale-3.5.7/.github/workflows/test.yml 
new/Radicale-3.5.8/.github/workflows/test.yml
--- old/Radicale-3.5.7/.github/workflows/test.yml       2025-09-27 
08:16:00.000000000 +0200
+++ new/Radicale-3.5.8/.github/workflows/test.yml       2025-11-06 
06:29:21.000000000 +0100
@@ -26,6 +26,7 @@
         run: tox -c pyproject.toml -e py
 
   coveralls-test:
+    if: github.event_name == 'push'
     strategy:
       matrix:
         os: [ubuntu-latest]
@@ -54,7 +55,6 @@
     needs: coveralls-test
     if: github.event_name == 'push'
     runs-on: ubuntu-latest
-    continue-on-error: true
     steps:
       - uses: actions/setup-python@v5
         with:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Radicale-3.5.7/CHANGELOG.md 
new/Radicale-3.5.8/CHANGELOG.md
--- old/Radicale-3.5.7/CHANGELOG.md     2025-09-27 08:16:00.000000000 +0200
+++ new/Radicale-3.5.8/CHANGELOG.md     2025-11-06 06:29:21.000000000 +0100
@@ -1,5 +1,13 @@
 # Changelog
 
+## 3.5.8
+* Extend [auth]: re-factor & overhaul LDAP authentication, especially for 
Python's ldap module
+* Fix: out-of-range timestamp on 32-bit systems
+* Feature: extend logging with response size in bytes and flag served as plain 
or gzip
+* Feature: [storage] strict_preconditions: new config option to enforce strict 
preconditions check on PUT in case item already exists [RFC6352#9.2]
+* Fix: format_ut problem on 32-bit systems
+* Doc: Telugu translation
+
 ## 3.5.7
 * Extend: [auth] dovecot: add support for version >= 2.4
 * Fix: report/getetag with enabled expand
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Radicale-3.5.7/DOCUMENTATION.md 
new/Radicale-3.5.8/DOCUMENTATION.md
--- old/Radicale-3.5.7/DOCUMENTATION.md 2025-09-27 08:16:00.000000000 +0200
+++ new/Radicale-3.5.8/DOCUMENTATION.md 2025-11-06 06:29:21.000000000 +0100
@@ -1,5 +1,9 @@
 # Documentation
 
+## Translations of this page
+
+* 
[Telugu](https://github.com/Kozea/Radicale/blob/master/docs/DOCUMENTATION.te.md)
+
 ## Getting started
 
 #### About Radicale
@@ -1523,6 +1527,14 @@
 
 Default: `True`
 
+##### strict_preconditions
+
+_(>= 3.5.8)_
+
+Strict preconditions check on PUT in case item already exists 
[RFC6352#9.2](https://www.rfc-editor.org/rfc/rfc6352#section-9.2)
+
+Default: `False`
+
 ##### hook
 
 Command that is run after changes to storage. See the
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Radicale-3.5.7/config new/Radicale-3.5.8/config
--- old/Radicale-3.5.7/config   2025-09-27 08:16:00.000000000 +0200
+++ new/Radicale-3.5.8/config   2025-11-06 06:29:21.000000000 +0100
@@ -181,6 +181,9 @@
 # Strip domain name from username
 #strip_domain = False
 
+# URL Decode the given username (when URL-encoded by the client - useful for 
iOS devices when using email address)
+#urldecode_username = False
+
 
 [rights]
 
@@ -197,8 +200,6 @@
 # Permit overwrite of a collection (global)
 #permit_overwrite_collection = True
 
-# URL Decode the given username (when URL-encoded by the client - useful for 
iOS devices when using email address)
-# urldecode_username = False
 
 [storage]
 
@@ -241,6 +242,9 @@
 # Skip broken item instead of triggering an exception
 #skip_broken_item = True
 
+# Strict preconditions check on PUT
+#strict_preconditions = False
+
 # Command that is run after changes to storage, default is emtpy
 #  Supported placeholders:
 #   %(user)s: logged-in user
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Radicale-3.5.7/contrib/logwatch/radicale 
new/Radicale-3.5.8/contrib/logwatch/radicale
--- old/Radicale-3.5.7/contrib/logwatch/radicale        2025-09-27 
08:16:00.000000000 +0200
+++ new/Radicale-3.5.8/contrib/logwatch/radicale        2025-11-06 
06:29:21.000000000 +0100
@@ -1,6 +1,6 @@
 # This file is related to Radicale - CalDAV and CardDAV server
 #  for logwatch (script)
-# Copyright © 2024-2024 Peter Bieringer <[email protected]>
+# Copyright © 2024-2025 Peter Bieringer <[email protected]>
 #
 # Detail levels
 # >= 5: Logins
@@ -9,6 +9,7 @@
 $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0;
 
 my %ResponseTimes;
+my %ResponseSizes;
 my %Responses;
 my %Requests;
 my %Logins;
@@ -39,6 +40,28 @@
    $ResponseTimes{$req}->{'sum'} += $time;
 }
 
+sub ResponseSizesMinMaxSum($$$) {
+   my $req = $_[0];
+   my $type = $_[1];
+   my $size = $_[2];
+
+   $ResponseSizes{$type}->{$req}->{'cnt'}++;
+
+   if (! defined $ResponseSizes{$type}->{$req}->{'min'}) {
+      $ResponseSizes{$type}->{$req}->{'min'} = $size;
+   } elsif ($ResponseSizes{$type}->{$req}->{'min'} > $size) {
+      $ResponseSizes{$type}->{$req}->{'min'} = $size;
+   }
+
+   if (! defined $ResponseSizes{$type}->{$req}->{'max'}) {
+      $ResponseSizes{$type}->{$req}{'max'} = $size;
+   } elsif ($ResponseSizes{$type}->{$req}->{'max'} < $size) {
+      $ResponseSizes{$type}->{$req}{'max'} = $size;
+   }
+
+   $ResponseSizes{$type}->{$req}->{'sum'} += $size;
+}
+
 sub Sum($) {
    my $phash = $_[0];
    my $sum = 0;
@@ -75,9 +98,17 @@
       if ( $ThisLine =~ / \S+ response status for .* with depth '(\d)' in 
([0-9.]+) seconds: (\d+)/o ) {
          $req .= ":D=" . $1 . ":R=" . $3;
          ResponseTimesMinMaxSum($req, $2) if ($Detail >= 10);
-      } elsif ( $ThisLine =~ / \S+ response status for .* in ([0-9.]+) 
seconds: (\d+)/ ) {
+      } elsif ( $ThisLine =~ / \S+ response status for .* in ([0-9.]+) 
seconds: (\d+)/o ) {
          $req .= ":R=" . $2;
          ResponseTimesMinMaxSum($req, $1) if ($Detail >= 10);
+      } elsif ( $ThisLine =~ / \S+ response status for .* with depth '(\d)' in 
([0-9.]+) seconds (\S+) (\d+) bytes: (\d+)/o ) {
+         $req .= ":D=" . $1 . ":R=" . $5;
+         ResponseTimesMinMaxSum($req, $2) if ($Detail >= 10);
+         ResponseSizesMinMaxSum($req, $3, $4) if ($Detail >= 10);
+      } elsif ( $ThisLine =~ / \S+ response status for .* in ([0-9.]+) seconds 
(\S+) (\d+) bytes: (\d+)/o ) {
+         $req .= ":R=" . $4;
+         ResponseTimesMinMaxSum($req, $1) if ($Detail >= 10);
+         ResponseSizesMinMaxSum($req, $2, $3) if ($Detail >= 10);
       }
       $Responses{$req}++;
    }
@@ -174,6 +205,22 @@
    print "-" x60 . "\n";
 }
 
+if (keys %ResponseSizes) {
+   for my $type (sort keys %ResponseSizes) {
+      print "\n**Response sizes (counts, bytes: $type) (D=<depth> 
R=<result>)**\n";
+      printf "%-18s | %7s | %9s | %9s | %9s |\n", "Response", "cnt", "min", 
"max", "avg";
+      print "-" x66 . "\n";
+      foreach my $req (sort keys %{$ResponseSizes{$type}}) {
+         printf "%-18s | %7d | %9d | %9d | %9d |\n", $req
+            , $ResponseSizes{$type}->{$req}->{'cnt'}
+            , $ResponseSizes{$type}->{$req}->{'min'}
+            , $ResponseSizes{$type}->{$req}->{'max'}
+            , $ResponseSizes{$type}->{$req}->{'sum'} / 
$ResponseSizes{$type}->{$req}->{'cnt'};
+      }
+      print "-" x66 . "\n";
+   }
+}
+
 if (keys %OtherEvents) {
    print "\n**Other Events**\n";
    foreach $ThisOne (sort keys %OtherEvents) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Radicale-3.5.7/docs/DOCUMENTATION.te.md 
new/Radicale-3.5.8/docs/DOCUMENTATION.te.md
--- old/Radicale-3.5.7/docs/DOCUMENTATION.te.md 1970-01-01 01:00:00.000000000 
+0100
+++ new/Radicale-3.5.8/docs/DOCUMENTATION.te.md 2025-11-06 06:29:21.000000000 
+0100
@@ -0,0 +1,288 @@
+> Last updated: 2025-10-20 by 
[@gowtham1412-p](https://github.com/gowtham1412-p)
+
+> Based on commit: [4fdc78760914040d5f74ece8978013b8836a712e] of 
[DOCUMENTATION.md](https://github.com/Kozea/Radicale/blob/master/DOCUMENTATION.md)
+
+\# డాక్యుమెంటేషన్
+
+
+
+\## ప్రారంభించడం
+
+
+
+\#### రాడికేల్ గురించి
+
+
+
+రాడికేల్ అనేది ఒక చిన్న కానీ శక్తివంతమైన CalDAV (క్యాలెండర్లు, చేయవలసిన 
జాబితాలు) మరియు CardDAV
+
+(పరిచయాలు) సర్వర్, ఇది:
+
+
+
+\* CalDAV, CardDAV మరియు HTTP ద్వారా క్యాలెండర్లు మరియు పరిచయ జాబితాలను 
పంచుకుంటుంది.
+
+\* ఈవెంట్‌లు, టోడోలు, జర్నల్ ఎంట్రీలు మరియు వ్యాపార కార్డులకు మద్దతు ఇస్తుంది.
+
+\* బాక్స్ వెలుపల పనిచేస్తుంది, సంక్లిష్టమైన సెటప్ లేదా కాన్ఫిగరేషన్ అవసరం లేదు.
+
+\* సౌకర్యవంతమైన ప్రామాణీకరణ ఎంపికలను అందిస్తుంది.
+
+\* అధికారం ద్వారా యాక్సెస్‌ను పరిమితం చేయవచ్చు.
+
+\* TLSతో కనెక్షన్‌లను సురక్షితం చేయవచ్చు.
+
+\* చాలా మందితో పనిచేస్తుంది
+
+\[CalDAV మరియు CardDAV క్లయింట్లు](#సపోర్టెడ్-క్లయింట్లు).
+
+\* ఫైల్ సిస్టమ్‌లోని అన్ని డేటాను సాధారణ ఫోల్డర్ నిర్మాణంలో నిల్వ చేస్తుంది.
+
+\* ప్లగిన్‌లతో పొడిగించవచ్చు.
+
+\* GPLv3-లైసెన్స్ పొందిన ఉచిత సాఫ్ట్‌వేర్.
+
+
+
+\#### ఇన్‌స్టాలేషన్
+
+
+
+తనిఖీ చేయండి
+
+
+
+\* \[ట్యుటోరియల్స్](#ట్యుటోరియల్స్)
+
+\* \[డాక్యుమెంటేషన్](#డాక్యుమెంటేషన్-1)
+
+\* \[GitHubలో వికీ](https://github.com/Kozea/Radicale/wiki)
+
+\* \[GitHubలో చర్చలు](https://github.com/Kozea/Radicale/discussions)
+
+\* \[GitHubలో తెరిచి ఉన్న మరియు ఇప్పటికే మూసివేయబడిన 
సమస్యలు](https://github.com/Kozea/Radicale/issues?q=is%3Aissue)
+
+
+
+\#### కొత్తగా ఏముంది?
+
+
+
+\[GitHubలో 
చేంజ్‌లాగ్](https://github.com/Kozea/Radicale/blob/master/CHANGELOG.md) చదవండి.
+
+
+
+\## ట్యుటోరియల్స్
+
+
+
+\### 5 నిమిషాల సులభమైన సెటప్
+
+
+
+మీరు Radicaleని ప్రయత్నించాలనుకుంటున్నారా కానీ మీ క్యాలెండర్‌లో 5 నిమిషాలు 
మాత్రమే ఖాళీగా ఉందా?
+
+
+
+ఇప్పుడే వెళ్లి Radicaleతో కొంచెం ఆడుదాం!
+
+
+
+ఈ విభాగం నుండి సెట్టింగ్‌లతో కాన్ఫిగర్ చేయబడిన సర్వర్, localhost
+
+కి మాత్రమే బైండ్ అవుతుంది (అంటే ఇది నెట్‌వర్క్ ద్వారా చేరుకోలేరు), మరియు మీరు 
ఏదైనా వినియోగదారు పేరు మరియు పాస్‌వర్డ్‌తో లాగిన్ అవ్వవచ్చు.
+
+
+
+ప్రతిదీ పనిచేసినప్పుడు, మీరు స్థానిక \[client](#supported-clients)
+
+ని పొందవచ్చు మరియు క్యాలెండర్‌లు మరియు చిరునామా పుస్తకాలను సృష్టించడం 
ప్రారంభించవచ్చు.
+
+
+
+Radicale మీ అవసరాలకు సరిపోతుంటే, రిమోట్ క్లయింట్‌లు మరియు కావలసిన ప్రామాణీకరణ 
రకానికి మద్దతు ఇవ్వడానికి కొంత \[ప్రాథమిక కాన్ఫిగరేషన్](#basic-configuration)
+
+కి సమయం కావచ్చు.
+
+
+
+మీ ఆపరేటింగ్ సిస్టమ్‌ను బట్టి దిగువన ఉన్న అధ్యాయాలలో ఒకదాన్ని అనుసరించండి.
+
+
+
+\#### Linux / \\\*BSD
+
+
+
+సూచన: PyPI నుండి డౌన్‌లోడ్ చేయడానికి బదులుగా, మీ 
\[distribution](#linux-distribution-packages) అందించిన ప్యాకేజీల కోసం చూడండి.
+
+
+
+అవి మీ పంపిణీలలో ఇంటిగ్రేట్ చేయబడిన స్టార్టప్ స్క్రిప్ట్‌లను కూడా కలిగి 
ఉంటాయి, ఇవి Radicaleని డెమోనైజ్ చేయడానికి అనుమతిస్తాయి.
+
+
+
+ముందుగా, \*\*python\*\* 3.9 లేదా తరువాత మరియు \*\*pip\*\* ఇన్‌స్టాల్ 
చేయబడిందని నిర్ధారించుకోండి. చాలా డిస్ట్రిబ్యూషన్లలో ``python3-pip`` ప్యాకేజీని 
ఇన్‌స్టాల్ చేయడానికి సరిపోతుంది.
+
+
+
+\##### సాధారణ వినియోగదారుగా
+
+
+
+పరీక్ష కోసం మాత్రమే సిఫార్సు చేయబడింది - కన్సోల్‌ను తెరిచి ఇలా టైప్ చేయండి:
+
+
+
+```bash
+
+\# ప్రస్తుత వినియోగదారు కోసం మాత్రమే ఇన్‌స్టాల్ చేయడానికి కింది ఆదేశాన్ని అమలు 
చేయండి
+
+python3 -m pip install --user --upgrade 
https://github.com/Kozea/Radicale/archive/master.tar.gz
+
+```
+
+
+
+\_install\_ పని చేయకపోతే మరియు బదులుగా `error: externally-managed-environment` 
ప్రదర్శించబడితే,
+
+ముందుగానే వర్చువల్ వాతావరణాన్ని సృష్టించండి మరియు సక్రియం చేయండి.
+
+
+
+```bash
+
+python3 -m venv ~/venv
+
+source ~/venv/bin/activate
+
+```
+
+
+
+మరియు దీనితో ఇన్‌స్టాల్ చేయడానికి ప్రయత్నించండి
+
+
+
+```bash
+
+python3 -m pip install --upgrade 
https://github.com/Kozea/Radicale/archive/master.tar.gz
+
+```
+
+
+
+సేవను మాన్యువల్‌గా ప్రారంభించండి, డేటా ప్రస్తుత వినియోగదారు కోసం మాత్రమే నిల్వ 
చేయబడుతుంది
+
+
+
+```bash
+
+\# ప్రారంభించు, డేటా ప్రస్తుత వినియోగదారు కోసం మాత్రమే నిల్వ చేయబడుతుంది
+
+python3 -m radicale 
--storage-filesystem-folder=~/.var/lib/radicale/collections --auth-type none
+
+```
+
+
+
+\#### సిస్టమ్ వినియోగదారుగా (లేదా రూట్‌గా)
+
+
+
+ప్రత్యామ్నాయంగా, మీరు సిస్టమ్ వినియోగదారుగా లేదా రూట్‌గా ఇన్‌స్టాల్ చేసి అమలు 
చేయవచ్చు (సిఫార్సు చేయబడలేదు):
+
+
+
+```bash
+
+\# కింది ఆదేశాన్ని రూట్ (సిఫార్సు చేయబడలేదు) లేదా రూట్ కాని వ్యవస్థ 
వినియోగదారుగా అమలు చేయండి
+
+\# (డిపెండెన్సీలు లేనప్పుడు తరువాతి వాటికి --user అవసరం కావచ్చు సిస్టమ్-వైడ్ 
మరియు/లేదా వర్చువల్ ఎన్విరాన్మెంట్ అందుబాటులో ఉంది)
+
+python3 -m pip install --upgrade 
https://github.com/Kozea/Radicale/archive/master.tar.gz
+
+```
+
+
+
+`/var/lib/radicale/collections` కింద సిస్టమ్ ఫోల్డర్‌లో నిల్వ చేయబడిన డేటాతో 
సేవను మాన్యువల్‌గా ప్రారంభించండి:
+
+
+
+```bash
+
+\# Start, డేటా సిస్టమ్ ఫోల్డర్‌లో నిల్వ చేయబడుతుంది 
(/var/lib/radicale/collections కు వ్రాయడానికి అనుమతులు అవసరం)
+
+python3 -m radicale --storage-filesystem-folder=/var/lib/radicale/collections 
--auth-type none
+
+```
+
+
+
+\#### Windows
+
+
+
+మొదటి దశ పైథాన్‌ను ఇన్‌స్టాల్ చేయడం.
+
+\[python.org](https://python.org) కు వెళ్లి పైథాన్ 3 యొక్క తాజా వెర్షన్‌ను 
డౌన్‌లోడ్ చేసుకోండి.
+
+తర్వాత ఇన్‌స్టాలర్‌ను అమలు చేయండి.
+
+ఇన్‌స్టాలర్ యొక్క మొదటి విండోలో, "PATH కు పైథాన్‌ను జోడించు" బాక్స్‌ను తనిఖీ 
చేసి,
+
+"ఇప్పుడే ఇన్‌స్టాల్ చేయి"పై క్లిక్ చేయండి. రెండు నిమిషాలు వేచి ఉండండి, 
పూర్తయింది!
+
+
+
+కమాండ్ ప్రాంప్ట్‌ను ప్రారంభించి ఇలా టైప్ చేయండి:
+
+
+
+```powershell
+
+python -m pip install --upgrade 
https://github.com/Kozea/Radicale/archive/master.tar.gz
+
+python -m radicale --storage-filesystem-folder=~/radicale/collections 
--auth-type none
+
+```
+
+
+
+\##### Common
+
+
+
+విజయవంతం!!! మీ బ్రౌజర్‌లో <http://localhost:5232> తెరవండి!
+
+ఉదాహరణ ఎంపిక `--auth-type none` ద్వారా ప్రామాణీకరణ అవసరం లేనందున మీరు ఏదైనా 
వినియోగదారు పేరు మరియు పాస్‌వర్డ్‌తో లాగిన్ అవ్వవచ్చు.
+
+ఇది \*\*సురక్షితం\*\*, మరిన్ని వివరాల కోసం \[కాన్ఫిగరేషన్/ప్రామాణీకరణ](#auth) 
చూడండి.
+
+
+
+భద్రతా కారణాల దృష్ట్యా డిఫాల్ట్ కాన్ఫిగరేషన్ సర్వర్‌ను `localhost` (IPv4: 
`127.0.0.1`, IPv6: `::1`) కు బంధిస్తుందని గమనించండి.
+
+
+
+మరిన్ని వివరాల కోసం \[చిరునామాలు](#చిరునామాలు) మరియు 
\[కాన్ఫిగరేషన్/సర్వర్](#సర్వర్) చూడండి.
+
+
+
+\### ప్రాథమిక కాన్ఫిగరేషన్
+
+
+
+ఇన్‌స్టాలేషన్ సూచనలను
+
+\[సరళమైన 5-నిమిషాల సెటప్](#సింపుల్-5-నిమిషాల-సెటప్) ట్యుటోరియల్‌లో చూడవచ్చు.
+
+
+
+రాడికేల్ `/etc/radicale/config` మరియు
+
+`~/.config/radicale/config` నుండి కాన్ఫిగరేషన్ ఫైల్‌లను లోడ్ చేయడానికి 
ప్రయత్నిస్తుంది.
+
+Cu
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Radicale-3.5.7/pyproject.toml 
new/Radicale-3.5.8/pyproject.toml
--- old/Radicale-3.5.7/pyproject.toml   2025-09-27 08:16:00.000000000 +0200
+++ new/Radicale-3.5.8/pyproject.toml   2025-11-06 06:29:21.000000000 +0100
@@ -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.7"
+version = "3.5.8"
 authors = [{name = "Guillaume Ayoub", email = "[email protected]"}, 
{name = "Unrud", email = "[email protected]"}, {name = "Peter Bieringer", email 
= "[email protected]"}]
 license = {text = "GNU GPL v3"}
 description = "CalDAV and CardDAV Server"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Radicale-3.5.7/radicale/app/__init__.py 
new/Radicale-3.5.8/radicale/app/__init__.py
--- old/Radicale-3.5.7/radicale/app/__init__.py 2025-09-27 08:16:00.000000000 
+0200
+++ new/Radicale-3.5.8/radicale/app/__init__.py 2025-11-06 06:29:21.000000000 
+0100
@@ -73,8 +73,6 @@
     _web_type: str
     _script_name: str
     _extra_headers: Mapping[str, str]
-    _permit_delete_collection: bool
-    _permit_overwrite_collection: bool
 
     def __init__(self, configuration: config.Configuration) -> None:
         """Initialize Application.
@@ -116,6 +114,8 @@
         self._extra_headers = dict()
         for key in self.configuration.options("headers"):
             self._extra_headers[key] = configuration.get("headers", key)
+        self._strict_preconditions = configuration.get("storage", 
"strict_preconditions")
+        logger.info("strict preconditions check: %s", 
self._strict_preconditions)
 
     def _scrub_headers(self, environ: types.WSGIEnviron) -> types.WSGIEnviron:
         """Mask passwords and cookies."""
@@ -164,6 +164,7 @@
                      answer: Union[None, str, bytes]) -> _IntermediateResponse:
             """Helper to create response from internal types.WSGIResponse"""
             headers = dict(headers)
+            content_encoding = "plain"
             # Set content length
             answers = []
             if answer is not None:
@@ -183,6 +184,7 @@
                     zcomp = zlib.compressobj(wbits=16 + zlib.MAX_WBITS)
                     answer = zcomp.compress(answer) + zcomp.flush()
                     headers["Content-Encoding"] = "gzip"
+                    content_encoding = "gzip"
 
                 headers["Content-Length"] = str(len(answer))
                 answers.append(answer)
@@ -194,9 +196,14 @@
             time_end = datetime.datetime.now()
             status_text = "%d %s" % (
                 status, client.responses.get(status, "Unknown"))
-            logger.info("%s response status for %r%s in %.3f seconds: %s",
-                        request_method, unsafe_path, depthinfo,
-                        (time_end - time_begin).total_seconds(), status_text)
+            if answer is not None:
+                logger.info("%s response status for %r%s in %.3f seconds %s %s 
bytes: %s",
+                            request_method, unsafe_path, depthinfo,
+                            (time_end - time_begin).total_seconds(), 
content_encoding, str(len(answer)), status_text)
+            else:
+                logger.info("%s response status for %r%s in %.3f seconds: %s",
+                            request_method, unsafe_path, depthinfo,
+                            (time_end - time_begin).total_seconds(), 
status_text)
             # Return response content
             return status_text, list(headers.items()), answers
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Radicale-3.5.7/radicale/app/base.py 
new/Radicale-3.5.8/radicale/app/base.py
--- old/Radicale-3.5.7/radicale/app/base.py     2025-09-27 08:16:00.000000000 
+0200
+++ new/Radicale-3.5.8/radicale/app/base.py     2025-11-06 06:29:21.000000000 
+0100
@@ -41,6 +41,7 @@
     _encoding: str
     _permit_delete_collection: bool
     _permit_overwrite_collection: bool
+    _strict_preconditions: bool
     _hook: hook.BaseHook
 
     def __init__(self, configuration: config.Configuration) -> None:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Radicale-3.5.7/radicale/app/put.py 
new/Radicale-3.5.8/radicale/app/put.py
--- old/Radicale-3.5.7/radicale/app/put.py      2025-09-27 08:16:00.000000000 
+0200
+++ new/Radicale-3.5.8/radicale/app/put.py      2025-11-06 06:29:21.000000000 
+0100
@@ -207,6 +207,9 @@
                 return httputils.NOT_ALLOWED
 
             etag = environ.get("HTTP_IF_MATCH", "")
+            if item and not etag and self._strict_preconditions:
+                logger.warning("Precondition failed for %r: existing item, no 
If-Match header, strict mode enabled", path)
+                return httputils.PRECONDITION_FAILED
             if not item and etag:
                 # Etag asked but no item found: item has been removed
                 logger.warning("Precondition failed on PUT request for %r 
(HTTP_IF_MATCH: %s, item not existing)", path, etag)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Radicale-3.5.7/radicale/auth/ldap.py 
new/Radicale-3.5.8/radicale/auth/ldap.py
--- old/Radicale-3.5.7/radicale/auth/ldap.py    2025-09-27 08:16:00.000000000 
+0200
+++ new/Radicale-3.5.8/radicale/auth/ldap.py    2025-11-06 06:29:21.000000000 
+0100
@@ -1,6 +1,7 @@
 # This file is part of Radicale - CalDAV and CardDAV server
 # Copyright © 2022-2024 Peter Varkoly
 # Copyright © 2024-2024 Peter Bieringer <[email protected]>
+# Copyright © 2024-2025 Peter Marschall <[email protected]>
 #
 # 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
@@ -67,10 +68,8 @@
     _ldap_group_filter: str
     _ldap_group_members_attr: str
     _ldap_module_version: int = 3
-    _use_encryption: bool = False
-    _ldap_use_ssl: bool = False
     _ldap_security: str = "none"
-    _ldap_ssl_verify_mode: int = ssl.CERT_REQUIRED
+    _ldap_ssl_verify_mode: str = "REQUIRED"
     _ldap_ssl_ca_file: str = ""
 
     def __init__(self, configuration: config.Configuration) -> None:
@@ -85,7 +84,7 @@
                 self._ldap_module_version = 2
                 self.ldap = ldap
             except ImportError as e:
-                raise RuntimeError("LDAP authentication requires the ldap3 
module") from e
+                raise RuntimeError("LDAP authentication requires the ldap3 or 
ldap module") from e
 
         self._ldap_ignore_attribute_create_modify_timestamp = 
configuration.get("auth", "ldap_ignore_attribute_create_modify_timestamp")
         self._ldap_uri = configuration.get("auth", "ldap_uri")
@@ -102,66 +101,77 @@
         if ldap_secret_file_path:
             with open(ldap_secret_file_path, 'r') as file:
                 self._ldap_secret = file.read().rstrip('\n')
-        if self._ldap_module_version == 3:
-            self._ldap_use_ssl = configuration.get("auth", "ldap_use_ssl")
-            self._ldap_security = configuration.get("auth", "ldap_security")
-            self._use_encryption = self._ldap_use_ssl or self._ldap_security 
in ("tls", "starttls")
-            if self._ldap_use_ssl and self._ldap_security == "starttls":
-                raise RuntimeError("Cannot set both 'ldap_use_ssl = True' and 
'ldap_security' = 'starttls'")
-            if self._ldap_use_ssl:
-                logger.warning("Configuration uses soon to be deprecated 
'ldap_use_ssl', use 'ldap_security' ('none', 'tls', 'starttls') instead.")
-            if self._use_encryption:
-                self._ldap_ssl_ca_file = configuration.get("auth", 
"ldap_ssl_ca_file")
-                tmp = configuration.get("auth", "ldap_ssl_verify_mode")
-                if tmp == "NONE":
-                    self._ldap_ssl_verify_mode = ssl.CERT_NONE
-                elif tmp == "OPTIONAL":
-                    self._ldap_ssl_verify_mode = ssl.CERT_OPTIONAL
-
-        logger.info("auth.ldap_uri             : %r" % self._ldap_uri)
-        logger.info("auth.ldap_base            : %r" % self._ldap_base)
-        logger.info("auth.ldap_reader_dn       : %r" % self._ldap_reader_dn)
-        logger.info("auth.ldap_filter          : %r" % self._ldap_filter)
+        self._ldap_security = configuration.get("auth", "ldap_security")
+        if self._ldap_security not in ("none", "tls", "starttls"):
+            raise RuntimeError("Illegal value for config setting 
´ldap_security'")
+        ldap_use_ssl = configuration.get("auth", "ldap_use_ssl")
+        if ldap_use_ssl:
+            logger.warning("Configuration uses deprecated 'ldap_use_ssl': use 
'ldap_security' ('none', 'tls', 'starttls') instead.")
+            if self._ldap_security == "starttls":
+                raise RuntimeError("Deprecated config setting 'ldap_use_ssl = 
True' conflicts with 'ldap_security' = 'starttls'")
+            elif self._ldap_security != "tls":
+                logger.warning("Update configuration: set 'ldap_security = 
tls' instead of deprecated 'ldap_use_ssl = True'")
+                self._ldap_security = "tls"
+        self._ldap_ssl_ca_file = configuration.get("auth", "ldap_ssl_ca_file")
+        self._ldap_ssl_verify_mode = configuration.get("auth", 
"ldap_ssl_verify_mode")
+        if self._ldap_ssl_verify_mode not in ("NONE", "OPTIONAL", "REQUIRED"):
+            raise RuntimeError("Illegal value for config setting 
´ldap_ssl_verify_mode'")
+
+        if self._ldap_uri.lower().startswith("ldaps://") and 
self._ldap_security not in ("tls", "starttls"):
+            logger.info("Inferring 'ldap_security' = tls from 'ldap_uri' 
starting with 'ldaps://'")
+            self._ldap_security = "tls"
+        if self._ldap_uri.lower().startswith("ldapi://") and 
self._ldap_ssl_verify_mode != "NONE":
+            logger.info("Lowering 'ldap_'ldap_ssl_verify_mode' to NONE for 
'ldap_uri' starting with 'ldapi://'")
+            self._ldap_ssl_verify_mode = "NONE"
+
+        if self._ldap_ssl_ca_file == "" and self._ldap_ssl_verify_mode != 
"NONE" and self._ldap_security in ("tls", "starttls"):
+            logger.warning("Certificate verification not possible: 
'ldap_ssl_ca_file' not set")
+        if self._ldap_ssl_ca_file and self._ldap_security not in ("tls", 
"starttls"):
+            logger.warning("Config setting 'ldap_ssl_ca_file' useless without 
encrypted LDAP connection")
+
+        logger.info("auth.ldap_uri               : %r" % self._ldap_uri)
+        logger.info("auth.ldap_base              : %r" % self._ldap_base)
+        logger.info("auth.ldap_reader_dn         : %r" % self._ldap_reader_dn)
+        logger.info("auth.ldap_filter            : %r" % self._ldap_filter)
         if self._ldap_user_attr:
-            logger.info("auth.ldap_user_attribute  : %r" % 
self._ldap_user_attr)
+            logger.info("auth.ldap_user_attribute    : %r" % 
self._ldap_user_attr)
         else:
-            logger.info("auth.ldap_user_attribute  : (not provided)")
+            logger.info("auth.ldap_user_attribute    : (not provided)")
         if self._ldap_groups_attr:
-            logger.info("auth.ldap_groups_attribute: %r" % 
self._ldap_groups_attr)
+            logger.info("auth.ldap_groups_attribute  : %r" % 
self._ldap_groups_attr)
         else:
-            logger.info("auth.ldap_groups_attribute: (not provided)")
+            logger.info("auth.ldap_groups_attribute  : (not provided)")
         if self._ldap_group_base:
-            logger.info("auth.ldap_group_base     : %r" % 
self._ldap_group_base)
+            logger.info("auth.ldap_group_base        : %r" % 
self._ldap_group_base)
         else:
-            logger.info("auth.ldap_group_base     : (not provided, using 
ldap_base)")
+            logger.info("auth.ldap_group_base        : (not provided, using 
ldap_base)")
             self._ldap_group_base = self._ldap_base
         if self._ldap_group_filter:
-            logger.info("auth.ldap_group_filter: %r" % self._ldap_group_filter)
+            logger.info("auth.ldap_group_filter      : %r" % 
self._ldap_group_filter)
         else:
-            logger.info("auth.ldap_group_filter: (not provided)")
+            logger.info("auth.ldap_group_filter      : (not provided)")
         if self._ldap_group_members_attr:
             logger.info("auth.ldap_group_members_attr: %r" % 
self._ldap_group_members_attr)
         else:
             logger.info("auth.ldap_group_members_attr: (not provided)")
         if ldap_secret_file_path:
-            logger.info("auth.ldap_secret_file_path: %r" % 
ldap_secret_file_path)
+            logger.info("auth.ldap_secret_file_path  : %r" % 
ldap_secret_file_path)
             if self._ldap_secret:
-                logger.info("auth.ldap_secret          : (from file)")
+                logger.info("auth.ldap_secret            : (from file)")
         else:
-            logger.info("auth.ldap_secret_file_path: (not provided)")
+            logger.info("auth.ldap_secret_file_path  : (not provided)")
             if self._ldap_secret:
-                logger.info("auth.ldap_secret          : (from config)")
+                logger.info("auth.ldap_secret            : (from config)")
         if self._ldap_reader_dn and not self._ldap_secret:
-            logger.error("auth.ldap_secret         : (not provided)")
+            logger.error("auth.ldap_secret           : (not provided)")
             raise RuntimeError("LDAP authentication requires ldap_secret for 
ldap_reader_dn")
-        logger.info("auth.ldap_use_ssl         : %s" % self._ldap_use_ssl)
-        logger.info("auth.ldap_security      : %s" % self._ldap_security)
-        if self._use_encryption:
-            logger.info("auth.ldap_ssl_verify_mode : %s" % 
self._ldap_ssl_verify_mode)
-            if self._ldap_ssl_ca_file:
-                logger.info("auth.ldap_ssl_ca_file     : %r" % 
self._ldap_ssl_ca_file)
-            else:
-                logger.info("auth.ldap_ssl_ca_file     : (not provided)")
+        logger.info("auth.ldap_use_ssl           : %s" % ldap_use_ssl)
+        logger.info("auth.ldap_security          : %s" % self._ldap_security)
+        logger.info("auth.ldap_ssl_verify_mode   : %s" % 
self._ldap_ssl_verify_mode)
+        if self._ldap_ssl_ca_file:
+            logger.info("auth.ldap_ssl_ca_file       : %r" % 
self._ldap_ssl_ca_file)
+        else:
+            logger.info("auth.ldap_ssl_ca_file       : (not provided)")
         if self._ldap_ignore_attribute_create_modify_timestamp:
             logger.info("auth.ldap_ignore_attribute_create_modify_timestamp 
applied (relevant for ldap3 only)")
         """Extend attributes to to be returned in the user query"""
@@ -169,15 +179,31 @@
             self._ldap_attributes.append(self._ldap_groups_attr)
         if self._ldap_user_attr:
             self._ldap_attributes.append(self._ldap_user_attr)
-        logger.info("ldap_attributes           : %r" % self._ldap_attributes)
+        logger.info("ldap_attributes             : %r" % self._ldap_attributes)
 
     def _login2(self, login: str, password: str) -> str:
         try:
             """Bind as reader dn"""
             logger.debug(f"_login2 {self._ldap_uri}, {self._ldap_reader_dn}")
             conn = self.ldap.initialize(self._ldap_uri)
-            conn.protocol_version = 3
+            conn.protocol_version = self.ldap.VERSION3
             conn.set_option(self.ldap.OPT_REFERRALS, 0)
+
+            if self._ldap_security in ("tls", "starttls"):
+                """certificate validation mode"""
+                verifyMode = {"NONE": self.ldap.OPT_X_TLS_NEVER,
+                              "OPTIONAL": self.ldap.OPT_X_TLS_ALLOW,
+                              "REQUIRED": self.ldap.OPT_X_TLS_DEMAND}
+                conn.set_option(self.ldap.OPT_X_TLS_REQUIRE_CERT, 
verifyMode[self._ldap_ssl_verify_mode])
+                """CA file to validate certificate against"""
+                if self._ldap_ssl_ca_file:
+                    conn.set_option(self.ldap.OPT_X_TLS_CACERTFILE, 
self._ldap_ssl_ca_file)
+                """create TLS context- this must be the last TLS setting"""
+                conn.set_option(self.ldap.OPT_X_TLS_NEWCTX, self.ldap.OPT_ON)
+
+                if self._ldap_security == "starttls":
+                    conn.start_tls_s()
+
             conn.simple_bind_s(self._ldap_reader_dn, self._ldap_secret)
             """Search for the dn of user to authenticate"""
             escaped_login = self.ldap.filter.escape_filter_chars(login)
@@ -219,16 +245,11 @@
                     for dn, entry in res:
                         groupDNs.append(dn)
 
-            """Close LDAP connection"""
-            conn.unbind()
         except Exception as e:
             raise RuntimeError(f"Invalid LDAP configuration:{e}")
 
         try:
             """Bind as user to authenticate"""
-            conn = self.ldap.initialize(self._ldap_uri)
-            conn.protocol_version = 3
-            conn.set_option(self.ldap.OPT_REFERRALS, 0)
             conn.simple_bind_s(user_dn, password)
             if self._ldap_user_attr:
                 if user_entry[1][self._ldap_user_attr]:
@@ -263,15 +284,15 @@
         """Connect the server"""
         try:
             logger.debug(f"_login3 {self._ldap_uri}, {self._ldap_reader_dn}")
-            if self._use_encryption:
+            if self._ldap_security in ("tls", "starttls"):
                 logger.debug("_login3 using encryption (reader)")
-                tls = self.ldap3.Tls(validate=self._ldap_ssl_verify_mode)
+                verifyMode = {"NONE": ssl.CERT_NONE,
+                              "OPTIONAL": ssl.CERT_OPTIONAL,
+                              "REQUIRED": ssl.CERT_REQUIRED}
+                tls = 
self.ldap3.Tls(validate=verifyMode[self._ldap_ssl_verify_mode])
                 if self._ldap_ssl_ca_file != "":
-                    tls = self.ldap3.Tls(
-                        validate=self._ldap_ssl_verify_mode,
-                        ca_certs_file=self._ldap_ssl_ca_file
-                        )
-                if self._ldap_use_ssl or self._ldap_security == "tls":
+                    tls = 
self.ldap3.Tls(validate=verifyMode[self._ldap_ssl_verify_mode], 
ca_certs_file=self._ldap_ssl_ca_file)
+                if self._ldap_security == "tls":
                     logger.debug("_login3 using ssl (reader)")
                     server = self.ldap3.Server(self._ldap_uri, use_ssl=True, 
tls=tls)
                 else:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Radicale-3.5.7/radicale/config.py 
new/Radicale-3.5.8/radicale/config.py
--- old/Radicale-3.5.7/radicale/config.py       2025-09-27 08:16:00.000000000 
+0200
+++ new/Radicale-3.5.8/radicale/config.py       2025-11-06 06:29:21.000000000 
+0100
@@ -430,6 +430,10 @@
             "value": "",
             "help": "command that is run after changes to storage",
             "type": str}),
+        ("strict_preconditions", {
+            "value": "False",
+            "help": "strict preconditions check on PUT",
+            "type": bool}),
         ("_filesystem_fsync", {
             "value": "True",
             "help": "sync all changes to filesystem during requests",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Radicale-3.5.7/radicale/tests/__init__.py 
new/Radicale-3.5.8/radicale/tests/__init__.py
--- old/Radicale-3.5.7/radicale/tests/__init__.py       2025-09-27 
08:16:00.000000000 +0200
+++ new/Radicale-3.5.8/radicale/tests/__init__.py       2025-11-06 
06:29:21.000000000 +0100
@@ -75,6 +75,10 @@
         if login is not None and not isinstance(login, str):
             raise TypeError("login argument must be %r, not %r" %
                             (str, type(login)))
+        http_if_match = kwargs.pop("http_if_match", None)
+        if http_if_match is not None and not isinstance(http_if_match, str):
+            raise TypeError("http_if_match argument must be %r, not %r" %
+                            (str, type(http_if_match)))
         environ: Dict[str, Any] = {k.upper(): v for k, v in kwargs.items()}
         for k, v in environ.items():
             if not isinstance(v, str):
@@ -84,6 +88,8 @@
         if login:
             environ["HTTP_AUTHORIZATION"] = "Basic " + base64.b64encode(
                     login.encode(encoding)).decode()
+        if http_if_match:
+            environ["HTTP_IF_MATCH"] = http_if_match
         environ["REQUEST_METHOD"] = method.upper()
         environ["PATH_INFO"] = path
         if data is not None:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Radicale-3.5.7/radicale/tests/test_base.py 
new/Radicale-3.5.8/radicale/tests/test_base.py
--- old/Radicale-3.5.7/radicale/tests/test_base.py      2025-09-27 
08:16:00.000000000 +0200
+++ new/Radicale-3.5.8/radicale/tests/test_base.py      2025-11-06 
06:29:21.000000000 +0100
@@ -229,6 +229,56 @@
         _, answer = self.get(path)
         assert "DTSTAMP:20130902T150159Z" in answer
 
+    def test_update_event_no_etag_strict_preconditions_true(self) -> None:
+        """Update an event without serving etag having strict_preconditions 
enabled (Precondition Failed)."""
+        self.configure({"storage": {"strict_preconditions": True}})
+        self.mkcalendar("/calendar.ics/")
+        event = get_file_content("event1.ics")
+        event_modified = get_file_content("event1_modified.ics")
+        path = "/calendar.ics/event1.ics"
+        self.put(path, event, check=201)
+        self.put(path, event_modified, check=412)
+
+    def test_update_event_with_etag_strict_preconditions_true(self) -> None:
+        """Update an event with serving equal etag having strict_preconditions 
enabled (OK)."""
+        self.configure({"storage": {"strict_preconditions": True}})
+        self.configure({"logging": {"response_content_on_debug": True}})
+        self.mkcalendar("/calendar.ics/")
+        event = get_file_content("event1.ics")
+        event_modified = get_file_content("event1_modified.ics")
+        path = "/calendar.ics/event1.ics"
+        self.put(path, event, check=201)
+        # get etag
+        _, responses = self.report("/calendar.ics/", """\
+<?xml version="1.0" encoding="utf-8" ?>
+<C:calendar-query xmlns:C="urn:ietf:params:xml:ns:caldav">
+    <D:prop xmlns:D="DAV:">
+        <D:getetag/>
+    </D:prop>
+</C:calendar-query>""")
+        assert len(responses) == 1
+        response = responses["/calendar.ics/event1.ics"]
+        assert not isinstance(response, int)
+        status, prop = response["D:getetag"]
+        assert status == 200 and prop.text
+        self.put(path, event_modified, check=204, http_if_match=prop.text)
+
+    def test_update_event_with_etag_mismatch(self) -> None:
+        """Update an event with serving mismatch etag (Precondition Failed)."""
+        self.mkcalendar("/calendar.ics/")
+        event = get_file_content("event1.ics")
+        event_modified = get_file_content("event1_modified.ics")
+        path = "/calendar.ics/event1.ics"
+        self.put(path, event, check=201)
+        self.put(path, event_modified, check=412, http_if_match="0000")
+
+    def test_add_event_with_etag(self) -> None:
+        """Add an event with serving etag (Precondition Failed)."""
+        self.mkcalendar("/calendar.ics/")
+        event = get_file_content("event1.ics")
+        path = "/calendar.ics/event1.ics"
+        self.put(path, event, check=412, http_if_match="0000")
+
     def test_update_event_uid_event(self) -> None:
         """Update an event with a different UID."""
         self.mkcalendar("/calendar.ics/")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Radicale-3.5.7/radicale/utils.py 
new/Radicale-3.5.8/radicale/utils.py
--- old/Radicale-3.5.7/radicale/utils.py        2025-09-27 08:16:00.000000000 
+0200
+++ new/Radicale-3.5.8/radicale/utils.py        2025-11-06 06:29:21.000000000 
+0100
@@ -47,8 +47,9 @@
                      Tuple[str, int, int, int]]
 
 
-# Max YEAR in datetime in unixtime
+# Max/Min YEAR in datetime in unixtime
 DATETIME_MAX_UNIXTIME: int = (datetime.MAXYEAR - 1970) * 365 * 24 * 60 * 60
+DATETIME_MIN_UNIXTIME: int = (datetime.MINYEAR - 1970) * 365 * 24 * 60 * 60
 
 
 def load_plugin(internal_types: Sequence[str], module_name: str,
@@ -279,12 +280,14 @@
     if sys.platform == "win32":
         # TODO check how to support this better
         return str(unixtime)
-    if unixtime < DATETIME_MAX_UNIXTIME:
+    if unixtime <= DATETIME_MIN_UNIXTIME:
+        r = str(unixtime) + "(<=MIN:" + str(DATETIME_MIN_UNIXTIME) + ")"
+    elif unixtime >= DATETIME_MAX_UNIXTIME:
+        r = str(unixtime) + "(>=MAX:" + str(DATETIME_MAX_UNIXTIME) + ")"
+    else:
         if sys.version_info < (3, 11):
             dt = datetime.datetime.utcfromtimestamp(unixtime)
         else:
             dt = datetime.datetime.fromtimestamp(unixtime, datetime.UTC)
         r = str(unixtime) + "(" + dt.strftime('%Y-%m-%dT%H:%M:%SZ') + ")"
-    else:
-        r = str(unixtime) + "(>MAX:" + str(DATETIME_MAX_UNIXTIME) + ")"
     return r
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Radicale-3.5.7/setup.py.legacy 
new/Radicale-3.5.8/setup.py.legacy
--- old/Radicale-3.5.7/setup.py.legacy  2025-09-27 08:16:00.000000000 +0200
+++ new/Radicale-3.5.8/setup.py.legacy  2025-11-06 06:29:21.000000000 +0100
@@ -20,7 +20,7 @@
 
 # When the version is updated, a new section in the CHANGELOG.md file must be
 # added too.
-VERSION = "3.5.7"
+VERSION = "3.5.8"
 
 with open("README.md", encoding="utf-8") as f:
     long_description = f.read()

Reply via email to