Initial commit
CalDAV and CardDAV checkers sharing a single Go module. Discovery follows RFC 6764 (/.well-known + SRV/TXT), authenticated probes cover principal, home-set, collections and a minimal REPORT query on top of go-webdav. Common shape in internal/dav/; CalDAV adds a scheduling rule. Surfaces its context URL (and each secure-SRV target) as TLS endpoints via the EndpointDiscoverer interface, so the dedicated TLS checker can pick them up without re-parsing observations. HTML report foregrounds common misconfigs (well-known returning 200, missing SRV, plaintext-only SRV, missing DAV capability, skipped auth phase) as action-item callouts before the full phase breakdown.
This commit is contained in:
commit
7eb0dbddc7
39 changed files with 3324 additions and 0 deletions
101
internal/dav/options.go
Normal file
101
internal/dav/options.go
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
package dav
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ProbeOptions issues an HTTP OPTIONS against url and reports the parsed DAV
|
||||
// headers. A missing DAV: header, or one that does not contain the kind's
|
||||
// required capability, is not treated as a transport error here — the caller
|
||||
// rule decides severity from the parsed values.
|
||||
func ProbeOptions(ctx context.Context, client *http.Client, url string) (OptionsResult, error) {
|
||||
res := OptionsResult{}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodOptions, url, nil)
|
||||
if err != nil {
|
||||
res.Error = err.Error()
|
||||
return res, err
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
res.Error = err.Error()
|
||||
return res, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
res.StatusCode = resp.StatusCode
|
||||
res.Server = resp.Header.Get("Server")
|
||||
res.DAVClasses = parseCSVHeader(resp.Header.Values("Dav"))
|
||||
res.AllowMethods = parseCSVHeader(resp.Header.Values("Allow"))
|
||||
|
||||
for _, h := range resp.Header.Values("Www-Authenticate") {
|
||||
if scheme := authScheme(h); scheme != "" {
|
||||
res.AuthSchemes = appendUnique(res.AuthSchemes, scheme)
|
||||
}
|
||||
}
|
||||
|
||||
if res.StatusCode >= 400 {
|
||||
res.Error = fmt.Sprintf("OPTIONS returned HTTP %d", res.StatusCode)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// HasCapability returns true when the OPTIONS response advertised cap in the
|
||||
// DAV: header. Matching is case-insensitive, per RFC 4918 §10.1.
|
||||
func (o OptionsResult) HasCapability(cap string) bool {
|
||||
for _, c := range o.DAVClasses {
|
||||
if strings.EqualFold(c, cap) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AllowsMethod returns true when the OPTIONS response's Allow: listed m.
|
||||
func (o OptionsResult) AllowsMethod(m string) bool {
|
||||
for _, a := range o.AllowMethods {
|
||||
if strings.EqualFold(a, m) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// parseCSVHeader splits one or more header values on commas, trims, and drops
|
||||
// empties. Multiple headers of the same name (net/http preserves them) are
|
||||
// merged.
|
||||
func parseCSVHeader(values []string) []string {
|
||||
var out []string
|
||||
for _, v := range values {
|
||||
for _, part := range strings.Split(v, ",") {
|
||||
if p := strings.TrimSpace(part); p != "" {
|
||||
out = append(out, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// authScheme returns the scheme token from a WWW-Authenticate header value
|
||||
// ("Basic realm=\"x\"" → "Basic"). Empty if the value is malformed.
|
||||
func authScheme(h string) string {
|
||||
h = strings.TrimSpace(h)
|
||||
if h == "" {
|
||||
return ""
|
||||
}
|
||||
if i := strings.IndexAny(h, " \t"); i > 0 {
|
||||
return h[:i]
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func appendUnique(s []string, v string) []string {
|
||||
for _, x := range s {
|
||||
if strings.EqualFold(x, v) {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return append(s, v)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue