checker-dav/internal/dav/types.go
Pierre-Olivier Mercier 7d5535fddf 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.
2026-04-26 21:47:40 +07:00

149 lines
4.9 KiB
Go

// Package dav holds code shared by the CalDAV and CardDAV checkers:
// discovery, OPTIONS probing, PROPFIND helpers, and report rendering.
package dav
import "time"
// Kind is carried end-to-end through a run so shared helpers branch on it
// rather than duplicating per-protocol code.
type Kind string
const (
KindCalDAV Kind = "caldav"
KindCardDAV Kind = "carddav"
)
// ServiceName returns the RFC 6764 SRV label, with the leading "_" but
// without the "_tcp" suffix.
func (k Kind) ServiceName(secure bool) string {
switch k {
case KindCalDAV:
if secure {
return "_caldavs"
}
return "_caldav"
case KindCardDAV:
if secure {
return "_carddavs"
}
return "_carddav"
}
return ""
}
func (k Kind) WellKnownPath() string {
return "/.well-known/" + string(k)
}
// RequiredCapability is the DAV: header token a compliant server must
// advertise.
func (k Kind) RequiredCapability() string {
switch k {
case KindCalDAV:
return "calendar-access"
case KindCardDAV:
return "addressbook"
}
return ""
}
// Observation is what each checker persists. Scheduling is CalDAV-only and
// left nil for CardDAV.
type Observation struct {
Kind Kind `json:"kind"`
Domain string `json:"domain"`
HasCredentials bool `json:"has_credentials"`
Discovery DiscoveryResult `json:"discovery"`
Transport TransportResult `json:"transport"`
Options OptionsResult `json:"options"`
Principal PrincipalResult `json:"principal"`
HomeSet HomeSetResult `json:"home_set"`
Collections CollectionsResult `json:"collections"`
Report ReportResult `json:"report"`
Scheduling *SchedulingResult `json:"scheduling,omitempty"`
CollectedAt time.Time `json:"collected_at"`
}
type SRVRecord struct {
Target string `json:"target"`
Port uint16 `json:"port"`
Priority uint16 `json:"priority"`
Weight uint16 `json:"weight"`
}
// DiscoveryResult records every signal seen during lookup, even on failure,
// so the report can pinpoint which leg of discovery broke.
type DiscoveryResult struct {
SecureSRV []SRVRecord `json:"secure_srv,omitempty"`
PlaintextSRV []SRVRecord `json:"plaintext_srv,omitempty"`
SRVError string `json:"srv_error,omitempty"`
TXTPath string `json:"txt_path,omitempty"`
TXTError string `json:"txt_error,omitempty"`
WellKnownURL string `json:"well_known_url,omitempty"`
WellKnownCode int `json:"well_known_code,omitempty"`
WellKnownChain []string `json:"well_known_chain,omitempty"`
WellKnownError string `json:"well_known_error,omitempty"`
ContextURL string `json:"context_url,omitempty"`
Source string `json:"source,omitempty"` // "explicit", "well-known", "srv-txt"
Error string `json:"error,omitempty"`
}
// TransportResult is intentionally minimal: cert validation is out of scope
// here, a dedicated TLS checker owns it.
type TransportResult struct {
Reached bool `json:"reached"`
Error string `json:"error,omitempty"`
}
type OptionsResult struct {
StatusCode int `json:"status_code"`
DAVClasses []string `json:"dav_classes,omitempty"`
AllowMethods []string `json:"allow_methods,omitempty"`
AuthSchemes []string `json:"auth_schemes,omitempty"`
Server string `json:"server,omitempty"`
Error string `json:"error,omitempty"`
}
// PrincipalResult.Skipped is set when no credentials were supplied; the
// rule turns that into StatusUnknown rather than a failure.
type PrincipalResult struct {
Skipped bool `json:"skipped,omitempty"`
URL string `json:"url,omitempty"`
Error string `json:"error,omitempty"`
}
type HomeSetResult struct {
Skipped bool `json:"skipped,omitempty"`
URL string `json:"url,omitempty"`
Error string `json:"error,omitempty"`
}
type CollectionInfo struct {
Path string `json:"path"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
MaxResourceSize int64 `json:"max_resource_size,omitempty"`
SupportedComponentSet []string `json:"supported_component_set,omitempty"` // CalDAV only
SupportedAddressData []string `json:"supported_address_data,omitempty"` // CardDAV only
}
type CollectionsResult struct {
Skipped bool `json:"skipped,omitempty"`
Items []CollectionInfo `json:"items,omitempty"`
Error string `json:"error,omitempty"`
}
type ReportResult struct {
Skipped bool `json:"skipped,omitempty"`
QueryOK bool `json:"query_ok,omitempty"`
ProbePath string `json:"probe_path,omitempty"`
Error string `json:"error,omitempty"`
}
// SchedulingResult is CalDAV-only.
type SchedulingResult struct {
Advertised bool `json:"advertised"`
InboxURL string `json:"inbox_url,omitempty"`
OutboxURL string `json:"outbox_url,omitempty"`
Error string `json:"error,omitempty"`
}