Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ Changelogs prior to v3.0 are pruned, but are available in the v3.1 release

This project should adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html), though for pre-releases PEP 440 takes precedence.

## Unreleased

### Added

* `compatibility_hints`: `auth.www-authenticate` records whether the server sends the `WWW-Authenticate` header RFC7235 section 3.1 requires on a 401, and `auth.www-authenticate.usable-scheme` whether the schemes it offers include one this library implements. A server failing either one never receives your password, and the 401 looks like a rejected one - so it needs `auth_type` pinned in the configuration, and a profile can now say which. Probed by caldav-server-tester. See https://github.com/python-caldav/caldav/issues/713.

## [3.3.1] - 2026-09-16

The two main things in this release:
Expand Down
21 changes: 21 additions & 0 deletions caldav/compatibility_hints.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,27 @@ class FeatureSet:
),
"default": {"support": "full"},
},
"auth": {
## Grouping node, needed so that the dotted children below resolve;
## it carries no verdict of its own. Note that it does not derive
## one from those children either: _derive_from_subfeatures skips
## every child that has an explicit default, which both of them do,
## so is_supported("auth") answers from the type default whatever
## the children say. Five existing nodes have the same shape.
},
"auth.www-authenticate": {
"description": "Server includes a WWW-Authenticate header in a 401 response, as RFC7235 section 3.1 requires. Without it a client has no scheme to negotiate with: this library builds no auth object, the supplied credentials are never transmitted, and the bare 401 surfaces as an AuthorizationError indistinguishable from a rejected password. 'unsupported' means a 401 came back carrying no WWW-Authenticate at all (Yahoo Calendar); the cure is to pin auth_type so the credentials go out unprompted - which scheme to pin is not something such a server tells you. 'unknown' means no 401 could be provoked, so the question was never put. The behaviour field carries the challenge the server sent, where it sent one.",
"default": {"support": "full"},
"links": [
"https://datatracker.ietf.org/doc/html/rfc7235#section-3.1",
"https://github.com/python-caldav/caldav/issues/713",
],
},
"auth.www-authenticate.usable-scheme": {
"description": "At least one authentication scheme the server offers in its WWW-Authenticate header is one this library implements (basic, digest or bearer - see _build_auth_from_401). A server naming only Negotiate or NTLM sends a conformant challenge and is still unreachable by negotiation: the library raises NotImplementedError instead, which is the same dead end as a missing header one step later. 'unknown' means no challenge was seen to judge - either no 401 came back, or the 401 carried no header at all (see the parent feature). The behaviour field lists the schemes offered. This is a property of the server, not of the run: it does not take into account which credentials happen to be configured.",
"default": {"support": "full"},
"links": ["https://datatracker.ietf.org/doc/html/rfc7235#section-2.1"],
},
"well-known": {
"description": "Server handles /.well-known/caldav discovery as specified in RFC 6764 section 5. A conformant server should respond with a redirect (301/302/307/308) from /.well-known/caldav to the actual CalDAV endpoint. 'full' means a redirect was observed; 'unsupported' means the server returned 404 or similar; 'unknown' means the check was skipped (e.g. localhost or request failed). Note: well-known is often provided by infrastructure (reverse proxy/hosting) rather than the CalDAV server itself, so 'unknown' is the expected default for self-hosted or test setups.",
"default": {"support": "unknown"},
Expand Down
24 changes: 24 additions & 0 deletions docs/design/TODO_COMPATIBILITY_HINTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,30 @@ Possibly into three files (or four, with the original compatibility_hints.py bei

There are three quite different things in the file now, the database of the feature names/flags, the database of server compatibility, and the match logic.

## 8. A grouping node cannot derive from a child that has a default

**Location**: `FeatureSet._derive_from_subfeatures`, the `continue` on
`'default' in subfeature_info`

A parent *with* its own default rightly ignores its children: it is an
independent capability (`create-calendar` is supported even where
`create-calendar.set-displayname` is not). But the skip is applied from the
other end too — a parent *without* a default, which exists only to group, still
skips every child that has one, and a child having a default is precisely what
marks it as independently probed. So a grouping node whose children are all
real, probed features derives nothing, falls through to `_default()`, and
answers `full` whatever its children say.

Six nodes have that shape today: `http`, `non-existing-raises-not-found`,
`save-load.event.recurrences.exception`, `save-load.icalendar`,
`url.encode-at` and `auth`. `is_supported("non-existing-raises-not-found")`
returns `full` on a server where both children are `unsupported`.

**Fix**: skip a child with its own default only when the *parent* has one too.
A grouping node should derive from all its children, real ones included. The
existing OR-semantics note on `principal-search` suggests the derivation rules
want a second look at the same time.

## Ordering / dependencies

Items 3 (old_flags) and 6 (rename) are independent and safe to do first. Items 1, 2,
Expand Down
Loading