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_dataand parsing it with a vCard gem such asvpim. - Layer 2 — Objects (future,
require 'carddav.rb/objects').CardDAV::Card,CardDAV::Addressbook, andCardDAV::Principalvalue objects with vCard parsing and convenience predicates. The principal object will exposeCARDDAV: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-backedCardDAV::Query::Addressbookexposing contacts as queryable rows with derived columns. Its fluentaddressbook-queryfilter builder will negotiateCARDDAV: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 aWebDAV::Response.addressbook_query(path, body:, depth:)— §8.6. REPORT with a<c:addressbook-query>body. Returns aCardDAV::MultiStatus.addressbook_multiget(path, body:, depth:)— §8.7. REPORT with a<c:addressbook-multiget>body. Returns aCardDAV::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 URLaddress_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'sputdoes not yet expose; writes arrive with Layer 2. - No vCard parsing. Use a vCard gem (e.g.
vpim) onresource.address_dataif 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
- Fork it https://github.com/thoran/carddav.rb/fork
- Create your feature branch (git checkout -b my-new-feature)
- Commit your changes (git commit -am 'Add some feature')
- Push to the branch (git push origin my-new-feature)
- Create a new pull request
Licence
MIT