async-caldav 1.2.1 → 1.2.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 +4 -4
- data/lib/async/caldav/storage/filesystem.rb +66 -23
- data/lib/async/caldav/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a322d3a753a3dbee66ebd1afa1e1d8d7c195b96a243c45e0593cbf4a1c64d93c
|
|
4
|
+
data.tar.gz: 377c6989fdc81a44d0e6999dbb012b633f1899e08b7cd33330d318f58c5e9aa0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: da4e219d79c691636fa2c6f13ca1f9f7a77bdee9e68005671494288c523115c14db068d585071bac19cd1f6c79b6723e186cbf81a185718225f4447c8b41a67d
|
|
7
|
+
data.tar.gz: b189f9332228946eb7da0ca7a5d23f28bbea5634ca1463661683c4b16328264ca15489a7f43f0c4bcd37737f6192c6c8dff9129d100dcec2c1a69ac99302fece
|
|
@@ -14,6 +14,8 @@ module Async
|
|
|
14
14
|
@root = root
|
|
15
15
|
@sync_snapshots = {}
|
|
16
16
|
FileUtils.mkdir_p(@root)
|
|
17
|
+
@root_normalized = ::File.expand_path(@root)
|
|
18
|
+
@root_with_separator = @root_normalized.end_with?(::File::SEPARATOR) ? @root_normalized : "#{@root_normalized}#{::File::SEPARATOR}"
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
# --- Collections ---
|
|
@@ -39,9 +41,9 @@ module Async
|
|
|
39
41
|
end
|
|
40
42
|
|
|
41
43
|
def delete_collection(path)
|
|
42
|
-
|
|
43
|
-
if
|
|
44
|
-
FileUtils.rm_rf(
|
|
44
|
+
s = stat(path)
|
|
45
|
+
if s && s.directory?
|
|
46
|
+
FileUtils.rm_rf(full_path(path))
|
|
45
47
|
true
|
|
46
48
|
else
|
|
47
49
|
false
|
|
@@ -89,13 +91,15 @@ module Async
|
|
|
89
91
|
# --- Items ---
|
|
90
92
|
|
|
91
93
|
def get_item(path)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
s = stat(path)
|
|
95
|
+
if s && s.file?
|
|
96
|
+
body = File.read(full_path(path))
|
|
97
|
+
content_type = guess_content_type(path)
|
|
98
|
+
etag = Protocol::Caldav::ETag.compute(body)
|
|
99
|
+
{ body: body, content_type: content_type, etag: etag }
|
|
100
|
+
else
|
|
101
|
+
nil
|
|
102
|
+
end
|
|
99
103
|
end
|
|
100
104
|
|
|
101
105
|
def put_item(path, body, content_type)
|
|
@@ -109,9 +113,9 @@ module Async
|
|
|
109
113
|
end
|
|
110
114
|
|
|
111
115
|
def delete_item(path)
|
|
112
|
-
|
|
113
|
-
if
|
|
114
|
-
File.delete(
|
|
116
|
+
s = stat(path)
|
|
117
|
+
if s && s.file?
|
|
118
|
+
File.delete(full_path(path))
|
|
115
119
|
true
|
|
116
120
|
else
|
|
117
121
|
false
|
|
@@ -136,13 +140,15 @@ module Async
|
|
|
136
140
|
end
|
|
137
141
|
|
|
138
142
|
def move_item(from_path, to_path)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
s = stat(from_path)
|
|
144
|
+
if s && s.file?
|
|
145
|
+
dst = full_path(to_path)
|
|
146
|
+
FileUtils.mkdir_p(File.dirname(dst))
|
|
147
|
+
FileUtils.mv(full_path(from_path), dst)
|
|
148
|
+
get_item(to_path)
|
|
149
|
+
else
|
|
150
|
+
nil
|
|
151
|
+
end
|
|
146
152
|
end
|
|
147
153
|
|
|
148
154
|
def get_multi(paths)
|
|
@@ -152,8 +158,11 @@ module Async
|
|
|
152
158
|
# --- General ---
|
|
153
159
|
|
|
154
160
|
def exists?(path)
|
|
155
|
-
|
|
156
|
-
|
|
161
|
+
if stat(path)
|
|
162
|
+
true
|
|
163
|
+
else
|
|
164
|
+
collection_exists?(path)
|
|
165
|
+
end
|
|
157
166
|
end
|
|
158
167
|
|
|
159
168
|
def etag(path)
|
|
@@ -208,7 +217,26 @@ module Async
|
|
|
208
217
|
private
|
|
209
218
|
|
|
210
219
|
def full_path(path)
|
|
211
|
-
|
|
220
|
+
if valid_path?(path)
|
|
221
|
+
expanded = ::File.expand_path(::File.join(@root, path))
|
|
222
|
+
if expanded == @root_normalized || expanded.start_with?(@root_with_separator)
|
|
223
|
+
expanded
|
|
224
|
+
else
|
|
225
|
+
raise ArgumentError, "path escapes root: #{path.inspect}"
|
|
226
|
+
end
|
|
227
|
+
else
|
|
228
|
+
raise ArgumentError, "invalid path: #{path.inspect}"
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def valid_path?(path)
|
|
233
|
+
path.is_a?(String) && path.valid_encoding? && !path.include?("\0")
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def stat(path)
|
|
237
|
+
::File.stat(full_path(path))
|
|
238
|
+
rescue Errno::ENOENT, Errno::ELOOP
|
|
239
|
+
nil
|
|
212
240
|
end
|
|
213
241
|
|
|
214
242
|
def guess_content_type(path)
|
|
@@ -361,6 +389,21 @@ test do
|
|
|
361
389
|
end
|
|
362
390
|
end
|
|
363
391
|
|
|
392
|
+
it "rejects path traversal attempts" do
|
|
393
|
+
Dir.mktmpdir do |dir|
|
|
394
|
+
s = Async::Caldav::Storage::Filesystem.new(dir)
|
|
395
|
+
lambda { s.get_item("/../../etc/passwd") }.should.raise ArgumentError
|
|
396
|
+
lambda { s.put_item("/cal/../../escape.ics", "x", "text/calendar") }.should.raise ArgumentError
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
it "rejects NUL byte in path" do
|
|
401
|
+
Dir.mktmpdir do |dir|
|
|
402
|
+
s = Async::Caldav::Storage::Filesystem.new(dir)
|
|
403
|
+
lambda { s.get_item("/cal\0/x.ics") }.should.raise ArgumentError
|
|
404
|
+
end
|
|
405
|
+
end
|
|
406
|
+
|
|
364
407
|
it "same body produces same etag across backends" do
|
|
365
408
|
body = "BEGIN:VCALENDAR\r\nEND:VCALENDAR"
|
|
366
409
|
mock = Async::Caldav::Storage::Mock.new
|
data/lib/async/caldav/version.rb
CHANGED