ffde47d4767700b991cf0520fd3987cd35b32195
用户 2026-10-13 再次反馈「还是不行,仍然提示找不到日历」。
按用户要求,在线上装了临时请求日志器(记录 TB 发来的每个请求与我们的每条响应)。
但真正的突破口是**读 Thunderbird 自己的源码**:TB 就在本机,
解开 C:/Program Files\Mozilla Thunderbird\omni.ja,拿到 modules/caldav/*.sys.mjs。
TB 157 的 PROPFIND 解析器(modules/caldav/CalDavRequest.sys.mjs,
PropfindResponse.get data)逐字是:
for (const propStat of response.querySelectorAll(":scope > propstat")) {
const status = propStat.querySelector(":scope > status").textContent; // ← 无 null 保护
...
}
我们的每个 <d:propstat> **都没有 <d:status> 子节点**(RFC 4918 要求有),
于是 querySelector 返回 null → null.textContent 抛 TypeError →
detectCollection() 整条发现链中断 → 客户端报「找不到日历 / 联系人也没有」。
**CalDAV 与 CardDAV 同型**,所以用户看到的「日历和联系人都没有」是同一个 bug。
为什么历史验证全都没抓到(三层叠加):
1. 老 DAV 探针只做 XML 正则匹配,从不校验 RFC 结构;
2. curl 不会因为缺 status 报错 —— 响应看着完全正常(207 + 属性齐全);
3. 只有「把客户端的解析器真跑一遍」才现形。
改动:
· dav.php 12 处 + carddav.php 12 处
`</d:prop></d:propstat>` → `</d:prop><d:status>HTTP/1.1 200 OK</d:status></d:propstat>`
验证(含新增的永久守护):
· 新增 `_tb_parse_repro.js`:用 jsdom 逐字复刻 TB 那段解析逻辑喂真实响应。
修复前:线上 10/10 个 propstat 全部 TypeError;修复后:0 个异常。
· 新增 `_tb_detect_sim.js`:完整复刻 TB 的发现算法(detectCollection /
handlePrincipal / handleHomeSet 三函数照抄)并真发请求。
修复前线上三条路径全部抛异常 ⇒ 0 本日历(与用户症状一致);
修复后线上 **发现 8 本日历**,CardDAV 1 个通讯录。
· `_fb_dav_tb.py` 新增 ⑨「TB 解析器结构守护」5 条断言(纯 Python,不依赖 Node)
→ 探针 39 → 44 项,全绿。
· `_b45_dav_falsify.py` 新增 2 个注入(dav/carddav 各去掉 status)证明断言会红
→ 反向验证 7 → 9 项,全绿。
· `familybook_deploy.sh` 新增静态断言(`</d:prop></d:propstat>` 出现次数必须为 0)。
· 本地 e2e 1510/0;TB 探针 44/0;部署脚本跑通。
⚠️ 顺带修一个反向验证自身的坑:`patch()` 原来只替换第一处出现,
而 `</d:prop><d:status>…</d:propstat>` 在文件里有 12 处、第一处在 /.well-known 分支,
导致注入没落到被测路径上 ⇒ 断言假绿。已改为替换全部出现。
FamilyBook
家书,记录家庭的全部生活,一本用岁月写成的家谱。
把一个家的「人」「日子」「去过的地方、看过的电影」收进同一本书里。
它是什么
一个自托管的家庭生活系统。它不是又一个待办清单,而是这个家的长期档案 —— 十年之后还能翻出孩子第一次观影的票根、那年暑假走过的路线。
围绕三个圈展开:
- 人:家庭成员、亲友通讯录,以及他们之间的共享关系
- 时间:日历、日程、提醒,以及任务的期限
- 事件:观影、旅行、其他活动 —— 一切都落在同一条时间轴上
它在做什么
| 阶段 | 主题 | 内容 |
|---|---|---|
| 0 | 地基 | 名册、账号、登录、权限模型、安装向导 |
| 1 | 通信 | 通讯录、共享给某个成员、CardDAV 服务端 |
| 2 | 时间 | 日历本、重复日程、提醒、CalDAV 服务端 |
| 3 | 归一 | 观影与旅行迁入统一事件模型 |
| 4 | 任务 | 任务管理,期限自动映射成日历事件 |
| 5 | 回看 | 统计、年度家书、时间轴 |
当前进度:阶段 0 起步,仓库里只有图标与脚手架。
技术选型
- PHP 7.4+(无框架、无 Composer,上传到站点目录即可运行)
- MySQL 5.7+ / 8.0(
utf8mb4) - 前端为原生 HTML / CSS / JavaScript,无构建步骤
⚠️ 注意 CalDAV / CardDAV 是双向协议,和单纯的 .ics 文件导出不是一个量级的东西。
部署
- 把代码放到站点根目录;
- 复制
config.example.php为config.php,填写数据库与app_key; - 访问站点根,按引导页确认 PHP 版本、扩展、配置、数据库连接四项都通过。
config.php 不入库(已在 .gitignore 中忽略),其中包含数据库口令与加密密钥。
⚠️ app_key 一旦开始记录数据就不能再改(换掉即解不开已有数据),
并且它必须单独备份 —— 密钥丢失等同于数据永久丢失。
隐私
身份证号、手机号属于敏感个人信息,一律加密存储。 (就家庭自用而言,身份证号其实没有多少必须存的场景,启用前请三思。)
图标
见 assets/icons/。概念是「这本书就是家」:屋顶即一本摊开的书,
屋顶下纸做的墙上,三行横线是这个家写下的日子。
开源协议
Languages
PHP
81.6%
JavaScript
9.3%
CSS
9.1%