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.
168 lines
6.3 KiB
Go
168 lines
6.3 KiB
Go
// Package dav holds the code shared between the CalDAV and CardDAV checkers:
|
|
// discovery (SRV/TXT, /.well-known), OPTIONS probing, PROPFIND helpers, and
|
|
// the HTML report CSS. The CalDAV/CardDAV-specific collect pipelines live in
|
|
// their own packages and compose these helpers.
|
|
package dav
|
|
|
|
import "time"
|
|
|
|
// Kind distinguishes the two protocol flavours. A single Kind value is carried
|
|
// end-to-end through a checker run so shared helpers can pick the right
|
|
// service names, well-known paths, and required DAV classes.
|
|
type Kind string
|
|
|
|
const (
|
|
KindCalDAV Kind = "caldav"
|
|
KindCardDAV Kind = "carddav"
|
|
)
|
|
|
|
// ServiceName returns the RFC 6764 SRV service label for kind, 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 ""
|
|
}
|
|
|
|
// WellKnownPath returns the RFC 6764 well-known path for kind.
|
|
func (k Kind) WellKnownPath() string {
|
|
return "/.well-known/" + string(k)
|
|
}
|
|
|
|
// RequiredCapability is the string that must appear in the DAV: response
|
|
// header for the server to qualify as a valid implementation.
|
|
func (k Kind) RequiredCapability() string {
|
|
switch k {
|
|
case KindCalDAV:
|
|
return "calendar-access"
|
|
case KindCardDAV:
|
|
return "addressbook"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// Observation is the root data structure persisted by either checker. The
|
|
// CalDAV-only fields (Scheduling) are populated for KindCalDAV runs and left
|
|
// zero-valued for KindCardDAV.
|
|
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"`
|
|
}
|
|
|
|
// SRVRecord is a flat, JSON-friendly view of a DNS SRV answer.
|
|
type SRVRecord struct {
|
|
Target string `json:"target"`
|
|
Port uint16 `json:"port"`
|
|
Priority uint16 `json:"priority"`
|
|
Weight uint16 `json:"weight"`
|
|
}
|
|
|
|
// DiscoveryResult captures every signal we gathered while locating the
|
|
// service: SRV secure/plaintext, TXT path hints, well-known redirects, and
|
|
// the ultimately-resolved context URL.
|
|
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 records whether the resolved context URL accepts HTTPS
|
|
// requests. TLS certificate validation is out of scope (a dedicated TLS
|
|
// checker covers it); we only report the raw transport-level error if any.
|
|
type TransportResult struct {
|
|
Reached bool `json:"reached"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
// OptionsResult captures the response to an OPTIONS request against the
|
|
// context URL: which DAV: classes are advertised, which HTTP methods are in
|
|
// Allow:, and which authentication schemes the server offered.
|
|
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 holds the `current-user-principal` URL discovered after
|
|
// authenticating. Skipped is set to true when no credentials were supplied
|
|
// (the rule surfaces this as StatusUnknown).
|
|
type PrincipalResult struct {
|
|
Skipped bool `json:"skipped,omitempty"`
|
|
URL string `json:"url,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
// HomeSetResult holds the CalDAV `calendar-home-set` or CardDAV
|
|
// `addressbook-home-set` URL for the authenticated principal.
|
|
type HomeSetResult struct {
|
|
Skipped bool `json:"skipped,omitempty"`
|
|
URL string `json:"url,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
// CollectionInfo describes a single discovered calendar or addressbook.
|
|
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
|
|
}
|
|
|
|
// CollectionsResult is the enumerated content of the home-set.
|
|
type CollectionsResult struct {
|
|
Skipped bool `json:"skipped,omitempty"`
|
|
Items []CollectionInfo `json:"items,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
// ReportResult is the outcome of a minimal REPORT probe against the first
|
|
// collection found (empty calendar-query/addressbook-query).
|
|
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: presence of inbox/outbox when the server
|
|
// advertises the `calendar-schedule` capability.
|
|
type SchedulingResult struct {
|
|
Advertised bool `json:"advertised"`
|
|
InboxURL string `json:"inbox_url,omitempty"`
|
|
OutboxURL string `json:"outbox_url,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|