protocol-caldav 1.0.0 → 1.0.2

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: 0b119c6f8e0f3fa86e9ca3606eea776a73cd872049fac80674a4f8ffba0bfaf4
4
- data.tar.gz: 7f4d5929f5c6227694375e5ec77130b18e7b7b72e72bc01f67c633a63f73aa3d
3
+ metadata.gz: f6f47958a3c9bccd45a0d66e1ab51c3f9dfb6a972831758f267e9181f25f46ba
4
+ data.tar.gz: 06142acc86bcf06cc71a4c148dd0e04793d48cd10ee5358a8e5fcf13258f110e
5
5
  SHA512:
6
- metadata.gz: 4c842735fafa37d1d224ac8d8a48ee18237897baada5e8dc755a327bc0c9e67d29a4060ca9ca0f1d1cd435ec7d701462cdbe84c145579e720d1dad85b32031ce
7
- data.tar.gz: b6187b613165259a2c277483f4ed46b404d103618545ff6b1c7caa77481428b96402c970711e70c9ea6db1e5bc8cbd5b70e942859a2d533dd2cb6ac9d7b8a9aa
6
+ metadata.gz: 77d54931bda2319b83cd344d3c89ee13675fbc381d00a35c0ec2618a117f096571b3f370f187bf02d15bd66a2eaf2f8805a58ab6312430523e59d630f491a621
7
+ data.tar.gz: c6501d31590539f4ca0f73bef8eaaaff00c411697944f52d429c227c4cf6946f0670f14b6ad92790953c2741de3a324f155e8af1e2b4a3e3f3d9150f56f0efa5
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "bundler/setup"
4
4
  require "scampi"
5
+ require "builder"
5
6
 
6
7
  require "protocol/caldav"
7
8
 
@@ -27,73 +28,79 @@ module Protocol
27
28
  self
28
29
  end
29
30
 
30
- def to_propfind_xml
31
- prop_lines = []
32
-
33
- if @type == :calendar
34
- prop_lines << '<d:resourcetype><d:collection/><c:calendar/></d:resourcetype>'
35
- elsif @type == :addressbook
36
- prop_lines << '<d:resourcetype><d:collection/><cr:addressbook/></d:resourcetype>'
37
- else
38
- prop_lines << '<d:resourcetype><d:collection/></d:resourcetype>'
31
+ def build_propfind(xml)
32
+ XmlBuilder.response(xml, href: @path.to_s) do
33
+ XmlBuilder.propstat_ok(xml) do
34
+ xml.tag!("d:resourcetype") do
35
+ xml.tag!("d:collection")
36
+ xml.tag!("c:calendar") if @type == :calendar
37
+ xml.tag!("cr:addressbook") if @type == :addressbook
38
+ end
39
+
40
+ xml.tag!("d:displayname", @displayname) if @displayname
41
+ xml.tag!("c:calendar-description", @description) if @description
42
+ xml.tag!("x:calendar-color", @color) if @color
43
+
44
+ item_etags = @path.storage_class.list_items(@path.to_s).map { |_, data| data[:etag] }
45
+ ctag = CTag.compute(
46
+ path: @path.to_s,
47
+ displayname: @displayname,
48
+ description: @description,
49
+ color: @color,
50
+ item_etags: item_etags
51
+ )
52
+ xml.tag!("cs:getctag", ctag)
53
+ xml.tag!("d:sync-token", "http://caldav.local/sync/#{ctag}")
54
+
55
+ if @type == :calendar
56
+ xml.tag!("c:supported-calendar-component-set") do
57
+ xml.tag!("c:comp", name: "VEVENT")
58
+ xml.tag!("c:comp", name: "VTODO")
59
+ xml.tag!("c:comp", name: "VJOURNAL")
60
+ end
61
+ end
62
+
63
+ xml.tag!("d:current-user-privilege-set") do
64
+ xml.tag!("d:privilege") { xml.tag!("d:read") }
65
+ xml.tag!("d:privilege") { xml.tag!("d:write") }
66
+ xml.tag!("d:privilege") { xml.tag!("d:all") }
67
+ end
68
+
69
+ @props.each do |key, value|
70
+ xml.tag!(key, value)
71
+ end
72
+ end
39
73
  end
74
+ end
40
75
 
41
- prop_lines << "<d:displayname>#{Xml.escape(@displayname)}</d:displayname>" if @displayname
42
- prop_lines << "<c:calendar-description>#{Xml.escape(@description)}</c:calendar-description>" if @description
43
- prop_lines << "<x:calendar-color>#{Xml.escape(@color)}</x:calendar-color>" if @color
44
-
45
- item_etags = @path.storage_class.list_items(@path.to_s).map { |_, data| data[:etag] }
46
- ctag = CTag.compute(
47
- path: @path.to_s,
48
- displayname: @displayname,
49
- description: @description,
50
- color: @color,
51
- item_etags: item_etags
52
- )
53
- prop_lines << "<cs:getctag>#{ctag}</cs:getctag>"
54
- prop_lines << "<d:sync-token>http://caldav.local/sync/#{ctag}</d:sync-token>"
55
-
56
- if @type == :calendar
57
- prop_lines << '<c:supported-calendar-component-set><c:comp name="VEVENT"/><c:comp name="VTODO"/><c:comp name="VJOURNAL"/></c:supported-calendar-component-set>'
76
+ def build_propname(xml)
77
+ XmlBuilder.response(xml, href: @path.to_s) do
78
+ XmlBuilder.propstat_ok(xml) do
79
+ xml.tag!("d:resourcetype")
80
+ xml.tag!("d:displayname") if @displayname
81
+ xml.tag!("c:calendar-description") if @description
82
+ xml.tag!("x:calendar-color") if @color
83
+ xml.tag!("cs:getctag")
84
+ xml.tag!("d:sync-token")
85
+ xml.tag!("c:supported-calendar-component-set") if @type == :calendar
86
+ end
58
87
  end
88
+ end
59
89
 
60
- @props.each do |key, value|
61
- prop_lines << "<#{key}>#{Xml.escape(value)}</#{key}>"
62
- end
90
+ def build_xml(xml)
91
+ build_propfind(xml)
92
+ end
63
93
 
64
- <<~XML
65
- <d:response>
66
- <d:href>#{Xml.escape(@path.to_s)}</d:href>
67
- <d:propstat>
68
- <d:prop>
69
- #{prop_lines.join("\n ")}
70
- </d:prop>
71
- <d:status>HTTP/1.1 200 OK</d:status>
72
- </d:propstat>
73
- </d:response>
74
- XML
94
+ def to_propfind_xml
95
+ x = Builder::XmlMarkup.new
96
+ build_propfind(x)
97
+ x.target!
75
98
  end
76
99
 
77
100
  def to_propname_xml
78
- names = ['<d:resourcetype/>']
79
- names << '<d:displayname/>' if @displayname
80
- names << '<c:calendar-description/>' if @description
81
- names << '<x:calendar-color/>' if @color
82
- names << '<cs:getctag/>'
83
- names << '<d:sync-token/>'
84
- names << '<c:supported-calendar-component-set/>' if @type == :calendar
85
-
86
- <<~XML
87
- <d:response>
88
- <d:href>#{Xml.escape(@path.to_s)}</d:href>
89
- <d:propstat>
90
- <d:prop>
91
- #{names.join("\n ")}
92
- </d:prop>
93
- <d:status>HTTP/1.1 200 OK</d:status>
94
- </d:propstat>
95
- </d:response>
96
- XML
101
+ x = Builder::XmlMarkup.new
102
+ build_propname(x)
103
+ x.target!
97
104
  end
98
105
  end
99
106
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "bundler/setup"
4
4
  require "scampi"
5
+ require "builder"
5
6
  require "protocol/caldav"
6
7
 
7
8
  module Protocol
@@ -21,49 +22,53 @@ module Protocol
21
22
  @new_record
22
23
  end
23
24
 
25
+ def build_propfind(xml)
26
+ XmlBuilder.response(xml, href: @path.to_s) do
27
+ XmlBuilder.propstat_ok(xml) do
28
+ xml.tag!("d:getetag", @etag)
29
+ xml.tag!("d:getcontenttype", @content_type)
30
+ end
31
+ end
32
+ end
33
+
34
+ def build_propname(xml)
35
+ XmlBuilder.response(xml, href: @path.to_s) do
36
+ XmlBuilder.propstat_ok(xml) do
37
+ xml.tag!("d:getetag")
38
+ xml.tag!("d:getcontenttype")
39
+ end
40
+ end
41
+ end
42
+
43
+ def build_report(xml, data_tag:)
44
+ XmlBuilder.response(xml, href: @path.to_s) do
45
+ XmlBuilder.propstat_ok(xml) do
46
+ xml.tag!("d:getetag", @etag)
47
+ xml.tag!(data_tag, @body)
48
+ end
49
+ end
50
+ end
51
+
52
+ def build_xml(xml)
53
+ build_propfind(xml)
54
+ end
55
+
24
56
  def to_propfind_xml
25
- <<~XML
26
- <d:response>
27
- <d:href>#{Xml.escape(@path.to_s)}</d:href>
28
- <d:propstat>
29
- <d:prop>
30
- <d:getetag>#{Xml.escape(@etag)}</d:getetag>
31
- <d:getcontenttype>#{Xml.escape(@content_type)}</d:getcontenttype>
32
- </d:prop>
33
- <d:status>HTTP/1.1 200 OK</d:status>
34
- </d:propstat>
35
- </d:response>
36
- XML
57
+ x = Builder::XmlMarkup.new
58
+ build_propfind(x)
59
+ x.target!
37
60
  end
38
61
 
39
62
  def to_propname_xml
40
- <<~XML
41
- <d:response>
42
- <d:href>#{Xml.escape(@path.to_s)}</d:href>
43
- <d:propstat>
44
- <d:prop>
45
- <d:getetag/>
46
- <d:getcontenttype/>
47
- </d:prop>
48
- <d:status>HTTP/1.1 200 OK</d:status>
49
- </d:propstat>
50
- </d:response>
51
- XML
63
+ x = Builder::XmlMarkup.new
64
+ build_propname(x)
65
+ x.target!
52
66
  end
53
67
 
54
68
  def to_report_xml(data_tag:)
55
- <<~XML
56
- <d:response>
57
- <d:href>#{Xml.escape(@path.to_s)}</d:href>
58
- <d:propstat>
59
- <d:prop>
60
- <d:getetag>#{Xml.escape(@etag)}</d:getetag>
61
- <#{data_tag}>#{Xml.escape(@body)}</#{data_tag}>
62
- </d:prop>
63
- <d:status>HTTP/1.1 200 OK</d:status>
64
- </d:propstat>
65
- </d:response>
66
- XML
69
+ x = Builder::XmlMarkup.new
70
+ build_report(x, data_tag: data_tag)
71
+ x.target!
67
72
  end
68
73
  end
69
74
  end
@@ -2,21 +2,23 @@
2
2
 
3
3
  require "bundler/setup"
4
4
  require "scampi"
5
+ require "builder"
5
6
 
6
7
  module Protocol
7
8
  module Caldav
8
9
  class Multistatus
9
- def initialize(responses)
10
+ def initialize(responses = [])
10
11
  @responses = responses
11
12
  end
12
13
 
13
- def to_xml
14
- <<~XML
15
- <?xml version="1.0" encoding="UTF-8"?>
16
- <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/">
17
- #{@responses.join}
18
- </d:multistatus>
19
- XML
14
+ def to_xml(&block)
15
+ XmlBuilder.multistatus do |xml|
16
+ if block
17
+ block.call(xml)
18
+ else
19
+ @responses.each { |r| r.build_xml(xml) }
20
+ end
21
+ end
20
22
  end
21
23
  end
22
24
  end
@@ -28,6 +30,17 @@ test do
28
30
  xml.gsub(/>\s+</, '><').strip
29
31
  end
30
32
 
33
+ # Minimal wrapper so tests can pass objects with build_xml
34
+ class FakeResponse
35
+ def initialize(href)
36
+ @href = href
37
+ end
38
+
39
+ def build_xml(xml)
40
+ xml.tag!("d:response") { xml.tag!("d:href", @href) }
41
+ end
42
+ end
43
+
31
44
  describe "Protocol::Caldav::Multistatus" do
32
45
  it "declares all four required namespaces" do
33
46
  xml = Protocol::Caldav::Multistatus.new([]).to_xml
@@ -38,8 +51,7 @@ test do
38
51
  end
39
52
 
40
53
  it "emits responses in the order given" do
41
- responses = ["<d:response><d:href>/a</d:href></d:response>",
42
- "<d:response><d:href>/b</d:href></d:response>"]
54
+ responses = [FakeResponse.new("/a"), FakeResponse.new("/b")]
43
55
  xml = Protocol::Caldav::Multistatus.new(responses).to_xml
44
56
  xml.index("/a").should.be < xml.index("/b")
45
57
  end
@@ -51,11 +63,21 @@ test do
51
63
  xml.should.include '</d:multistatus>'
52
64
  end
53
65
 
54
- it "does not double-escape pre-escaped XML in responses" do
55
- response = "<d:response><d:href>/Work &amp; Personal</d:href></d:response>"
56
- xml = Protocol::Caldav::Multistatus.new([response]).to_xml
66
+ it "does not double-escape when using builder" do
67
+ xml = Protocol::Caldav::Multistatus.new.to_xml do |x|
68
+ x.tag!("d:response") { x.tag!("d:href", "/Work & Personal") }
69
+ end
57
70
  xml.should.include '&amp;'
58
71
  xml.should.not.include '&amp;amp;'
59
72
  end
73
+
74
+ it "supports block form for custom content" do
75
+ xml = Protocol::Caldav::Multistatus.new.to_xml do |x|
76
+ x.tag!("d:response") { x.tag!("d:href", "/test") }
77
+ x.tag!("d:sync-token", "token-123")
78
+ end
79
+ xml.should.include '/test'
80
+ xml.should.include 'token-123'
81
+ end
60
82
  end
61
83
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "bundler/setup"
4
4
  require "scampi"
5
+ require "builder"
5
6
  require "protocol/caldav"
6
7
 
7
8
  module Protocol
@@ -66,18 +67,22 @@ module Protocol
66
67
  to_s == other.to_s
67
68
  end
68
69
 
70
+ def build_propfind(xml)
71
+ XmlBuilder.response(xml, href: @to_s) do
72
+ XmlBuilder.propstat_ok(xml) do
73
+ xml.tag!("d:resourcetype") { xml.tag!("d:collection") }
74
+ end
75
+ end
76
+ end
77
+
78
+ def build_xml(xml)
79
+ build_propfind(xml)
80
+ end
81
+
69
82
  def to_propfind_xml
70
- <<~XML
71
- <d:response>
72
- <d:href>#{Xml.escape(@to_s)}</d:href>
73
- <d:propstat>
74
- <d:prop>
75
- <d:resourcetype><d:collection/></d:resourcetype>
76
- </d:prop>
77
- <d:status>HTTP/1.1 200 OK</d:status>
78
- </d:propstat>
79
- </d:response>
80
- XML
83
+ x = Builder::XmlMarkup.new
84
+ build_propfind(x)
85
+ x.target!
81
86
  end
82
87
  end
83
88
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Protocol
4
4
  module Caldav
5
- VERSION = "1.0.0"
5
+ VERSION = "1.0.2"
6
6
  end
7
7
  end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "builder"
4
+
5
+ module Protocol
6
+ module Caldav
7
+ module XmlBuilder
8
+ NAMESPACES = {
9
+ "xmlns:d" => "DAV:",
10
+ "xmlns:c" => "urn:ietf:params:xml:ns:caldav",
11
+ "xmlns:cr" => "urn:ietf:params:xml:ns:carddav",
12
+ "xmlns:cs" => "http://calendarserver.org/ns/",
13
+ "xmlns:x" => "http://apple.com/ns/ical/"
14
+ }.freeze
15
+
16
+ module_function
17
+
18
+ def multistatus
19
+ xml = Builder::XmlMarkup.new
20
+ xml.instruct! :xml, version: "1.0", encoding: "UTF-8"
21
+ xml.tag!("d:multistatus", NAMESPACES) do
22
+ yield xml
23
+ end
24
+ xml.target!
25
+ end
26
+
27
+ def response(xml, href:)
28
+ xml.tag!("d:response") do
29
+ xml.tag!("d:href", href)
30
+ yield xml if block_given?
31
+ end
32
+ end
33
+
34
+ def propstat_ok(xml)
35
+ xml.tag!("d:propstat") do
36
+ xml.tag!("d:prop") do
37
+ yield xml
38
+ end
39
+ xml.tag!("d:status", "HTTP/1.1 200 OK")
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -5,6 +5,7 @@ require_relative 'caldav/constants'
5
5
  require_relative 'caldav/etag'
6
6
  require_relative 'caldav/ctag'
7
7
  require_relative 'caldav/xml'
8
+ require_relative 'caldav/xml_builder'
8
9
  require_relative 'caldav/multistatus'
9
10
  require_relative 'caldav/path'
10
11
  require_relative 'caldav/storage'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: protocol-caldav
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan K
@@ -9,6 +9,20 @@ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-01 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: builder
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
12
26
  - !ruby/object:Gem::Dependency
13
27
  name: rexml
14
28
  requirement: !ruby/object:Gem::Requirement
@@ -70,6 +84,7 @@ files:
70
84
  - lib/protocol/caldav/vcard/parser.rb
71
85
  - lib/protocol/caldav/version.rb
72
86
  - lib/protocol/caldav/xml.rb
87
+ - lib/protocol/caldav/xml_builder.rb
73
88
  homepage: https://github.com/n-at-han-k/protocol-caldav
74
89
  licenses:
75
90
  - Apache-2.0