fix: SOGo VLIST-Kontaktlisten aus CardDAV-Antworten filtern
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,6 +1,7 @@
|
||||
/.vscode/
|
||||
/.ki-workspace/
|
||||
/dist/
|
||||
*.exe
|
||||
|
||||
.env
|
||||
state.json
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user