Class HttpCalDavClient

java.lang.Object
org.exoplatform.caldav.client.HttpCalDavClient
All Implemented Interfaces:
CalDavClient

@Component public class HttpCalDavClient extends Object implements CalDavClient
CalDAV over the JDK's own HTTP client, modelled line for line on email-connector's HttpCardDavClient — the hand-written client that already works against the same BlueMind that defeated the browser — with the disciplines the relay established carried over:
  • registry-only targets — endpoints are minted from the server registry alone (endpoint(Long, String)), hrefs resolve against that endpoint's authority, and a foreign-host href is refused;
  • no redirects — a declared server must not be able to bounce this client's credentialed requests to another host, so the transport never follows a 3xx (which also rules the well-known discovery flow out by design: discovery starts at the registered base path instead);
  • explicit timeouts and a bounded body read, both deployment-tunable;
  • no auth material anywhere near a log or an exception — every message names the method and the URI, never a header.
The XML is parsed with doctypes and external entities disabled: the payload comes from whatever server an administrator declared, which is still attacker-influenced input, and a stock parser would happily read files off this machine on its behalf. And every property is read through its propstat status: BlueMind answers 207 with failing propstats where a naive client reads 2xx as success — the false-success class that shipped a bug the week this client was designed.
  • Constructor Details

    • HttpCalDavClient

      @Autowired public HttpCalDavClient(CaldavServerService caldavServerService)
      The client Spring builds. Redirects are NEVER followed: the target host comes from the registry, and a compromised or misconfigured registered server must not be able to bounce credentialed requests anywhere else.
      Parameters:
      caldavServerService - the registry every endpoint is resolved from
  • Method Details

    • endpoint

      public CalDavEndpoint endpoint(Long serverId, String davUsername)
      Description copied from interface: CalDavClient
      Mints the one kind of target this client will talk to: the account base of a declared server, resolved exclusively from the server registry — the row the account references, else the seed row, else the legacy deployment property — with the {username} placeholder replaced by the account's DAV username, percent-encoded for its path position.
      Specified by:
      endpoint in interface CalDavClient
      Parameters:
      serverId - registry row the user's account references, or null for the seed-row / legacy-property resolution
      davUsername - the account's username on the CalDAV server
      Returns:
      the endpoint every other method addresses
    • discoverCalendarHome

      public String discoverCalendarHome(CalDavEndpoint endpoint, String username, String password)
      Description copied from interface: CalDavClient
      Walks discovery from the endpoint base: asks who the authenticated user is (current-user-principal), then where that principal's calendars live (calendar-home-set). Started at the registered base path directly — never at /.well-known/caldav, whose contract is a redirect and this client follows none.
      Specified by:
      discoverCalendarHome in interface CalDavClient
      Parameters:
      endpoint - the declared server to discover on
      username - the account to authenticate as
      password - that account's password
      Returns:
      the calendar home's server-absolute raw path
    • listCalendars

      public List<CalendarCollection> listCalendars(CalDavEndpoint endpoint, String homeHref, String username, String password)
      Description copied from interface: CalDavClient
      Lists the calendar collections of a home with every property the sync engine binds on: display name, resource type, ctag, sync token, supported reports, the user's privileges, and the calendar colour.
      Specified by:
      listCalendars in interface CalDavClient
      Parameters:
      endpoint - the declared server
      homeHref - the calendar home's server-absolute path
      username - the account to authenticate as
      password - that account's password
      Returns:
      the calendar collections, never null — non-calendar members of the home (the home itself included) are filtered out here by resource type, never guessed from the path
    • readCalendar

      public CalendarCollection readCalendar(CalDavEndpoint endpoint, String href, String username, String password)
      Description copied from interface: CalDavClient
      Reads one collection's properties at Depth:0 — what a bound pair re-reads on each run without paying a full home listing.
      Specified by:
      readCalendar in interface CalDavClient
      Parameters:
      endpoint - the declared server
      href - the collection's server-absolute path
      username - the account to authenticate as
      password - that account's password
      Returns:
      the collection, or null when the resource exists but is not a calendar collection
    • getCtag

      public String getCtag(CalDavEndpoint endpoint, String href, String username, String password)
      Description copied from interface: CalDavClient
      Reads the collection's current ctag, the cheap tier-2 question asked first on every run.
      Specified by:
      getCtag in interface CalDavClient
      Parameters:
      endpoint - the declared server
      href - the collection's server-absolute path
      username - the account to authenticate as
      password - that account's password
      Returns:
      the current ctag, or null when the server does not implement it
    • listResourceEtags

      public Map<String,String> listResourceEtags(CalDavEndpoint endpoint, String collectionHref, String username, String password)
      Description copied from interface: CalDavClient
      Lists every object in the collection with its version — the listResourceEtags pattern the plan reuses verbatim from email-connector, and what tells the engine who is new, who changed and who is gone. Also the mirror-verification listing of the metadata design.
      Specified by:
      listResourceEtags in interface CalDavClient
      Parameters:
      endpoint - the declared server
      collectionHref - the collection's server-absolute path
      username - the account to authenticate as
      password - that account's password
      Returns:
      object path to object version, insertion-ordered, never null — the collection itself and any member without an ETag are skipped by that one rule, never by guessing from the path
    • calendarQuery

      public List<CalendarObject> calendarQuery(CalDavEndpoint endpoint, String collectionHref, Instant start, Instant end, String username, String password)
      Description copied from interface: CalDavClient
      Runs a calendar-query REPORT for the VEVENTs of a time window — the tier-3 floor for servers whose listings carry no usable ETags, and the ranged read the events endpoint will serve from.
      Specified by:
      calendarQuery in interface CalDavClient
      Parameters:
      endpoint - the declared server
      collectionHref - the collection's server-absolute path
      start - window start, inclusive, or null for an unbounded start
      end - window end, exclusive, or null for an unbounded end
      username - the account to authenticate as
      password - that account's password
      Returns:
      the matching objects with their calendar data, never null
    • multiget

      public List<CalendarObject> multiget(CalDavEndpoint endpoint, String collectionHref, List<String> hrefs, String username, String password)
      Description copied from interface: CalDavClient
      Fetches the calendar data of the given objects in one calendar-multiget REPORT — why a changed collection costs one round trip per batch rather than one per event.
      Specified by:
      multiget in interface CalDavClient
      Parameters:
      endpoint - the declared server
      collectionHref - the collection's server-absolute path
      hrefs - the object paths to fetch
      username - the account to authenticate as
      password - that account's password
      Returns:
      the objects the server returned, never null; an href it did not return is simply absent, which the caller must tolerate
    • syncCollection

      public SyncCollectionResult syncCollection(CalDavEndpoint endpoint, String collectionHref, String syncToken, String username, String password)
      Description copied from interface: CalDavClient
      Runs an RFC 6578 sync-collection REPORT with a stored token — tier 1, one round trip for everything changed or deleted since that token.
      Specified by:
      syncCollection in interface CalDavClient
      Parameters:
      endpoint - the declared server
      collectionHref - the collection's server-absolute path
      syncToken - the stored token, or null/blank for the initial sync that establishes one
      username - the account to authenticate as
      password - that account's password
      Returns:
      the changes and the token to store — or the invalid-token result when the server rejected the presented token, which the caller answers by falling through to the listing tier for this run
    • probeCapabilities

      public ServerCapabilities probeCapabilities(CalDavEndpoint endpoint, String collectionHref, String username, String password)
      Description copied from interface: CalDavClient
      Probes which report tiers a collection's server can serve, in one Depth:0 PROPFIND. Stateless: the answer is bind-time information whose caching belongs to the caller's pair state, not to this client.
      Specified by:
      probeCapabilities in interface CalDavClient
      Parameters:
      endpoint - the declared server
      collectionHref - the collection's server-absolute path
      username - the account to authenticate as
      password - that account's password
      Returns:
      the capability answer, never null
    • fetchObject

      public CalendarObject fetchObject(CalDavEndpoint endpoint, String href, String username, String password)
      Description copied from interface: CalDavClient
      Reads one object's current data and version in a single GET — what a conflict re-read asks, so the three-way runs against the object as the server holds it NOW rather than as the last sync remembers it.
      Specified by:
      fetchObject in interface CalDavClient
      Parameters:
      endpoint - the declared server
      href - the object's server-absolute path
      username - the account to authenticate as
      password - that account's password
      Returns:
      the object as the server returned it, or null when the server says there is no such object — including BlueMind's way of saying it, a 500 for an .ics that is simply not there; reporting "absent" on a server error is safe rather than optimistic because every creating write keeps its If-None-Match: *, so the worst case is a refused write, never a silently overwritten one
    • putObject

      public PutResult putObject(CalDavEndpoint endpoint, String href, String icsData, String username, String password)
      Description copied from interface: CalDavClient
      Creates one object — always under If-None-Match: *, with no unconditional variant on purpose: the server itself then guarantees the PUT can only CREATE, and an object already at that path answers 412 instead of being overwritten, whatever race led there.
      Specified by:
      putObject in interface CalDavClient
      Parameters:
      endpoint - the declared server
      href - the object's server-absolute path to create at
      icsData - the iCalendar text to store
      username - the account to authenticate as
      password - that account's password
      Returns:
      the status and, when the server sent them, the stored version and the object's real location; a 412 is answered, not thrown
    • updateObject

      public PutResult updateObject(CalDavEndpoint endpoint, String href, String icsData, String ifMatch, String username, String password)
      Description copied from interface: CalDavClient
      Replaces one existing object — guarded the opposite way from CalDavClient.putObject(org.exoplatform.caldav.client.CalDavEndpoint, java.lang.String, java.lang.String, java.lang.String, java.lang.String): If-Match with the version the caller just read means the server only accepts the write when nobody changed the object since that read. A blank precondition is refused here rather than sent as an unconditional overwrite: "every conditional write stays conditional" is the connector doctrine this client enforces structurally.
      Specified by:
      updateObject in interface CalDavClient
      Parameters:
      endpoint - the declared server
      href - the object's server-absolute path
      icsData - the iCalendar text to store
      ifMatch - the If-Match value — the ETag the caller's own read just answered, exactly as sent, quotes and all
      username - the account to authenticate as
      password - that account's password
      Returns:
      the status and the stored version when the server sent one; a 412 is answered, not thrown
    • deleteObject

      public int deleteObject(CalDavEndpoint endpoint, String href, String ifMatch, String username, String password)
      Description copied from interface: CalDavClient
      Deletes one object, conditionally when a version is given.
      Specified by:
      deleteObject in interface CalDavClient
      Parameters:
      endpoint - the declared server
      href - the object's server-absolute path
      ifMatch - the If-Match value to guard the delete with, or null to delete unconditionally — tolerated here (unlike CalDavClient.updateObject(org.exoplatform.caldav.client.CalDavEndpoint, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String)) because a delete of an already-diverged object is recoverable where an overwrite is not, and some flows legitimately hold no version
      username - the account to authenticate as
      password - that account's password
      Returns:
      the status the server answered: 200/204 deleted, 404/410 already gone — a fact, not a fault — and 412 a refused precondition
    • mkCalendar

      public MkCalendarResult mkCalendar(CalDavEndpoint endpoint, String href, String displayName, String color, String username, String password)
      Description copied from interface: CalDavClient
      Asks the server to create a calendar collection, display name and optional colour set atomically. The request always declares supported-calendar-component-set with VEVENT: BlueMind derives the created collection's kind from it, and without it answers 201 while creating nothing (proven live 2026-08-20). The answer is a claim, never a fact — see MkCalendarResult for why the one statement of success a caller may trust is the collection's presence in its own fresh listing.
      Specified by:
      mkCalendar in interface CalDavClient
      Parameters:
      endpoint - the declared server
      href - the collection's server-absolute path to create at
      displayName - the display name to create with
      color - the Apple calendar-color to set, or null to set none
      username - the account to authenticate as
      password - that account's password
      Returns:
      the raw outcome, refusals included — a server declining MKCALENDAR gives an answer the caller maps to its degraded states, not an error