Hacktoberfest 2026:维护者为十月标记出来的 issue,仍然开放、适合新手。 浏览 Hacktoberfest issue

iCloud: URL.join raises "can't be joined" when REPORT hrefs use caldav.icloud.com after client.url was rewritten to the pNN partition host

已关闭
#730 2 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

已经有一个关联 PR 被合并了。

  • #733 来自 @tobixen —— 已合并

评估

难度
4/5
预计耗时
3-5 天
新手友好度
52/100
Issue 类型
缺陷
描述清晰度
基本清楚
活跃度
活跃
技术栈
python

调研方向

Start by reading caldav/lib/url.py around URL.join, caldav/davobject.py around DAVObject.__init__, and the iCloud handling in collection.py near calendar_home_set and _post_request_report_build_resultlist. Use the provided no-network reproduction to confirm the hostname mismatch, then add a regression test for an absolute iCloud href on the alternate host. Done means same-service iCloud URLs are handled without weakening rejection of unrelated host mismatches.

由索引模型根据 Issue 内容生成。

描述

Disclosure (per AI-POLICY.md): this bug was hit on my Home Assistant install. The diagnosis, the reproduction script and the suggested fix below were produced with Claude (Anthropic, via Claude Code); I've checked the results against my live setup. I'm happy to answer questions and test a fix.

What happens

With iCloud, searches on a calendar intermittently fail with:

ValueError: https://p49-caldav.icloud.com:443/12345678/calendars/ can't be joined with https://caldav.icloud.com/12345678/calendars/<calendar-uuid>/<event-uuid>.ics

Traceback (caldav 3.3.0a1, as pinned by Home Assistant 2026.10.0; the same code is on master at b8497e9):

  File "caldav/collection.py", line 1864, in search
  File "caldav/search.py", line 999, in search
  ...
  File "caldav/collection.py", line 1663, in _request_report_build_resultlist
  File "caldav/collection.py", line 1640, in _post_request_report_build_resultlist
  File "caldav/calendarobjectresource.py", line 171, in __init__
  File "caldav/davobject.py", line 108, in __init__
  File "caldav/lib/url.py", line 264, in join
    raise ValueError("%s can't be joined with %s" % (self, path))

Once it starts, every search fails until the client is rebuilt. In Home Assistant that means the calendar entity is unavailable until a restart. I saw two windows of about 1–2.5 hours on one day.

Why
  1. The client is created with url="https://caldav.icloud.com".
  2. calendar-home-set comes back on the account's partition host (https://p49-caldav.icloud.com:443/12345678/calendars/). The setter in Principal.calendar_home_set (the "Here be dragons" block in collection.py) then rewrites client.url to that host.
  3. iCloud does not always use the same host in its responses. When I query the generic host, every href in a REPORT response is absolute on caldav.icloud.com (36 of 36 in one test). When the client talks to the partition host, the hrefs normally match it. Sometimes, though, a response to the partition host contains hrefs on caldav.icloud.com.
  4. DAVObject.__init__ does client.url.join(url), and URL.join raises whenever the hostnames differ.

The two hosts serve the same account, so the href is valid. It just names the other front end.

Minimal reproduction (no network)
from caldav import DAVClient
from caldav.calendarobjectresource import Event

client = DAVClient(url="https://p49-caldav.icloud.com/12345678/calendars/", username="u", password="p")
Event(client, url="https://caldav.icloud.com/12345678/calendars/cal/event.ics")
# ValueError: https://p49-caldav.icloud.com/12345678/calendars/ can't be joined with https://caldav.icloud.com/12345678/calendars/cal/event.ics

The same thing happens with a Calendar whose URL is on the generic host.

Suggested fix

When an href is absolute but on a different host, and both hosts are the same service (for iCloud: both end in .icloud.com, same scheme), join by path onto the client's host instead of raising. Requests then only ever go to the host the client already uses, so no request (and no credential) goes to a new host. Every other host mismatch still raises, which keeps the protection join gives today.

As a local workaround I'm wrapping URL.join like this:

_original_join = URL.join

def _join(self, path):
    try:
        return _original_join(self, path)
    except ValueError:
        other = URL.objectify(path)
        if other is None:
            raise
        hosts = (self.hostname or "", other.hostname or "")
        if not all(h.endswith(".icloud.com") for h in hosts) \
                or (self.scheme or "https") != (other.scheme or "https"):
            raise
        return _original_join(self, other.path + (f"?{other.query}" if other.query else ""))

A cleaner upstream fix might belong where the "icloud hack" already lives in _post_request_report_build_resultlist, or in the calendar_home_set setter. One option is to remember the original host as an alias of the rewritten one, so hrefs on either host are accepted. I'm happy to turn this into a PR with a test if you tell me which approach you prefer.

Environment
  • caldav 3.3.0a1 (Home Assistant 2026.10.0, Python 3.14); same URL.join and DAVObject.__init__ logic on master b8497e9
  • Server: iCloud (caldav.icloud.com → partition p49-caldav.icloud.com)
主要语言
Python
星标
412
派生
113
平均合并
1 天 14 小时
30 天内合并 PR
26

环境准备

  • 没有 Dockerfile 或 Docker Compose 文件
  • 没有 Pull Request 模板
  • 阅读贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

python-caldav/caldav 的其他 Issue

查看 python-caldav/caldav 的全部 Issue

相似的 Issue

更多 Python Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。