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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0517bf6a8c74d0d76b09a3ea8115e6dd65114f3991e25d3c97578ec69cb64a9c
4
- data.tar.gz: 5f83b3cb2b118fe7ef22eef7140df410eea5f167f41e86b317dd260dbc6e0fc7
3
+ metadata.gz: a322d3a753a3dbee66ebd1afa1e1d8d7c195b96a243c45e0593cbf4a1c64d93c
4
+ data.tar.gz: 377c6989fdc81a44d0e6999dbb012b633f1899e08b7cd33330d318f58c5e9aa0
5
5
  SHA512:
6
- metadata.gz: 46c8c7806937d6945f1a1766a8ab6999d272b0e3d599a34bdc33fab780f10a106ae0e36ae380a45f17bf9a7af5a5bb302b9c8f9ec9cd1a8c84584e7b30baebe9
7
- data.tar.gz: ec019c2ee4e1d7e53ab85dabcb74098c6f03cbf97e3808df67f2e7ebd270a994a67ddd2cd2de970e8080b7f2588955534c9587469f67228bffac8ddef333b683
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
- dir = full_path(path)
43
- if File.directory?(dir)
44
- FileUtils.rm_rf(dir)
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
- file = full_path(path)
93
- return nil unless File.file?(file)
94
-
95
- body = File.read(file)
96
- content_type = guess_content_type(path)
97
- etag = Protocol::Caldav::ETag.compute(body)
98
- { body: body, content_type: content_type, etag: etag }
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
- file = full_path(path)
113
- if File.file?(file)
114
- File.delete(file)
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
- src = full_path(from_path)
140
- dst = full_path(to_path)
141
- return nil unless File.file?(src)
142
-
143
- FileUtils.mkdir_p(File.dirname(dst))
144
- FileUtils.mv(src, dst)
145
- get_item(to_path)
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
- fp = full_path(path)
156
- File.exist?(fp) || collection_exists?(path)
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
- File.join(@root, path)
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Async
4
4
  module Caldav
5
- VERSION = "1.2.1"
5
+ VERSION = "1.2.2"
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.1
4
+ version: 1.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan K