dav4rack_ext 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.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.