Disambiguate shared calendars with a name colliding with the owner's
If a calendar shared with a user has the same name as one of the user's own calendars (or as another share from a different owner), DAVx5 would show two calendars with an identical, indistinguishable title. The shared calendar's CalDAV display name (Calendar.Name, exposed via displayname/DAVx5's calendar list) now gets the owning user's username appended in parentheses in that case, e.g. "personal (alice)" — the underlying calendar path/URL is unaffected, and a user's own calendars are never renamed (only shared ones can collide with something else). internal/caldav/backend.go: calendarMeta split into calendarMeta (own calendars, unchanged name) and calendarMetaNamed (explicit display name); new sharedDisplayName(requester, owner, realName) checks requester's own calendars and other shares for a collision. Wired into both ListCalendars and GetCalendar (the latter used by direct PROPFIND/REPORT against a shared calendar's own URL). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -277,3 +277,57 @@ func TestPropFindExplicitCalendarColorRequest(t *testing.T) {
|
||||
t.Fatalf("expected a single 200 OK propstat with no 404, got: %s", body)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSharedCalendarNameCollidingWithOwnGetsDisambiguated covers the case
|
||||
// where a calendar shared with a user has the same name as one of that
|
||||
// user's own calendars: DAVx5 (and any other client) would otherwise show
|
||||
// two calendars with an identical, indistinguishable title, so the shared
|
||||
// one's display name gets the owner's username appended in parentheses.
|
||||
func TestSharedCalendarNameCollidingWithOwnGetsDisambiguated(t *testing.T) {
|
||||
b, dbase := newTestBackend(t)
|
||||
|
||||
// alice creates her own "personal" calendar, colliding with bob's
|
||||
// pre-existing "personal" calendar (see newTestBackend), then shares
|
||||
// it with bob.
|
||||
if err := dbase.CreateCalendar("alice", "personal"); err != nil {
|
||||
t.Fatalf("CreateCalendar alice/personal: %v", err)
|
||||
}
|
||||
if err := dbase.ShareCalendar("alice", "personal", "bob", db.PermRead); err != nil {
|
||||
t.Fatalf("ShareCalendar: %v", err)
|
||||
}
|
||||
if _, err := b.ListCalendars(ctxFor("alice")); err != nil {
|
||||
t.Fatalf("ListCalendars(alice): %v", err)
|
||||
}
|
||||
|
||||
cals, err := b.ListCalendars(ctxFor("bob"))
|
||||
if err != nil {
|
||||
t.Fatalf("ListCalendars(bob): %v", err)
|
||||
}
|
||||
|
||||
wantPath := calHomePath() + sharedCalendarName("alice", "personal") + "/"
|
||||
var sharedName, ownName string
|
||||
for _, c := range cals {
|
||||
if c.Path == wantPath {
|
||||
sharedName = c.Name
|
||||
}
|
||||
if c.Path == calHomePath()+"personal/" {
|
||||
ownName = c.Name
|
||||
}
|
||||
}
|
||||
if ownName != "personal" {
|
||||
t.Fatalf("expected bob's own calendar to keep its plain name, got %q", ownName)
|
||||
}
|
||||
if want := "personal (alice)"; sharedName != want {
|
||||
t.Fatalf("expected shared calendar display name %q, got %q", want, sharedName)
|
||||
}
|
||||
|
||||
// GetCalendar (single-collection lookup, as used by PROPFIND on the
|
||||
// calendar's own URL) must disambiguate the same way.
|
||||
cal, err := b.GetCalendar(ctxFor("bob"), wantPath)
|
||||
if err != nil {
|
||||
t.Fatalf("GetCalendar: %v", err)
|
||||
}
|
||||
if want := "personal (alice)"; cal.Name != want {
|
||||
t.Fatalf("GetCalendar: expected display name %q, got %q", want, cal.Name)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user