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) }