dav4rack_ext 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.md CHANGED
@@ -13,6 +13,19 @@ This gem extends dav4rack to provide a CardDAV extension, CalDAV is not currentl
13
13
 
14
14
  Have a look at the examle folder, this is a standard Rack application and should run with any compliant server.
15
15
 
16
+ You can run the example with thin like this:
17
+
18
+ ```bash
19
+ $ cd example
20
+ $ bundle exec thin start
21
+ ```
22
+
23
+ Once the server is started you can connect to it using http://127.0.0.1:3000/u/cards with any login/password
24
+ (the example has no authentication set up)
25
+
26
+ # Supported clients
27
+ - Mac OS X (tested with Mountain Lion )
28
+ - iPhone 5.x and 6.x
16
29
 
17
30
  # Setting up development environment
18
31
 
@@ -6,8 +6,8 @@ module DAV4Rack
6
6
  DAV_EXTENSIONS = ["access-control", "addressbook"].freeze
7
7
 
8
8
  def self.app(root_path = '/', opts = {})
9
- logger = opts.delete(:logger) || ::Logger.new('/dev/null')
10
- current_user = opts.delete(:current_user)
9
+ logger = opts.delete(:logger) || ::Logger.new('/dev/null')
10
+ current_user = opts.delete(:current_user)
11
11
  root_uri_path = opts.delete(:root_uri_path) || root_path
12
12
 
13
13
  if (root_path != '/') && root_path[-1] == '/'
@@ -2,6 +2,13 @@ module DAV4Rack
2
2
  module Carddav
3
3
 
4
4
  class Controller < DAV4Rack::Controller
5
+ include DAV4Rack::Utils
6
+
7
+ NAMESPACES = {
8
+ 'D' => 'DAV:',
9
+ 'C' => 'urn:ietf:params:xml:ns:carddav'
10
+ }
11
+
5
12
  def initialize(*args, options, env)
6
13
  super(*args, options.merge(env: env))
7
14
  end
@@ -46,67 +53,45 @@ module DAV4Rack
46
53
  end
47
54
  "*[local-name()='#{name}' and namespace-uri()='#{ns_uri}']"
48
55
  end
49
-
50
- include DAV4Rack::Utils
51
-
52
- def xpath_element(name, ns_uri=:dav)
53
- case ns_uri
54
- when :dav
55
- ns_uri = 'DAV:'
56
- when :carddav
57
- ns_uri = 'urn:ietf:params:xml:ns:carddav'
56
+
57
+ def addressbook_multiget(request_document)
58
+ # TODO: Include a DAV:error response
59
+ # CardDAV §8.7 clearly states Depth must equal zero for this report
60
+ # But Apple's AddressBook.app sets the depth to infinity anyhow.
61
+ unless depth == 0 or depth == :infinity
62
+ render_xml(:error) do |xml|
63
+ xml.send :'invalid-depth'
58
64
  end
59
- "*[local-name()='#{name}' and namespace-uri()='#{ns_uri}']"
65
+ raise BadRequest
60
66
  end
61
-
62
- def addressbook_multiget(request_document)
63
- # TODO: Include a DAV:error response
64
- # CardDAV §8.7 clearly states Depth must equal zero for this report
65
- # But Apple's AddressBook.app sets the depth to infinity anyhow.
66
- unless depth == 0 or depth == :infinity
67
- render_xml(:error) do |xml|
68
- xml.send :'invalid-depth'
69
- end
70
- raise BadRequest
71
- end
72
67
 
73
- props = request_document.xpath("/#{xpath_element('addressbook-multiget', :carddav)}/#{xpath_element('prop')}").children.find_all(&:element?).map{|n|
74
- to_element_hash(n)
75
- }
76
- # Handle the address-data element
77
- # - Check for child properties (vCard fields)
78
- # - Check for mime-type and version. If present they must match vCard 3.0 for now since we don't support anything else.
79
- hrefs = request_document.xpath("/#{xpath_element('addressbook-multiget', :carddav)}/#{xpath_element('href')}").collect{|n|
80
- text = n.text
81
- # TODO: Make sure that the hrefs passed into the report are either paths or fully qualified URLs with the right host+protocol+port prefix
82
- path = URI.parse(text).path
83
- Logger.debug "Scanned this HREF: #{text} PATH: #{path}"
84
- text
85
- }.compact
86
-
87
- if hrefs.empty?
88
- xml_error(BadRequest) do |err|
89
- err.send :'href-missing'
90
- end
68
+ # props = request_document.css("C|addressbook-multiget C|address-data > C|prop", namespaces).map do |el|
69
+ props = []
70
+ request_document.css("C|addressbook-multiget > D|prop", NAMESPACES).each do |el|
71
+ el.children.select(&:element?).each do |child|
72
+ props << to_element_hash(child)
91
73
  end
74
+ end
75
+
76
+ # collect the requested urls
77
+ hrefs = request_document.css("C|addressbook-multiget D|href", NAMESPACES).map(&:content)
92
78
 
93
- multistatus do |xml|
94
- hrefs.each do |_href|
95
- xml.response do
96
- xml.href _href
79
+ multistatus do |xml|
80
+ hrefs.each do |_href|
81
+ xml.response do
82
+ xml.href _href
97
83
 
98
- path = File.split(URI.parse(_href).path).last
99
- Logger.debug "Creating child w/ ORIG=#{resource.public_path} HREF=#{_href} FILE=#{path}!"
100
-
101
- cur_resource = resource.is_self?(_href) ? resource : resource.find_child(File.split(path).last)
84
+ path = File.split(URI.parse(_href).path).last
85
+ Logger.debug "Creating child w/ ORIG=#{resource.public_path} HREF=#{_href} FILE=#{path}!"
102
86
 
103
- if cur_resource && cur_resource.exist?
104
- propstats(xml, get_properties(cur_resource, props))
105
- else
106
- xml.status "#{http_version} #{NotFound.status_line}"
107
- end
87
+ cur_resource = resource.is_self?(_href) ? resource : resource.find_child(File.split(path).last)
108
88
 
89
+ if cur_resource && cur_resource.exist?
90
+ propstats(xml, get_properties(cur_resource, props))
91
+ else
92
+ xml.status "#{http_version} #{NotFound.status_line}"
109
93
  end
94
+
110
95
  end
111
96
  end
112
97
  end
@@ -18,12 +18,7 @@ module DAV4Rack
18
18
  end
19
19
 
20
20
  def user_agent
21
- env = options[:env]
22
- if env
23
- env['HTTP_USER_AGENT'] || ""
24
- else
25
- ""
26
- end
21
+ options[:env]['HTTP_USER_AGENT'].to_s rescue ""
27
22
  end
28
23
 
29
24
  def router_params
@@ -31,10 +26,9 @@ module DAV4Rack
31
26
  end
32
27
 
33
28
  def setup
34
-
35
29
  @propstat_relative_path = true
36
30
  @root_xml_attributes = {
37
- 'xmlns:C' => CARDAV_NS,
31
+ 'xmlns:C' => CARDAV_NS,
38
32
  'xmlns:APPLE1' => 'http://calendarserver.org/ns/'
39
33
  }
40
34
  end
@@ -51,9 +45,8 @@ module DAV4Rack
51
45
  namespace = element[:ns_href]
52
46
 
53
47
  key = "#{namespace}*#{name}"
54
-
55
- handler = self.class.properties[key]
56
- if handler
48
+
49
+ if handler = self.class.properties[key]
57
50
  ret = instance_exec(element, &handler[0])
58
51
  # TODO: find better than that
59
52
  if ret.is_a?(String) && ret.include?('<')
@@ -93,7 +86,6 @@ module DAV4Rack
93
86
 
94
87
  def properties
95
88
  selected_properties = self.class.properties.reject{|key, arr| arr[1] == true }
96
- ret = {}
97
89
  selected_properties.keys.map do |key|
98
90
  ns, name = key.split('*')
99
91
  {:name => name, :ns_href => ns}
@@ -4,7 +4,7 @@ module DAV4Rack
4
4
  class AddressbookCollectionResource < Resource
5
5
 
6
6
  def exist?
7
- return true
7
+ true
8
8
  end
9
9
 
10
10
  def collection?
@@ -42,8 +42,6 @@ module DAV4Rack
42
42
  end
43
43
  end
44
44
  end
45
-
46
-
47
45
 
48
46
  def collection?
49
47
  false
@@ -56,10 +54,8 @@ module DAV4Rack
56
54
 
57
55
  def setup
58
56
  super
59
-
60
57
  @address_book = @options[:_parent_] || current_user.current_addressbook()
61
58
  @contact = @options[:_object_] || current_user.current_contact()
62
-
63
59
  end
64
60
 
65
61
  def put(request, response)
@@ -84,9 +80,8 @@ module DAV4Rack
84
80
 
85
81
  # If the client has explicitly stated they want a new contact
86
82
  raise Conflict if (want_new_contact and @contact)
87
-
88
- if_match = request.env['HTTP_IF_MATCH']
89
- if if_match
83
+
84
+ if if_match = request.env['HTTP_IF_MATCH']
90
85
  # client wants to update a contact, return an error if no
91
86
  # contact was found
92
87
  if (if_match == '*') || !@contact
@@ -109,18 +104,15 @@ module DAV4Rack
109
104
 
110
105
  @contact.update_from_vcard(vcf)
111
106
 
112
- if @contact.save
107
+ if @contact.save(user_agent)
113
108
  new_public = @public_path.split('/')[0...-1]
114
109
  new_public << @contact.uid.to_s
115
-
110
+
116
111
  @public_path = new_public.join('/')
117
112
  response['ETag'] = @contact.etag
118
- Created
119
- else
120
- # Mac OS X Contact will reload the contact
121
- # from the server
122
- raise Forbidden
123
113
  end
114
+
115
+ Created
124
116
  end
125
117
 
126
118
  def parent
@@ -4,15 +4,13 @@ module DAV4Rack
4
4
  class PrincipalResource < Resource
5
5
 
6
6
  def exist?
7
- ret = (path == '') || (path == '/')
8
- return ret
7
+ (path == '') || (path == '/')
9
8
  end
10
9
 
11
10
  def collection?
12
- return true
11
+ true
13
12
  end
14
-
15
-
13
+
16
14
  define_properties('DAV:') do
17
15
  property('alternate-URI-set') do
18
16
  # "<D:alternate-URI-set xmlns:D='DAV:' />"
@@ -66,7 +64,6 @@ module DAV4Rack
66
64
  EOS
67
65
  end
68
66
 
69
-
70
67
  property('resourcetype') do
71
68
  <<-EOS
72
69
  <resourcetype>
@@ -104,7 +101,6 @@ module DAV4Rack
104
101
  property('principal-address') do
105
102
  ""
106
103
  end
107
-
108
104
  end
109
105
  end
110
106
 
@@ -1,51 +1,45 @@
1
1
  require 'dav4rack/http_status'
2
2
 
3
3
  module DAV4RackExt
4
-
5
4
  class Handler
6
5
  # include DAV4Rack::HTTPStatus
7
-
8
- def initialize(options= {})
9
- @options = options.dup
10
- @logger = options.delete(:logger)
6
+
7
+ def initialize(options = {})
8
+ @options = options.dup
9
+ @logger = options.delete(:logger)
10
+ @controller_class = options.delete(:controller_class) || DAV4Rack::Controller
11
11
  end
12
12
 
13
13
  def call(env)
14
+ request = Rack::Request.new(env)
15
+ response = Rack::Response.new
16
+
14
17
  begin
15
- request = Rack::Request.new(env)
16
- response = Rack::Response.new
17
-
18
- controller = nil
19
- begin
20
-
21
- controller_class = @options[:controller_class] || DAV4Rack::Controller
22
- controller = controller_class.new(request, response, @options.dup, env)
23
- res = controller.send(request.request_method.downcase)
24
- response.status = res.code if res.respond_to?(:code)
25
-
26
- rescue DAV4Rack::HTTPStatus::Status => status
27
- response.status = status.code
28
- end
29
-
30
- response['Content-Length'] = response.body.to_s.bytesize unless response['Content-Length'] || !response.body.is_a?(String)
31
- response.body = [response.body] unless response.body.respond_to? :each
32
- response.status = response.status ? response.status.to_i : 200
33
- response.headers.keys.each do |k|
34
- response.headers[k] = response[k].to_s
35
- end
36
-
37
- while request.body.read(8192)
38
- # Apache wants the body dealt with, so just read it and junk it
39
- end
40
-
41
- response.finish
42
- rescue Exception => e
43
- @logger.error "DAV Error: #{e}\n#{e.backtrace.join("\n")}"
44
- raise e
18
+ controller = @controller_class.new(request, response, @options.dup, env)
19
+ res = controller.send(request.request_method.downcase)
20
+ response.status = res.code if res.respond_to?(:code)
21
+
22
+ rescue DAV4Rack::HTTPStatus::Status => status
23
+ response.status = status.code
24
+ end
25
+
26
+ response['Content-Length'] = response.body.to_s.bytesize unless response['Content-Length'] || !response.body.is_a?(String)
27
+ response.body = [response.body] unless response.body.respond_to? :each
28
+ response.status = response.status ? response.status.to_i : 200
29
+ response.headers.keys.each do |k|
30
+ response.headers[k] = response[k].to_s
45
31
  end
32
+
33
+ while request.body.read(8192)
34
+ # Apache wants the body dealt with, so just read it and junk it
35
+ end
36
+
37
+ response.finish
38
+ rescue Exception => e
39
+ @logger.error "DAV Error: #{e}\n#{e.backtrace.join("\n")}"
40
+ raise e
46
41
  end
47
-
48
- end
49
42
 
43
+ end
50
44
  end
51
45
 
@@ -1,55 +1,55 @@
1
- module Helpers
2
- module Properties
3
- class MethodMissingRedirector
4
- def initialize(*methods, &block)
5
- @block = block
6
- @methods = methods
7
- end
8
-
9
- def method_missing(name, *args, &block)
10
- if @methods.empty? || @methods.include?(name)
11
- @block.call(name, *args, &block)
12
- end
13
- end
14
- end
15
-
16
- def self.extended(klass)
17
- class << klass
18
- include MetaClassMethods
19
- end
20
- end
21
-
22
- # inheritable accessor
23
- module MetaClassMethods
24
- def define_property(namespace, name, explicit = false, &block)
25
- _properties["#{namespace}*#{name}"] = [block, explicit]
26
- end
27
-
28
- def properties
29
- inherited = superclass.respond_to?(:properties) ? superclass.properties : {}
30
- inherited.merge(_properties)
31
- end
32
-
33
- def _properties
34
- @properties ||= {}
35
- end
1
+ module Helpers
2
+ module Properties
3
+ class MethodMissingRedirector
4
+ def initialize(*methods, &block)
5
+ @block = block
6
+ @methods = methods
7
+ end
8
+
9
+ def method_missing(name, *args, &block)
10
+ if @methods.empty? || @methods.include?(name)
11
+ @block.call(name, *args, &block)
36
12
  end
37
-
38
- def define_properties(namespace, &block)
39
- explicit = false
40
- obj = MethodMissingRedirector.new(:property, :explicit) do |method_name, name, &block|
41
- if method_name == :property
42
- define_property(namespace, name.to_s, explicit, &block)
43
- elsif method_name == :explicit
44
- explicit = true
45
- block.call
46
- else
47
- raise NoMethodError, method_name
48
- end
49
- end
50
-
51
- obj.instance_eval(&block)
13
+ end
14
+ end
15
+
16
+ def self.extended(klass)
17
+ class << klass
18
+ include MetaClassMethods
19
+ end
20
+ end
21
+
22
+ # inheritable accessor
23
+ module MetaClassMethods
24
+ def define_property(namespace, name, explicit = false, &block)
25
+ _properties["#{namespace}*#{name}"] = [block, explicit]
26
+ end
27
+
28
+ def properties
29
+ inherited = superclass.respond_to?(:properties) ? superclass.properties : {}
30
+ inherited.merge(_properties)
31
+ end
32
+
33
+ def _properties
34
+ @properties ||= {}
35
+ end
36
+ end
37
+
38
+ def define_properties(namespace, &block)
39
+ explicit = false
40
+ obj = MethodMissingRedirector.new(:property, :explicit) do |method_name, name, &block|
41
+ if method_name == :property
42
+ define_property(namespace, name.to_s, explicit, &block)
43
+ elsif method_name == :explicit
44
+ explicit = true
45
+ block.call
46
+ else
47
+ raise NoMethodError, method_name
52
48
  end
53
-
54
49
  end
50
+
51
+ obj.instance_eval(&block)
55
52
  end
53
+
54
+ end
55
+ end
@@ -1,3 +1,3 @@
1
1
  module Dav4rackExt
2
- VERSION = "0.0.5"
2
+ VERSION = "0.0.6"
3
3
  end
@@ -255,6 +255,9 @@ END:VCARD
255
255
  # '*=' = include
256
256
  ensure_element_exists(response, %{D|href[text()*="1234-5678-9000-2"] + D|status[text()*="404"]}, 'D' => @dav_ns)
257
257
 
258
+ # <D:getetag>"23ba4d-ff11fb"</D:getetag>
259
+ etag = ensure_element_exists(response, %{D|href[text()*="1234-5678-9000-1"] + D|propstat > D|prop > D|getetag}, 'D' => @dav_ns)
260
+ etag.text.should == 'ETAG'
258
261
 
259
262
  vcard = ensure_element_exists(response, %{D|href[text()*="1234-5678-9000-1"] + D|propstat > D|prop > C|address-data}, 'D' => @dav_ns, 'C' => @carddav_ns)
260
263
  vcard.text.should.include? <<-EOS
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dav4rack_ext
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-09 00:00:00.000000000 Z
12
+ date: 2013-02-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: dav4rack
@@ -116,7 +116,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
116
116
  version: '0'
117
117
  segments:
118
118
  - 0
119
- hash: 2833907520245342566
119
+ hash: -2586617098076442739
120
120
  required_rubygems_version: !ruby/object:Gem::Requirement
121
121
  none: false
122
122
  requirements:
@@ -125,10 +125,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
125
125
  version: '0'
126
126
  segments:
127
127
  - 0
128
- hash: 2833907520245342566
128
+ hash: -2586617098076442739
129
129
  requirements: []
130
130
  rubyforge_project:
131
- rubygems_version: 1.8.23
131
+ rubygems_version: 1.8.24
132
132
  signing_key:
133
133
  specification_version: 3
134
134
  summary: CardDAV / CalDAV implementation.