// 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"` }