Skip to content

fix: repeat a redirected PUT with its body - #837

Merged
tobixen merged 4 commits into
masterfrom
fix/redirected-put
Oct 29, 2026
Merged

tobixen merged 4 commits into
masterfrom
fix/redirected-put

Conversation

@tobixen

@tobixen tobixen commented Oct 28, 2026

Copy link
Copy Markdown
Member

Disclaimer: the code and the text below the line are AI-generated, by Claude Opus 5.5, from the prompts:

Look into sed -n '375,405p' [docs/TODO.md in caldav-server-tester]. There is something fishy there. We do have an external zimbra server which tests can be run towards - work-tests
Fix the probe, fix the caldav bug ([the caldav library], we'll do it directly to master this time), what should be unsupported you said? How to deal with the wrong port issue? We don't want a broken docker setup pollute our zimbra gradings.
Create a PR for the work here. Use worktrees, because there are other sessions active right now. There is a known problem with lychee blocking pushes from workbranches, I can manually push with --no-verify if needed.


When Zimbra gets a PUT under any name other than <uid>.ics, it answers 302 with a Location pointing at <uid>.ics, and it stores nothing. niquests/requests follow a 302 by sending a GET without the body, which returns 404. The data was lost and put() raised no error. With httpx, save() even reported success and moved the object's url to the Location.

This PR changes how redirects are handled for requests other than GET and HEAD:

  • A 301, 302, 307 or 308 answer is repeated at the Location with the same method and body. This only happens if the Location is on the same host, port and scheme, or is an upgrade from http to https. Otherwise the credentials would go to a host they were not meant for.
  • save() and calendar creation move the object's url to where the request ended up.
  • A 303 answer to a PUT counts as success, with the object at the Location.
  • A redirect that is not followed makes save() raise PutError instead of reporting success.
    • One case of this: the Zimbra docker image redirects to its internal port 8443.
    • This also changes behaviour for other methods. For example, a PROPFIND redirected to another host is no longer followed. The changelog fragment says so.

The Zimbra profile gets forces_uid_ics: True on save-load.stable-url. Its comment no longer claims that Zimbra keeps the name an object was stored under.

Tested against a production Zimbra server and the docker image. caldav-server-tester grades both servers the same way: save-load.stable-url: unsupported, forces_uid_ics: true. The matching probe change in caldav-server-tester will follow in a separate PR.

Comment thread tests/test_caldav_unit.py Fixed
Comment thread tests/test_caldav_unit.py Fixed
Comment thread tests/test_caldav_unit.py Fixed
niquests and requests follow a 302 by sending a GET without the body,
and httpx follows nothing.  Zimbra redirects a PUT under a name other
than <uid>.ics to <uid>.ics and stores nothing, so the data was lost
without an error.  A request other than GET/HEAD answered 301, 302,
307 or 308 is now repeated at the Location with method and body, on
the same host, port and scheme or an http-to-https upgrade.  save()
and calendar creation move the url there; a 303 to a PUT counts as
stored at the Location; save() raises PutError on a redirect not
followed.

Prompt: Look into [zimbra claims in docs/TODO.md in caldav-server-tester]`.  There is something fishy there.  We do have an external zimbra server which tests can be run towards - work-tests
Followup-Prompt: Fix the probe, fix the caldav bug ([the caldav library], we'll do it directly to master this time), what should be unsupported you said?  How to deal with the wrong port issue?  We don't want a broken docker setup pollute our zimbra gradings.
Prompts (comments made inline in the document while reviewing it):
> (on "A 303 answer to a PUT now raises PutError") A 303 probably indicates that the URL of the object has changed?  E.g. `my_unicorn_calendar/my_unicorn_ical.ics` -> `calendar-04f2c005-d51d-405d-a148-d9e531d0c16c/12345678-1234-5678-9abc-12345678.ics

Assisted-By: Claude Opus 5.5
Reviewed-by: Tobias Brox <tobias@redpill-linpro.com>
The Zimbra profile said Zimbra keeps the name an object was stored
under.  It does for <uid>.ics only: a PUT under another name is
redirected to <uid>.ics, so save-load.stable-url gets forces_uid_ics,
and the feature description names Zimbra as such a server.  Measured
with caldav-server-tester against a production Zimbra and the docker
image, which now agree.

Prompt: Look into `sed -n '375,405p' [docs/TODO.md in caldav-server-tester]`.  There is something fishy there.  We do have an external zimbra server which tests can be run towards - work-tests
Followup-Prompt: Fix the probe, fix the caldav bug ([the caldav library], we'll do it directly to master this time), what should be unsupported you said?  How to deal with the wrong port issue?  We don't want a broken docker setup pollute our zimbra gradings.
Prompts (comments made inline in the document while reviewing it):
> (on "Zimbra hint and the stable-url description disagree") This sounds like a doc fix?  For changing the grades, it's needed to run the caldav-server-tester

Assisted-By: Claude Opus 5.5
Reviewed-by: Tobias Brox <tobias@redpill-linpro.com>
@tobixen
tobixen force-pushed the fix/redirected-put branch from 18d9a88 to c21fb09 Compare October 29, 2026 01:32
Code scanning flagged `calls` as unused in three of the new redirect
tests.  Rather than discard it, assert on it: a followed redirect makes
two requests, a refused one only the first, a 307-308 chain three.

Prompt: please fix and rebase [the failing test and the three
unused-variable alerts on #837]

Assisted-By: Claude Opus 5.5
Reviewed-by: Tobias Brox <tobias@redpill-linpro.com>
An object or calendar that moved to a redirect's Location got a bare
URL, dropping the alias_at and host_aliases its old URL carried, so
later joins and @-quoting on it could behave differently.  Relocate the
old URL instead, for 301/302/307/308 on PUT and MKCALENDAR and for a
303 to a PUT; on a redirect onto a host alias, the old host becomes the
alias.

Prompt: [fix issues found during the review process]

Assisted-By: Claude Opus 5.5
Reviewed-by: Tobias Brox <tobias@redpill-linpro.com>
Comment thread tests/test_caldav_unit.py
``host_aliases``, as every URL derived from its old one does."""
base = "https://cal.example.com/cal/"
for status in (302, 303):
mocked.side_effect, calls = self._routes(
@tobixen
tobixen merged commit 7abf0da into master Oct 29, 2026
17 checks passed
@tobixen
tobixen deleted the fix/redirected-put branch October 29, 2026 08:43
tobixen added a commit to python-caldav/caldav-server-tester that referenced this pull request Oct 30, 2026
caldav 3.7.0 has forces_uid_ics and the repeated PUT for Zimbra
(python-caldav/caldav#837), so the separate
note on them goes.  With the PRs merged side by side, a bullet saying
"the same" no longer follows the entry it meant, and the
calendaring-sync credit read as covering every Added entry.

Prompt: Caldav 3.7.0 is released, so we should try to merge all the pull requests.  Prepare a "train".

Assisted-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewed-by: Tobias Brox <tobias@redpill-linpro.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant