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 +4 -4
- data/CHANGELOG +14 -0
- data/README.md +12 -8
- data/lib/CardDAV/VERSION.rb +1 -1
- data/lib/carddav.rb +51 -8
- data/test/carddav_test.rb +49 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6f9a882a50cfe2e0a7b002c79b037846dbd1c6c4a597c4b96c5391d523597bc4
|
|
4
|
+
data.tar.gz: 86b1b9f289f156b3cf72f3016abf8557cf668f3791be5dee1c390b21db0bd412
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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/',
|
|
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
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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`:
|
data/lib/CardDAV/VERSION.rb
CHANGED
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.
|
|
27
|
-
#
|
|
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
|
|
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
|
-
|
|
36
|
-
|
|
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.
|
|
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.
|
|
137
|
+
rubygems_version: 4.0.15
|
|
138
138
|
specification_version: 4
|
|
139
139
|
summary: A Ruby CardDAV client library.
|
|
140
140
|
test_files: []
|