carddav.rb

A CardDAV client library for Ruby, built on webdav and implementing RFC 6352. This release ships Layer 1 (Protocol) of a planned three-layer ecosystem.

Installation

gem install carddav.rb

Or in your Gemfile:

gem 'carddav.rb'

Concepts

carddav.rb is designed in three layers, each a strict superset of the one below. This release ships Layer 1 only; the higher layers are future releases with reserved require paths.

  • Layer 1 — Protocol (this release). The CardDAV verbs, multistatus responses, and namespace-aware navigation accessors. The equivalent of a raw protocol library: it returns CardDAV-typed responses but does not parse vCard payloads or construct domain objects. Most users will want Layer 2 once it ships; until then, the protocol layer is usable directly by reading resource.address_data and parsing it with a vCard gem such as vpim.
  • Layer 2 — Objects (future, require 'carddav.rb/objects'). CardDAV::Card, CardDAV::Addressbook, and CardDAV::Principal value objects with vCard parsing and convenience predicates. The principal object will expose CARDDAV:principal-address (RFC 6352 §7.1.2) — the vCard that identifies the principal — which Layer 1 cannot surface, since discovery returns bare strings rather than principal objects.
  • Layer 3 — Queryable (future, require 'carddav.rb/queryable'). A Namo-backed CardDAV::Query::Addressbook exposing contacts as queryable rows with derived columns. Its fluent addressbook-query filter builder will negotiate CARDDAV:supported-collation-set (RFC 6352 §8.3.1) for text-match collations — also reserved until then.

Usage

require 'carddav.rb'

carddav = CardDAV.new('https://carddav.example.com/dav/', username: 'user', password: 'pass')

Discovery

principal = carddav.current_user_principal
home = carddav.addressbook_home_set(principal)
carddav.addressbooks(home).each{|href| puts href}

Each discovery helper defaults its argument to the result of the previous step, so carddav.addressbooks alone walks principal → home-set → addressbooks.

Discovery begins by PROPFINDing the path of the base URL. Many servers do not expose the current-user-principal at /, so point the base URL at the server's CardDAV context — e.g. https://carddav.example.com/dav/ rather than https://carddav.example.com/. (RFC 6764 .well-known/carddav redirects are not auto-followed for PROPFIND in this release.) You can also override the starting point per call: carddav.current_user_principal('/dav/').

Querying an addressbook

result = carddav.addressbook_query('/addressbooks/user/contacts/', body: query_xml)
result.resources.each do |resource|
  puts resource.href
  puts resource.address_data  # the raw vCard string
end

Fetching specific cards

result = carddav.addressbook_multiget('/addressbooks/user/contacts/', body: multiget_xml)

Creating an addressbook

carddav.mkcol('/addressbooks/user/new/', body: mkcol_xml)

CardDAV has no MKCARDDAV method; an addressbook is created with an extended MKCOL (RFC 6352 §6.3.1) — a standard MKCOL carrying a body that sets resourcetype to include <c:addressbook/>. CardDAV#mkcol overrides webdav's zero-body #mkcol to accept that optional body.

Methods

Protocol verbs (RFC 6352)

  • mkcol(path, body:) — §6.3.1. Create an addressbook collection via extended MKCOL. Returns a WebDAV::Response.
  • addressbook_query(path, body:, depth:) — §8.6. REPORT with a <c:addressbook-query> body. Returns a CardDAV::MultiStatus.
  • addressbook_multiget(path, body:, depth:) — §8.7. REPORT with a <c:addressbook-multiget> body. Returns a CardDAV::MultiStatus.

Discovery

  • current_user_principal(path = base URL path) — returns the principal URL string. PROPFINDs the base URL's path by default; pass a path to start discovery elsewhere.
  • addressbook_home_set(principal = current_user_principal) — returns the addressbook-home-set URL string.
  • addressbooks(home = addressbook_home_set) — returns an array of addressbook collection URL strings.

Responses

The REPORT verbs return CardDAV::MultiStatus, a type-preserving subclass of WebDAV::MultiStatus whose resources are CardDAV::Resource objects. Each CardDAV::Resource adds CardDAV-namespace navigation accessors over the underlying webdav resource:

  • href — the resource URL
  • address_data — the vCard string from <c:address-data>
  • addressbook_description<c:addressbook-description>
  • supported_address_data<c:supported-address-data>
  • max_resource_size<c:max-resource-size>
  • is_addressbook? — true when <d:resourcetype> includes <c:addressbook/>

These are strictly navigation: they return raw strings and values, never parsed vCard objects. Parsing is Layer 2's job.

Limitations

This is a Layer 1 protocol release. Known boundaries:

  • Read-only. Conditional writes need If-Match, which webdav's put does not yet expose; writes arrive with Layer 2.
  • No vCard parsing. Use a vCard gem (e.g. vpim) on resource.address_data if you need parsed contacts now.
  • Basic auth only. No OAuth.
  • Not yet verified against a live server. The integration suite is in place but ships without a committed cassette; supply credentials to record one and exercise it (see below).
  • No sync-collection. Incremental sync is deferred.

Dependencies

Testing

rake

Unit tests stub at the request boundary and need no network. A separate set of integration tests (test/integration_test.rb) run against a real CardDAV server via VCR: they record real interactions into host- and credential-scrubbed cassettes under test/cassettes/ on first run, then replay offline. Without a cassette and without credentials they skip, so the default suite stays green.

To record against a live account, supply the server and credentials through the environment and run rake:

CARDDAV_URL='https://your-carddav-host/' \
CARDDAV_USERNAME='you@example.com' \
CARDDAV_PASSWORD='app-password' \
rake

See test/cassettes/README.md for details.

Contributing

  1. Fork it https://github.com/thoran/carddav.rb/fork
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new pull request

Licence

MIT