v42 第 42 批:十一条反馈(界面 9 条 + CalDAV 两处新根因)
CalDAV(用户「还是不行,thunderbird 提示找不到日历」): - 🔴 `.well-known/caldav` 原来**只回 301**。Thunderbird 建账号时常是 **带鉴权直接 PROPFIND** 这个地址(把它当 DAV 服务入口探),而它对这一跳的 301 **不跟随** ⇒ 判定"这里没有 CalDAV" ⇒ 找不到日历。 ⇒ 按方法分流:带鉴权 PROPFIND → 直接 207 给发现内容(principal / home-set / 日历本清单,一步到位);其余仍是 301。carddav 同修。 - 🔴 `Location` 由**相对** `/dav/` 改成**绝对 URL**(按真实 HTTP_HOST 拼, 协议从 HTTPS / X-Forwarded-Proto 取)。TB 在服务发现这一跳对相对跳转不可靠。 - 线上终验:匿名 GET 301+绝对URL;带鉴权 PROPFIND 直接 207(含 principal/home-set); Depth:0 只回自身;Depth:1 列出 12 本日历。 界面九条: - ① 新建旅程不再带入旧数据(真根因:`_list_body.php` 的 `foreach ($rows as $t)` 循环后 `$t` 残留,而 `form.php` 读的正是 `$t` ⇒ 弹框被填上列表最后一趟的数据)。 修:循环变量改 `$row` + `form.php` 契约兼容(`$tIn` 优先)。 - ②③④ 行程弹框里的 AntD 时间控件 / 出行方式联动车牌与车次 / 高德地址联想全部失效 (真根因:AJAX 片段里的内联 `<script>` **既不执行也不保留**)。 ⇒ 加 `runInlineScripts()` 把片段内联脚本抠出来执行; 另修 `app.js`:`if (!hasMonth) return` 的短路让 `window.fbBootAntd` 从未被定义, 把它提到所有 return 之前。 - ④b⑤ 标签改「照片」「时间范围」。 - ⑥ 简介补"还没有简介,点这里写一句"的占位(简介本身一直在,是那条 summary 为空)。 - ⑦ 资源编辑复用新建弹框(`#resAddCard`),并在其底栏补「共享」「附件」入口 (第 7 条改动后这两个能力一度无入口)。 - ⑧ 纪念日「月/日」→「日期」+ AntD 纯日期控件(新增 mountAntdDate / [data-antd-date]), 存储契约仍是 month + day。 - ⑨ 编辑纪念日弹框关不掉(关闭逻辑原来只在 days/index.php,而 days/edit 是整页) ⇒ 搬进 days/form.php 自包含。 验证:e2e 1510/0;v42 真机 33/0;DAV·TB 35/0;DAV 反向 8/0; b23 55/0;b28 39/0;b32 26/0;b32 反向 5/0;v31 DAV 28/0;b31 反向 8/0; 部署断言空跑 337/0;完整流水线 PUSH_RC=0。
This commit is contained in:
@@ -90,8 +90,79 @@ $path = (string)parse_url((string)($_SERVER['REQUEST_URI'] ?? '/dav/'), PHP_UR
|
||||
* (本批实测踩过:跳到 principal 只对"严格按 RFC 5397 走的"客户端够用。)
|
||||
*/
|
||||
if ($path === '/.well-known/caldav') {
|
||||
/* 🔴🔴 v42(用户第 10 条)核心修复之一:
|
||||
这条路径原来**只回 301**(跳 `/dav/`)。匿名 GET 时这是对的;
|
||||
但 Thunderbird 建账号时**常常是带鉴权直接 PROPFIND 这个地址**
|
||||
(它把 `.well-known/caldav` 当成"DAV 服务的入口"来探,而不是只当跳转)。
|
||||
对带鉴权的 PROPFIND 回 301 的后果:**TB 不跟这个跳转**
|
||||
(它对"服务发现跳转"的跟随策略与浏览器不同),于是判定
|
||||
"这里没有 CalDAV 服务" ⇒ 报「找不到日历」。
|
||||
⇒ 修法:**按方法分流**
|
||||
· 有鉴权凭据 + PROPFIND → 直接给出与 `/dav/` 一致的发现响应(207,
|
||||
含 current-user-principal / calendar-home-set / 日历本清单),
|
||||
客户端**一步到位**,不依赖它对 301 的跟随;
|
||||
· 其余(匿名、GET、OPTIONS…)→ 仍是 301 到绝对 URL `/dav/`(RFC 6764 语义)。
|
||||
⚠️ 免鉴权这条必须保住:匿名 OPTIONS/GET 不能 401(否则客户端以为不支持 DAV)。 */
|
||||
$wkWantPropfind = (strtoupper($method) === 'PROPFIND');
|
||||
$wkHasAuth = !empty($user) && $pass !== null && $pass !== '';
|
||||
if ($wkWantPropfind && $wkHasAuth) {
|
||||
$wkWho = Auth::davAuth($user, $pass);
|
||||
if (!$wkWho) {
|
||||
header('WWW-Authenticate: Basic realm="FamilyBook DAV"');
|
||||
dav_fail(401, '需要 DAV 口令(在「个人设置 → DAV 同步」页生成,不是登录密码)。');
|
||||
}
|
||||
$wkFamily = (int)$wkWho['family_id'];
|
||||
$wkMe = (int)$wkWho['person_id'];
|
||||
$wkParts = [
|
||||
'<d:response><d:href>/dav/</d:href><d:propstat><d:prop>'
|
||||
. '<d:resourcetype><d:collection/></d:resourcetype>'
|
||||
. '<d:displayname>' . CalDav::x('家书日历') . '</d:displayname>'
|
||||
. '<d:current-user-principal><d:href>' . CalDav::x(CalDav::PRINCIPAL_PATH)
|
||||
. '</d:href></d:current-user-principal>'
|
||||
. '<d:principal-URL><d:href>' . CalDav::x(CalDav::PRINCIPAL_PATH)
|
||||
. '</d:href></d:principal-URL>'
|
||||
. '<c:calendar-home-set><d:href>' . CalDav::x(CalDav::HOME_PATH)
|
||||
. '</d:href></c:calendar-home-set>'
|
||||
. '<card:addressbook-home-set><d:href>' . CalDav::x(CalDav::CARD_HOME_PATH)
|
||||
. '</d:href></card:addressbook-home-set>'
|
||||
. '</d:prop></d:propstat></d:response>',
|
||||
];
|
||||
foreach (CalDav::books($wkFamily, $wkMe) as $wkC) {
|
||||
$wkParts[] = '<d:response><d:href>' . CalDav::x(CalDav::hrefOf($wkC)) . '</d:href>'
|
||||
. '<d:propstat><d:prop>'
|
||||
. '<d:resourcetype><d:collection/><c:calendar/></d:resourcetype>'
|
||||
. '<d:displayname>' . CalDav::x('家书 · ' . (string)$wkC['name']) . '</d:displayname>'
|
||||
. '<c:supported-calendar-component-set><c:comp name="VEVENT"/></c:supported-calendar-component-set>'
|
||||
. '</d:prop></d:propstat></d:response>';
|
||||
}
|
||||
CalDav::multis();
|
||||
echo '<?xml version="1.0" encoding="utf-8"?>' . "\n"
|
||||
. '<d:multistatus xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav" '
|
||||
. 'xmlns:card="urn:ietf:params:xml:ns:carddav">'
|
||||
. implode('', $wkParts) . '</d:multistatus>';
|
||||
exit;
|
||||
}
|
||||
http_response_code(301);
|
||||
header('Location: /dav/');
|
||||
/* 🔴🔴 v42(用户 2026-10-11 第 10 条:「caldav 还是不行,thunderbird 提示找不到日历」)
|
||||
这里原来是**相对路径** `Location: /dav/`。
|
||||
⚠️ RFC 7231 允许相对 Location(客户端按 RFC 3986 解析),
|
||||
但 Thunderbird 的 CalDAV 服务发现在这一跳上**对相对 Location 不可靠** ——
|
||||
`curl -L` 会跟、浏览器会跟,但 TB 自己不跟,
|
||||
它把这个 301 当成"这里没有 CalDAV 服务" ⇒ 报「找不到日历」。
|
||||
而我们的探针用 curl/urllib(都会自动跟)⇒ 全绿,
|
||||
于是"探针全过、真客户端不行"这个组合一直复现不出来。
|
||||
⇒ 改成**绝对 URL**(RFC 6764 §6.1 的示例也是绝对 URL)。
|
||||
⚠️ 按**真实请求的 host**拼(不能写死域名):
|
||||
本站既可能被 family.example.com 访问,也可能被 IP / 其它别名访问,
|
||||
写死会把客户端带到别的域(跨域后鉴权头会丢)。 */
|
||||
$wkHost = (string)($_SERVER['HTTP_HOST'] ?? '');
|
||||
$wkProto = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
|
||||
// 走代理(宝塔 nginx→apache)时协议可能丢,再从 X-Forwarded-Proto 补一次
|
||||
if ($wkProto === 'http' && !empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
|
||||
$wkProto = (string)$_SERVER['HTTP_X_FORWARDED_PROTO'];
|
||||
}
|
||||
$wkTarget = ($wkHost !== '') ? ($wkProto . '://' . $wkHost . '/dav/') : '/dav/';
|
||||
header('Location: ' . $wkTarget);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user