rack-webdav 0.4.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 +7 -0
- data/.gitignore +8 -0
- data/LICENSE +42 -0
- data/README.rdoc +336 -0
- data/bin/rack-webdav +126 -0
- data/lib/rack-webdav.rb +14 -0
- data/lib/rack-webdav/controller.rb +601 -0
- data/lib/rack-webdav/file.rb +37 -0
- data/lib/rack-webdav/file_resource_lock.rb +169 -0
- data/lib/rack-webdav/handler.rb +63 -0
- data/lib/rack-webdav/http_status.rb +108 -0
- data/lib/rack-webdav/interceptor.rb +22 -0
- data/lib/rack-webdav/interceptor_resource.rb +119 -0
- data/lib/rack-webdav/lock.rb +40 -0
- data/lib/rack-webdav/lock_store.rb +61 -0
- data/lib/rack-webdav/logger.rb +30 -0
- data/lib/rack-webdav/remote_file.rb +148 -0
- data/lib/rack-webdav/resource.rb +485 -0
- data/lib/rack-webdav/resources/file_resource.rb +379 -0
- data/lib/rack-webdav/resources/mongo_resource.rb +341 -0
- data/lib/rack-webdav/utils.rb +40 -0
- data/lib/rack-webdav/version.rb +17 -0
- data/rack-webdav.gemspec +31 -0
- metadata +206 -0
data/lib/rack-webdav.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'uri'
|
3
|
+
require 'nokogiri'
|
4
|
+
|
5
|
+
require 'rack'
|
6
|
+
require 'rack-webdav/utils'
|
7
|
+
require 'rack-webdav/http_status'
|
8
|
+
require 'rack-webdav/resource'
|
9
|
+
require 'rack-webdav/handler'
|
10
|
+
require 'rack-webdav/controller'
|
11
|
+
|
12
|
+
module RackWebDAV
|
13
|
+
IS_18 = RUBY_VERSION[0,3] == '1.8'
|
14
|
+
end
|
@@ -0,0 +1,601 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module RackWebDAV
|
4
|
+
|
5
|
+
class Controller
|
6
|
+
include RackWebDAV::HTTPStatus
|
7
|
+
include RackWebDAV::Utils
|
8
|
+
|
9
|
+
attr_reader :request, :response, :resource
|
10
|
+
|
11
|
+
# request:: Rack::Request
|
12
|
+
# response:: Rack::Response
|
13
|
+
# options:: Options hash
|
14
|
+
# Create a new Controller.
|
15
|
+
# NOTE: options will be passed to Resource
|
16
|
+
def initialize(request, response, options={})
|
17
|
+
raise Forbidden if request.path_info.include?('..')
|
18
|
+
@request = request
|
19
|
+
@response = response
|
20
|
+
@options = options
|
21
|
+
|
22
|
+
@dav_extensions = options.delete(:dav_extensions) || []
|
23
|
+
@always_include_dav_header = options.delete(:always_include_dav_header)
|
24
|
+
|
25
|
+
@resource = resource_class.new(actual_path, implied_path, @request, @response, @options)
|
26
|
+
|
27
|
+
if(@always_include_dav_header)
|
28
|
+
add_dav_header
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# s:: string
|
33
|
+
# Escape URL string
|
34
|
+
def url_format(resource)
|
35
|
+
ret = URI.escape(resource.public_path)
|
36
|
+
if resource.collection? and ret[-1,1] != '/'
|
37
|
+
ret += '/'
|
38
|
+
end
|
39
|
+
ret
|
40
|
+
end
|
41
|
+
|
42
|
+
# s:: string
|
43
|
+
# Unescape URL string
|
44
|
+
def url_unescape(s)
|
45
|
+
URI.unescape(s)
|
46
|
+
end
|
47
|
+
|
48
|
+
def add_dav_header
|
49
|
+
unless(response['Dav'])
|
50
|
+
dav_support = %w(1 2) + @dav_extensions
|
51
|
+
response['Dav'] = dav_support.join(', ')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Return response to OPTIONS
|
56
|
+
def options
|
57
|
+
add_dav_header
|
58
|
+
response['Allow'] = 'OPTIONS,HEAD,GET,PUT,POST,DELETE,PROPFIND,PROPPATCH,MKCOL,COPY,MOVE,LOCK,UNLOCK'
|
59
|
+
response['Ms-Author-Via'] = 'DAV'
|
60
|
+
OK
|
61
|
+
end
|
62
|
+
|
63
|
+
# Return response to HEAD
|
64
|
+
def head
|
65
|
+
if(resource.exist?)
|
66
|
+
response['Etag'] = resource.etag
|
67
|
+
response['Content-Type'] = resource.content_type
|
68
|
+
response['Last-Modified'] = resource.last_modified.httpdate
|
69
|
+
OK
|
70
|
+
else
|
71
|
+
NotFound
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Return response to GET
|
76
|
+
def get
|
77
|
+
if(resource.exist?)
|
78
|
+
res = resource.get(request, response)
|
79
|
+
if(res == OK && !resource.collection?)
|
80
|
+
response['Etag'] = resource.etag
|
81
|
+
response['Content-Type'] = resource.content_type
|
82
|
+
response['Content-Length'] = resource.content_length.to_s
|
83
|
+
response['Last-Modified'] = resource.last_modified.httpdate
|
84
|
+
end
|
85
|
+
res
|
86
|
+
else
|
87
|
+
NotFound
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Return response to PUT
|
92
|
+
def put
|
93
|
+
if(resource.collection?)
|
94
|
+
Forbidden
|
95
|
+
elsif(!resource.parent_exists? || !resource.parent_collection?)
|
96
|
+
Conflict
|
97
|
+
else
|
98
|
+
resource.lock_check if resource.supports_locking?
|
99
|
+
status = resource.put(request, response)
|
100
|
+
response['Location'] = "#{scheme}://#{host}:#{port}#{url_format(resource)}" if status == Created
|
101
|
+
response.body = response['Location']
|
102
|
+
status
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Return response to POST
|
107
|
+
def post
|
108
|
+
resource.post(request, response)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Return response to DELETE
|
112
|
+
def delete
|
113
|
+
if(resource.exist?)
|
114
|
+
resource.lock_check if resource.supports_locking?
|
115
|
+
resource.delete
|
116
|
+
else
|
117
|
+
NotFound
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Return response to MKCOL
|
122
|
+
def mkcol
|
123
|
+
resource.lock_check if resource.supports_locking?
|
124
|
+
status = resource.make_collection
|
125
|
+
gen_url = "#{scheme}://#{host}:#{port}#{url_format(resource)}" if status == Created
|
126
|
+
if(resource.use_compat_mkcol_response?)
|
127
|
+
multistatus do |xml|
|
128
|
+
xml.response do
|
129
|
+
xml.href gen_url
|
130
|
+
xml.status "#{http_version} #{status.status_line}"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
else
|
134
|
+
response['Location'] = gen_url
|
135
|
+
status
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Return response to COPY
|
140
|
+
def copy
|
141
|
+
move(:copy)
|
142
|
+
end
|
143
|
+
|
144
|
+
# args:: Only argument used: :copy
|
145
|
+
# Move Resource to new location. If :copy is provided,
|
146
|
+
# Resource will be copied (implementation ease)
|
147
|
+
def move(*args)
|
148
|
+
unless(resource.exist?)
|
149
|
+
NotFound
|
150
|
+
else
|
151
|
+
resource.lock_check if resource.supports_locking? && !args.include(:copy)
|
152
|
+
destination = url_unescape(env['HTTP_DESTINATION'].sub(%r{https?://([^/]+)}, ''))
|
153
|
+
dest_host = $1
|
154
|
+
if(dest_host && dest_host.gsub(/:\d{2,5}$/, '') != request.host)
|
155
|
+
BadGateway
|
156
|
+
elsif(destination == resource.public_path)
|
157
|
+
Forbidden
|
158
|
+
else
|
159
|
+
collection = resource.collection?
|
160
|
+
dest = resource_class.new(destination, clean_path(destination), @request, @response, @options.merge(:user => resource.user))
|
161
|
+
status = nil
|
162
|
+
if(args.include?(:copy))
|
163
|
+
status = resource.copy(dest, overwrite)
|
164
|
+
else
|
165
|
+
return Conflict unless depth.is_a?(Symbol) || depth > 1
|
166
|
+
status = resource.move(dest, overwrite)
|
167
|
+
end
|
168
|
+
response['Location'] = "#{scheme}://#{host}:#{port}#{url_format(dest)}" if status == Created
|
169
|
+
# RFC 2518
|
170
|
+
if collection
|
171
|
+
multistatus do |xml|
|
172
|
+
xml.response do
|
173
|
+
xml.href "#{scheme}://#{host}:#{port}#{url_format(status == Created ? dest : resource)}"
|
174
|
+
xml.status "#{http_version} #{status.status_line}"
|
175
|
+
end
|
176
|
+
end
|
177
|
+
else
|
178
|
+
status
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Return response to PROPFIND
|
185
|
+
def propfind
|
186
|
+
unless(resource.exist?)
|
187
|
+
NotFound
|
188
|
+
else
|
189
|
+
unless(request_document.xpath("//#{ns}propfind/#{ns}allprop").empty?)
|
190
|
+
properties = resource.properties
|
191
|
+
else
|
192
|
+
check = request_document.xpath("//#{ns}propfind")
|
193
|
+
if(check && !check.empty?)
|
194
|
+
properties = request_document.xpath(
|
195
|
+
"//#{ns}propfind/#{ns}prop"
|
196
|
+
).children.find_all{ |item|
|
197
|
+
item.element?
|
198
|
+
}.map{ |item|
|
199
|
+
# We should do this, but Nokogiri transforms prefix w/ null href into
|
200
|
+
# something valid. Oops.
|
201
|
+
# TODO: Hacky grep fix that's horrible
|
202
|
+
hsh = to_element_hash(item)
|
203
|
+
if(hsh.namespace.nil? && !ns.empty?)
|
204
|
+
raise BadRequest if request_document.to_s.scan(%r{<#{item.name}[^>]+xmlns=""}).empty?
|
205
|
+
end
|
206
|
+
hsh
|
207
|
+
}.compact
|
208
|
+
else
|
209
|
+
raise BadRequest
|
210
|
+
end
|
211
|
+
end
|
212
|
+
multistatus do |xml|
|
213
|
+
find_resources.each do |resource|
|
214
|
+
xml.response do
|
215
|
+
unless(resource.propstat_relative_path)
|
216
|
+
xml.href "#{scheme}://#{host}:#{port}#{url_format(resource)}"
|
217
|
+
else
|
218
|
+
xml.href url_format(resource)
|
219
|
+
end
|
220
|
+
propstats(xml, get_properties(resource, properties.empty? ? resource.properties : properties))
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
# Return response to PROPPATCH
|
228
|
+
def proppatch
|
229
|
+
unless(resource.exist?)
|
230
|
+
NotFound
|
231
|
+
else
|
232
|
+
resource.lock_check if resource.supports_locking?
|
233
|
+
prop_actions = []
|
234
|
+
request_document.xpath("/#{ns}propertyupdate").children.each do |element|
|
235
|
+
case element.name
|
236
|
+
when 'set', 'remove'
|
237
|
+
prp = element.children.detect{|e|e.name == 'prop'}
|
238
|
+
if(prp)
|
239
|
+
prp.children.each do |elm|
|
240
|
+
next if elm.name == 'text'
|
241
|
+
prop_actions << {:type => element.name, :name => to_element_hash(elm), :value => elm.text}
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
multistatus do |xml|
|
247
|
+
find_resources.each do |resource|
|
248
|
+
xml.response do
|
249
|
+
xml.href "#{scheme}://#{host}:#{port}#{url_format(resource)}"
|
250
|
+
prop_actions.each do |action|
|
251
|
+
case action[:type]
|
252
|
+
when 'set'
|
253
|
+
propstats(xml, set_properties(resource, action[:name] => action[:value]))
|
254
|
+
when 'remove'
|
255
|
+
rm_properties(resource, action[:name] => action[:value])
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
|
265
|
+
# Lock current resource
|
266
|
+
# NOTE: This will pass an argument hash to Resource#lock and
|
267
|
+
# wait for a success/failure response.
|
268
|
+
def lock
|
269
|
+
lockinfo = request_document.xpath("//#{ns}lockinfo")
|
270
|
+
asked = {}
|
271
|
+
asked[:timeout] = request.env['Timeout'].split(',').map{|x|x.strip} if request.env['Timeout']
|
272
|
+
asked[:depth] = depth
|
273
|
+
unless([0, :infinity].include?(asked[:depth]))
|
274
|
+
BadRequest
|
275
|
+
else
|
276
|
+
asked[:scope] = lockinfo.xpath("//#{ns}lockscope").children.find_all{|n|n.element?}.map{|n|n.name}.first
|
277
|
+
asked[:type] = lockinfo.xpath("#{ns}locktype").children.find_all{|n|n.element?}.map{|n|n.name}.first
|
278
|
+
asked[:owner] = lockinfo.xpath("//#{ns}owner/#{ns}href").children.map{|n|n.text}.first
|
279
|
+
begin
|
280
|
+
lock_time, locktoken = resource.lock(asked)
|
281
|
+
render_xml(:prop) do |xml|
|
282
|
+
xml.lockdiscovery do
|
283
|
+
xml.activelock do
|
284
|
+
if(asked[:scope])
|
285
|
+
xml.lockscope do
|
286
|
+
xml.send(asked[:scope])
|
287
|
+
end
|
288
|
+
end
|
289
|
+
if(asked[:type])
|
290
|
+
xml.locktype do
|
291
|
+
xml.send(asked[:type])
|
292
|
+
end
|
293
|
+
end
|
294
|
+
xml.depth asked[:depth].to_s
|
295
|
+
xml.timeout lock_time ? "Second-#{lock_time}" : 'infinity'
|
296
|
+
xml.locktoken do
|
297
|
+
xml.href locktoken
|
298
|
+
end
|
299
|
+
if(asked[:owner])
|
300
|
+
xml.owner asked[:owner]
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
response.headers['Lock-Token'] = locktoken
|
306
|
+
response.status = resource.exist? ? OK : Created
|
307
|
+
rescue LockFailure => e
|
308
|
+
multistatus do |xml|
|
309
|
+
e.path_status.each_pair do |path, status|
|
310
|
+
xml.response do
|
311
|
+
xml.href path
|
312
|
+
xml.status "#{http_version} #{status.status_line}"
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
# Unlock current resource
|
321
|
+
def unlock
|
322
|
+
resource.unlock(lock_token)
|
323
|
+
end
|
324
|
+
|
325
|
+
# Perform authentication
|
326
|
+
# NOTE: Authentication will only be performed if the Resource
|
327
|
+
# has defined an #authenticate method
|
328
|
+
def authenticate
|
329
|
+
authed = true
|
330
|
+
if(resource.respond_to?(:authenticate, true))
|
331
|
+
authed = false
|
332
|
+
uname = nil
|
333
|
+
password = nil
|
334
|
+
if(request.env['HTTP_AUTHORIZATION'])
|
335
|
+
auth = Rack::Auth::Basic::Request.new(request.env)
|
336
|
+
if(auth.basic? && auth.credentials)
|
337
|
+
uname = auth.credentials[0]
|
338
|
+
password = auth.credentials[1]
|
339
|
+
end
|
340
|
+
end
|
341
|
+
authed = resource.send(:authenticate, uname, password)
|
342
|
+
end
|
343
|
+
raise Unauthorized unless authed
|
344
|
+
end
|
345
|
+
|
346
|
+
private
|
347
|
+
|
348
|
+
# Request environment variables
|
349
|
+
def env
|
350
|
+
@request.env
|
351
|
+
end
|
352
|
+
|
353
|
+
# Current request scheme (http/https)
|
354
|
+
def scheme
|
355
|
+
request.scheme
|
356
|
+
end
|
357
|
+
|
358
|
+
# Request host
|
359
|
+
def host
|
360
|
+
request.host
|
361
|
+
end
|
362
|
+
|
363
|
+
# Request port
|
364
|
+
def port
|
365
|
+
request.port
|
366
|
+
end
|
367
|
+
|
368
|
+
# Class of the resource in use
|
369
|
+
def resource_class
|
370
|
+
@options[:resource_class]
|
371
|
+
end
|
372
|
+
|
373
|
+
# Root URI path for the resource
|
374
|
+
def root_uri_path
|
375
|
+
@options[:root_uri_path]
|
376
|
+
end
|
377
|
+
|
378
|
+
# Returns Resource path with root URI removed
|
379
|
+
def implied_path
|
380
|
+
clean_path(@request.path.dup)
|
381
|
+
end
|
382
|
+
|
383
|
+
# x:: request path
|
384
|
+
# Unescapes path and removes root URI if applicable
|
385
|
+
def clean_path(x)
|
386
|
+
ip = url_unescape(x)
|
387
|
+
ip.gsub!(/^#{Regexp.escape(root_uri_path)}/, '') if root_uri_path
|
388
|
+
ip
|
389
|
+
end
|
390
|
+
|
391
|
+
# Unescaped request path
|
392
|
+
def actual_path
|
393
|
+
url_unescape(@request.path.dup)
|
394
|
+
end
|
395
|
+
|
396
|
+
# Lock token if provided by client
|
397
|
+
def lock_token
|
398
|
+
env['HTTP_LOCK_TOKEN'] || nil
|
399
|
+
end
|
400
|
+
|
401
|
+
# Requested depth
|
402
|
+
def depth
|
403
|
+
d = env['HTTP_DEPTH']
|
404
|
+
if(d =~ /^\d+$/)
|
405
|
+
d = d.to_i
|
406
|
+
else
|
407
|
+
d = :infinity
|
408
|
+
end
|
409
|
+
d
|
410
|
+
end
|
411
|
+
|
412
|
+
# Current HTTP version being used
|
413
|
+
def http_version
|
414
|
+
env['HTTP_VERSION'] || env['SERVER_PROTOCOL'] || 'HTTP/1.0'
|
415
|
+
end
|
416
|
+
|
417
|
+
# Overwrite is allowed
|
418
|
+
def overwrite
|
419
|
+
env['HTTP_OVERWRITE'].to_s.upcase != 'F'
|
420
|
+
end
|
421
|
+
|
422
|
+
# Find resources at depth requested
|
423
|
+
def find_resources(with_current_resource=true)
|
424
|
+
ary = nil
|
425
|
+
case depth
|
426
|
+
when 0
|
427
|
+
ary = []
|
428
|
+
when 1
|
429
|
+
ary = resource.children
|
430
|
+
else
|
431
|
+
ary = resource.descendants
|
432
|
+
end
|
433
|
+
with_current_resource ? [resource] + ary : ary
|
434
|
+
end
|
435
|
+
|
436
|
+
# XML parsed request
|
437
|
+
def request_document
|
438
|
+
@request_document ||= Nokogiri.XML(request.body.read)
|
439
|
+
rescue
|
440
|
+
raise BadRequest
|
441
|
+
end
|
442
|
+
|
443
|
+
# Namespace being used within XML document
|
444
|
+
# TODO: Make this better
|
445
|
+
def ns(wanted_uri="DAV:")
|
446
|
+
_ns = ''
|
447
|
+
if(request_document && request_document.root && request_document.root.namespace_definitions.size > 0)
|
448
|
+
_ns = request_document.root.namespace_definitions.collect{|__ns| __ns if __ns.href == wanted_uri}.compact
|
449
|
+
if _ns.empty?
|
450
|
+
_ns = request_document.root.namespace_definitions.first.prefix.to_s if _ns.empty?
|
451
|
+
else
|
452
|
+
_ns = _ns.first
|
453
|
+
_ns = _ns.prefix.nil? ? 'xmlns' : _ns.prefix.to_s
|
454
|
+
end
|
455
|
+
_ns += ':' unless _ns.empty?
|
456
|
+
end
|
457
|
+
_ns
|
458
|
+
end
|
459
|
+
|
460
|
+
# root_type:: Root tag name
|
461
|
+
# Render XML and set Rack::Response#body= to final XML
|
462
|
+
def render_xml(root_type)
|
463
|
+
raise ArgumentError.new 'Expecting block' unless block_given?
|
464
|
+
doc = Nokogiri::XML::Builder.new do |xml_base|
|
465
|
+
xml_base.send(root_type.to_s, {'xmlns:D' => 'DAV:'}.merge(resource.root_xml_attributes)) do
|
466
|
+
xml_base.parent.namespace = xml_base.parent.namespace_definitions.first
|
467
|
+
xml = xml_base['D']
|
468
|
+
yield xml
|
469
|
+
end
|
470
|
+
end
|
471
|
+
|
472
|
+
if(@options[:pretty_xml])
|
473
|
+
response.body = doc.to_xml
|
474
|
+
else
|
475
|
+
response.body = doc.to_xml(
|
476
|
+
:save_with => Nokogiri::XML::Node::SaveOptions::AS_XML
|
477
|
+
)
|
478
|
+
end
|
479
|
+
response["Content-Type"] = 'text/xml; charset="utf-8"'
|
480
|
+
response["Content-Length"] = response.body.size.to_s
|
481
|
+
end
|
482
|
+
|
483
|
+
# block:: block
|
484
|
+
# Creates a multistatus response using #render_xml and
|
485
|
+
# returns the correct status
|
486
|
+
def multistatus(&block)
|
487
|
+
render_xml(:multistatus, &block)
|
488
|
+
MultiStatus
|
489
|
+
end
|
490
|
+
|
491
|
+
# xml:: Nokogiri::XML::Builder
|
492
|
+
# errors:: Array of errors
|
493
|
+
# Crafts responses for errors
|
494
|
+
def response_errors(xml, errors)
|
495
|
+
for path, status in errors
|
496
|
+
xml.response do
|
497
|
+
xml.href "#{scheme}://#{host}:#{port}#{URI.escape(path)}"
|
498
|
+
xml.status "#{http_version} #{status.status_line}"
|
499
|
+
end
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
# resource:: Resource
|
504
|
+
# elements:: Property hashes (name, ns_href, children)
|
505
|
+
# Returns array of property values for given names
|
506
|
+
def get_properties(resource, elements)
|
507
|
+
stats = Hash.new { |h, k| h[k] = [] }
|
508
|
+
for element in elements
|
509
|
+
begin
|
510
|
+
val = resource.get_property(element)
|
511
|
+
stats[OK] << [element, val]
|
512
|
+
rescue Unauthorized => u
|
513
|
+
raise u
|
514
|
+
rescue Status
|
515
|
+
stats[$!.class] << element
|
516
|
+
end
|
517
|
+
end
|
518
|
+
stats
|
519
|
+
end
|
520
|
+
|
521
|
+
# resource:: Resource
|
522
|
+
# elements:: Property hashes (name, namespace, children)
|
523
|
+
# Removes the given properties from a resource
|
524
|
+
def rm_properties(resource, elements)
|
525
|
+
for element, value in elements
|
526
|
+
resource.remove_property(element)
|
527
|
+
end
|
528
|
+
end
|
529
|
+
|
530
|
+
# resource:: Resource
|
531
|
+
# elements:: Property hashes (name, namespace, children)
|
532
|
+
# Sets the given properties
|
533
|
+
def set_properties(resource, elements)
|
534
|
+
stats = Hash.new { |h, k| h[k] = [] }
|
535
|
+
for element, value in elements
|
536
|
+
begin
|
537
|
+
stats[OK] << [element, resource.set_property(element, value)]
|
538
|
+
rescue Unauthorized => u
|
539
|
+
raise u
|
540
|
+
rescue Status
|
541
|
+
stats[$!.class] << element
|
542
|
+
end
|
543
|
+
end
|
544
|
+
stats
|
545
|
+
end
|
546
|
+
|
547
|
+
# xml:: Nokogiri::XML::Builder
|
548
|
+
# stats:: Array of stats
|
549
|
+
# Build propstats response
|
550
|
+
def propstats(xml, stats)
|
551
|
+
return if stats.empty?
|
552
|
+
for status, props in stats
|
553
|
+
xml.propstat do
|
554
|
+
xml.prop do
|
555
|
+
for element, value in props
|
556
|
+
defn = xml.doc.root.namespace_definitions.find{|ns_def| ns_def.href == element[:ns_href]}
|
557
|
+
if defn.nil?
|
558
|
+
if element[:ns_href] and not element[:ns_href].empty?
|
559
|
+
_ns = "unknown#{rand(65536)}"
|
560
|
+
xml.doc.root.add_namespace_definition(_ns, element[:ns_href])
|
561
|
+
else
|
562
|
+
_ns = nil
|
563
|
+
end
|
564
|
+
else
|
565
|
+
# Unfortunately Nokogiri won't let the null href, non-null prefix happen
|
566
|
+
# So we can't properly handle that error.
|
567
|
+
_ns = element[:ns_href].nil? ? nil : defn.prefix
|
568
|
+
end
|
569
|
+
ns_xml = _ns.nil? ? xml : xml[_ns]
|
570
|
+
if (value.is_a?(Nokogiri::XML::Node)) or (value.is_a?(Nokogiri::XML::DocumentFragment))
|
571
|
+
xml.__send__ :insert, value
|
572
|
+
elsif(value.is_a?(Symbol))
|
573
|
+
ns_xml.send(element[:name]) do
|
574
|
+
ns_xml.send(value)
|
575
|
+
end
|
576
|
+
else
|
577
|
+
ns_xml.send(element[:name], value) do |x|
|
578
|
+
# Make sure we return valid XML
|
579
|
+
x.parent.namespace = nil if _ns.nil?
|
580
|
+
end
|
581
|
+
end
|
582
|
+
|
583
|
+
# This is gross, but make sure we set the current namespace back to DAV:
|
584
|
+
xml['D']
|
585
|
+
end
|
586
|
+
end
|
587
|
+
xml.status "#{http_version} #{status.status_line}"
|
588
|
+
end
|
589
|
+
end
|
590
|
+
end
|
591
|
+
|
592
|
+
# xml:: Nokogiri::XML::Builder
|
593
|
+
# element:: Nokogiri::XML::Element
|
594
|
+
# Converts element into proper text
|
595
|
+
def xml_convert(xml, element)
|
596
|
+
xml.doc.root.add_child(element)
|
597
|
+
end
|
598
|
+
|
599
|
+
end
|
600
|
+
|
601
|
+
end
|