rack_dav 0.4.0 → 0.4.1

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
  SHA1:
3
- metadata.gz: e79ae582f10f164cfcd53a6a2b5a48aaad203635
4
- data.tar.gz: e385c0976e3a93eecac509509c53ca7e304b44b8
3
+ metadata.gz: 849a36645773dd942cbf87d7e41a685ff5814157
4
+ data.tar.gz: 5269572bc9f41e2c6fa73eb23f1e31a7ac17aa58
5
5
  SHA512:
6
- metadata.gz: 0a3e8849bbed3be0100b1978a52d77510c1ccf4801079a2df9647155de3142dfd51911486f13c00a602cbb4db8fa4a46fbf953dbb091599c958ee1a57b42031c
7
- data.tar.gz: 01d765203ece91396cf9eb80584c4cfd14f99a8f356d0f5fa1a8fa9539905589d9919ca297a06a08e341a1fb52a96b8959ea1e2291c38cd8516443cb99c5f2ed
6
+ metadata.gz: b6e6183c272dbd1c9c292c2de86fda4afef9282db0e190cc9fa31f1d7d0af7d3d175e917442f83d11c53231bf07f676fa4990bf722781a385f29d2dfcbdab299
7
+ data.tar.gz: c591239bce9cff7162639b6bf1b24b28b4e1d193a87eafda61c1aa0477085f491683270622cdd78eaeaa9855fb2c1a039b0816bfa0da7b66e986b11c0c15b25c
@@ -1,26 +1,33 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rack_dav (0.3.1)
5
- nokogiri
6
- rack (>= 1.4.0)
4
+ rack_dav (0.4.0)
5
+ ffi-xattr (~> 0.1)
6
+ nokogiri (~> 1.5)
7
+ rack (~> 1.4)
7
8
 
8
9
  GEM
9
10
  remote: http://rubygems.org/
10
11
  specs:
11
- diff-lcs (1.2.1)
12
- nokogiri (1.5.8)
13
- nokogiri (1.5.8-java)
14
- rack (1.5.2)
15
- rake (10.0.3)
16
- rspec (2.13.0)
17
- rspec-core (~> 2.13.0)
18
- rspec-expectations (~> 2.13.0)
19
- rspec-mocks (~> 2.13.0)
20
- rspec-core (2.13.1)
21
- rspec-expectations (2.13.0)
12
+ diff-lcs (1.2.5)
13
+ ffi (1.9.10)
14
+ ffi (1.9.10-java)
15
+ ffi-xattr (0.1.2)
16
+ ffi
17
+ mini_portile (0.6.2)
18
+ nokogiri (1.6.6.2)
19
+ mini_portile (~> 0.6.0)
20
+ nokogiri (1.6.6.2-java)
21
+ rack (1.6.4)
22
+ rake (0.9.6)
23
+ rspec (2.99.0)
24
+ rspec-core (~> 2.99.0)
25
+ rspec-expectations (~> 2.99.0)
26
+ rspec-mocks (~> 2.99.0)
27
+ rspec-core (2.99.2)
28
+ rspec-expectations (2.99.2)
22
29
  diff-lcs (>= 1.1.3, < 2.0)
23
- rspec-mocks (2.13.0)
30
+ rspec-mocks (2.99.3)
24
31
 
25
32
  PLATFORMS
26
33
  java
@@ -28,5 +35,5 @@ PLATFORMS
28
35
 
29
36
  DEPENDENCIES
30
37
  rack_dav!
31
- rake (>= 0.9.0)
32
- rspec (>= 2.11.0)
38
+ rake (~> 0.9)
39
+ rspec (~> 2.11)
data/README.md CHANGED
@@ -29,9 +29,9 @@ script looks like this:
29
29
 
30
30
  require 'rubygems'
31
31
  require 'rack_dav'
32
-
32
+
33
33
  use Rack::CommonLogger
34
-
34
+
35
35
  run RackDAV::Handler.new(:root => '/path/to/docs')
36
36
 
37
37
  ## Implementing your own WebDAV resource
@@ -85,6 +85,10 @@ to retrieve and change the resources:
85
85
 
86
86
  * __make\_collection__: Create this resource as collection.
87
87
 
88
+ * __set_custom_property(name, value)__: Set a custom property on the resource. If the value is nil, delete the custom property.
89
+
90
+ * __get_custom_property(name)__: Return the value of the named custom property.
91
+
88
92
  * __lock(locktoken, timeout, lockscope=nil, locktype=nil, owner=nil)__: Lock this resource.
89
93
  If scope, type and owner are nil, refresh the given lock.
90
94
 
@@ -61,6 +61,7 @@ module RackDAV
61
61
  map_exceptions do
62
62
  resource.put
63
63
  end
64
+ response.status = Created
64
65
  end
65
66
 
66
67
  def post
@@ -70,6 +71,8 @@ module RackDAV
70
71
  end
71
72
 
72
73
  def delete
74
+ raise NotFound if not resource.exist?
75
+
73
76
  delete_recursive(resource, errors = [])
74
77
 
75
78
  if errors.empty?
@@ -82,6 +85,10 @@ module RackDAV
82
85
  end
83
86
 
84
87
  def mkcol
88
+ # Reject message bodies - RFC2518:8.3.1
89
+ body = @request.body.read(8)
90
+ fail UnsupportedMediaType if !body.nil? && body.length > 0
91
+
85
92
  map_exceptions do
86
93
  resource.make_collection
87
94
  end
@@ -98,6 +105,8 @@ module RackDAV
98
105
  raise Forbidden if destination == resource.path
99
106
 
100
107
  dest = resource_class.new(destination, @request, @response, @options)
108
+ raise PreconditionFailed if dest.exist? && !overwrite
109
+
101
110
  dest = dest.child(resource.name) if dest.collection?
102
111
 
103
112
  dest_existed = dest.exist?
@@ -125,9 +134,10 @@ module RackDAV
125
134
  raise Forbidden if destination == resource.path
126
135
 
127
136
  dest = resource_class.new(destination, @request, @response, @options)
128
- dest = dest.child(resource.name) if dest.collection?
137
+ raise PreconditionFailed if dest.exist? && !overwrite
129
138
 
130
139
  dest_existed = dest.exist?
140
+ dest = dest.child(resource.name) if dest.collection?
131
141
 
132
142
  raise Conflict if depth <= 1
133
143
 
@@ -149,11 +159,26 @@ module RackDAV
149
159
  raise NotFound if not resource.exist?
150
160
 
151
161
  if not request_match("/d:propfind/d:allprop").empty?
152
- names = resource.property_names
162
+ nodes = all_prop_nodes
153
163
  else
154
- names = request_match("/d:propfind/d:prop/d:*").map { |e| e.name }
155
- names = resource.property_names if names.empty?
156
- raise BadRequest if names.empty?
164
+ nodes = request_match("/d:propfind/d:prop/*")
165
+ nodes = all_prop_nodes if nodes.empty?
166
+ end
167
+
168
+ nodes.each do |n|
169
+ # Don't allow empty namespace declarations
170
+ # See litmus props test 3
171
+ raise BadRequest if n.namespace.nil? && n.namespace_definitions.empty?
172
+
173
+ # Set a blank namespace if one is included in the request
174
+ # See litmus props test 16
175
+ # <propfind xmlns="DAV:"><prop><nonamespace xmlns=""/></prop></propfind>
176
+ if n.namespace.nil?
177
+ nd = n.namespace_definitions.first
178
+ if nd.prefix.nil? && nd.href.empty?
179
+ n.add_namespace(nil, '')
180
+ end
181
+ end
157
182
  end
158
183
 
159
184
  multistatus do |xml|
@@ -161,7 +186,7 @@ module RackDAV
161
186
  resource.path.gsub!(/\/\//, '/')
162
187
  xml.response do
163
188
  xml.href "http://#{host}#{url_escape resource.path}"
164
- propstats xml, get_properties(resource, names)
189
+ propstats xml, get_properties(resource, nodes)
165
190
  end
166
191
  end
167
192
  end
@@ -170,19 +195,28 @@ module RackDAV
170
195
  def proppatch
171
196
  raise NotFound if not resource.exist?
172
197
 
173
- prop_rem = request_match("/d:propertyupdate/d:remove/d:prop/d:*").map { |e| [e.name] }
174
- prop_set = request_match("/d:propertyupdate/d:set/d:prop/d:*").map { |e| [e.name, e.text] }
198
+ nodes = request_match("/d:propertyupdate[d:remove/d:prop/* or d:set/d:prop/*]//d:prop/*")
199
+
200
+ # Set a blank namespace if one is included in the request
201
+ # See litmus props test 15
202
+ # <propertyupdate xmlns="DAV:"><set>
203
+ # <prop><nonamespace xmlns="">randomvalue</nonamespace></prop>
204
+ # </set></propertyupdate>
205
+ nodes.each do |n|
206
+ nd = n.namespace_definitions.first
207
+ if !nd.nil? && nd.prefix.nil? && nd.href.empty?
208
+ n.add_namespace(nil, '')
209
+ end
210
+ end
175
211
 
176
212
  multistatus do |xml|
177
213
  for resource in find_resources
178
214
  xml.response do
179
215
  xml.href "http://#{host}#{resource.path}"
180
- propstats xml, set_properties(resource, prop_set)
216
+ propstats xml, set_properties(resource, nodes)
181
217
  end
182
218
  end
183
219
  end
184
-
185
- resource.save
186
220
  end
187
221
 
188
222
  def lock
@@ -310,6 +344,22 @@ module RackDAV
310
344
  request_document.xpath(pattern, 'd' => 'DAV:')
311
345
  end
312
346
 
347
+ def qualified_node_name(node)
348
+ node.namespace.nil? || node.namespace.prefix.nil? ? node.name : "#{node.namespace.prefix}:#{node.name}"
349
+ end
350
+
351
+ def qualified_property_name(node)
352
+ node.namespace.nil? || node.namespace.href == 'DAV:' ? node.name : "{#{node.namespace.href}}#{node.name}"
353
+ end
354
+
355
+ def all_prop_nodes
356
+ resource.property_names.map do |n|
357
+ node = Nokogiri::XML::Element.new(n, request_document)
358
+ node.add_namespace(nil, 'DAV:')
359
+ node
360
+ end
361
+ end
362
+
313
363
  # Quick and dirty parsing of the WEBDAV Timeout header.
314
364
  # Refuses infinity, rejects anything but Second- timeouts
315
365
  #
@@ -371,29 +421,29 @@ module RackDAV
371
421
  end
372
422
  end
373
423
 
374
- def get_properties(resource, names)
424
+ def get_properties(resource, nodes)
375
425
  stats = Hash.new { |h, k| h[k] = [] }
376
- for name in names
426
+ for node in nodes
377
427
  begin
378
428
  map_exceptions do
379
- stats[OK] << [name, resource.get_property(name)]
429
+ stats[OK] << [node, resource.get_property(qualified_property_name(node))]
380
430
  end
381
431
  rescue Status
382
- stats[$!] << name
432
+ stats[$!] << node
383
433
  end
384
434
  end
385
435
  stats
386
436
  end
387
437
 
388
- def set_properties(resource, pairs)
438
+ def set_properties(resource, nodes)
389
439
  stats = Hash.new { |h, k| h[k] = [] }
390
- for name, value in pairs
440
+ for node in nodes
391
441
  begin
392
442
  map_exceptions do
393
- stats[OK] << [name, resource.set_property(name, value)]
443
+ stats[OK] << [node, resource.set_property(qualified_property_name(node), node.text)]
394
444
  end
395
445
  rescue Status
396
- stats[$!] << name
446
+ stats[$!] << node
397
447
  end
398
448
  end
399
449
  stats
@@ -404,13 +454,22 @@ module RackDAV
404
454
  for status, props in stats
405
455
  xml.propstat do
406
456
  xml.prop do
407
- for name, value in props
457
+ for node, value in props
408
458
  if value.is_a?(Nokogiri::XML::Node)
409
- xml.send(name) do
459
+ xml.send(qualified_node_name(node).to_sym) do
410
460
  rexml_convert(xml, value)
411
461
  end
412
462
  else
413
- xml.send(name, value)
463
+ attrs = {}
464
+ unless node.namespace.nil?
465
+ unless node.namespace.prefix.nil?
466
+ attrs = { "xmlns:#{node.namespace.prefix}" => node.namespace.href }
467
+ else
468
+ attrs = { 'xmlns' => node.namespace.href }
469
+ end
470
+ end
471
+
472
+ xml.send(qualified_node_name(node).to_sym, value, attrs)
414
473
  end
415
474
  end
416
475
  end
@@ -1,4 +1,5 @@
1
1
  require 'digest'
2
+ require 'ffi-xattr'
2
3
 
3
4
  module RackDAV
4
5
 
@@ -65,6 +66,28 @@ module RackDAV
65
66
  stat.size
66
67
  end
67
68
 
69
+ def set_custom_property(name, value)
70
+ if value.nil? || value.empty?
71
+ begin
72
+ xattr.remove("rack_dav:#{name}")
73
+ rescue Errno::ENOATTR
74
+ # If the attribute being deleted doesn't exist, just do nothing
75
+ end
76
+ else
77
+ xattr["rack_dav:#{name}"] = value
78
+ end
79
+ end
80
+
81
+ def get_custom_property(name)
82
+ value = xattr["rack_dav:#{name}"]
83
+ raise HTTPStatus::NotFound if value.nil?
84
+ value
85
+ end
86
+
87
+ def list_custom_properties
88
+ xattr.list.select { |a| a.start_with?('rack_dav') }.map { |a| a.sub(/^rack_dav:/, '') }
89
+ end
90
+
68
91
  # HTTP GET request.
69
92
  #
70
93
  # Write the content of the resource to the response.body.
@@ -119,6 +142,10 @@ module RackDAV
119
142
  open(file_path, "rb") do |file|
120
143
  dest.write(file)
121
144
  end
145
+
146
+ list_custom_properties.each do |prop|
147
+ dest.set_custom_property(prop, get_custom_property(prop))
148
+ end
122
149
  end
123
150
  end
124
151
 
@@ -167,6 +194,10 @@ module RackDAV
167
194
  @stat ||= File.stat(file_path)
168
195
  end
169
196
 
197
+ def xattr
198
+ @xattr ||= Xattr.new(file_path)
199
+ end
200
+
170
201
  def content_md5_pass?(env)
171
202
  expected = env['HTTP_CONTENT_MD5'] or return true
172
203
 
@@ -148,6 +148,7 @@ module RackDAV
148
148
  when 'getcontenttype' then content_type
149
149
  when 'getetag' then etag
150
150
  when 'getlastmodified' then last_modified.httpdate
151
+ else self.get_custom_property(name) if self.respond_to?(:get_custom_property)
151
152
  end
152
153
  end
153
154
 
@@ -157,13 +158,14 @@ module RackDAV
157
158
  when 'getcontenttype' then self.content_type = value
158
159
  when 'getetag' then self.etag = value
159
160
  when 'getlastmodified' then self.last_modified = Time.httpdate(value)
161
+ else self.set_custom_property(name, value) if self.respond_to?(:set_custom_property)
160
162
  end
161
163
  rescue ArgumentError
162
164
  raise HTTPStatus::Conflict
163
165
  end
164
166
 
165
167
  def remove_property(name)
166
- raise HTTPStatus::Forbidden
168
+ raise HTTPStatus::Forbidden if property_names.include?(name)
167
169
  end
168
170
 
169
171
  def parent
@@ -4,7 +4,7 @@ module RackDAV
4
4
  module Version
5
5
  MAJOR = 0
6
6
  MINOR = 4
7
- PATCH = 0
7
+ PATCH = 1
8
8
  BUILD = nil
9
9
 
10
10
  STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join(".")
@@ -20,6 +20,7 @@ Gem::Specification.new do |s|
20
20
 
21
21
  s.add_dependency("rack", "~> 1.4")
22
22
  s.add_dependency('nokogiri', "~> 1.5")
23
+ s.add_dependency("ffi-xattr", "~> 0.1")
23
24
  s.add_development_dependency("rspec", "~> 2.11")
24
25
  s.add_development_dependency("rake","~> 0.9")
25
26
  end
@@ -26,6 +26,12 @@ class Rack::MockResponse
26
26
 
27
27
  end
28
28
 
29
+ if ENV['TRAVIS']
30
+ RSpec.configure do |c|
31
+ c.filter_run_excluding :has_xattr_support => true
32
+ end
33
+ end
34
+
29
35
  describe RackDAV::Handler do
30
36
 
31
37
  DOC_ROOT = File.expand_path(File.dirname(__FILE__) + '/htdocs')
@@ -66,7 +72,7 @@ describe RackDAV::Handler do
66
72
 
67
73
  describe "LOCK" do
68
74
  before(:each) do
69
- put("/test", :input => "body").should be_ok
75
+ put("/test", :input => "body").should be_created
70
76
  lock("/test", :input => File.read(fixture("requests/lock.xml")))
71
77
  end
72
78
 
@@ -78,7 +84,7 @@ describe RackDAV::Handler do
78
84
  it "sets a compliant rack response" do
79
85
  body = response.original_response.body
80
86
  body.should be_a(Array)
81
- body.should have(1).part
87
+ expect(body.size).to eq(1)
82
88
  end
83
89
 
84
90
  it "prints the lockdiscovery" do
@@ -128,7 +134,7 @@ describe RackDAV::Handler do
128
134
 
129
135
  describe "UNLOCK" do
130
136
  before(:each) do
131
- put("/test", :input => "body").should be_ok
137
+ put("/test", :input => "body").should be_created
132
138
  lock("/test", :input => File.read(fixture("requests/lock.xml"))).should be_ok
133
139
  end
134
140
 
@@ -176,12 +182,12 @@ describe RackDAV::Handler do
176
182
 
177
183
  describe "uri escaping" do
178
184
  it "allows url escaped utf-8" do
179
- put('/D%C3%B6ner').should be_ok
185
+ put('/D%C3%B6ner').should be_created
180
186
  get('/D%C3%B6ner').should be_ok
181
187
  end
182
188
 
183
189
  it "allows url escaped iso-8859" do
184
- put('/D%F6ner').should be_ok
190
+ put('/D%F6ner').should be_created
185
191
  get('/D%F6ner').should be_ok
186
192
  end
187
193
  end
@@ -222,7 +228,7 @@ describe RackDAV::Handler do
222
228
  end
223
229
 
224
230
  it 'should be successful' do
225
- response.should be_ok
231
+ response.should be_created
226
232
  end
227
233
 
228
234
  it 'should create the resource' do
@@ -233,7 +239,7 @@ describe RackDAV::Handler do
233
239
  end
234
240
 
235
241
  it 'should return headers' do
236
- put('/test.html', :input => '<html/>').should be_ok
242
+ put('/test.html', :input => '<html/>').should be_created
237
243
  head('/test.html').should be_ok
238
244
 
239
245
  response.headers['etag'].should_not be_nil
@@ -251,25 +257,25 @@ describe RackDAV::Handler do
251
257
  end
252
258
 
253
259
  it 'should create a resource and allow its retrieval' do
254
- put('/test', :input => 'body').should be_ok
260
+ put('/test', :input => 'body').should be_created
255
261
  get('/test').should be_ok
256
262
  response.body.should == 'body'
257
263
  end
258
264
  it 'should create and find a url with escaped characters' do
259
- put(url_escape('/a b'), :input => 'body').should be_ok
265
+ put(url_escape('/a b'), :input => 'body').should be_created
260
266
  get(url_escape('/a b')).should be_ok
261
267
  response.body.should == 'body'
262
268
  end
263
269
 
264
270
  it 'should delete a single resource' do
265
- put('/test', :input => 'body').should be_ok
271
+ put('/test', :input => 'body').should be_created
266
272
  delete('/test').should be_no_content
267
273
  end
268
274
 
269
275
  it 'should delete recursively' do
270
276
  mkcol('/folder').should be_created
271
- put('/folder/a', :input => 'body').should be_ok
272
- put('/folder/b', :input => 'body').should be_ok
277
+ put('/folder/a', :input => 'body').should be_created
278
+ put('/folder/b', :input => 'body').should be_created
273
279
 
274
280
  delete('/folder').should be_no_content
275
281
  get('/folder').should be_not_found
@@ -277,52 +283,59 @@ describe RackDAV::Handler do
277
283
  get('/folder/b').should be_not_found
278
284
  end
279
285
 
286
+ it 'should return not found when deleting a non-existent resource' do
287
+ delete('/not_found').should be_not_found
288
+ end
289
+
280
290
  it 'should not allow copy to another domain' do
281
- put('/test', :input => 'body').should be_ok
291
+ put('/test', :input => 'body').should be_created
282
292
  copy('http://localhost/', 'HTTP_DESTINATION' => 'http://another/').should be_bad_gateway
283
293
  end
284
294
 
285
295
  it 'should not allow copy to the same resource' do
286
- put('/test', :input => 'body').should be_ok
296
+ put('/test', :input => 'body').should be_created
287
297
  copy('/test', 'HTTP_DESTINATION' => '/test').should be_forbidden
288
298
  end
289
299
 
290
300
  it 'should not allow an invalid destination uri' do
291
- put('/test', :input => 'body').should be_ok
301
+ put('/test', :input => 'body').should be_created
292
302
  copy('/test', 'HTTP_DESTINATION' => '%').should be_bad_request
293
303
  end
294
304
 
295
305
  it 'should copy a single resource' do
296
- put('/test', :input => 'body').should be_ok
306
+ put('/test', :input => 'body').should be_created
297
307
  copy('/test', 'HTTP_DESTINATION' => '/copy').should be_created
298
308
  get('/copy').body.should == 'body'
299
309
  end
300
310
 
301
311
  it 'should copy a resource with escaped characters' do
302
- put(url_escape('/a b'), :input => 'body').should be_ok
312
+ put(url_escape('/a b'), :input => 'body').should be_created
303
313
  copy(url_escape('/a b'), 'HTTP_DESTINATION' => url_escape('/a c')).should be_created
304
314
  get(url_escape('/a c')).should be_ok
305
315
  response.body.should == 'body'
306
316
  end
307
317
 
308
318
  it 'should deny a copy without overwrite' do
309
- put('/test', :input => 'body').should be_ok
310
- put('/copy', :input => 'copy').should be_ok
311
- copy('/test', 'HTTP_DESTINATION' => '/copy', 'HTTP_OVERWRITE' => 'F')
312
-
313
- multistatus_response('/d:href').first.text.should == 'http://localhost/test'
314
- multistatus_response('/d:status').first.text.should match(/412 Precondition Failed/)
319
+ put('/test', :input => 'body').should be_created
320
+ put('/copy', :input => 'copy').should be_created
321
+ copy('/test', 'HTTP_DESTINATION' => '/copy', 'HTTP_OVERWRITE' => 'F').should be_precondition_failed
315
322
 
316
323
  get('/copy').body.should == 'copy'
317
324
  end
318
325
 
319
326
  it 'should allow a copy with overwrite' do
320
- put('/test', :input => 'body').should be_ok
321
- put('/copy', :input => 'copy').should be_ok
327
+ put('/test', :input => 'body').should be_created
328
+ put('/copy', :input => 'copy').should be_created
322
329
  copy('/test', 'HTTP_DESTINATION' => '/copy', 'HTTP_OVERWRITE' => 'T').should be_no_content
323
330
  get('/copy').body.should == 'body'
324
331
  end
325
332
 
333
+ it 'should deny a move to an existing resource without overwrite' do
334
+ put('/test', :input => 'body').should be_created
335
+ put('/copy', :input => 'copy').should be_created
336
+ move('/test', 'HTTP_DESTINATION' => '/copy', 'HTTP_OVERWRITE' => 'F').should be_precondition_failed
337
+ end
338
+
326
339
  it 'should copy a collection' do
327
340
  mkcol('/folder').should be_created
328
341
  copy('/folder', 'HTTP_DESTINATION' => '/copy').should be_created
@@ -332,8 +345,8 @@ describe RackDAV::Handler do
332
345
 
333
346
  it 'should copy a collection resursively' do
334
347
  mkcol('/folder').should be_created
335
- put('/folder/a', :input => 'A').should be_ok
336
- put('/folder/b', :input => 'B').should be_ok
348
+ put('/folder/a', :input => 'A').should be_created
349
+ put('/folder/b', :input => 'B').should be_created
337
350
 
338
351
  copy('/folder', 'HTTP_DESTINATION' => '/copy').should be_created
339
352
  propfind('/copy', :input => propfind_xml(:resourcetype))
@@ -345,8 +358,8 @@ describe RackDAV::Handler do
345
358
 
346
359
  it 'should move a collection recursively' do
347
360
  mkcol('/folder').should be_created
348
- put('/folder/a', :input => 'A').should be_ok
349
- put('/folder/b', :input => 'B').should be_ok
361
+ put('/folder/a', :input => 'A').should be_created
362
+ put('/folder/b', :input => 'B').should be_created
350
363
 
351
364
  move('/folder', 'HTTP_DESTINATION' => '/move').should be_created
352
365
  propfind('/move', :input => propfind_xml(:resourcetype))
@@ -358,19 +371,30 @@ describe RackDAV::Handler do
358
371
  get('/folder/b').should be_not_found
359
372
  end
360
373
 
374
+ it 'should not move a collection onto an existing collection without overwrite' do
375
+ mkcol('/folder').should be_created
376
+ mkcol('/dest').should be_created
377
+
378
+ move('/folder', 'HTTP_DESTINATION' => '/dest', 'HTTP_OVERWRITE' => 'F').should be_precondition_failed
379
+ end
380
+
361
381
  it 'should create a collection' do
362
382
  mkcol('/folder').should be_created
363
383
  propfind('/folder', :input => propfind_xml(:resourcetype))
364
384
  multistatus_response('/d:propstat/d:prop/d:resourcetype/d:collection').should_not be_empty
365
385
  end
366
386
 
387
+ it 'should not create a collection with a body' do
388
+ mkcol('/folder', :input => 'body').should be_unsupported_media_type
389
+ end
390
+
367
391
  it 'should not find properties for nonexistent resources' do
368
392
  propfind('/non').should be_not_found
369
393
  end
370
394
 
371
395
  it 'should find all properties' do
372
396
  xml = render do |xml|
373
- xml.propfind('xmlns:d' => "DAV:") do
397
+ xml.propfind('xmlns' => "DAV:") do
374
398
  xml.allprop
375
399
  end
376
400
  end
@@ -386,15 +410,53 @@ describe RackDAV::Handler do
386
410
  end
387
411
 
388
412
  it 'should find named properties' do
389
- put('/test.html', :input => '<html/>').should be_ok
413
+ put('/test.html', :input => '<html/>').should be_created
390
414
  propfind('/test.html', :input => propfind_xml(:getcontenttype, :getcontentlength))
391
415
 
392
416
  multistatus_response('/d:propstat/d:prop/d:getcontenttype').first.text.should == 'text/html'
393
417
  multistatus_response('/d:propstat/d:prop/d:getcontentlength').first.text.should == '7'
394
418
  end
395
419
 
420
+ it 'should set custom properties in the dav namespace', :has_xattr_support => true do
421
+ put('/prop', :input => 'A').should be_created
422
+ proppatch('/prop', :input => propset_xml([:foo, 'testing']))
423
+ multistatus_response('/d:propstat/d:prop/d:foo').should_not be_empty
424
+
425
+ propfind('/prop', :input => propfind_xml(:foo))
426
+ multistatus_response('/d:propstat/d:prop/d:foo').first.text.should == 'testing'
427
+ end
428
+
429
+ it 'should set custom properties in custom namespaces', :has_xattr_support => true do
430
+ xmlns = { 'xmlns:s' => 'SPEC:' }
431
+ put('/prop', :input => 'A').should be_created
432
+ proppatch('/prop', :input => propset_xml(['s:foo'.to_sym, 'testing', xmlns]))
433
+ multistatus_response('/d:propstat/d:prop/s:foo', xmlns).should_not be_empty
434
+
435
+ propfind('/prop', :input => propfind_xml(['s:foo'.to_sym, xmlns]))
436
+ multistatus_response('/d:propstat/d:prop/s:foo', xmlns).first.text.should == 'testing'
437
+ end
438
+
439
+ it 'should copy custom properties', :has_xattr_support => true do
440
+ xmlns = { 'xmlns:s' => 'SPEC:' }
441
+ put('/prop', :input => 'A').should be_created
442
+ proppatch('/prop', :input => propset_xml(['s:foo'.to_sym, 'testing', xmlns]))
443
+ multistatus_response('/d:propstat/d:prop/s:foo', xmlns).should_not be_empty
444
+
445
+ copy('/prop', 'HTTP_DESTINATION' => '/propcopy').should be_created
446
+ propfind('/propcopy', :input => propfind_xml(['s:foo'.to_sym, xmlns]))
447
+ multistatus_response('/d:propstat/d:prop/s:foo', xmlns).first.text.should == 'testing'
448
+ end
449
+
450
+ it 'should not set properties for a non-existent resource' do
451
+ proppatch('/not_found', :input => propset_xml([:foo, 'testing'])).should be_not_found
452
+ end
453
+
454
+ it 'should not return properties for non-existent resource' do
455
+ propfind('/prop', :input => propfind_xml(:foo)).should be_not_found
456
+ end
457
+
396
458
  it 'should return the correct charset (utf-8)' do
397
- put('/test.html', :input => '<html/>').should be_ok
459
+ put('/test.html', :input => '<html/>').should be_created
398
460
  propfind('/test.html', :input => propfind_xml(:getcontenttype, :getcontentlength))
399
461
 
400
462
  charset = @response.media_type_params['charset']
@@ -402,7 +464,7 @@ describe RackDAV::Handler do
402
464
  end
403
465
 
404
466
  it 'should not support LOCK' do
405
- put('/test', :input => 'body').should be_ok
467
+ put('/test', :input => 'body').should be_created
406
468
 
407
469
  xml = render do |xml|
408
470
  xml.lockinfo('xmlns:d' => "DAV:") do
@@ -416,7 +478,7 @@ describe RackDAV::Handler do
416
478
  end
417
479
 
418
480
  it 'should not support UNLOCK' do
419
- put('/test', :input => 'body').should be_ok
481
+ put('/test', :input => 'body').should be_created
420
482
  unlock('/test', :input => '').should be_method_not_allowed
421
483
  end
422
484
 
@@ -477,22 +539,39 @@ describe RackDAV::Handler do
477
539
  match['/d:locktoken/d:href'].first.text.should == token
478
540
  end
479
541
 
480
- def multistatus_response(pattern)
542
+ def multistatus_response(pattern, ns=nil)
543
+ xmlns = { 'd' => 'DAV:' }
544
+ xmlns.merge!(ns) unless ns.nil?
545
+
481
546
  @response.should be_multi_status
482
- response_xml.xpath("/d:multistatus/d:response", 'd' => 'DAV:').should_not be_empty
483
- response_xml.xpath("/d:multistatus/d:response" + pattern, 'd' => 'DAV:')
547
+ response_xml.xpath("/d:multistatus/d:response", xmlns).should_not be_empty
548
+ response_xml.xpath("/d:multistatus/d:response" + pattern, xmlns)
484
549
  end
485
550
 
486
551
  def propfind_xml(*props)
487
552
  render do |xml|
488
- xml.propfind('xmlns:d' => "DAV:") do
553
+ xml.propfind('xmlns' => "DAV:") do
489
554
  xml.prop do
490
- props.each do |prop|
491
- xml.send prop.to_sym
555
+ props.each do |prop, attrs|
556
+ xml.send(prop.to_sym, attrs)
492
557
  end
493
558
  end
494
559
  end
495
560
  end
496
561
  end
497
562
 
563
+ def propset_xml(*props)
564
+ render do |xml|
565
+ xml.propertyupdate('xmlns' => 'DAV:') do
566
+ xml.set do
567
+ xml.prop do
568
+ props.each do |prop, value, attrs|
569
+ attrs = {} if attrs.nil?
570
+ xml.send(prop.to_sym, value, attrs)
571
+ end
572
+ end
573
+ end
574
+ end
575
+ end
576
+ end
498
577
  end
metadata CHANGED
@@ -1,69 +1,83 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack_dav
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthias Georgi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-01-28 00:00:00.000000000 Z
11
+ date: 2015-08-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ~>
18
18
  - !ruby/object:Gem::Version
19
19
  version: '1.4'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ~>
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.4'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: nokogiri
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ~>
32
32
  - !ruby/object:Gem::Version
33
33
  version: '1.5'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ~>
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.5'
41
+ - !ruby/object:Gem::Dependency
42
+ name: ffi-xattr
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '0.1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '0.1'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: rspec
43
57
  requirement: !ruby/object:Gem::Requirement
44
58
  requirements:
45
- - - "~>"
59
+ - - ~>
46
60
  - !ruby/object:Gem::Version
47
61
  version: '2.11'
48
62
  type: :development
49
63
  prerelease: false
50
64
  version_requirements: !ruby/object:Gem::Requirement
51
65
  requirements:
52
- - - "~>"
66
+ - - ~>
53
67
  - !ruby/object:Gem::Version
54
68
  version: '2.11'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: rake
57
71
  requirement: !ruby/object:Gem::Requirement
58
72
  requirements:
59
- - - "~>"
73
+ - - ~>
60
74
  - !ruby/object:Gem::Version
61
75
  version: '0.9'
62
76
  type: :development
63
77
  prerelease: false
64
78
  version_requirements: !ruby/object:Gem::Requirement
65
79
  requirements:
66
- - - "~>"
80
+ - - ~>
67
81
  - !ruby/object:Gem::Version
68
82
  version: '0.9'
69
83
  description: WebDAV handler for Rack.
@@ -74,8 +88,8 @@ extensions: []
74
88
  extra_rdoc_files:
75
89
  - README.md
76
90
  files:
77
- - ".gitignore"
78
- - ".travis.yml"
91
+ - .gitignore
92
+ - .travis.yml
79
93
  - CHANGELOG.md
80
94
  - Gemfile
81
95
  - Gemfile.lock
@@ -110,17 +124,17 @@ require_paths:
110
124
  - lib
111
125
  required_ruby_version: !ruby/object:Gem::Requirement
112
126
  requirements:
113
- - - ">="
127
+ - - '>='
114
128
  - !ruby/object:Gem::Version
115
129
  version: '0'
116
130
  required_rubygems_version: !ruby/object:Gem::Requirement
117
131
  requirements:
118
- - - ">="
132
+ - - '>='
119
133
  - !ruby/object:Gem::Version
120
134
  version: '0'
121
135
  requirements: []
122
136
  rubyforge_project:
123
- rubygems_version: 2.4.3
137
+ rubygems_version: 2.0.14
124
138
  signing_key:
125
139
  specification_version: 4
126
140
  summary: WebDAV handler for Rack.