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