3 Commits
docs ... master

Author SHA1 Message Date
14152524f3 Merge pull request 'fix: SOGo VLIST-Kontaktlisten aus CardDAV-Antworten filtern' (#20) from release-0.4.2 into master
All checks were successful
Run Tests / test (push) Successful in 4m33s
Build & Publish / build (release) Successful in 44s
Reviewed-on: #20
2026-03-30 09:05:26 +00:00
da699b2ce9 fix: SOGo VLIST-Kontaktlisten aus CardDAV-Antworten filtern
All checks were successful
Build Test Docker Image / docker-test (pull_request) Successful in 1m36s
Run Tests / test (pull_request) Successful in 4m59s
2026-03-30 10:44:22 +02:00
cdf713b5cf Merge pull request 'docs: Zeile aus Readme entfernt' (#19) from docs into master
Reviewed-on: #19
2026-03-29 22:52:36 +00:00
2 changed files with 118 additions and 1 deletions

1
.gitignore vendored
View File

@@ -1,6 +1,7 @@
/.vscode/
/.ki-workspace/
/dist/
*.exe
.env
state.json

View File

@@ -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 <response>
// 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
// <D:response> elements whose <C:address-data> 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 <D:response> 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
}