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.
This commit is contained in:
commit
7eb0dbddc7
39 changed files with 3324 additions and 0 deletions
168
internal/dav/types.go
Normal file
168
internal/dav/types.go
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
// 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"`
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue