async-caldav 1.2.0 → 1.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e23cfa0fc31bf66c4994ae8f4d8926c7bdafe3218e6fe6bfd528c8506953d090
4
- data.tar.gz: 657db45a19ee2ef645d50c9ebfaa4c4884dbcd3438d0952fd13c78b9bdac42e4
3
+ metadata.gz: 0517bf6a8c74d0d76b09a3ea8115e6dd65114f3991e25d3c97578ec69cb64a9c
4
+ data.tar.gz: 5f83b3cb2b118fe7ef22eef7140df410eea5f167f41e86b317dd260dbc6e0fc7
5
5
  SHA512:
6
- metadata.gz: 4ec7a0a7735fe520949cbda2fe9bc67b8deed47e08496fbd8e1a3d3ae1624be342373fd6d614693b494964ec3f8f5c2e1bbad619d2e9dbd9feca64d978a50fa1
7
- data.tar.gz: ebc1e849267dc0cf28493e2530e54c7195e46e8db9526b9f1e9308cb55f172cd0b2738488c9162365f4096d09f349422d4d49b0e12c96ce98136f1c29a9c173f
6
+ metadata.gz: 46c8c7806937d6945f1a1766a8ab6999d272b0e3d599a34bdc33fab780f10a106ae0e36ae380a45f17bf9a7af5a5bb302b9c8f9ec9cd1a8c84584e7b30baebe9
7
+ data.tar.gz: ec019c2ee4e1d7e53ab85dabcb74098c6f03cbf97e3808df67f2e7ebd270a994a67ddd2cd2de970e8080b7f2588955534c9587469f67228bffac8ddef333b683
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "bundler/setup"
4
4
  require "scampi"
5
+ require "builder"
5
6
 
6
7
  module Async
7
8
  module Caldav
@@ -16,22 +17,13 @@ module Async
16
17
  end
17
18
 
18
19
  def contacts(filter: nil)
19
- body = if filter
20
- <<~XML
21
- <?xml version="1.0" encoding="UTF-8"?>
22
- <cr:addressbook-query xmlns:d="DAV:" xmlns:cr="urn:ietf:params:xml:ns:carddav">
23
- <d:prop><d:getetag/><cr:address-data/></d:prop>
24
- #{filter}
25
- </cr:addressbook-query>
26
- XML
27
- else
28
- <<~XML
29
- <?xml version="1.0" encoding="UTF-8"?>
30
- <cr:addressbook-query xmlns:d="DAV:" xmlns:cr="urn:ietf:params:xml:ns:carddav">
31
- <d:prop><d:getetag/><cr:address-data/></d:prop>
32
- </cr:addressbook-query>
33
- XML
20
+ x = Builder::XmlMarkup.new
21
+ x.instruct! :xml, version: "1.0", encoding: "UTF-8"
22
+ x.tag!("cr:addressbook-query", "xmlns:d" => "DAV:", "xmlns:cr" => "urn:ietf:params:xml:ns:carddav") do
23
+ x.tag!("d:prop") { x.tag!("d:getetag"); x.tag!("cr:address-data") }
24
+ x << filter if filter
34
25
  end
26
+ body = x.target!
35
27
 
36
28
  status, _, resp_body = @client.request('REPORT', @path, body: body, headers: { 'Content-Type' => 'text/xml' })
37
29
  raise Error, "REPORT failed: #{status}" unless status == 207
@@ -95,17 +87,17 @@ module Async
95
87
  end
96
88
 
97
89
  def proppatch(displayname: nil)
98
- props = []
99
- props << "<d:displayname>#{Protocol::Caldav::Xml.escape(displayname)}</d:displayname>" if displayname
100
-
101
- body = <<~XML
102
- <?xml version="1.0" encoding="UTF-8"?>
103
- <d:propertyupdate xmlns:d="DAV:">
104
- <d:set><d:prop>#{props.join}</d:prop></d:set>
105
- </d:propertyupdate>
106
- XML
90
+ x = Builder::XmlMarkup.new
91
+ x.instruct! :xml, version: "1.0", encoding: "UTF-8"
92
+ x.tag!("d:propertyupdate", "xmlns:d" => "DAV:") do
93
+ x.tag!("d:set") do
94
+ x.tag!("d:prop") do
95
+ x.tag!("d:displayname", displayname) if displayname
96
+ end
97
+ end
98
+ end
107
99
 
108
- status, = @client.request('PROPPATCH', @path, body: body, headers: { 'Content-Type' => 'text/xml' })
100
+ status, = @client.request('PROPPATCH', @path, body: x.target!, headers: { 'Content-Type' => 'text/xml' })
109
101
  raise Error, "PROPPATCH failed: #{status}" unless status == 207
110
102
 
111
103
  @displayname = displayname if displayname
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "bundler/setup"
4
4
  require "scampi"
5
+ require "builder"
5
6
 
6
7
  module Async
7
8
  module Caldav
@@ -19,22 +20,13 @@ module Async
19
20
  end
20
21
 
21
22
  def events(filter: nil)
22
- body = if filter
23
- <<~XML
24
- <?xml version="1.0" encoding="UTF-8"?>
25
- <c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
26
- <d:prop><d:getetag/><c:calendar-data/></d:prop>
27
- #{filter}
28
- </c:calendar-query>
29
- XML
30
- else
31
- <<~XML
32
- <?xml version="1.0" encoding="UTF-8"?>
33
- <c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
34
- <d:prop><d:getetag/><c:calendar-data/></d:prop>
35
- </c:calendar-query>
36
- XML
23
+ x = Builder::XmlMarkup.new
24
+ x.instruct! :xml, version: "1.0", encoding: "UTF-8"
25
+ x.tag!("c:calendar-query", "xmlns:d" => "DAV:", "xmlns:c" => "urn:ietf:params:xml:ns:caldav") do
26
+ x.tag!("d:prop") { x.tag!("d:getetag"); x.tag!("c:calendar-data") }
27
+ x << filter if filter
37
28
  end
29
+ body = x.target!
38
30
 
39
31
  status, _, resp_body = @client.request('REPORT', @path, body: body, headers: { 'Content-Type' => 'text/xml' })
40
32
  raise Error, "REPORT failed: #{status}" unless status == 207
@@ -104,19 +96,19 @@ module Async
104
96
  end
105
97
 
106
98
  def proppatch(displayname: nil, description: nil, color: nil)
107
- props = []
108
- props << "<d:displayname>#{Protocol::Caldav::Xml.escape(displayname)}</d:displayname>" if displayname
109
- props << "<c:calendar-description>#{Protocol::Caldav::Xml.escape(description)}</c:calendar-description>" if description
110
- props << "<x:calendar-color>#{Protocol::Caldav::Xml.escape(color)}</x:calendar-color>" if color
111
-
112
- body = <<~XML
113
- <?xml version="1.0" encoding="UTF-8"?>
114
- <d:propertyupdate xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav" xmlns:x="http://apple.com/ns/ical/">
115
- <d:set><d:prop>#{props.join}</d:prop></d:set>
116
- </d:propertyupdate>
117
- XML
118
-
119
- status, = @client.request('PROPPATCH', @path, body: body, headers: { 'Content-Type' => 'text/xml' })
99
+ x = Builder::XmlMarkup.new
100
+ x.instruct! :xml, version: "1.0", encoding: "UTF-8"
101
+ x.tag!("d:propertyupdate", "xmlns:d" => "DAV:", "xmlns:c" => "urn:ietf:params:xml:ns:caldav", "xmlns:x" => "http://apple.com/ns/ical/") do
102
+ x.tag!("d:set") do
103
+ x.tag!("d:prop") do
104
+ x.tag!("d:displayname", displayname) if displayname
105
+ x.tag!("c:calendar-description", description) if description
106
+ x.tag!("x:calendar-color", color) if color
107
+ end
108
+ end
109
+ end
110
+
111
+ status, = @client.request('PROPPATCH', @path, body: x.target!, headers: { 'Content-Type' => 'text/xml' })
120
112
  raise Error, "PROPPATCH failed: #{status}" unless status == 207
121
113
 
122
114
  @displayname = displayname if displayname
@@ -126,14 +118,17 @@ module Async
126
118
  end
127
119
 
128
120
  def sync(token: nil)
129
- token_xml = token ? "<d:sync-token>#{token}</d:sync-token>" : "<d:sync-token/>"
130
- body = <<~XML
131
- <?xml version="1.0" encoding="UTF-8"?>
132
- <d:sync-collection xmlns:d="DAV:">
133
- <d:prop><d:getetag/></d:prop>
134
- #{token_xml}
135
- </d:sync-collection>
136
- XML
121
+ x = Builder::XmlMarkup.new
122
+ x.instruct! :xml, version: "1.0", encoding: "UTF-8"
123
+ x.tag!("d:sync-collection", "xmlns:d" => "DAV:") do
124
+ x.tag!("d:prop") { x.tag!("d:getetag") }
125
+ if token
126
+ x.tag!("d:sync-token", token)
127
+ else
128
+ x.tag!("d:sync-token")
129
+ end
130
+ end
131
+ body = x.target!
137
132
 
138
133
  status, _, resp_body = @client.request('REPORT', @path, body: body, headers: { 'Content-Type' => 'text/xml' })
139
134
 
@@ -82,18 +82,18 @@ module Async
82
82
 
83
83
  def create_calendar(name, displayname: nil, description: nil, color: nil)
84
84
  path = "/calendars/#{@user}/#{name}/"
85
- props = []
86
- props << "<d:displayname>#{Protocol::Caldav::Xml.escape(displayname || name)}</d:displayname>" if displayname || name
87
- props << "<c:calendar-description>#{Protocol::Caldav::Xml.escape(description)}</c:calendar-description>" if description
88
-
89
- body = <<~XML
90
- <?xml version="1.0" encoding="UTF-8"?>
91
- <c:mkcalendar xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
92
- <d:set><d:prop>#{props.join}</d:prop></d:set>
93
- </c:mkcalendar>
94
- XML
95
-
96
- status, = request('MKCALENDAR', path, body: body, headers: { 'Content-Type' => 'text/xml' })
85
+ x = Builder::XmlMarkup.new
86
+ x.instruct! :xml, version: "1.0", encoding: "UTF-8"
87
+ x.tag!("c:mkcalendar", "xmlns:d" => "DAV:", "xmlns:c" => "urn:ietf:params:xml:ns:caldav") do
88
+ x.tag!("d:set") do
89
+ x.tag!("d:prop") do
90
+ x.tag!("d:displayname", displayname || name) if displayname || name
91
+ x.tag!("c:calendar-description", description) if description
92
+ end
93
+ end
94
+ end
95
+
96
+ status, = request('MKCALENDAR', path, body: x.target!, headers: { 'Content-Type' => 'text/xml' })
97
97
  raise Error, "MKCALENDAR failed: #{status}" unless status == 201
98
98
 
99
99
  Calendar.new(self, path, displayname: displayname || name, description: description, color: color)
@@ -101,17 +101,18 @@ module Async
101
101
 
102
102
  def create_addressbook(name, displayname: nil)
103
103
  path = "/addressbooks/#{@user}/#{name}/"
104
- body = <<~XML
105
- <?xml version="1.0" encoding="UTF-8"?>
106
- <d:mkcol xmlns:d="DAV:" xmlns:cr="urn:ietf:params:xml:ns:carddav">
107
- <d:set><d:prop>
108
- <d:resourcetype><d:collection/><cr:addressbook/></d:resourcetype>
109
- <d:displayname>#{Protocol::Caldav::Xml.escape(displayname || name)}</d:displayname>
110
- </d:prop></d:set>
111
- </d:mkcol>
112
- XML
113
-
114
- status, = request('MKCOL', path, body: body, headers: { 'Content-Type' => 'text/xml' })
104
+ x = Builder::XmlMarkup.new
105
+ x.instruct! :xml, version: "1.0", encoding: "UTF-8"
106
+ x.tag!("d:mkcol", "xmlns:d" => "DAV:", "xmlns:cr" => "urn:ietf:params:xml:ns:carddav") do
107
+ x.tag!("d:set") do
108
+ x.tag!("d:prop") do
109
+ x.tag!("d:resourcetype") { x.tag!("d:collection"); x.tag!("cr:addressbook") }
110
+ x.tag!("d:displayname", displayname || name)
111
+ end
112
+ end
113
+ end
114
+
115
+ status, = request('MKCOL', path, body: x.target!, headers: { 'Content-Type' => 'text/xml' })
115
116
  raise Error, "MKCOL failed: #{status}" unless status == 201
116
117
 
117
118
  Addressbook.new(self, path, displayname: displayname || name)
@@ -13,6 +13,7 @@ module Async
13
13
  def call(path:, storage:, user:, headers: {}, body: nil, **)
14
14
  depth = headers['depth'] || '1'
15
15
  propname = body&.include?('propname')
16
+ build = propname ? :build_propname : :build_propfind
16
17
 
17
18
  col_path = path.ensure_trailing_slash
18
19
  collection = storage.get_collection(col_path.to_s)
@@ -23,97 +24,86 @@ module Async
23
24
  return [404, { 'content-type' => 'text/plain' }, ['Not Found']]
24
25
  end
25
26
 
26
- responses = []
27
-
28
- if collection
29
- col = Protocol::Caldav::Collection.new(
30
- path: col_path,
31
- type: collection[:type],
32
- displayname: collection[:displayname],
33
- description: collection[:description],
34
- color: collection[:color],
35
- props: collection[:props]
36
- )
37
- responses << (propname ? col.to_propname_xml : col.to_propfind_xml)
38
-
39
- if depth != '0'
40
- # Child collections
41
- storage.list_collections(col_path.to_s).each do |child_path, child_data|
42
- child_p = Protocol::Caldav::Path.new(child_path, storage_class: storage)
43
- child_col = Protocol::Caldav::Collection.new(
44
- path: child_p,
45
- type: child_data[:type],
46
- displayname: child_data[:displayname],
47
- description: child_data[:description],
48
- color: child_data[:color],
49
- props: child_data[:props]
50
- )
51
- responses << (propname ? child_col.to_propname_xml : child_col.to_propfind_xml)
27
+ xml = Protocol::Caldav::Multistatus.new.to_xml do |x|
28
+ if collection
29
+ col = Protocol::Caldav::Collection.new(
30
+ path: col_path,
31
+ type: collection[:type],
32
+ displayname: collection[:displayname],
33
+ description: collection[:description],
34
+ color: collection[:color],
35
+ props: collection[:props]
36
+ )
37
+ col.send(build, x)
38
+
39
+ if depth != '0'
40
+ storage.list_collections(col_path.to_s).each do |child_path, child_data|
41
+ child_p = Protocol::Caldav::Path.new(child_path, storage_class: storage)
42
+ child_col = Protocol::Caldav::Collection.new(
43
+ path: child_p,
44
+ type: child_data[:type],
45
+ displayname: child_data[:displayname],
46
+ description: child_data[:description],
47
+ color: child_data[:color],
48
+ props: child_data[:props]
49
+ )
50
+ child_col.send(build, x)
51
+ end
52
+
53
+ storage.list_items(col_path.to_s).each do |item_path, data|
54
+ item_p = Protocol::Caldav::Path.new(item_path, storage_class: storage)
55
+ item = Protocol::Caldav::Item.new(
56
+ path: item_p,
57
+ body: data[:body],
58
+ content_type: data[:content_type],
59
+ etag: data[:etag]
60
+ )
61
+ item.send(build, x)
62
+ end
52
63
  end
53
-
54
- # Child items
55
- storage.list_items(col_path.to_s).each do |item_path, data|
56
- item_p = Protocol::Caldav::Path.new(item_path, storage_class: storage)
57
- item = Protocol::Caldav::Item.new(
58
- path: item_p,
59
- body: data[:body],
60
- content_type: data[:content_type],
61
- etag: data[:etag]
62
- )
63
- responses << (propname ? item.to_propname_xml : item.to_propfind_xml)
64
- end
65
- end
66
- elsif item_data
67
- item = Protocol::Caldav::Item.new(
68
- path: path,
69
- body: item_data[:body],
70
- content_type: item_data[:content_type],
71
- etag: item_data[:etag]
72
- )
73
- responses << (propname ? item.to_propname_xml : item.to_propfind_xml)
74
- else
75
- # Shallow path with no collection — return basic discovery info
76
- responses << build_discovery_xml(path, user)
77
-
78
- # Still list child collections/items for depth=1
79
- if depth != '0'
80
- storage.list_collections(col_path.to_s).each do |child_path, child_data|
81
- child_p = Protocol::Caldav::Path.new(child_path, storage_class: storage)
82
- child_col = Protocol::Caldav::Collection.new(
83
- path: child_p,
84
- type: child_data[:type],
85
- displayname: child_data[:displayname],
86
- description: child_data[:description],
87
- color: child_data[:color],
88
- props: child_data[:props]
89
- )
90
- responses << (propname ? child_col.to_propname_xml : child_col.to_propfind_xml)
64
+ elsif item_data
65
+ item = Protocol::Caldav::Item.new(
66
+ path: path,
67
+ body: item_data[:body],
68
+ content_type: item_data[:content_type],
69
+ etag: item_data[:etag]
70
+ )
71
+ item.send(build, x)
72
+ else
73
+ build_discovery(x, path, user)
74
+
75
+ if depth != '0'
76
+ storage.list_collections(col_path.to_s).each do |child_path, child_data|
77
+ child_p = Protocol::Caldav::Path.new(child_path, storage_class: storage)
78
+ child_col = Protocol::Caldav::Collection.new(
79
+ path: child_p,
80
+ type: child_data[:type],
81
+ displayname: child_data[:displayname],
82
+ description: child_data[:description],
83
+ color: child_data[:color],
84
+ props: child_data[:props]
85
+ )
86
+ child_col.send(build, x)
87
+ end
91
88
  end
92
89
  end
93
90
  end
94
91
 
95
- xml = Protocol::Caldav::Multistatus.new(responses).to_xml
96
92
  [207, Protocol::Caldav::Constants::DAV_HEADERS, [xml]]
97
93
  end
98
94
 
99
- def build_discovery_xml(path, user)
100
- <<~XML
101
- <d:response>
102
- <d:href>#{Protocol::Caldav::Xml.escape(path.to_s)}</d:href>
103
- <d:propstat>
104
- <d:prop>
105
- <d:resourcetype><d:collection/></d:resourcetype>
106
- <d:current-user-principal><d:href>/#{user}/</d:href></d:current-user-principal>
107
- <c:calendar-home-set><d:href>/calendars/#{user}/</d:href></c:calendar-home-set>
108
- <cr:addressbook-home-set><d:href>/addressbooks/#{user}/</d:href></cr:addressbook-home-set>
109
- </d:prop>
110
- <d:status>HTTP/1.1 200 OK</d:status>
111
- </d:propstat>
112
- </d:response>
113
- XML
95
+ def build_discovery(xml, path, user)
96
+ Protocol::Caldav::XmlBuilder.response(xml, href: path.to_s) do
97
+ Protocol::Caldav::XmlBuilder.propstat_ok(xml) do
98
+ xml.tag!("d:resourcetype") { xml.tag!("d:collection") }
99
+ xml.tag!("d:current-user-principal") { xml.tag!("d:href", "/#{user}/") }
100
+ xml.tag!("c:calendar-home-set") { xml.tag!("d:href", "/calendars/#{user}/") }
101
+ xml.tag!("cr:addressbook-home-set") { xml.tag!("d:href", "/addressbooks/#{user}/") }
102
+ end
103
+ end
114
104
  end
115
105
 
116
- private_class_method :build_discovery_xml
106
+ private_class_method :build_discovery
117
107
  end
118
108
  end
119
109
  end
@@ -37,17 +37,14 @@ module Async
37
37
 
38
38
  storage.update_collection(col_path.to_s, updates)
39
39
 
40
- response_xml = <<~XML
41
- <d:response>
42
- <d:href>#{Protocol::Caldav::Xml.escape(col_path.to_s)}</d:href>
43
- <d:propstat>
44
- <d:prop/>
45
- <d:status>HTTP/1.1 200 OK</d:status>
46
- </d:propstat>
47
- </d:response>
48
- XML
49
-
50
- xml = Protocol::Caldav::Multistatus.new([response_xml]).to_xml
40
+ xml = Protocol::Caldav::Multistatus.new.to_xml do |x|
41
+ Protocol::Caldav::XmlBuilder.response(x, href: col_path.to_s) do
42
+ x.tag!("d:propstat") do
43
+ x.tag!("d:prop")
44
+ x.tag!("d:status", "HTTP/1.1 200 OK")
45
+ end
46
+ end
47
+ end
51
48
  [207, Protocol::Caldav::Constants::DAV_HEADERS, [xml]]
52
49
  end
53
50
  end
@@ -43,45 +43,46 @@ module Async
43
43
  items = multi.select { |_, data| data }
44
44
  end
45
45
 
46
- responses = items.filter_map do |item_path, data|
47
- next unless data
48
-
49
- # Apply filter
50
- if filter
51
- if resource_type == :addressbook
52
- card = Protocol::Caldav::Vcard::Parser.parse(data[:body])
53
- next unless card && Protocol::Caldav::Filter::Match.addressbook?(filter, card)
54
- else
55
- component = Protocol::Caldav::Ical::Parser.parse(data[:body])
56
- next unless component && Protocol::Caldav::Filter::Match.calendar?(filter, component)
46
+ xml = Protocol::Caldav::Multistatus.new.to_xml do |x|
47
+ items.each do |item_path, data|
48
+ next unless data
49
+
50
+ # Apply filter
51
+ if filter
52
+ if resource_type == :addressbook
53
+ card = Protocol::Caldav::Vcard::Parser.parse(data[:body])
54
+ next unless card && Protocol::Caldav::Filter::Match.addressbook?(filter, card)
55
+ else
56
+ component = Protocol::Caldav::Ical::Parser.parse(data[:body])
57
+ next unless component && Protocol::Caldav::Filter::Match.calendar?(filter, component)
58
+ end
57
59
  end
58
- end
59
60
 
60
- item_body = data[:body]
61
-
62
- # Apply expand if requested (calendar items only)
63
- if expand_range && resource_type != :addressbook
64
- component = Protocol::Caldav::Ical::Parser.parse(item_body)
65
- if component
66
- item_body = Protocol::Caldav::Ical::Expand.expand(
67
- component,
68
- range_start: expand_range[:start],
69
- range_end: expand_range[:end]
70
- )
61
+ item_body = data[:body]
62
+
63
+ # Apply expand if requested (calendar items only)
64
+ if expand_range && resource_type != :addressbook
65
+ component = Protocol::Caldav::Ical::Parser.parse(item_body)
66
+ if component
67
+ item_body = Protocol::Caldav::Ical::Expand.expand(
68
+ component,
69
+ range_start: expand_range[:start],
70
+ range_end: expand_range[:end]
71
+ )
72
+ end
71
73
  end
72
- end
73
74
 
74
- item_p = Protocol::Caldav::Path.new(item_path, storage_class: storage)
75
- item = Protocol::Caldav::Item.new(
76
- path: item_p,
77
- body: item_body,
78
- content_type: data[:content_type],
79
- etag: data[:etag]
80
- )
81
- item.to_report_xml(data_tag: data_tag)
75
+ item_p = Protocol::Caldav::Path.new(item_path, storage_class: storage)
76
+ item = Protocol::Caldav::Item.new(
77
+ path: item_p,
78
+ body: item_body,
79
+ content_type: data[:content_type],
80
+ etag: data[:etag]
81
+ )
82
+ item.build_report(x, data_tag: data_tag)
83
+ end
82
84
  end
83
85
 
84
- xml = Protocol::Caldav::Multistatus.new(responses).to_xml
85
86
  [207, Protocol::Caldav::Constants::DAV_HEADERS, [xml]]
86
87
  end
87
88
 
@@ -97,67 +98,48 @@ module Async
97
98
  # Incremental sync
98
99
  result = storage.sync_changes(col_path, old_token)
99
100
  unless result
100
- # Invalid token
101
- error_xml = <<~XML
102
- <?xml version="1.0" encoding="UTF-8"?>
103
- <d:error xmlns:d="DAV:">
104
- <d:valid-sync-token/>
105
- </d:error>
106
- XML
107
- return [403, { 'content-type' => 'application/xml' }, [error_xml]]
101
+ error_xml = Builder::XmlMarkup.new
102
+ error_xml.instruct! :xml, version: "1.0", encoding: "UTF-8"
103
+ error_xml.tag!("d:error", "xmlns:d" => "DAV:") do
104
+ error_xml.tag!("d:valid-sync-token")
105
+ end
106
+ return [403, { 'content-type' => 'application/xml' }, [error_xml.target!]]
108
107
  end
109
108
 
110
109
  new_token, changes = result
111
- responses = changes.map do |item_path, status|
112
- if status == :deleted
113
- <<~XML
114
- <d:response>
115
- <d:href>#{Protocol::Caldav::Xml.escape(item_path)}</d:href>
116
- <d:status>HTTP/1.1 404 Not Found</d:status>
117
- </d:response>
118
- XML
119
- else
120
- etag = storage.etag(item_path)
121
- <<~XML
122
- <d:response>
123
- <d:href>#{Protocol::Caldav::Xml.escape(item_path)}</d:href>
124
- <d:propstat>
125
- <d:prop>
126
- <d:getetag>#{Protocol::Caldav::Xml.escape(etag)}</d:getetag>
127
- </d:prop>
128
- <d:status>HTTP/1.1 200 OK</d:status>
129
- </d:propstat>
130
- </d:response>
131
- XML
110
+ xml = Protocol::Caldav::XmlBuilder.multistatus do |x|
111
+ changes.each do |item_path, status|
112
+ if status == :deleted
113
+ Protocol::Caldav::XmlBuilder.response(x, href: item_path) do
114
+ x.tag!("d:status", "HTTP/1.1 404 Not Found")
115
+ end
116
+ else
117
+ etag = storage.etag(item_path)
118
+ Protocol::Caldav::XmlBuilder.response(x, href: item_path) do
119
+ Protocol::Caldav::XmlBuilder.propstat_ok(x) do
120
+ x.tag!("d:getetag", etag)
121
+ end
122
+ end
123
+ end
132
124
  end
125
+ x.tag!("d:sync-token", new_token)
133
126
  end
134
127
  else
135
128
  # Initial sync — return all items
136
129
  new_token = storage.snapshot_sync(col_path)
137
130
  items = storage.list_items(col_path)
138
- responses = items.map do |item_path, data|
139
- <<~XML
140
- <d:response>
141
- <d:href>#{Protocol::Caldav::Xml.escape(item_path)}</d:href>
142
- <d:propstat>
143
- <d:prop>
144
- <d:getetag>#{Protocol::Caldav::Xml.escape(data[:etag])}</d:getetag>
145
- </d:prop>
146
- <d:status>HTTP/1.1 200 OK</d:status>
147
- </d:propstat>
148
- </d:response>
149
- XML
131
+ xml = Protocol::Caldav::XmlBuilder.multistatus do |x|
132
+ items.each do |item_path, data|
133
+ Protocol::Caldav::XmlBuilder.response(x, href: item_path) do
134
+ Protocol::Caldav::XmlBuilder.propstat_ok(x) do
135
+ x.tag!("d:getetag", data[:etag])
136
+ end
137
+ end
138
+ end
139
+ x.tag!("d:sync-token", new_token)
150
140
  end
151
141
  end
152
142
 
153
- xml = <<~XML
154
- <?xml version="1.0" encoding="UTF-8"?>
155
- <d:multistatus xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav" xmlns:cr="urn:ietf:params:xml:ns:carddav" xmlns:cs="http://calendarserver.org/ns/" xmlns:x="http://apple.com/ns/ical/">
156
- #{responses.join}
157
- <d:sync-token>#{Protocol::Caldav::Xml.escape(new_token)}</d:sync-token>
158
- </d:multistatus>
159
- XML
160
-
161
143
  [207, Protocol::Caldav::Constants::DAV_HEADERS, [xml]]
162
144
  end
163
145
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Async
4
4
  module Caldav
5
- VERSION = "1.2.0"
5
+ VERSION = "1.2.1"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async-caldav
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan K