simple_dav 0.0.5 → 0.0.6
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.
- data/README +51 -0
- data/TODO +7 -0
- data/lib/simple_dav/address_book.rb +164 -0
- data/lib/simple_dav/base.rb +45 -0
- data/lib/simple_dav/card.rb +205 -0
- metadata +7 -2
data/README
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
This class acts as a Ruby dav client. First Goal is to sync sogo with RoR apps
|
2
|
+
|
3
|
+
First release allow to connect to sogo address book
|
4
|
+
|
5
|
+
INSTALL :
|
6
|
+
|
7
|
+
> gem install simple_dav
|
8
|
+
|
9
|
+
USE :
|
10
|
+
|
11
|
+
Example:
|
12
|
+
|
13
|
+
include SimpleDav
|
14
|
+
|
15
|
+
server = "mysogoserver.net"
|
16
|
+
ab = "personal"
|
17
|
+
user = "user"
|
18
|
+
pass = "secret"
|
19
|
+
|
20
|
+
adb = AddressBook.new(ab, :server => server,:verify => false, :ssl => true, :user => user, :pass => pass, :type => "sogo")
|
21
|
+
|
22
|
+
cards = Card.where(:email => "od@idfuze.com")
|
23
|
+
card = cards.first
|
24
|
+
card.to_s
|
25
|
+
> PRODID:-//IDFuze//SimpleDav//EN
|
26
|
+
> VERSION:3.0
|
27
|
+
> CLASS:PUBLIC
|
28
|
+
> PROFILE:VCARD
|
29
|
+
> N:titi
|
30
|
+
> EMAIL:od@idfuze.com
|
31
|
+
> UID:38294-1348845607-45923.vcf
|
32
|
+
> END:VCARD
|
33
|
+
card.update(:n => "Olivier", :fn => "dev")
|
34
|
+
> PRODID:-//IDFuze//SimpleDav//EN
|
35
|
+
> VERSION:3.0
|
36
|
+
> CLASS:PUBLIC
|
37
|
+
> PROFILE:VCARD
|
38
|
+
> N:Olivier
|
39
|
+
> EMAIL:od@idfuze.com
|
40
|
+
> UID:38294-1348845607-45923.vcf
|
41
|
+
> FN:dev
|
42
|
+
> END:VCARD
|
43
|
+
card.uid
|
44
|
+
> "UID:38294-1348845607-45923.vcf"
|
45
|
+
card.delete
|
46
|
+
|
47
|
+
|
48
|
+
Many thanks to SOGo team you make a wonderfull work and i really want to add a little brick with that !
|
49
|
+
|
50
|
+
|
51
|
+
Copyright (c) 2012 IDFUZE.COM / Olivier DIRRENBERGER
|
data/TODO
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
module SimpleDav
|
2
|
+
class AddressBook < Base
|
3
|
+
attr_reader :group
|
4
|
+
def initialize(params)
|
5
|
+
abu = "personal"
|
6
|
+
@vcard = nil
|
7
|
+
super(params)
|
8
|
+
@abu = @type == "sogo" ? "Contacts/#{abu}/" : "#{abu}"
|
9
|
+
@uri.path += @abu
|
10
|
+
@group = nil #store group uid
|
11
|
+
Card.adb = self
|
12
|
+
end
|
13
|
+
|
14
|
+
# select address book
|
15
|
+
def change_group(abu)
|
16
|
+
#todo select uid or nil if personnal
|
17
|
+
# old style folders
|
18
|
+
#@uri.path -= @abu
|
19
|
+
#@uri.path += (@abu = abu)
|
20
|
+
# new style v4 group
|
21
|
+
groups = Card.where({"X-ADDRESSBOOKSERVER-KIND" => "group", "f" => abu})
|
22
|
+
@group = groups && groups.first && groups.first.uid
|
23
|
+
end
|
24
|
+
|
25
|
+
# list available address books
|
26
|
+
#find all X-ADDRESSBOOKSERVER-KIND
|
27
|
+
def list(name)
|
28
|
+
#Card.where("X-ADDRESSBOOKSERVER-KIND" => "group")
|
29
|
+
if name
|
30
|
+
query = Nokogiri::XML::Builder.new(:encoding => "UTF-8") do |xml|
|
31
|
+
#xml.send('B:addressbook-query', 'xmlns:B' => "urn:ietf:params:xml:ns:carddav") do
|
32
|
+
xml.send('D:principal-property-search', 'xmlns:D' => "DAV:") do
|
33
|
+
xml.send('D:property-search') do
|
34
|
+
xml.send('D:prop') do
|
35
|
+
xml.send('D:displayname')
|
36
|
+
end
|
37
|
+
xml.send('D:match') do
|
38
|
+
xml << name
|
39
|
+
end
|
40
|
+
end
|
41
|
+
xml.send('D:prop') do
|
42
|
+
xml.send('C:addressbook-home-set', 'xmlns:C' => 'urn:ietf:params:xml:ns:carddav')
|
43
|
+
xml.send('D:displayname')
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
#end
|
49
|
+
|
50
|
+
end
|
51
|
+
else
|
52
|
+
query = Nokogiri::XML::Builder.new(:encoding => "UTF-8") do |xml|
|
53
|
+
xml.send('A:propfind', 'xmlns:A' => 'DAV:') do
|
54
|
+
xml.send('A:prop') do
|
55
|
+
xml.send('D:addressbook-home-set', 'xmlns:D' => "urn:ietf:params:xml:ns:carddav")
|
56
|
+
xml.send('D:directory-gateway', 'xmlns:D' => "urn:ietf:params:xml:ns:carddav")
|
57
|
+
xml.send('A:displayname')
|
58
|
+
xml.send('C:email-address-set', 'xmlns:C' => "http://calendarserver.org/ns/")
|
59
|
+
xml.send('A:principal-collection-set')
|
60
|
+
xml.send('A:principal-URL')
|
61
|
+
xml.send('A:resource-id')
|
62
|
+
xml.send('A:supported-report-set')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
headers = {
|
69
|
+
"content-Type" => "text/xml; charset=\"utf-8\"",
|
70
|
+
"depth" => 0,
|
71
|
+
"brief" => "t",
|
72
|
+
"Content-Length" => "#{query.to_xml.to_s.size}"
|
73
|
+
}
|
74
|
+
content = @client.request('REPORT', @uri, nil, query.to_xml.to_s, headers)
|
75
|
+
puts content.body + "<<<<<\n\n"
|
76
|
+
xml = Nokogiri::XML(content.body)
|
77
|
+
vcards = []
|
78
|
+
xml.xpath('//C:address-data').each do |card|
|
79
|
+
vcards << Card.new(card.text)
|
80
|
+
end
|
81
|
+
return vcards
|
82
|
+
end
|
83
|
+
|
84
|
+
# find addresse resource by uid
|
85
|
+
def self.find(uid)
|
86
|
+
Card.find(self, uid)
|
87
|
+
end
|
88
|
+
|
89
|
+
# create collection : not working actually
|
90
|
+
def create_folder(name, displayname = "", description = "")
|
91
|
+
query = Nokogiri::XML::Builder.new(:encoding => "UTF-8") do |xml|
|
92
|
+
xml.send('D:mkcol', 'xmlns:D' => "DAV:", 'xmlns:C' => "urn:ietf:params:xml:ns:carddav") do
|
93
|
+
xml.send('D:set') do
|
94
|
+
xml.send('D:prop')
|
95
|
+
xml.send('D:resourcetype') do
|
96
|
+
xml.send('D:collection')
|
97
|
+
xml.send('C:addressbook')
|
98
|
+
end
|
99
|
+
end
|
100
|
+
xml.send('D:displayname') do
|
101
|
+
xml << displayname
|
102
|
+
end
|
103
|
+
xml.send('C:addressbook-description', 'xml:lang' => "en") do
|
104
|
+
xml << description
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
headers = {
|
110
|
+
"content-Type" => "text/xml; charset=\"utf-8\"",
|
111
|
+
"Content-Length" => query.to_xml.to_s.size
|
112
|
+
}
|
113
|
+
|
114
|
+
res = @client.request('MKCOL', @uri, nil, query.to_xml.to_s, headers)
|
115
|
+
|
116
|
+
if res.status < 200 or res.status >= 300
|
117
|
+
raise "create failed: #{res.inspect}"
|
118
|
+
else
|
119
|
+
true
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# create address book group
|
124
|
+
def create(name, description = "")
|
125
|
+
uid = "#{gen_uid}"
|
126
|
+
|
127
|
+
@vcard = Card.new(GROUPVCF)
|
128
|
+
@vcard.add_attribute("X-ADDRESSBOOKSERVER-KIND", "group")
|
129
|
+
@vcard.add_attribute("rev", Time.now.utc.iso8601(2))
|
130
|
+
@vcard.add_attribute("uid", uid)
|
131
|
+
@vcard.add_attribute(:f, name)
|
132
|
+
@vcard.add_attribute(:fn, description)
|
133
|
+
|
134
|
+
headers = {
|
135
|
+
"If-None-Match" => "*",
|
136
|
+
"Content-Type" => "text/vcard",
|
137
|
+
"Content-Length" => @vcard.to_s.size
|
138
|
+
}
|
139
|
+
|
140
|
+
unc = @uri.clone
|
141
|
+
unc.path += "#{uid}.vcf"
|
142
|
+
res = @client.request('PUT', unc, nil, @vcard.to_s, headers)
|
143
|
+
|
144
|
+
if res.status < 200 or res.status >= 300
|
145
|
+
@uid = nil
|
146
|
+
raise "create failed: #{res.inspect}"
|
147
|
+
else
|
148
|
+
@uid = @group = uid
|
149
|
+
end
|
150
|
+
@vcard
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
# access to current vcard object
|
155
|
+
def vcard
|
156
|
+
@vcard
|
157
|
+
end
|
158
|
+
|
159
|
+
def debug_dev=(dev)
|
160
|
+
@client.debug_dev = dev
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
BASEVCF = <<EOF
|
2
|
+
BEGIN:VCARD
|
3
|
+
PRODID:-//IDFuze//SimpleDav//EN
|
4
|
+
VERSION:3.0
|
5
|
+
CLASS:PUBLIC
|
6
|
+
PROFILE:VCARD
|
7
|
+
END:VCARD
|
8
|
+
EOF
|
9
|
+
|
10
|
+
GROUPVCF = BASEVCF
|
11
|
+
|
12
|
+
module SimpleDav
|
13
|
+
class Base
|
14
|
+
attr_reader :headers, :uri, :client
|
15
|
+
|
16
|
+
# generate unique id to create resources
|
17
|
+
def gen_uid
|
18
|
+
"#{rand(100000)}-#{Time.now.to_i}-#{rand(100000)}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(params = {})
|
22
|
+
begin
|
23
|
+
url = params[:ssl] ? "https://#{params[:server]}/" : "http://#{params[:server]}/"
|
24
|
+
url += case (@type = params[:type])
|
25
|
+
when "sogo" then "SOGo/dav/#{params[:user]}/"
|
26
|
+
else ""
|
27
|
+
end
|
28
|
+
@uri = URI.parse(url)
|
29
|
+
@uid = nil
|
30
|
+
@headers = {}
|
31
|
+
#open(uri) if uri
|
32
|
+
proxy = ENV['HTTP_PROXY'] || ENV['http_proxy'] || nil
|
33
|
+
@client = HTTPClient.new(proxy)
|
34
|
+
@client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE unless params[:verify]
|
35
|
+
if (@user = params[:user]) && params[:pass]
|
36
|
+
@client.set_basic_auth(@uri, @user, params[:pass])
|
37
|
+
end
|
38
|
+
rescue
|
39
|
+
raise RuntimeError.exception("Init Failed!! (#{$!.to_s})")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,205 @@
|
|
1
|
+
# attributes : n|email|title|nickname|tel|bday|fn|org|note|uid
|
2
|
+
# todo change for another vcard managment class
|
3
|
+
module SimpleDav
|
4
|
+
class Card
|
5
|
+
class << self; attr_accessor :adb end
|
6
|
+
@adb = nil
|
7
|
+
|
8
|
+
def initialize(text = BASEVCF)
|
9
|
+
@plain = text
|
10
|
+
return self
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.find(uid)
|
14
|
+
where(:uid => uid)
|
15
|
+
end
|
16
|
+
|
17
|
+
# create address resource
|
18
|
+
def self.create(params)
|
19
|
+
@vcard = Card.new
|
20
|
+
params.each do |k,v|
|
21
|
+
@vcard.update_attribute(k,v)
|
22
|
+
end
|
23
|
+
|
24
|
+
headers = {
|
25
|
+
"If-None-Match" => "*",
|
26
|
+
"Content-Type" => "text/vcard",
|
27
|
+
"Content-Length" => @vcard.to_s.size
|
28
|
+
}
|
29
|
+
uid = "#{adb.gen_uid}.vcf"
|
30
|
+
|
31
|
+
@vcard.update_attribute(:uid, uid)
|
32
|
+
if adb && adb.group
|
33
|
+
@vcard.add_attribute("X-ADDRESSBOOKSERVER-MEMBER", "urn:uuid:#{adb.group}")
|
34
|
+
end
|
35
|
+
|
36
|
+
unc = adb.uri.clone
|
37
|
+
unc.path += uid
|
38
|
+
res = adb.client.request('PUT', unc, nil, @vcard.to_s, headers)
|
39
|
+
|
40
|
+
if res.status < 200 or res.status >= 300
|
41
|
+
@uid = nil
|
42
|
+
raise "create failed: #{res.inspect}"
|
43
|
+
else
|
44
|
+
@uid = uid
|
45
|
+
end
|
46
|
+
@vcard
|
47
|
+
end
|
48
|
+
|
49
|
+
def update(params)
|
50
|
+
params.each do |k,v|
|
51
|
+
update_attribute(k,v)
|
52
|
+
end
|
53
|
+
|
54
|
+
headers = {
|
55
|
+
"Content-Type" => "text/vcard",
|
56
|
+
"Content-Length" => @plain.size
|
57
|
+
}
|
58
|
+
uid = self.uid
|
59
|
+
|
60
|
+
unc = Card.adb.uri.clone
|
61
|
+
unc.path += uid
|
62
|
+
res = Card.adb.client.request('PUT', unc, nil, @plain, headers)
|
63
|
+
|
64
|
+
if res.status < 200 or res.status >= 300
|
65
|
+
@uid = nil
|
66
|
+
raise "create failed: #{res.inspect}"
|
67
|
+
else
|
68
|
+
@uid = uid
|
69
|
+
end
|
70
|
+
self
|
71
|
+
end
|
72
|
+
|
73
|
+
def delete
|
74
|
+
if @uid && Card.adb
|
75
|
+
|
76
|
+
headers = {
|
77
|
+
#"If-None-Match" => "*",
|
78
|
+
"Content-Type" => "text/xml; charset=\"utf-8\""
|
79
|
+
}
|
80
|
+
unc = adb.uri.clone
|
81
|
+
unc.path += @uid
|
82
|
+
res = adb.client.request('DELETE', unc, nil, nil, headers)
|
83
|
+
|
84
|
+
if res.status < 200 or res.status >= 300
|
85
|
+
@uid = nil
|
86
|
+
raise "delete failed: #{res.inspect}"
|
87
|
+
else
|
88
|
+
@uid = nil
|
89
|
+
true
|
90
|
+
end
|
91
|
+
else
|
92
|
+
raise "Failed : no connection or null id"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def retreive
|
97
|
+
path = "#{self.uid}.vcf"
|
98
|
+
unc = adb.uri.clone
|
99
|
+
unc.path += path
|
100
|
+
res = adb.client.request('GET', unc)
|
101
|
+
|
102
|
+
if res.status < 200 or res.status >= 300
|
103
|
+
@uid = nil
|
104
|
+
raise "delete failed: #{res.inspect}"
|
105
|
+
else
|
106
|
+
@plain = res.body
|
107
|
+
@uid = uid
|
108
|
+
true
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def update_attribute(a, v)
|
113
|
+
@plain.match(/^#{a.to_s.upcase}:(.+)$/) ? @plain.gsub!(/^#{a.to_s.upcase}:(.+)$/, "#{a.to_s.upcase}:#{v}") : add_attribute(a, v)
|
114
|
+
end
|
115
|
+
|
116
|
+
def add_attribute(a, v)
|
117
|
+
@plain["END:VCARD"] = "#{a.to_s.upcase}:#{v}\nEND:VCARD"
|
118
|
+
end
|
119
|
+
|
120
|
+
def method_missing(meth, *args, &block)
|
121
|
+
case meth.to_s
|
122
|
+
when /^((n|email|title|nickname|tel|bday|fn|org|note|uid|X-ADDRESSBOOKSERVER-KIND)=?)$/
|
123
|
+
run_on_field($1, *args, &block)
|
124
|
+
when /^find_by_(.+)$/
|
125
|
+
run_find_by_method($1, *args, &block)
|
126
|
+
else
|
127
|
+
super
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def run_on_field(attrs, *args, &block)
|
132
|
+
field = attrs.upcase
|
133
|
+
field["EMAIL"] = "EMAIL;TYPE=work" if field.match("EMAIL")
|
134
|
+
|
135
|
+
if field =~ /=/
|
136
|
+
field = field[0..-2]
|
137
|
+
update_attribute(field, args)
|
138
|
+
else
|
139
|
+
if m = @plain.match(/#{field}:(.+)$/)
|
140
|
+
m[1]
|
141
|
+
else
|
142
|
+
nil
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# find where RoR style
|
148
|
+
def self.where(conditions)
|
149
|
+
limit = nil #1
|
150
|
+
query = Nokogiri::XML::Builder.new(:encoding => "UTF-8") do |xml|
|
151
|
+
xml.send('B:addressbook-query', 'xmlns:B' => "urn:ietf:params:xml:ns:carddav") do
|
152
|
+
xml.send('A:prop', 'xmlns:A' => "DAV:") do
|
153
|
+
xml.send('A:getetag')
|
154
|
+
xml.send('B:address-data')
|
155
|
+
|
156
|
+
end
|
157
|
+
#xml.send('C:filter', 'test' => "anyof") do
|
158
|
+
xml.send('B:filter', 'test' => 'anyof') do
|
159
|
+
conditions.each do |k,v|
|
160
|
+
xml.send('B:prop-filter', 'test' => 'allof','name' => k.to_s) do
|
161
|
+
#xml.send('C:text-match', 'collation' => "i;unicode-casemap", 'match-type' => "contains") do
|
162
|
+
xml.send('B:text-match', 'collation' => "i;unicode-casemap", 'match-type' => "contains") do
|
163
|
+
xml << v
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
if limit
|
169
|
+
xml.send('C:limit') do
|
170
|
+
xml.send('C:nresults') do
|
171
|
+
xml << "#{limit}"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
179
|
+
headers = {
|
180
|
+
"content-Type" => "text/xml; charset=\"utf-8\"",
|
181
|
+
"depth" => 1,
|
182
|
+
"Content-Length" => "#{query.to_xml.to_s.size}"
|
183
|
+
}
|
184
|
+
content = adb.client.request('REPORT', adb.uri, nil, query.to_xml.to_s, headers)
|
185
|
+
xml = Nokogiri::XML(content.body)
|
186
|
+
vcards = []
|
187
|
+
xml.xpath('//C:address-data').each do |card|
|
188
|
+
vcards << Card.new(card.text)
|
189
|
+
end
|
190
|
+
return vcards
|
191
|
+
end
|
192
|
+
|
193
|
+
def run_find_by_method(attrs, *args, &block)
|
194
|
+
attrs = attrs.split('_and_')
|
195
|
+
attrs_with_args = [attrs, args].transpose
|
196
|
+
conditions = Hash[attrs_with_args]
|
197
|
+
where(conditions)
|
198
|
+
end
|
199
|
+
|
200
|
+
def to_s
|
201
|
+
@plain.to_s
|
202
|
+
end
|
203
|
+
|
204
|
+
end
|
205
|
+
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
8
|
+
- 6
|
9
|
+
version: 0.0.6
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Olivier DIRRENBERGER
|
@@ -54,7 +54,12 @@ extensions: []
|
|
54
54
|
extra_rdoc_files: []
|
55
55
|
|
56
56
|
files:
|
57
|
+
- lib/simple_dav/address_book.rb
|
58
|
+
- lib/simple_dav/base.rb
|
59
|
+
- lib/simple_dav/card.rb
|
57
60
|
- lib/simple_dav.rb
|
61
|
+
- README
|
62
|
+
- TODO
|
58
63
|
has_rdoc: true
|
59
64
|
homepage: https://github.com/reivilo/simple_dav
|
60
65
|
licenses: []
|