Implement context path discovery for CalDav/CardDav endpoint

This change set implements the "context path" discovery for the
CalDav/CardDav endpoints.

This basically implements the bootstrapping process as defined in
RFC6764 section 6, point 2 and 3.

What's missing in this implementation is the fallback that is described
in point 3, subpoint 3, which says that if the context path discovered
in the TXT RR is not reachable the .well-known URI should be used
instead.

I propose to implement this in a future iteration.
This commit is contained in:
Timo Furrer
2025-02-20 21:08:12 +01:00
committed by Simon Ser
parent 172968d292
commit aba953c3b6

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"context"
"encoding/xml"
"errors"
"fmt"
"io"
"mime"
@@ -16,7 +17,9 @@ import (
)
// DiscoverContextURL performs a DNS-based CardDAV/CalDAV service discovery as
// described in RFC 6352 section 11. It returns the URL to the CardDAV server.
// described in RFC 6764. It returns the URL to the CardDAV/CalDAV server.
// Specifically it implements points 2 and 3 from the bootstrapping procedure
// defined in RFC 6764 section 6.
func DiscoverContextURL(ctx context.Context, service, domain string) (string, error) {
var resolver net.Resolver
@@ -31,23 +34,52 @@ func DiscoverContextURL(ctx context.Context, service, domain string) (string, er
}
if len(addrs) == 0 {
return "", fmt.Errorf("webdav: domain doesn't have an SRV record")
return "", errors.New("webdav: domain doesn't have an SRV record")
}
addr := addrs[0]
target := strings.TrimSuffix(addr.Target, ".")
if target == "" {
return "", fmt.Errorf("webdav: empty target in SRV record")
return "", errors.New("webdav: empty target in SRV record")
}
// TODO: perform a TXT lookup, check for a "path" key in the response
u := url.URL{Scheme: "https"}
txtName := fmt.Sprintf("_%ss._tcp.%s", service, domain)
txtRecords, err := resolver.LookupTXT(ctx, txtName)
if dnsErr, ok := err.(*net.DNSError); ok {
if dnsErr.IsTemporary {
return "", err
}
} else if err != nil {
return "", err
}
var path string
switch len(txtRecords) {
case 0:
path = "/.well-known/" + service
case 1:
record := txtRecords[0]
if !strings.HasPrefix(record, "path=") {
return "", fmt.Errorf("webdav: TXT record for %s does not contain the path key", txtName)
}
path = strings.TrimPrefix(record, "path=")
if path == "" {
return "", fmt.Errorf("webdav: empty path for %s TXT record", txtName)
}
default: // more than 1
return "", fmt.Errorf("webdav: more than one entry found on %s discovery TXT record", txtName)
}
u := url.URL{
Scheme: "https",
Path: path,
}
if addr.Port == 443 {
u.Host = target
} else {
u.Host = fmt.Sprintf("%v:%v", target, addr.Port)
}
u.Path = "/.well-known/" + service
return u.String(), nil
}