simple_dav 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
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,7 @@
1
+ Add look up for other calendar than personnal
2
+ Add events support
3
+ Add tasks support
4
+ Add files support
5
+ Support for locking
6
+ Support for access control
7
+ Support for vcard group managment
@@ -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
- - 5
9
- version: 0.0.5
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: []