carddav.rb 0.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9430d7a069267b260526ee0cfc83d48015b5ed3246d9cbb665929915d1146e8a
4
- data.tar.gz: 312d5939b30b496bcf96c747ed1e3aea39cafd53225dde43668c4bbe865e71b9
3
+ metadata.gz: 6f9a882a50cfe2e0a7b002c79b037846dbd1c6c4a597c4b96c5391d523597bc4
4
+ data.tar.gz: 86b1b9f289f156b3cf72f3016abf8557cf668f3791be5dee1c390b21db0bd412
5
5
  SHA512:
6
- metadata.gz: ab268a645a1fb8769ff36517f908469655d2407c553271892c220b90f303a61120824542a2505da6b6b51043b5c8e873a282da6541547bfee97baf0fdac41b33
7
- data.tar.gz: 131b86e9cdfea2c4bf6892effb27d1f8d5386a50721a812d2c65891b35ddb10f8f9c710afc2c075836261b86f94ac874ff33cf9d614429968b1812786547423e
6
+ metadata.gz: 013f037d7b799411fc78fbac0111ba6accf852bea198d77d88be73ff8c4884300bf17b298f32c6a49403e6294124365c972806410b28bbff8af345bad7c299e8
7
+ data.tar.gz: 22657fdcc2eec3bce60c230f749ff167995001449f6667a2ce8628f26c7d84afa7f066d5f85cc5651cec504a354b7f6e4d0e733541e58c9703d09f7a3e7c0cf4
data/CHANGELOG CHANGED
@@ -1,5 +1,19 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 20260708
4
+
5
+ 0.1.0: Querying without a body.
6
+
7
+ 1. ~ CardDAV#addressbook_query: body: now optional; a filterless prop-request body is constructed from properties: (default: getetag, address-data) when absent.
8
+ 2. ~ CardDAV#addressbook_multiget: body: now optional; constructed from hrefs: and properties: when absent.
9
+ 3. + CardDAV::DEFAULT_REPORT_PROPERTIES
10
+ 4. + CardDAV::DAV_PROPERTY_NAMES
11
+ 5. + CardDAV#addressbook_query_body
12
+ 6. + CardDAV#addressbook_multiget_body
13
+ 7. + CardDAV#property_element
14
+ 8. + CardDAV#property_elements
15
+ 9. + CardDAV#href_elements
16
+
3
17
  ## 20260623
4
18
 
5
19
  0.0.0: Initial release.
data/README.md CHANGED
@@ -48,7 +48,12 @@ Discovery begins by PROPFINDing the **path of the base URL**. Many servers do no
48
48
  ### Querying an addressbook
49
49
 
50
50
  ```ruby
51
+ # Every card in the addressbook — no caller XML needed.
52
+ result = carddav.addressbook_query('/addressbooks/user/contacts/')
53
+
54
+ # Or supply a hand-rolled filter XML body verbatim.
51
55
  result = carddav.addressbook_query('/addressbooks/user/contacts/', body: query_xml)
56
+
52
57
  result.resources.each do |resource|
53
58
  puts resource.href
54
59
  puts resource.address_data # the raw vCard string
@@ -58,7 +63,7 @@ end
58
63
  ### Fetching specific cards
59
64
 
60
65
  ```ruby
61
- result = carddav.addressbook_multiget('/addressbooks/user/contacts/', body: multiget_xml)
66
+ result = carddav.addressbook_multiget('/addressbooks/user/contacts/', hrefs: ['/addressbooks/user/contacts/a.vcf'])
62
67
  ```
63
68
 
64
69
  ### Creating an addressbook
@@ -75,8 +80,8 @@ CardDAV has no `MKCARDDAV` method; an addressbook is created with an **extended
75
80
  ### Protocol verbs (RFC 6352)
76
81
 
77
82
  - `mkcol(path, body:)` — §6.3.1. Create an addressbook collection via extended MKCOL. Returns a `WebDAV::Response`.
78
- - `addressbook_query(path, body:, depth:)` — §8.6. REPORT with a `<c:addressbook-query>` body. Returns a `CardDAV::MultiStatus`.
79
- - `addressbook_multiget(path, body:, depth:)` — §8.7. REPORT with a `<c:addressbook-multiget>` body. Returns a `CardDAV::MultiStatus`.
83
+ - `addressbook_query(path, body: nil, properties:, depth:)` — §8.6. REPORT with a `<c:addressbook-query>` body. Returns a `CardDAV::MultiStatus`. `body:` is optional: when omitted, a filterless prop-request body is built from `properties:` (default `%w{getetag address-data}`), so the common "return every card" query needs no caller XML. Pass `body:` to supply a hand-rolled filter verbatim.
84
+ - `addressbook_multiget(path, hrefs:, body: nil, properties:, depth:)` — §8.7. REPORT with a `<c:addressbook-multiget>` body. Returns a `CardDAV::MultiStatus`. `body:` is optional: when omitted, a body is built from `hrefs:` (the cards to fetch) and `properties:`. Raises `ArgumentError` if neither `body:` nor `hrefs:` is given.
80
85
 
81
86
  ### Discovery
82
87
 
@@ -106,7 +111,6 @@ This is a Layer 1 protocol release. Known boundaries:
106
111
  - **Read-only.** Conditional writes need `If-Match`, which webdav's `put` does not yet expose; writes arrive with Layer 2.
107
112
  - **No vCard parsing.** Use a vCard gem (e.g. `vpim`) on `resource.address_data` if you need parsed contacts now.
108
113
  - **Basic auth only.** No OAuth.
109
- - **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).
110
114
  - **No sync-collection.** Incremental sync is deferred.
111
115
 
112
116
 
@@ -123,10 +127,10 @@ rake
123
127
 
124
128
  Unit tests stub at the request boundary and need no network. A separate set of
125
129
  integration tests (`test/integration_test.rb`) run against a real CardDAV server
126
- via [VCR](https://github.com/vcr/vcr): they record real interactions into
127
- host- and credential-scrubbed cassettes under `test/cassettes/` on first run, then
128
- replay offline. Without a cassette and without credentials they skip, so the
129
- default suite stays green.
130
+ via [VCR](https://github.com/vcr/vcr): they replay host- and credential-scrubbed
131
+ cassettes committed under `test/cassettes/` (`discovery.yml`, `addressbook_query.yml`),
132
+ so discovery and query have round-tripped against a live server and the default
133
+ suite stays green offline. Supply credentials to re-record against your own account.
130
134
 
131
135
  To record against a live account, supply the server and credentials through the
132
136
  environment and run `rake`:
@@ -4,5 +4,5 @@
4
4
  class WebDAV; end
5
5
 
6
6
  class CardDAV < WebDAV
7
- VERSION = '0.0.0'
7
+ VERSION = '0.1.0'
8
8
  end
data/lib/carddav.rb CHANGED
@@ -8,8 +8,10 @@ require_relative './CardDAV/Resource'
8
8
  require_relative './CardDAV/VERSION'
9
9
 
10
10
  class CardDAV < WebDAV
11
- NAMESPACE = 'urn:ietf:params:xml:ns:carddav'
12
11
  DAV_NAMESPACE = 'DAV:'
12
+ DAV_PROPERTY_NAMES = %w{displayname getcontenttype getetag getlastmodified resourcetype}
13
+ DEFAULT_REPORT_PROPERTIES = %w{getetag address-data}
14
+ NAMESPACE = 'urn:ietf:params:xml:ns:carddav'
13
15
 
14
16
  # Protocol verbs (RFC 6352)
15
17
 
@@ -23,17 +25,20 @@ class CardDAV < WebDAV
23
25
  handle_response(response)
24
26
  end
25
27
 
26
- # RFC 6352 §8.6. REPORT with a <c:addressbook-query> body. The filter grammar
27
- # (prop-filter/text-match) is the caller's XML at Layer 1; a fluent filter
28
+ # RFC 6352 §8.6. REPORT with a <c:addressbook-query> body. When no body is
29
+ # given, a filterless prop-request body is constructed from properties:, so the
30
+ # common "return every card" query needs no caller XML. The filter grammar
31
+ # (prop-filter/text-match) remains the caller's XML at Layer 1; a fluent filter
28
32
  # builder — which would negotiate CARDDAV:supported-collation-set (§8.3.1) — is
29
33
  # reserved for Layer 3.
30
- def addressbook_query(path, body:, depth: '1')
31
- report_as_carddav(path, body: body, depth: depth)
34
+ def addressbook_query(path, body: nil, properties: DEFAULT_REPORT_PROPERTIES, depth: '1')
35
+ report_as_carddav(path, body: body || addressbook_query_body(properties), depth: depth)
32
36
  end
33
37
 
34
- # RFC 6352 §8.7. REPORT with a <c:addressbook-multiget> body.
35
- def addressbook_multiget(path, body:, depth: '0')
36
- report_as_carddav(path, body: body, depth: depth)
38
+ # RFC 6352 §8.7. REPORT with a <c:addressbook-multiget> body. When no body is
39
+ # given, one is constructed from hrefs: and properties:.
40
+ def addressbook_multiget(path, hrefs: nil, body: nil, properties: DEFAULT_REPORT_PROPERTIES, depth: '0')
41
+ report_as_carddav(path, body: body || addressbook_multiget_body(hrefs, properties), depth: depth)
37
42
  end
38
43
 
39
44
  # Discovery helpers. Return paths/strings. An object-oriented discovery API
@@ -135,4 +140,42 @@ class CardDAV < WebDAV
135
140
  </d:propfind>
136
141
  XML
137
142
  end
143
+
144
+ # Resolves a bare property name to its namespace prefix: the known DAV live
145
+ # properties get d:, everything else is assumed CardDAV and gets c:. A name
146
+ # that already carries a prefix (contains a colon, e.g. 'd:sync-token') is
147
+ # emitted verbatim — the escape hatch for DAV properties outside the known set,
148
+ # and the growth path towards namespace-qualified property names.
149
+ def property_element(name)
150
+ return "<#{name}/>" if name.include?(':')
151
+ "<#{DAV_PROPERTY_NAMES.include?(name) ? 'd' : 'c'}:#{name}/>"
152
+ end
153
+
154
+ def property_elements(properties)
155
+ properties.collect{|name| property_element(name)}.join
156
+ end
157
+
158
+ def href_elements(hrefs)
159
+ hrefs.collect{|href| "<d:href>#{href}</d:href>"}.join
160
+ end
161
+
162
+ def addressbook_query_body(properties)
163
+ <<~XML
164
+ <?xml version="1.0" encoding="UTF-8"?>
165
+ <c:addressbook-query xmlns:d="DAV:" xmlns:c="#{NAMESPACE}">
166
+ <d:prop>#{property_elements(properties)}</d:prop>
167
+ </c:addressbook-query>
168
+ XML
169
+ end
170
+
171
+ def addressbook_multiget_body(hrefs, properties)
172
+ raise ArgumentError, 'addressbook_multiget requires hrefs: when no body: is given' unless hrefs
173
+ <<~XML
174
+ <?xml version="1.0" encoding="UTF-8"?>
175
+ <c:addressbook-multiget xmlns:d="DAV:" xmlns:c="#{NAMESPACE}">
176
+ <d:prop>#{property_elements(properties)}</d:prop>
177
+ #{href_elements(hrefs)}
178
+ </c:addressbook-multiget>
179
+ XML
180
+ end
138
181
  end
data/test/carddav_test.rb CHANGED
@@ -164,4 +164,53 @@ describe CardDAV do
164
164
  end
165
165
  end
166
166
  end
167
+
168
+ describe "#addressbook_query without a body" do
169
+ it "constructs a filterless prop-request body from the default properties" do
170
+ captured = nil
171
+ responder = ->(verb, path, **kw){captured = kw[:body]; multistatus_response}
172
+ carddav.stub(:request, responder) do
173
+ carddav.addressbook_query('/addressbooks/user/contacts/')
174
+ end
175
+ _(captured).must_include '<c:addressbook-query'
176
+ _(captured).must_include '<d:getetag/>'
177
+ _(captured).must_include '<c:address-data/>'
178
+ _(captured).wont_include '<c:filter>'
179
+ end
180
+
181
+ it "uses a supplied body verbatim" do
182
+ captured = nil
183
+ responder = ->(verb, path, **kw){captured = kw[:body]; multistatus_response}
184
+ carddav.stub(:request, responder) do
185
+ carddav.addressbook_query('/addressbooks/user/contacts/', body: '<custom/>')
186
+ end
187
+ _(captured).must_equal '<custom/>'
188
+ end
189
+
190
+ it "emits a prefixed property name verbatim rather than re-namespacing it" do
191
+ captured = nil
192
+ responder = ->(verb, path, **kw){captured = kw[:body]; multistatus_response}
193
+ carddav.stub(:request, responder) do
194
+ carddav.addressbook_query('/addressbooks/user/contacts/', properties: %w{d:sync-token address-data})
195
+ end
196
+ _(captured).must_include '<d:sync-token/>'
197
+ _(captured).wont_include '<c:d:sync-token/>'
198
+ end
199
+ end
200
+
201
+ describe "#addressbook_multiget without a body" do
202
+ it "constructs a multiget body from hrefs" do
203
+ captured = nil
204
+ responder = ->(verb, path, **kw){captured = kw[:body]; multistatus_response}
205
+ carddav.stub(:request, responder) do
206
+ carddav.addressbook_multiget('/addressbooks/user/contacts/', hrefs: ['/addressbooks/user/contacts/a.vcf'])
207
+ end
208
+ _(captured).must_include '<c:addressbook-multiget'
209
+ _(captured).must_include '<d:href>/addressbooks/user/contacts/a.vcf</d:href>'
210
+ end
211
+
212
+ it "requires hrefs when no body is given" do
213
+ _{carddav.addressbook_multiget('/addressbooks/user/contacts/')}.must_raise ArgumentError
214
+ end
215
+ end
167
216
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: carddav.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - thoran
@@ -134,7 +134,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
134
134
  - !ruby/object:Gem::Version
135
135
  version: '0'
136
136
  requirements: []
137
- rubygems_version: 4.0.10
137
+ rubygems_version: 4.0.15
138
138
  specification_version: 4
139
139
  summary: A Ruby CardDAV client library.
140
140
  test_files: []