From da699b2ce9d14143e6ee29fa638bf14b855ebb25 Mon Sep 17 00:00:00 2001 From: "Patrick Asmus (scriptos)" Date: Mon, 30 Mar 2026 10:40:11 +0200 Subject: [PATCH] fix: SOGo VLIST-Kontaktlisten aus CardDAV-Antworten filtern --- .gitignore | 1 + cmd/mcbdd/carddav.go | 118 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 118 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9ba06ee..fffe0fb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /.vscode/ /.ki-workspace/ /dist/ +*.exe .env state.json diff --git a/cmd/mcbdd/carddav.go b/cmd/mcbdd/carddav.go index 9a411d8..bafcd19 100644 --- a/cmd/mcbdd/carddav.go +++ b/cmd/mcbdd/carddav.go @@ -1,8 +1,13 @@ package main import ( + "bytes" "context" + "encoding/xml" + "errors" + "io" "log/slog" + "net/http" "net/url" "time" @@ -11,6 +16,117 @@ import ( "github.com/emersion/go-webdav/carddav" ) +// vlistFilterClient wraps a webdav.HTTPClient and removes DAV +// elements whose address-data contains a BEGIN:VLIST block before the +// carddav/vcard decoder processes them. +type vlistFilterClient struct { + inner webdav.HTTPClient +} + +func (c *vlistFilterClient) Do(req *http.Request) (*http.Response, error) { + resp, err := c.inner.Do(req) + if err != nil { + return resp, err + } + body, err := io.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return nil, err + } + body, err = stripVListResponses(body) + if err != nil { + return nil, err + } + resp.Body = io.NopCloser(bytes.NewReader(body)) + resp.ContentLength = int64(len(body)) + return resp, nil +} + +// stripVListResponses scans a WebDAV multistatus XML body and removes any +// elements whose chardata contains BEGIN:VLIST. +// It uses the XML decoder only to find byte offsets; it never re-encodes, so +// namespace declarations and formatting are preserved exactly. +// Non-multistatus bodies (no VLIST present) are returned unchanged. +func stripVListResponses(body []byte) ([]byte, error) { + if !bytes.Contains(body, []byte("BEGIN:VLIST")) { + return body, nil + } + + const davNS = "DAV:" + const cardNS = "urn:ietf:params:xml:ns:carddav" + + dec := xml.NewDecoder(bytes.NewReader(body)) + + // ranges holds [start, end) byte offsets of blocks to drop. + type byteRange struct{ start, end int64 } + var drop []byteRange + + var responseStart int64 + responseDepth := 0 + inAddrData := false + addrDataDepth := 0 + isVList := false + + for { + offset := dec.InputOffset() + tok, err := dec.Token() + if errors.Is(err, io.EOF) { + break + } + if err != nil { + return body, nil // unparseable — return original unchanged + } + + switch t := tok.(type) { + case xml.StartElement: + if t.Name.Space == davNS && t.Name.Local == "response" && responseDepth == 0 { + responseStart = offset + responseDepth = 1 + isVList = false + } else if responseDepth > 0 { + // Increment depth before checking the element name so that + // addrDataDepth records the post-increment value, matching the + // depth at which the corresponding EndElement will fire. + responseDepth++ + if t.Name.Space == cardNS && t.Name.Local == "address-data" { + inAddrData = true + addrDataDepth = responseDepth + } + } + case xml.EndElement: + if responseDepth > 0 { + if inAddrData && responseDepth == addrDataDepth { + inAddrData = false + } + responseDepth-- + if responseDepth == 0 { + if isVList { + drop = append(drop, byteRange{responseStart, dec.InputOffset()}) + } + } + } + case xml.CharData: + if inAddrData && bytes.Contains(t, []byte("BEGIN:VLIST")) { + isVList = true + } + } + } + + if len(drop) == 0 { + return body, nil + } + + var out bytes.Buffer + out.Grow(len(body)) + pos := int64(0) + for _, r := range drop { + out.Write(body[pos:r.start]) + pos = r.end + } + out.Write(body[pos:]) + return out.Bytes(), nil +} + // ContactType unterscheidet zwischen Geburtstagen und Jahrestagen. type ContactType int @@ -32,7 +148,7 @@ func (d *Daemon) getBirthdays(ctx context.Context, httpClient webdav.HTTPClient, if err != nil { return nil, err } - cl, err := carddav.NewClient(httpClient, endpoint) + cl, err := carddav.NewClient(&vlistFilterClient{inner: httpClient}, endpoint) if err != nil { return nil, err } -- 2.54.0