From 9c900b1c666f066768b14bce85e3dff9e17f10b1 Mon Sep 17 00:00:00 2001 From: oliverpool Date: Fri, 4 Oct 2024 21:12:08 +0200 Subject: [PATCH] caldav, carddav: redirect on wrong path it is much more user-friendly to redirect to the correct principalPath/homeSetPath when possible --- caldav/server.go | 36 +++++++++++++++++++++--------------- caldav/server_test.go | 32 ++++++++++++++++++++++++++++++++ carddav/carddav_test.go | 37 +++++++++++++++++++++++++++++++++++++ carddav/server.go | 36 +++++++++++++++++++++--------------- internal/server.go | 9 ++------- server.go | 12 ++++++------ 6 files changed, 119 insertions(+), 43 deletions(-) diff --git a/caldav/server.go b/caldav/server.go index 7ddfffc..b001113 100644 --- a/caldav/server.go +++ b/caldav/server.go @@ -366,7 +366,7 @@ func (b *backend) HeadGet(w http.ResponseWriter, r *http.Request) error { return nil } -func (b *backend) PropFind(r *http.Request, propfind *internal.PropFind, depth internal.Depth) (*internal.MultiStatus, error) { +func (b *backend) PropFind(w http.ResponseWriter, r *http.Request, propfind *internal.PropFind, depth internal.Depth) error { resType := b.resourceTypeAtPath(r.URL.Path) var dataReq CalendarCompRequest @@ -376,86 +376,92 @@ func (b *backend) PropFind(r *http.Request, propfind *internal.PropFind, depth i case resourceTypeRoot: resp, err := b.propFindRoot(r.Context(), propfind) if err != nil { - return nil, err + return err } resps = append(resps, *resp) case resourceTypeUserPrincipal: principalPath, err := b.Backend.CurrentUserPrincipal(r.Context()) if err != nil { - return nil, err + return err } if r.URL.Path == principalPath { resp, err := b.propFindUserPrincipal(r.Context(), propfind) if err != nil { - return nil, err + return err } resps = append(resps, *resp) if depth != internal.DepthZero { resp, err := b.propFindHomeSet(r.Context(), propfind) if err != nil { - return nil, err + return err } resps = append(resps, *resp) if depth == internal.DepthInfinity { resps_, err := b.propFindAllCalendars(r.Context(), propfind, true) if err != nil { - return nil, err + return err } resps = append(resps, resps_...) } } + } else { + http.Redirect(w, r, principalPath, http.StatusPermanentRedirect) // keep http method + return nil } case resourceTypeCalendarHomeSet: homeSetPath, err := b.Backend.CalendarHomeSetPath(r.Context()) if err != nil { - return nil, err + return err } if r.URL.Path == homeSetPath { resp, err := b.propFindHomeSet(r.Context(), propfind) if err != nil { - return nil, err + return err } resps = append(resps, *resp) if depth != internal.DepthZero { recurse := depth == internal.DepthInfinity resps_, err := b.propFindAllCalendars(r.Context(), propfind, recurse) if err != nil { - return nil, err + return err } resps = append(resps, resps_...) } + } else { + http.Redirect(w, r, homeSetPath, http.StatusPermanentRedirect) // keep http method + return nil } case resourceTypeCalendar: ab, err := b.Backend.GetCalendar(r.Context(), r.URL.Path) if err != nil { - return nil, err + return err } resp, err := b.propFindCalendar(r.Context(), propfind, ab) if err != nil { - return nil, err + return err } resps = append(resps, *resp) if depth != internal.DepthZero { resps_, err := b.propFindAllCalendarObjects(r.Context(), propfind, ab) if err != nil { - return nil, err + return err } resps = append(resps, resps_...) } case resourceTypeCalendarObject: ao, err := b.Backend.GetCalendarObject(r.Context(), r.URL.Path, &dataReq) if err != nil { - return nil, err + return err } resp, err := b.propFindCalendarObject(r.Context(), propfind, ao) if err != nil { - return nil, err + return err } resps = append(resps, *resp) } - return internal.NewMultiStatus(resps...), nil + return internal.ServeMultiStatus(w, internal.NewMultiStatus(resps...)) } func (b *backend) propFindRoot(ctx context.Context, propfind *internal.PropFind) (*internal.Response, error) { diff --git a/caldav/server_test.go b/caldav/server_test.go index 594b87b..b26a73d 100644 --- a/caldav/server_test.go +++ b/caldav/server_test.go @@ -233,3 +233,35 @@ func (t testBackend) ListCalendarObjects(ctx context.Context, path string, req * func (t testBackend) QueryCalendarObjects(ctx context.Context, path string, query *CalendarQuery) ([]CalendarObject, error) { return nil, nil } + +func TestRedirections(t *testing.T) { + ctx := context.Background() + + calendars := []Calendar{{Path: "/user/calendars/a"}} + ts := httptest.NewServer(&Handler{&testBackend{ + calendars: calendars, + }, ""}) + defer ts.Close() + + client, err := NewClient(nil, ts.URL) + if err != nil { + t.Fatalf("error creating client: %s", err) + } + hsp, err := client.FindCalendarHomeSet(ctx, "/must-be-redirected/") + if err != nil { + t.Fatalf("error finding home set path: %s", err) + } + if want := "/user/calendars/"; hsp != want { + t.Fatalf("Found home set path '%s', expected '%s'", hsp, want) + } + abs, err := client.FindCalendars(ctx, "/must-be-redirected/again/") + if err != nil { + t.Fatalf("error finding calendars: %s", err) + } + if len(abs) != 1 { + t.Fatalf("Found %d calendars, expected 1", len(abs)) + } + if want := "/user/calendars/a"; abs[0].Path != want { + t.Fatalf("Found calendar at %s, expected %s", abs[0].Path, want) + } +} diff --git a/carddav/carddav_test.go b/carddav/carddav_test.go index 3527a3e..8444406 100644 --- a/carddav/carddav_test.go +++ b/carddav/carddav_test.go @@ -194,6 +194,43 @@ func TestAddressBookDiscovery(t *testing.T) { } } +func TestRedirections(t *testing.T) { + ctx := context.Background() + + h := Handler{&testBackend{}, ""} + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + ctx = context.WithValue(ctx, currentUserPrincipalKey, "/principal/") + ctx = context.WithValue(ctx, homeSetPathKey, "/principal/contacts/") + ctx = context.WithValue(ctx, addressBookPathKey, "/principal/contacts/default") + r = r.WithContext(ctx) + (&h).ServeHTTP(w, r) + })) + defer ts.Close() + + client, err := NewClient(nil, ts.URL) + if err != nil { + t.Fatalf("error creating client: %s", err) + } + hsp, err := client.FindAddressBookHomeSet(ctx, "/must-be-redirected/") + if err != nil { + t.Fatalf("error finding home set path: %s", err) + } + if want := "/principal/contacts/"; hsp != want { + t.Fatalf("Found home set path '%s', expected '%s'", hsp, want) + } + abs, err := client.FindAddressBooks(ctx, "/must-be-redirected/again/") + if err != nil { + t.Fatalf("error finding address books: %s", err) + } + if len(abs) != 1 { + t.Fatalf("Found %d address books, expected 1", len(abs)) + } + if want := "/principal/contacts/default"; abs[0].Path != want { + t.Fatalf("Found address book at %s, expected %s", abs[0].Path, want) + } +} + var mkcolRequestBody = `