Skip to content

Commit e899cdb

Browse files
sh1pt-actions-fleet[bot]ralyodioclaude
authored
dev2 webmail: Google Calendar sync fixes; image cal5 (#235)
- patches.php (build-time, exact-match): libcalendaring toDateTime() crashed with 'assign property _dateonly on false' on a recurring event and aborted the whole CalDAV sync of a 4k-event Google calendar; fall back to the timestamp. - googlecal: refuse (and revoke) a Google connection without the calendar scope (granular consent checkbox left unticked -> every CalDAV call 403). Verified live: 7 Google calendars synced (primary 4,188 events); raw CalDAV create/read/update/delete on the primary calendar 201/200/204/200/204/404. Co-authored-by: Anthony Ettinger <anthony@profullstack.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 23387aa commit e899cdb

4 files changed

Lines changed: 71 additions & 1 deletion

File tree

‎dev2/sites.d/webmail.profullstack.com.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"service": "webmail.profullstack.com",
44
"repo": null,
55
"branch": null,
6-
"image": "profullstack/webmail:1.7.5-cal3",
6+
"image": "profullstack/webmail:1.7.5-cal5",
77
"start_command": "apache2-foreground",
88
"port_var": "80",
99
"domains": [

‎dev2/webmail/Dockerfile‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,9 @@ RUN f=/usr/src/roundcubemail/plugins/calendar/lib/calendar_ui.php \
3434
&& sed -i "s#html::div('col-sm-8', \$select->show(1))#html::div('col-sm-8', \$select->show(0))#" $f \
3535
&& grep -c "select->show(0))" $f
3636

37+
# Exact-match source patches (fail the build if upstream moves): see patches.php.
38+
COPY patches.php /tmp/patches.php
39+
RUN php /tmp/patches.php && php -l /usr/src/roundcubemail/plugins/libcalendaring/lib/libcalendaring_recurrence.php && rm /tmp/patches.php
40+
3741
COPY --chmod=755 googlecal /usr/src/roundcubemail/plugins/googlecal
3842
COPY --chmod=644 carddav.config.inc.php /usr/src/roundcubemail/plugins/carddav/config.inc.php

‎dev2/webmail/googlecal/googlecal.php‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,14 @@ public function callback()
119119
$this->rc->output->redirect($back);
120120
return;
121121
}
122+
// Google's consent screen shows calendar access as its own checkbox; without it
123+
// every CalDAV call is 403 insufficientPermissions. Refuse instead of half-connecting.
124+
if (strpos((string) ($r['scope'] ?? ''), 'https://www.googleapis.com/auth/calendar') === false) {
125+
$this->post(self::REVOKE_URL, ['token' => $r['refresh_token']]);
126+
$this->rc->output->show_message('Google did not grant calendar access. Click Connect again and tick "See, edit, share, and permanently delete all the calendars" on Google\'s screen.', 'error', null, true, 30);
127+
$this->rc->output->redirect($back);
128+
return;
129+
}
122130
$email = '';
123131
if (!empty($r['id_token']) && count($parts = explode('.', $r['id_token'])) === 3) {
124132
$claims = json_decode(base64_decode(strtr($parts[1], '-_', '+/')), true);

‎dev2/webmail/patches.php‎

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
// Build-time source patches for plugins installed by composer. Each patch is an exact
3+
// string replacement that must match once; the build fails if upstream changes it.
4+
$patches = [
5+
// libcalendaring 3.6.1: toDateTime() re-parses a recurrence's last occurrence with
6+
// createFromFormat, which can return false; the next line then dies with "Attempt to
7+
// assign property _dateonly on false" and the whole CalDAV sync of that calendar
8+
// aborts (seen on a Google calendar with ~800 events). Fall back to the timestamp,
9+
// and return false (no end) only when there is no date at all.
10+
'/usr/src/roundcubemail/plugins/libcalendaring/lib/libcalendaring_recurrence.php' => [[
11+
<<<'OLD'
12+
if ($date instanceof DateTimeInterface) {
13+
$date = libcalendaring_datetime::createFromFormat(
14+
'Y-m-d\\TH:i:s',
15+
$date->format('Y-m-d\\TH:i:s'),
16+
// Sabre will loose timezone on all-day events, use the event start's timezone
17+
$this->start->getTimezone()
18+
);
19+
}
20+
21+
$date->_dateonly = $this->dateonly;
22+
OLD,
23+
<<<'NEW'
24+
if ($date instanceof DateTimeInterface) {
25+
$src = $date;
26+
$date = libcalendaring_datetime::createFromFormat(
27+
'Y-m-d\\TH:i:s',
28+
$date->format('Y-m-d\\TH:i:s'),
29+
// Sabre will loose timezone on all-day events, use the event start's timezone
30+
$this->start->getTimezone()
31+
);
32+
if (!$date) {
33+
$date = new libcalendaring_datetime('@' . $src->getTimestamp());
34+
$date->setTimezone($this->start->getTimezone());
35+
}
36+
}
37+
38+
if (!is_object($date)) {
39+
return false;
40+
}
41+
42+
$date->_dateonly = $this->dateonly;
43+
NEW,
44+
]],
45+
];
46+
47+
foreach ($patches as $file => $list) {
48+
$src = file_get_contents($file);
49+
foreach ($list as [$old, $new]) {
50+
if (substr_count($src, $old) !== 1) {
51+
fwrite(STDERR, "patch does not apply to $file\n");
52+
exit(1);
53+
}
54+
$src = str_replace($old, $new, $src);
55+
}
56+
file_put_contents($file, $src);
57+
echo "patched $file\n";
58+
}

0 commit comments

Comments
 (0)