rack_dav 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
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.