Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
0e319c4
fix(jmap): correctness fixes + dedup from June 2026 review
tobixen Jun 19, 2026
a33fba1
chore: server compatibility matrix
tobixen Jun 15, 2026
604185c
test: server-compatibility test suite
tobixen Jun 16, 2026
315cf97
fix: compatibility: comp-type-less search
tobixen Jun 16, 2026
2f25ae7
docs(tests): correcting some misinformation
tobixen Jun 4, 2026
68bba72
fix: compatibility: set calendar canonical URL on create
tobixen Jun 16, 2026
5405115
fix: config-section handling for get_davclient
tobixen Jun 10, 2026
a738822
fix: some icalendar handling was incorrect
tobixen Jun 11, 2026
e3ed5e8
fix: eight crash/wrong-result bugs from code review
tobixen Jun 11, 2026
9355f68
fix: URL.canonical() __eq__-problems
tobixen Jun 11, 2026
81ae88e
fix: search.py code-review bugs
tobixen Jun 11, 2026
a0e2eea
fix: Several silent wrong-result bugs
tobixen Jun 11, 2026
a3567b7
fix: require_tls=True not enforced on well-known URI redirect
tobixen Jun 11, 2026
304f97d
fix: XML parser in response.py lacked entity hardening
tobixen Jun 11, 2026
575e084
fix: three more crash bugs from code review
tobixen Jun 11, 2026
4a027f0
fix: let PYTHON_CALDAV_COMMDUMP yield warnings
tobixen Jun 18, 2026
f843c0f
fix: code-review edge-case bugs (§1.2–1.5, §2.14, §2.15)
tobixen Jun 11, 2026
8d5399b
docs: SECURITY notes + code review cleanup
tobixen Jun 13, 2026
d957f8b
fix: replace empty except-blocks with warn/skip logic
tobixen Jun 14, 2026
0fbeeab
perf: use calendar-multiget on unloaded search results
tobixen Jun 14, 2026
bbd3109
refactor: unify search.py sync/async driver protocol
tobixen Jun 14, 2026
6b6aa11
refactor: dedup rate-limit/get_calendars logic between sync/async
tobixen Jun 14, 2026
7901e3d
refactor: dedup calendarobjectresource (a)sync twins
tobixen Jun 14, 2026
3ace848
refactor: dedup sync/async multistatus/multiget twins (§5.7)
tobixen Jun 14, 2026
b91ca1f
refactor: collapse sync/async get_objects_by_sync_token twins
tobixen Jun 14, 2026
e663abe
refactor: accept generic <error>/<responsedescription>
tobixen Jun 14, 2026
bd2d587
docs: CHANGELOG for v3.3.0, and rewrite the http-libraries background
tobixen Jun 15, 2026
b303a30
fix: async Principal.calendar() did not work
tobixen Jun 16, 2026
1521c8b
ci: bump pre-commit-hook versions to current latest
tobixen Jun 28, 2026
ee6ac1b
docs: www.open-xchange.com 301 -> ox.io
tobixen Jul 26, 2026
51e8bac
ci: let the link checker reuse and close a single report issue
tobixen Jul 26, 2026
751e406
ci: add pip-audit dependency audit as a tox env and a scheduled workflow
tobixen Aug 11, 2026
ef8eba0
chore: clear out two leftovers from the move off setuptools
tobixen Aug 11, 2026
fc963e1
ci: make the tox envlist take effect, and drop its bogus y39 entry
tobixen Aug 11, 2026
68530fc
test(nextcloud): maintenance of test server infrastructure
tobixen Aug 16, 2026
8458978
fix: A 207 Multi-Status containing only 404 is equivalent with 404
tobixen Aug 16, 2026
04b5b3b
build: keep local junk out of the sdist, and check it in CI
tobixen Aug 16, 2026
4827868
test: make `enabled: false` actually disable a docker test server
tobixen Aug 16, 2026
34e4ec6
docs: CHANGELOG accuracy fixes for v3.3.0
tobixen Aug 16, 2026
91e4894
docs(tests): write down why testCheckCompatibility is not in CI
tobixen Aug 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .github/workflows/audit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
name: audit

# Dependency vulnerability audit (pip-audit, see the `audit` env in tox.ini).
#
# This is deliberately *not* wired into the `tests` workflow: a new advisory
# can be published against an unchanged dependency tree, so the audit is
# time-triggered rather than change-triggered. The pull_request trigger is
# narrowed to the files that can change the dependency tree.
on:
pull_request:
paths:
- pyproject.toml
- tox.ini
- .github/workflows/audit.yml
workflow_dispatch:
schedule:
# Mondays 04:17 UTC, well clear of the nightly link check (22:03).
- cron: "17 4 * * 1"

concurrency:
group: audit-${{ github.ref }}
cancel-in-progress: false

jobs:
pip-audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
# hatch-vcs derives the version from git tags; without them the
# project metadata pip-audit reads cannot be built.
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: pip|${{ hashFiles('pyproject.toml') }}|${{ hashFiles('tox.ini') }}
- run: pip install tox
- name: Audit dependencies for known vulnerabilities
run: tox -e audit
Comment on lines +27 to +43
54 changes: 45 additions & 9 deletions .github/workflows/linkcheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,65 @@ on:
schedule:
- cron: "03 22 * * *"

concurrency:
group: linkcheck-${{ github.ref }}
cancel-in-progress: false

jobs:
linkcheck:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- uses: actions/checkout@v5
- name: Restore lychee cache
uses: actions/cache@v4
with:
path: .lycheecache
key: cache-lychee-${{ github.run_id }}
restore-keys: cache-lychee-
- name: Check links with Lychee
id: lychee
uses: lycheeverse/lychee-action@v2
with:
fail: false
args: >-
--root-dir "$(pwd)"
--timeout 20
--max-retries 3
--timeout 30
--max-retries 6
--retry-wait-time 2
--cache
--max-cache-age 14d
.
- name: Create Issue From File
if: steps.lychee.outputs.exit_code != 0
uses: peter-evans/create-issue-from-file@v5
with:
title: Link Checker Report
content-filepath: ./lychee/out.md
labels: report, automated issue
# The exit_code comparisons are quoted on purpose. A missing output is the
# empty string, and GitHub coerces '' to 0 when comparing against a number -
# so an unquoted `== 0` would treat "lychee did not run" as "all links are
# healthy" and close every open report. Comparing two strings does no
# coercion.
- name: Create or update Link Checker issue
if: steps.lychee.outputs.exit_code != '' && steps.lychee.outputs.exit_code != '0' && github.ref == 'refs/heads/master'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Keep the oldest open report issue as canonical, fold duplicates in.
# Both labels are required: `gh issue list` ANDs them, so a
# human-written issue that merely carries "report" cannot become the
# canonical one and have its body overwritten by lychee output.
ISSUES=$(gh issue list --label "report" --label "automated issue" --state open --json number --jq '.[].number' | sort -n)
CANON=$(printf '%s\n' "$ISSUES" | head -1)
for n in $(printf '%s\n' "$ISSUES" | tail -n +2); do
gh issue close "$n" --comment "Duplicate of #${CANON} - auto-closed by the link checker."
done
if [ -n "$CANON" ]; then
gh issue edit "$CANON" --body-file ./lychee/out.md
else
gh issue create --title "Link Checker Report" --body-file ./lychee/out.md --label "report" --label "automated issue"
fi
- name: Close Link Checker issue if all links are healthy
if: steps.lychee.outputs.exit_code == '0' && github.ref == 'refs/heads/master'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
for n in $(gh issue list --label "report" --label "automated issue" --state open --json number --jq '.[].number'); do
gh issue close "$n" --comment "All links are now healthy."
done
57 changes: 57 additions & 0 deletions .github/workflows/package.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
name: package

# Builds the release artifacts and verifies their contents.
#
# Nothing in CI used to build an sdist at all, so what actually went into a
# release was only ever discovered after it was published: caldav-3.2.1.tar.gz
# shipped .claude/settings.json and 1755 files under venv/. The check runs on
# every change to the packaging configuration, and nightly, so a stray file in
# a contributor's tree cannot ride along into a tarball unnoticed.
#
# See tests/tools/check_dist.py for what is verified.
on:
push:
branches:
- master
pull_request:
paths:
- pyproject.toml
- tox.ini
- MANIFEST.in
- .gitignore
- tests/tools/check_dist.py
- .github/workflows/package.yml
workflow_dispatch:
schedule:
# Sundays 05:23 UTC, clear of the Monday audit (04:17) and the nightly
# link check (22:03).
- cron: "23 5 * * 0"

concurrency:
group: package-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
# hatch-vcs derives the version from git tags.
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: pip|${{ hashFiles('pyproject.toml') }}|${{ hashFiles('tox.ini') }}
- run: pip install tox
- name: Build sdist and wheel, and check what is in them
run: tox -e package
- uses: actions/upload-artifact@v4
with:
name: dist
path: .tox/package/tmp/dist/*
if-no-files-found: error
Comment on lines +37 to +57
37 changes: 26 additions & 11 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ jobs:
- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: pip|${{ hashFiles('setup.py') }}|${{ hashFiles('tox.ini') }}
key: pip|${{ hashFiles('pyproject.toml') }}|${{ hashFiles('tox.ini') }}
- run: pip install tox
- name: Configure Baikal with pre-seeded database
run: |
Expand Down Expand Up @@ -183,13 +183,28 @@ jobs:
docker exec ${{ job.services.nextcloud.id }} php occ config:system:set ratelimit.whitelist.0 --value='172.17.0.0/16' || true
docker exec ${{ job.services.nextcloud.id }} php occ config:system:set ratelimit.whitelist.1 --value='127.0.0.1' || true

# Clear rate limit cache
docker exec ${{ job.services.nextcloud.id }} php -r "
\$db = new PDO('sqlite:/var/www/html/data/nextcloud.db');
\$db->exec('DELETE FROM oc_ratelimit_entries');
\$db->exec('DELETE FROM oc_bruteforce_attempts');
echo 'Cleared rate limit and bruteforce caches\n';
" || true
# Clear rate limit cache. The SQLite file is named after the `dbname`
# config value, which defaults to `owncloud` — look it up rather than
# guessing, and check it exists first: PDO creates a missing SQLite
# file, so a wrong path silently yields "no such table" for every
# DELETE below.
DB_NAME=$(docker exec ${{ job.services.nextcloud.id }} php occ config:system:get dbname 2>/dev/null | tr -d '\r\n')
DB_PATH="/var/www/html/data/${DB_NAME:-owncloud}.db"
if docker exec ${{ job.services.nextcloud.id }} test -f "$DB_PATH"; then
docker exec ${{ job.services.nextcloud.id }} php -r "
\$db = new PDO('sqlite:$DB_PATH');
foreach (['oc_ratelimit_entries', 'oc_bruteforce_attempts'] as \$table) {
try {
\$db->exec(\"DELETE FROM \$table\");
} catch (PDOException \$e) {
fwrite(STDERR, \"skipping \$table: \" . \$e->getMessage() . \"\n\");
}
}
echo \"Cleared rate limit and bruteforce caches\n\";
" || true
else
echo "No database found at $DB_PATH — skipping cache cleanup"
fi

echo "Nextcloud is configured!"
- name: Configure Cyrus
Expand Down Expand Up @@ -326,7 +341,7 @@ jobs:
- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: pip|${{ hashFiles('setup.py') }}|${{ hashFiles('tox.ini') }}
key: pip|${{ hashFiles('pyproject.toml') }}|${{ hashFiles('tox.ini') }}
- run: pip install tox
- run: tox -e docs
style:
Expand All @@ -339,7 +354,7 @@ jobs:
- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: pip|${{ hashFiles('setup.py') }}|${{ hashFiles('tox.ini') }}
key: pip|${{ hashFiles('pyproject.toml') }}|${{ hashFiles('tox.ini') }}
- uses: actions/cache@v4
with:
path: ~/.cache/pre-commit
Expand All @@ -356,7 +371,7 @@ jobs:
- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: pip|${{ hashFiles('setup.py') }}|${{ hashFiles('tox.ini') }}
key: pip|${{ hashFiles('pyproject.toml') }}|${{ hashFiles('tox.ini') }}
- run: pip install tox
- run: tox -e deptry
# The three async-* jobs below exist to test the async backend *selection* logic,
Expand Down
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,23 @@ accountific.db
tests/.noseids
*.bak
*~
#*#
# Emacs auto-save files. The backslashes are required: a bare "#" starts a
# comment, so the pattern was silently a no-op.
\#*\#
caldav.egg-info/
tests/conf_private.py
.tox
.eggs
.venv
venv
caldav/_version.py
tests/docker-test-servers/baikal/baikal-backup/
tests/docker-test-servers/*/baikal-backup/
# But keep the pre-configured Specific directory for Baikal
!tests/docker-test-servers/baikal/Specific/
# Local test server configuration (may contain credentials)
tests/caldav_test_servers.yaml
# Lychee link checker cache
.lycheecache
# Scratch files from AI sessions (review notes, draft commit messages)
docs/design/tmp-*
6 changes: 6 additions & 0 deletions .lycheeignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
https?://your\.server\.example\.com/.*
https?://.*\.example\.com(:\d+)?(/.*)?$
https?://domain/.*
https?://evil.attacker.com/caldav/

# Localhost URLs for test servers (not accessible in CI)
http://localhost:\d+/.*
Expand All @@ -17,6 +18,7 @@ https://caldav\.gmx\.net/.*
https://caldav\.icloud\.com/.*
https://p\d+-caldav\.icloud\.com/.*
https://posteo\.de:\d+/.*
https://sync\.infomaniak\.com/.*
https://purelymail\.com/.*
https://webmail\.all-inkl\.com/.*
https://www\.google\.com/calendar/dav/.*
Expand All @@ -36,6 +38,10 @@ https://oauth2\.googleapis\.com/.*
# Personal/demo test server (may be down)
https?://davical\.bekkenstenveien53c\.oslo\.no/.*

# Sites that serve 403 to non-browser clients. The links are fine in a
# browser; lychee just isn't one.
https://stackoverflow\.com/.*

# Dead or broken links we can't fix
http://fsf\.org/.*
http://oxpedia\.org/.*
Expand Down
14 changes: 7 additions & 7 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
---
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.9.4
rev: v0.15.20
hooks:
- id: ruff
- id: ruff-check
args: [--fix]
- id: ruff-format

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
rev: v6.0.0
hooks:
- id: check-byte-order-marker
- id: fix-byte-order-marker
- id: trailing-whitespace
- id: end-of-file-fixer

- repo: https://github.com/pycalendar/ai-prompt-auto-commit
rev: v0.0.5
rev: v0.0.8
hooks:
- id: unstage-ai-prompts
- id: append-ai-prompts
Expand All @@ -26,13 +26,13 @@ repos:
stages: [manual]

- repo: https://github.com/compilerla/conventional-pre-commit
rev: v3.4.0
rev: v4.4.0
hooks:
- id: conventional-pre-commit
stages: [commit-msg]

- repo: https://github.com/lycheeverse/lychee
rev: lychee-v0.24.1
rev: lychee-v0.24.2
hooks:
- id: lychee
args: ["--no-progress", "--timeout", "10", "--exclude-path", ".lycheeignore", "--max-cache-age=30d", "--cache"]
Expand Down
Loading
Loading