protocol-caldav 1.0.2 → 1.1.0
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 +4 -4
- data/lib/protocol/caldav/collection.rb +99 -75
- data/lib/protocol/caldav/constants.rb +23 -27
- data/lib/protocol/caldav/content_line.rb +94 -91
- data/lib/protocol/caldav/ctag.rb +46 -50
- data/lib/protocol/caldav/etag.rb +24 -28
- data/lib/protocol/caldav/filter/addressbook.rb +41 -28
- data/lib/protocol/caldav/filter/calendar.rb +65 -44
- data/lib/protocol/caldav/filter/match.rb +632 -556
- data/lib/protocol/caldav/filter/parser.rb +273 -252
- data/lib/protocol/caldav/ical/component.rb +49 -46
- data/lib/protocol/caldav/ical/expand.rb +148 -134
- data/lib/protocol/caldav/ical/freebusy.rb +106 -74
- data/lib/protocol/caldav/ical/parser.rb +139 -139
- data/lib/protocol/caldav/ical/property.rb +22 -21
- data/lib/protocol/caldav/ical/rrule.rb +346 -235
- data/lib/protocol/caldav/item.rb +41 -43
- data/lib/protocol/caldav/multistatus.rb +43 -46
- data/lib/protocol/caldav/path.rb +82 -79
- data/lib/protocol/caldav/storage.rb +24 -28
- data/lib/protocol/caldav/vcard/card.rb +25 -27
- data/lib/protocol/caldav/vcard/parser.rb +56 -59
- data/lib/protocol/caldav/version.rb +1 -1
- data/lib/protocol/caldav/xml.rb +84 -80
- data/lib/protocol/caldav/xml_builder.rb +4 -2
- metadata +18 -4
data/lib/protocol/caldav/ctag.rb
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "bundler/setup"
|
|
4
|
-
require "scampi"
|
|
5
|
-
|
|
6
3
|
require 'digest'
|
|
7
4
|
|
|
8
5
|
module Protocol
|
|
@@ -18,61 +15,60 @@ module Protocol
|
|
|
18
15
|
end
|
|
19
16
|
end
|
|
20
17
|
|
|
18
|
+
__END__
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
end
|
|
20
|
+
describe "Protocol::Caldav::CTag" do
|
|
21
|
+
it "is stable for the same collection state" do
|
|
22
|
+
a = Protocol::Caldav::CTag.compute(path: "/cal/", displayname: "Work")
|
|
23
|
+
b = Protocol::Caldav::CTag.compute(path: "/cal/", displayname: "Work")
|
|
24
|
+
a.should.equal b
|
|
25
|
+
end
|
|
29
26
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
27
|
+
it "changes when displayname changes" do
|
|
28
|
+
a = Protocol::Caldav::CTag.compute(path: "/cal/", displayname: "Work")
|
|
29
|
+
b = Protocol::Caldav::CTag.compute(path: "/cal/", displayname: "Personal")
|
|
30
|
+
a.should.not.equal b
|
|
31
|
+
end
|
|
35
32
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
33
|
+
it "changes when description changes" do
|
|
34
|
+
a = Protocol::Caldav::CTag.compute(path: "/cal/", displayname: "Work", description: "desc1")
|
|
35
|
+
b = Protocol::Caldav::CTag.compute(path: "/cal/", displayname: "Work", description: "desc2")
|
|
36
|
+
a.should.not.equal b
|
|
37
|
+
end
|
|
41
38
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
it "changes when color changes" do
|
|
40
|
+
a = Protocol::Caldav::CTag.compute(path: "/cal/", displayname: "Work", color: "#ff0000")
|
|
41
|
+
b = Protocol::Caldav::CTag.compute(path: "/cal/", displayname: "Work", color: "#00ff00")
|
|
42
|
+
a.should.not.equal b
|
|
43
|
+
end
|
|
47
44
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
45
|
+
it "changes when an item is added" do
|
|
46
|
+
a = Protocol::Caldav::CTag.compute(path: "/cal/", displayname: "Work", item_etags: ["etag1"])
|
|
47
|
+
b = Protocol::Caldav::CTag.compute(path: "/cal/", displayname: "Work", item_etags: ["etag1", "etag2"])
|
|
48
|
+
a.should.not.equal b
|
|
49
|
+
end
|
|
53
50
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
51
|
+
it "changes when an item etag changes" do
|
|
52
|
+
a = Protocol::Caldav::CTag.compute(path: "/cal/", displayname: "Work", item_etags: ["etag1"])
|
|
53
|
+
b = Protocol::Caldav::CTag.compute(path: "/cal/", displayname: "Work", item_etags: ["etag2"])
|
|
54
|
+
a.should.not.equal b
|
|
55
|
+
end
|
|
59
56
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
57
|
+
it "changes when an item is removed" do
|
|
58
|
+
a = Protocol::Caldav::CTag.compute(path: "/cal/", displayname: "Work", item_etags: ["etag1", "etag2"])
|
|
59
|
+
b = Protocol::Caldav::CTag.compute(path: "/cal/", displayname: "Work", item_etags: ["etag1"])
|
|
60
|
+
a.should.not.equal b
|
|
61
|
+
end
|
|
65
62
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
63
|
+
it "does not depend on item_etags order" do
|
|
64
|
+
a = Protocol::Caldav::CTag.compute(path: "/cal/", displayname: "Work", item_etags: ["b", "a"])
|
|
65
|
+
b = Protocol::Caldav::CTag.compute(path: "/cal/", displayname: "Work", item_etags: ["a", "b"])
|
|
66
|
+
a.should.equal b
|
|
67
|
+
end
|
|
71
68
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
end
|
|
69
|
+
it "same state on different paths produces different ctags" do
|
|
70
|
+
a = Protocol::Caldav::CTag.compute(path: "/cal/a/", displayname: "Work")
|
|
71
|
+
b = Protocol::Caldav::CTag.compute(path: "/cal/b/", displayname: "Work")
|
|
72
|
+
a.should.not.equal b
|
|
77
73
|
end
|
|
78
74
|
end
|
data/lib/protocol/caldav/etag.rb
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "bundler/setup"
|
|
4
|
-
require "scampi"
|
|
5
|
-
|
|
6
3
|
require 'digest'
|
|
7
4
|
|
|
8
5
|
module Protocol
|
|
@@ -17,35 +14,34 @@ module Protocol
|
|
|
17
14
|
end
|
|
18
15
|
end
|
|
19
16
|
|
|
17
|
+
__END__
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
end
|
|
19
|
+
describe "Protocol::Caldav::ETag" do
|
|
20
|
+
it "is stable: same body produces same etag" do
|
|
21
|
+
a = Protocol::Caldav::ETag.compute("hello")
|
|
22
|
+
b = Protocol::Caldav::ETag.compute("hello")
|
|
23
|
+
a.should.equal b
|
|
24
|
+
end
|
|
28
25
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
it "different bodies produce different etags" do
|
|
27
|
+
a = Protocol::Caldav::ETag.compute("hello")
|
|
28
|
+
b = Protocol::Caldav::ETag.compute("world")
|
|
29
|
+
a.should.not.equal b
|
|
30
|
+
end
|
|
34
31
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
it "is double-quoted per RFC 7232" do
|
|
33
|
+
etag = Protocol::Caldav::ETag.compute("test")
|
|
34
|
+
etag.should.match(/\A"[0-9a-f]+"/)
|
|
35
|
+
end
|
|
39
36
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
37
|
+
it "truncates to consistent length" do
|
|
38
|
+
etag = Protocol::Caldav::ETag.compute("test")
|
|
39
|
+
inner = etag[1..-2] # strip quotes
|
|
40
|
+
inner.length.should.equal 16
|
|
41
|
+
end
|
|
45
42
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
end
|
|
43
|
+
it "is binary-safe: body with null bytes produces an etag" do
|
|
44
|
+
etag = Protocol::Caldav::ETag.compute("hello\x00world")
|
|
45
|
+
etag.should.match(/\A"[0-9a-f]+"/)
|
|
50
46
|
end
|
|
51
47
|
end
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "bundler/setup"
|
|
4
|
-
require "scampi"
|
|
5
|
-
|
|
6
3
|
module Protocol
|
|
7
4
|
module Caldav
|
|
8
5
|
module Filter
|
|
@@ -13,19 +10,36 @@ module Protocol
|
|
|
13
10
|
end
|
|
14
11
|
end
|
|
15
12
|
|
|
16
|
-
PropFilter = Struct.new(
|
|
13
|
+
PropFilter = Struct.new(
|
|
14
|
+
:name,
|
|
15
|
+
:is_not_defined,
|
|
16
|
+
:text_match,
|
|
17
|
+
:param_filters,
|
|
18
|
+
keyword_init: true,
|
|
19
|
+
) do
|
|
17
20
|
def initialize(name:, is_not_defined: false, text_match: nil, param_filters: [])
|
|
18
21
|
super
|
|
19
22
|
end
|
|
20
23
|
end
|
|
21
24
|
|
|
22
|
-
ParamFilter = Struct.new(
|
|
25
|
+
ParamFilter = Struct.new(
|
|
26
|
+
:name,
|
|
27
|
+
:is_not_defined,
|
|
28
|
+
:text_match,
|
|
29
|
+
keyword_init: true,
|
|
30
|
+
) do
|
|
23
31
|
def initialize(name:, is_not_defined: false, text_match: nil)
|
|
24
32
|
super
|
|
25
33
|
end
|
|
26
34
|
end
|
|
27
35
|
|
|
28
|
-
TextMatch = Struct.new(
|
|
36
|
+
TextMatch = Struct.new(
|
|
37
|
+
:value,
|
|
38
|
+
:collation,
|
|
39
|
+
:match_type,
|
|
40
|
+
:negate_condition,
|
|
41
|
+
keyword_init: true,
|
|
42
|
+
) do
|
|
29
43
|
def initialize(value:, collation: 'i;ascii-casemap', match_type: 'contains', negate_condition: false)
|
|
30
44
|
super
|
|
31
45
|
end
|
|
@@ -35,31 +49,30 @@ module Protocol
|
|
|
35
49
|
end
|
|
36
50
|
end
|
|
37
51
|
|
|
52
|
+
__END__
|
|
38
53
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
end
|
|
54
|
+
describe "Protocol::Caldav::Filter::Addressbook" do
|
|
55
|
+
it "Filter defaults test to anyof" do
|
|
56
|
+
f = Protocol::Caldav::Filter::Addressbook::Filter.new
|
|
57
|
+
f.test.should.equal "anyof"
|
|
58
|
+
f.prop_filters.should.equal []
|
|
59
|
+
end
|
|
46
60
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
61
|
+
it "Filter accepts allof" do
|
|
62
|
+
f = Protocol::Caldav::Filter::Addressbook::Filter.new(test: "allof")
|
|
63
|
+
f.test.should.equal "allof"
|
|
64
|
+
end
|
|
51
65
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
66
|
+
it "PropFilter defaults" do
|
|
67
|
+
pf = Protocol::Caldav::Filter::Addressbook::PropFilter.new(name: "FN")
|
|
68
|
+
pf.name.should.equal "FN"
|
|
69
|
+
pf.is_not_defined.should.equal false
|
|
70
|
+
pf.text_match.should.be.nil
|
|
71
|
+
end
|
|
58
72
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
end
|
|
73
|
+
it "no comp-filter or time-range in addressbook" do
|
|
74
|
+
pf = Protocol::Caldav::Filter::Addressbook::PropFilter.new(name: "FN")
|
|
75
|
+
pf.should.not.respond_to(:time_range)
|
|
76
|
+
pf.should.not.respond_to(:comp_filters)
|
|
64
77
|
end
|
|
65
78
|
end
|
|
@@ -1,31 +1,53 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "bundler/setup"
|
|
4
|
-
require "scampi"
|
|
5
|
-
|
|
6
3
|
module Protocol
|
|
7
4
|
module Caldav
|
|
8
5
|
module Filter
|
|
9
6
|
module Calendar
|
|
10
|
-
CompFilter = Struct.new(
|
|
7
|
+
CompFilter = Struct.new(
|
|
8
|
+
:name,
|
|
9
|
+
:is_not_defined,
|
|
10
|
+
:time_range,
|
|
11
|
+
:prop_filters,
|
|
12
|
+
:comp_filters,
|
|
13
|
+
keyword_init: true,
|
|
14
|
+
) do
|
|
11
15
|
def initialize(name:, is_not_defined: false, time_range: nil, prop_filters: [], comp_filters: [])
|
|
12
16
|
super
|
|
13
17
|
end
|
|
14
18
|
end
|
|
15
19
|
|
|
16
|
-
PropFilter = Struct.new(
|
|
20
|
+
PropFilter = Struct.new(
|
|
21
|
+
:name,
|
|
22
|
+
:is_not_defined,
|
|
23
|
+
:time_range,
|
|
24
|
+
:text_match,
|
|
25
|
+
:param_filters,
|
|
26
|
+
keyword_init: true,
|
|
27
|
+
) do
|
|
17
28
|
def initialize(name:, is_not_defined: false, time_range: nil, text_match: nil, param_filters: [])
|
|
18
29
|
super
|
|
19
30
|
end
|
|
20
31
|
end
|
|
21
32
|
|
|
22
|
-
ParamFilter = Struct.new(
|
|
33
|
+
ParamFilter = Struct.new(
|
|
34
|
+
:name,
|
|
35
|
+
:is_not_defined,
|
|
36
|
+
:text_match,
|
|
37
|
+
keyword_init: true,
|
|
38
|
+
) do
|
|
23
39
|
def initialize(name:, is_not_defined: false, text_match: nil)
|
|
24
40
|
super
|
|
25
41
|
end
|
|
26
42
|
end
|
|
27
43
|
|
|
28
|
-
TextMatch = Struct.new(
|
|
44
|
+
TextMatch = Struct.new(
|
|
45
|
+
:value,
|
|
46
|
+
:collation,
|
|
47
|
+
:match_type,
|
|
48
|
+
:negate_condition,
|
|
49
|
+
keyword_init: true,
|
|
50
|
+
) do
|
|
29
51
|
def initialize(value:, collation: 'i;ascii-casemap', match_type: 'contains', negate_condition: false)
|
|
30
52
|
super
|
|
31
53
|
end
|
|
@@ -37,47 +59,46 @@ module Protocol
|
|
|
37
59
|
end
|
|
38
60
|
end
|
|
39
61
|
|
|
62
|
+
__END__
|
|
40
63
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
end
|
|
64
|
+
describe "Protocol::Caldav::Filter::Calendar" do
|
|
65
|
+
it "CompFilter accepts documented fields" do
|
|
66
|
+
cf = Protocol::Caldav::Filter::Calendar::CompFilter.new(name: "VCALENDAR")
|
|
67
|
+
cf.name.should.equal "VCALENDAR"
|
|
68
|
+
cf.is_not_defined.should.equal false
|
|
69
|
+
cf.time_range.should.be.nil
|
|
70
|
+
cf.prop_filters.should.equal []
|
|
71
|
+
cf.comp_filters.should.equal []
|
|
72
|
+
end
|
|
51
73
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
74
|
+
it "PropFilter defaults" do
|
|
75
|
+
pf = Protocol::Caldav::Filter::Calendar::PropFilter.new(name: "SUMMARY")
|
|
76
|
+
pf.name.should.equal "SUMMARY"
|
|
77
|
+
pf.is_not_defined.should.equal false
|
|
78
|
+
pf.text_match.should.be.nil
|
|
79
|
+
pf.param_filters.should.equal []
|
|
80
|
+
end
|
|
59
81
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
82
|
+
it "TextMatch defaults" do
|
|
83
|
+
tm = Protocol::Caldav::Filter::Calendar::TextMatch.new(value: "test")
|
|
84
|
+
tm.collation.should.equal "i;ascii-casemap"
|
|
85
|
+
tm.match_type.should.equal "contains"
|
|
86
|
+
tm.negate_condition.should.equal false
|
|
87
|
+
end
|
|
66
88
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
89
|
+
it "TimeRange holds start and end" do
|
|
90
|
+
tr = Protocol::Caldav::Filter::Calendar::TimeRange.new(start_time: "20260101T000000Z", end_time: "20260201T000000Z")
|
|
91
|
+
tr.start_time.should.equal "20260101T000000Z"
|
|
92
|
+
tr.end_time.should.equal "20260201T000000Z"
|
|
93
|
+
end
|
|
72
94
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
end
|
|
95
|
+
it "CompFilter with is_not_defined and other fields is valid" do
|
|
96
|
+
cf = Protocol::Caldav::Filter::Calendar::CompFilter.new(
|
|
97
|
+
name: "VTODO",
|
|
98
|
+
is_not_defined: true,
|
|
99
|
+
prop_filters: [Protocol::Caldav::Filter::Calendar::PropFilter.new(name: "X")]
|
|
100
|
+
)
|
|
101
|
+
cf.is_not_defined.should.equal true
|
|
102
|
+
cf.prop_filters.length.should.equal 1
|
|
82
103
|
end
|
|
83
104
|
end
|