protocol-caldav 1.0.3 → 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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4506a98c42a98f929d194a834d1cd3b9e3ba5d75824a4a2f2921d7fb67a8ee8f
|
|
4
|
+
data.tar.gz: 92c8aa4a1b645aacca6f03f5343cbf57d842120019401436e6f3d9781841f0ac
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7937306d109796181723817e98460e5c61efdc41c38c7a629eff641f12b12715930f16ce2042fcbada7e296541ff51a484c54465456ae876db052980a7a8d45a
|
|
7
|
+
data.tar.gz: 28ee48c158bba16390fb0d3f8fe855d0adcde6ecc5cb569cbf63a8dba01a27623a5a2b2cbf7388a7c2412256be53c87e8e64cf7d79d8f0224d87934e9c7aef23
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "bundler/setup"
|
|
4
|
-
require "scampi"
|
|
5
3
|
require "builder"
|
|
6
4
|
|
|
7
5
|
require "protocol/caldav"
|
|
@@ -21,10 +19,18 @@ module Protocol
|
|
|
21
19
|
end
|
|
22
20
|
|
|
23
21
|
def update_attrs(updates)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
if updates.key?(:displayname)
|
|
23
|
+
@displayname = updates[:displayname]
|
|
24
|
+
end
|
|
25
|
+
if updates.key?(:description)
|
|
26
|
+
@description = updates[:description]
|
|
27
|
+
end
|
|
28
|
+
if updates.key?(:color)
|
|
29
|
+
@color = updates[:color]
|
|
30
|
+
end
|
|
31
|
+
if updates.key?(:props)
|
|
32
|
+
@props = (@props || {}).merge(updates[:props])
|
|
33
|
+
end
|
|
28
34
|
self
|
|
29
35
|
end
|
|
30
36
|
|
|
@@ -33,21 +39,31 @@ module Protocol
|
|
|
33
39
|
XmlBuilder.propstat_ok(xml) do
|
|
34
40
|
xml.tag!("d:resourcetype") do
|
|
35
41
|
xml.tag!("d:collection")
|
|
36
|
-
|
|
37
|
-
|
|
42
|
+
if @type == :calendar
|
|
43
|
+
xml.tag!("c:calendar")
|
|
44
|
+
end
|
|
45
|
+
if @type == :addressbook
|
|
46
|
+
xml.tag!("cr:addressbook")
|
|
47
|
+
end
|
|
38
48
|
end
|
|
39
49
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
50
|
+
if @displayname
|
|
51
|
+
xml.tag!("d:displayname", @displayname)
|
|
52
|
+
end
|
|
53
|
+
if @description
|
|
54
|
+
xml.tag!("c:calendar-description", @description)
|
|
55
|
+
end
|
|
56
|
+
if @color
|
|
57
|
+
xml.tag!("x:calendar-color", @color)
|
|
58
|
+
end
|
|
43
59
|
|
|
44
60
|
item_etags = @path.storage_class.list_items(@path.to_s).map { |_, data| data[:etag] }
|
|
45
61
|
ctag = CTag.compute(
|
|
46
|
-
path:
|
|
62
|
+
path: @path.to_s,
|
|
47
63
|
displayname: @displayname,
|
|
48
64
|
description: @description,
|
|
49
|
-
color:
|
|
50
|
-
item_etags:
|
|
65
|
+
color: @color,
|
|
66
|
+
item_etags: item_etags,
|
|
51
67
|
)
|
|
52
68
|
xml.tag!("cs:getctag", ctag)
|
|
53
69
|
xml.tag!("d:sync-token", "http://caldav.local/sync/#{ctag}")
|
|
@@ -77,12 +93,20 @@ module Protocol
|
|
|
77
93
|
XmlBuilder.response(xml, href: @path.to_s) do
|
|
78
94
|
XmlBuilder.propstat_ok(xml) do
|
|
79
95
|
xml.tag!("d:resourcetype")
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
96
|
+
if @displayname
|
|
97
|
+
xml.tag!("d:displayname")
|
|
98
|
+
end
|
|
99
|
+
if @description
|
|
100
|
+
xml.tag!("c:calendar-description")
|
|
101
|
+
end
|
|
102
|
+
if @color
|
|
103
|
+
xml.tag!("x:calendar-color")
|
|
104
|
+
end
|
|
83
105
|
xml.tag!("cs:getctag")
|
|
84
106
|
xml.tag!("d:sync-token")
|
|
85
|
-
|
|
107
|
+
if @type == :calendar
|
|
108
|
+
xml.tag!("c:supported-calendar-component-set")
|
|
109
|
+
end
|
|
86
110
|
end
|
|
87
111
|
end
|
|
88
112
|
end
|
|
@@ -106,75 +130,75 @@ module Protocol
|
|
|
106
130
|
end
|
|
107
131
|
end
|
|
108
132
|
|
|
109
|
-
|
|
110
|
-
def normalize(xml)
|
|
111
|
-
xml.gsub(/>\s+</, '><').strip
|
|
112
|
-
end
|
|
133
|
+
__END__
|
|
113
134
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
135
|
+
def normalize(xml)
|
|
136
|
+
xml.gsub(/>\s+</, '><').strip
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Minimal mock storage for Collection tests
|
|
140
|
+
class MockStorageForCollection < Protocol::Caldav::Storage
|
|
141
|
+
def list_items(_path)
|
|
142
|
+
[]
|
|
119
143
|
end
|
|
144
|
+
end
|
|
120
145
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
146
|
+
describe "Protocol::Caldav::Collection" do
|
|
147
|
+
def make_collection(type: :calendar, displayname: "Work", **opts)
|
|
148
|
+
storage = MockStorageForCollection.new
|
|
149
|
+
path = Protocol::Caldav::Path.new("/calendars/admin/work/", storage_class: storage)
|
|
150
|
+
Protocol::Caldav::Collection.new(path: path, type: type, displayname: displayname, **opts)
|
|
151
|
+
end
|
|
127
152
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
153
|
+
it "renders calendar resourcetype" do
|
|
154
|
+
xml = make_collection(type: :calendar).to_propfind_xml
|
|
155
|
+
xml.should.include "<c:calendar/>"
|
|
156
|
+
end
|
|
132
157
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
158
|
+
it "renders addressbook resourcetype" do
|
|
159
|
+
xml = make_collection(type: :addressbook).to_propfind_xml
|
|
160
|
+
xml.should.include "<cr:addressbook/>"
|
|
161
|
+
end
|
|
137
162
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
163
|
+
it "includes displayname when set" do
|
|
164
|
+
xml = make_collection(displayname: "Work").to_propfind_xml
|
|
165
|
+
xml.should.include "<d:displayname>Work</d:displayname>"
|
|
166
|
+
end
|
|
142
167
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
168
|
+
it "omits displayname when nil" do
|
|
169
|
+
xml = make_collection(displayname: nil).to_propfind_xml
|
|
170
|
+
xml.should.not.include "displayname"
|
|
171
|
+
end
|
|
147
172
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
173
|
+
it "includes ctag" do
|
|
174
|
+
xml = make_collection.to_propfind_xml
|
|
175
|
+
xml.should.include "<cs:getctag>"
|
|
176
|
+
end
|
|
152
177
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
178
|
+
it "includes supported-calendar-component-set for calendars" do
|
|
179
|
+
xml = make_collection(type: :calendar).to_propfind_xml
|
|
180
|
+
xml.should.include "supported-calendar-component-set"
|
|
181
|
+
end
|
|
157
182
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
183
|
+
it "omits supported-calendar-component-set for addressbooks" do
|
|
184
|
+
xml = make_collection(type: :addressbook).to_propfind_xml
|
|
185
|
+
xml.should.not.include "supported-calendar-component-set"
|
|
186
|
+
end
|
|
162
187
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
188
|
+
it "escapes special characters in displayname" do
|
|
189
|
+
xml = make_collection(displayname: "Work & <Personal>").to_propfind_xml
|
|
190
|
+
xml.should.include "Work & <Personal>"
|
|
191
|
+
end
|
|
167
192
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
193
|
+
it "update_attrs modifies named fields" do
|
|
194
|
+
col = make_collection(displayname: "Old")
|
|
195
|
+
col.update_attrs(displayname: "New")
|
|
196
|
+
col.displayname.should.equal "New"
|
|
197
|
+
end
|
|
173
198
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
end
|
|
199
|
+
it "update_attrs leaves unmentioned fields alone" do
|
|
200
|
+
col = make_collection(displayname: "Work", description: "Desc")
|
|
201
|
+
col.update_attrs(displayname: "New")
|
|
202
|
+
col.description.should.equal "Desc"
|
|
179
203
|
end
|
|
180
204
|
end
|
|
@@ -1,15 +1,12 @@
|
|
|
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 Constants
|
|
9
6
|
DAV_HEADERS = {
|
|
10
|
-
'dav'
|
|
11
|
-
'allow'
|
|
12
|
-
'content-type' => 'text/xml; charset=utf-8'
|
|
7
|
+
'dav' => '1, 2, 3, calendar-access, addressbook, extended-mkcol',
|
|
8
|
+
'allow' => 'OPTIONS, GET, HEAD, PUT, DELETE, PROPFIND, PROPPATCH, MKCALENDAR, MKCOL, MOVE, REPORT',
|
|
9
|
+
'content-type' => 'text/xml; charset=utf-8',
|
|
13
10
|
}.freeze
|
|
14
11
|
|
|
15
12
|
DAV_NS = 'DAV:'
|
|
@@ -24,31 +21,30 @@ module Protocol
|
|
|
24
21
|
end
|
|
25
22
|
end
|
|
26
23
|
|
|
24
|
+
__END__
|
|
27
25
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
end
|
|
26
|
+
describe "Protocol::Caldav::Constants" do
|
|
27
|
+
it "DAV_HEADERS is frozen" do
|
|
28
|
+
Protocol::Caldav::Constants::DAV_HEADERS.should.be.frozen
|
|
29
|
+
end
|
|
33
30
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
it "DAV_HEADERS includes calendar-access" do
|
|
32
|
+
Protocol::Caldav::Constants::DAV_HEADERS['dav'].should.include 'calendar-access'
|
|
33
|
+
end
|
|
37
34
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
it "DAV_HEADERS includes addressbook" do
|
|
36
|
+
Protocol::Caldav::Constants::DAV_HEADERS['dav'].should.include 'addressbook'
|
|
37
|
+
end
|
|
41
38
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
39
|
+
it "defines all required namespace URIs" do
|
|
40
|
+
Protocol::Caldav::Constants::DAV_NS.should.equal 'DAV:'
|
|
41
|
+
Protocol::Caldav::Constants::CALDAV_NS.should.equal 'urn:ietf:params:xml:ns:caldav'
|
|
42
|
+
Protocol::Caldav::Constants::CARDDAV_NS.should.equal 'urn:ietf:params:xml:ns:carddav'
|
|
43
|
+
Protocol::Caldav::Constants::CALSERVER_NS.should.equal 'http://calendarserver.org/ns/'
|
|
44
|
+
end
|
|
48
45
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
end
|
|
46
|
+
it "defines media types" do
|
|
47
|
+
Protocol::Caldav::Constants::CALENDAR_MEDIA_TYPE.should.equal 'text/calendar'
|
|
48
|
+
Protocol::Caldav::Constants::VCARD_MEDIA_TYPE.should.equal 'text/vcard'
|
|
53
49
|
end
|
|
54
50
|
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 ContentLine
|
|
@@ -31,124 +28,130 @@ module Protocol
|
|
|
31
28
|
end
|
|
32
29
|
end
|
|
33
30
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
31
|
+
if colon_idx
|
|
32
|
+
left = line[0...colon_idx]
|
|
33
|
+
value = line[(colon_idx + 1)..]
|
|
34
|
+
parts = split_params(left)
|
|
35
|
+
name = parts.shift
|
|
36
|
+
params = {}
|
|
37
|
+
parts.each do |param_str|
|
|
38
|
+
key, val = param_str.split('=', 2)
|
|
39
|
+
unless key
|
|
40
|
+
next
|
|
41
|
+
end
|
|
42
|
+
if val&.start_with?('"') && val&.end_with?('"')
|
|
43
|
+
val = val[1..-2]
|
|
44
|
+
end
|
|
45
|
+
params[key.upcase] = val || ''
|
|
46
|
+
end
|
|
47
|
+
[name, params, value]
|
|
48
|
+
else
|
|
49
|
+
nil
|
|
47
50
|
end
|
|
48
|
-
|
|
49
|
-
[name, params, value]
|
|
50
51
|
end
|
|
51
52
|
|
|
52
53
|
# Split the left side of a content line by semicolons,
|
|
53
54
|
# respecting quoted values.
|
|
54
55
|
def split_params(str)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
[].tap do |parts|
|
|
57
|
+
current = +''
|
|
58
|
+
in_quotes = false
|
|
59
|
+
|
|
60
|
+
str.each_char do |ch|
|
|
61
|
+
if ch == '"'
|
|
62
|
+
in_quotes = !in_quotes
|
|
63
|
+
current << ch
|
|
64
|
+
elsif ch == ';' && !in_quotes
|
|
65
|
+
parts << current
|
|
66
|
+
current = +''
|
|
67
|
+
else
|
|
68
|
+
current << ch
|
|
69
|
+
end
|
|
70
|
+
end
|
|
58
71
|
|
|
59
|
-
|
|
60
|
-
if ch == '"'
|
|
61
|
-
in_quotes = !in_quotes
|
|
62
|
-
current << ch
|
|
63
|
-
elsif ch == ';' && !in_quotes
|
|
72
|
+
unless current.empty?
|
|
64
73
|
parts << current
|
|
65
|
-
current = +''
|
|
66
|
-
else
|
|
67
|
-
current << ch
|
|
68
74
|
end
|
|
69
75
|
end
|
|
70
|
-
parts << current unless current.empty?
|
|
71
|
-
parts
|
|
72
76
|
end
|
|
73
77
|
end
|
|
74
78
|
end
|
|
75
79
|
end
|
|
76
80
|
|
|
81
|
+
__END__
|
|
77
82
|
|
|
78
|
-
|
|
79
|
-
describe "
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
end
|
|
83
|
+
describe "Protocol::Caldav::ContentLine" do
|
|
84
|
+
describe ".unfold" do
|
|
85
|
+
it "unfolds CRLF SPACE continuations" do
|
|
86
|
+
Protocol::Caldav::ContentLine.unfold("SUMMARY:Annual\r\n planning").should.equal "SUMMARY:Annualplanning"
|
|
87
|
+
end
|
|
84
88
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
89
|
+
it "unfolds CRLF TAB continuations" do
|
|
90
|
+
Protocol::Caldav::ContentLine.unfold("SUMMARY:Annual\r\n\tplanning").should.equal "SUMMARY:Annualplanning"
|
|
91
|
+
end
|
|
88
92
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
it "does not unfold CRLF followed by non-whitespace" do
|
|
94
|
+
result = Protocol::Caldav::ContentLine.unfold("SUMMARY:A\r\nDTSTART:B")
|
|
95
|
+
result.should.equal "SUMMARY:A\nDTSTART:B"
|
|
96
|
+
end
|
|
93
97
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
98
|
+
it "removes the space/tab character (not kept in value)" do
|
|
99
|
+
Protocol::Caldav::ContentLine.unfold("X:hello\r\n world").should.equal "X:helloworld"
|
|
100
|
+
end
|
|
97
101
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
102
|
+
it "handles three consecutive folded lines" do
|
|
103
|
+
Protocol::Caldav::ContentLine.unfold("X:a\r\n b\r\n c").should.equal "X:abc"
|
|
104
|
+
end
|
|
101
105
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
end
|
|
106
|
+
it "handles LF-only line endings" do
|
|
107
|
+
Protocol::Caldav::ContentLine.unfold("X:a\n b").should.equal "X:ab"
|
|
105
108
|
end
|
|
109
|
+
end
|
|
106
110
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
111
|
+
describe ".parse_line" do
|
|
112
|
+
it "parses a property with no parameters" do
|
|
113
|
+
name, params, value = Protocol::Caldav::ContentLine.parse_line("SUMMARY:Hello")
|
|
114
|
+
name.should.equal "SUMMARY"
|
|
115
|
+
params.should.equal({})
|
|
116
|
+
value.should.equal "Hello"
|
|
117
|
+
end
|
|
114
118
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
119
|
+
it "parses a property with one parameter" do
|
|
120
|
+
name, params, value = Protocol::Caldav::ContentLine.parse_line("DTSTART;TZID=America/New_York:20260101T090000")
|
|
121
|
+
name.should.equal "DTSTART"
|
|
122
|
+
params["TZID"].should.equal "America/New_York"
|
|
123
|
+
value.should.equal "20260101T090000"
|
|
124
|
+
end
|
|
121
125
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
126
|
+
it "parses a parameter with a quoted value" do
|
|
127
|
+
name, params, value = Protocol::Caldav::ContentLine.parse_line('ATTENDEE;CN="Smith, John":mailto:j@e.com')
|
|
128
|
+
params["CN"].should.equal "Smith, John"
|
|
129
|
+
value.should.equal "mailto:j@e.com"
|
|
130
|
+
end
|
|
127
131
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
132
|
+
it "handles a colon inside a quoted parameter value" do
|
|
133
|
+
name, params, value = Protocol::Caldav::ContentLine.parse_line('X;FOO="a:b":realvalue')
|
|
134
|
+
params["FOO"].should.equal "a:b"
|
|
135
|
+
value.should.equal "realvalue"
|
|
136
|
+
end
|
|
133
137
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
+
it "handles a property value containing colons" do
|
|
139
|
+
_, _, value = Protocol::Caldav::ContentLine.parse_line("URL:https://example.com:8080/path")
|
|
140
|
+
value.should.equal "https://example.com:8080/path"
|
|
141
|
+
end
|
|
138
142
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
+
it "handles an empty property value" do
|
|
144
|
+
_, _, value = Protocol::Caldav::ContentLine.parse_line("DESCRIPTION:")
|
|
145
|
+
value.should.equal ""
|
|
146
|
+
end
|
|
143
147
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
148
|
+
it "returns nil for a line with no colon" do
|
|
149
|
+
Protocol::Caldav::ContentLine.parse_line("NOCOLON").should.be.nil
|
|
150
|
+
end
|
|
147
151
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
end
|
|
152
|
+
it "uppercases parameter names" do
|
|
153
|
+
_, params, _ = Protocol::Caldav::ContentLine.parse_line("X;tzid=utc:val")
|
|
154
|
+
params.key?("TZID").should.equal true
|
|
152
155
|
end
|
|
153
156
|
end
|
|
154
157
|
end
|