package dav import ( "context" "fmt" "net/http" "strings" ) // ProbeOptions never treats a missing/incomplete DAV: header as a transport // error: severity is the caller rule's decision, not ours. 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 matches case-insensitively 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 } func (o OptionsResult) AllowsMethod(m string) bool { for _, a := range o.AllowMethods { if strings.EqualFold(a, m) { return true } } return false } // parseCSVHeader merges repeated headers (net/http keeps them separate) // into a single split-and-trimmed slice. 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 } 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) }