rack_dav_sp 0.2.dev

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ *~
2
+ doc/*
3
+ pkg/*
4
+ test/repo
@@ -0,0 +1,27 @@
1
+ = Changelog
2
+
3
+ == master
4
+
5
+ * NEW: Added Rakefile, package and test tasks.
6
+
7
+ * NEW: Added Gemfile and Bundler tasks.
8
+
9
+ * CHANGED: Upgraded to RSpec 2.0.
10
+
11
+ * CHANGED: Bump dependency to rack >= 1.2.0
12
+
13
+ * CHANGED: Removed response.status override/casting in RackDAV::Handler
14
+ because already performed in Rack::Response#finish.
15
+
16
+ * FIXED: rack_dav binary crashes if Mongrel is not installed.
17
+
18
+
19
+ == Release 0.1.3
20
+
21
+ * Unknown
22
+
23
+
24
+ == Release 0.1.2
25
+
26
+ * Unknown
27
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in rack_dav.gemspec
4
+ gemspec
@@ -0,0 +1,31 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rack_dav (0.2.dev)
5
+ builder
6
+ rack (>= 1.2.0)
7
+
8
+ GEM
9
+ remote: http://rubygems.org/
10
+ specs:
11
+ builder (3.0.0)
12
+ diff-lcs (1.1.2)
13
+ rack (1.2.3)
14
+ rspec (2.6.0)
15
+ rspec-core (~> 2.6.0)
16
+ rspec-expectations (~> 2.6.0)
17
+ rspec-mocks (~> 2.6.0)
18
+ rspec-core (2.6.3)
19
+ rspec-expectations (2.6.0)
20
+ diff-lcs (~> 1.1.2)
21
+ rspec-mocks (2.6.0)
22
+
23
+ PLATFORMS
24
+ java
25
+ ruby
26
+
27
+ DEPENDENCIES
28
+ builder
29
+ rack (>= 1.2.0)
30
+ rack_dav!
31
+ rspec (~> 2.6.0)
data/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2009 Matthias Georgi <http://www.matthias-georgi.de>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to
5
+ deal in the Software without restriction, including without limitation the
6
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7
+ sell copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,107 @@
1
+ ---
2
+ title: RackDAV - Web Authoring for Rack
3
+ ---
4
+
5
+ RackDAV is Handler for [Rack][1], which allows content authoring over
6
+ HTTP. RackDAV brings its own file backend, but other backends are
7
+ possible by subclassing RackDAV::Resource.
8
+
9
+ ## Install
10
+
11
+ Just install the gem from RubyGems:
12
+
13
+ $ gem install rack_dav
14
+
15
+ ## Quickstart
16
+
17
+ If you just want to share a folder over WebDAV, you can just start a
18
+ simple server with:
19
+
20
+ $ rack_dav
21
+
22
+ This will start a WEBrick server on port 3000, which you can connect
23
+ to without authentication.
24
+
25
+ ## Rack Handler
26
+
27
+ Using RackDAV inside a rack application is quite easy. A simple rackup
28
+ script looks like this:
29
+
30
+ require 'rubygems'
31
+ require 'rack_dav'
32
+
33
+ use Rack::CommonLogger
34
+
35
+ run RackDAV::Handler.new(:root => '/path/to/docs')
36
+
37
+ ## Implementing your own WebDAV resource
38
+
39
+ RackDAV::Resource is an abstract base class and defines an interface
40
+ for accessing resources.
41
+
42
+ Each resource will be initialized with a path, which should be used to
43
+ find the real resource.
44
+
45
+ RackDAV::Handler needs to be initialized with the actual resource class:
46
+
47
+ RackDAV::Handler.new(:resource_class => MyResource)
48
+
49
+ RackDAV needs some information about the resources, so you have to
50
+ implement following methods:
51
+
52
+ * __children__: If this is a collection, return the child resources.
53
+
54
+ * __collection?__: Is this resource a collection?
55
+
56
+ * __exist?__: Does this recource exist?
57
+
58
+ * __creation\_date__: Return the creation time.
59
+
60
+ * __last\_modified__: Return the time of last modification.
61
+
62
+ * __last\_modified=(time)__: Set the time of last modification.
63
+
64
+ * __etag__: Return an Etag, an unique hash value for this resource.
65
+
66
+ * __content_type__: Return the mime type of this resource.
67
+
68
+ * __content\_length__: Return the size in bytes for this resource.
69
+
70
+
71
+ Most importantly you have to implement the actions, which are called
72
+ to retrieve and change the resources:
73
+
74
+ * __get(request, response)__: Write the content of the resource to the response.body.
75
+
76
+ * __put(request, response)__: Save the content of the request.body.
77
+
78
+ * __post(request, response)__: Usually forbidden.
79
+
80
+ * __delete__: Delete this resource.
81
+
82
+ * __copy(dest)__: Copy this resource to given destination resource.
83
+
84
+ * __move(dest)__: Move this resource to given destination resource.
85
+
86
+ * __make\_collection__: Create this resource as collection.
87
+
88
+ * __lock(token, timeout, scope, type, owner)__: Lock this resource.
89
+ If scope, type and owner are nil, refresh the given lock.
90
+
91
+ * __unlock(token)__: Unlock this resource
92
+
93
+ Note, that it is generally possible, that a resource object is
94
+ instantiated for a not yet existing resource.
95
+
96
+ For inspiration you should have a look at the FileResource
97
+ implementation. Please let me now, if you are going to implement a new
98
+ type of resource.
99
+
100
+
101
+ ### RackDAV on GitHub
102
+
103
+ Download or fork the project on its [Github page][2]
104
+
105
+
106
+ [1]: http://github.com/chneukirchen/rack
107
+ [2]: http://github.com/georgi/rack_dav
@@ -0,0 +1,36 @@
1
+ require 'bundler'
2
+ require "rspec/core/rake_task"
3
+
4
+ Bundler::GemHelper.install_tasks
5
+
6
+
7
+ task :default => :spec
8
+
9
+ # Run all the specs in the /spec folder
10
+ RSpec::Core::RakeTask.new
11
+
12
+
13
+ namespace :spec do
14
+ desc "Run RSpec against all Ruby versions"
15
+ task :rubies => "spec:rubies:default"
16
+
17
+ namespace :rubies do
18
+ RUBIES = %w( 1.8.7-p330 1.9.2-p0 jruby-1.5.6 ree-1.8.7-2010.02 )
19
+
20
+ task :default => :ensure_rvm do
21
+ sh "rvm #{RUBIES.join(",")} rake default"
22
+ end
23
+
24
+ task :ensure_rvm do
25
+ File.exist?(File.expand_path("~/.rvm/scripts/rvm")) || abort("RVM is not available")
26
+ end
27
+
28
+ RUBIES.each do |ruby|
29
+ desc "Run RSpec against Ruby #{ruby}"
30
+ task ruby => :ensure_rvm do
31
+ sh "rvm #{ruby} rake default"
32
+ end
33
+ end
34
+ end
35
+
36
+ end
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift(File.expand_path("../../lib", __FILE__))
4
+
5
+ require 'rack_dav'
6
+
7
+ app = Rack::Builder.new do
8
+ use Rack::ShowExceptions
9
+ use Rack::CommonLogger
10
+ use Rack::Reloader
11
+ use Rack::Lint
12
+
13
+ run RackDAV::Handler.new
14
+
15
+ end.to_app
16
+
17
+ port = ARGV.first || 3000
18
+
19
+ begin
20
+ Rack::Handler::Mongrel.run(app, :Port => port)
21
+ rescue LoadError
22
+ Rack::Handler::WEBrick.run(app, :Port => port)
23
+ end
@@ -0,0 +1,14 @@
1
+ require 'rubygems'
2
+ require 'builder'
3
+ require 'time'
4
+ require 'uri'
5
+ require 'rexml/document'
6
+ require 'webrick/httputils'
7
+
8
+ require 'rack'
9
+ require 'rack_dav/builder_namespace'
10
+ require 'rack_dav/http_status'
11
+ require 'rack_dav/resource'
12
+ require 'rack_dav/file_resource'
13
+ require 'rack_dav/handler'
14
+ require 'rack_dav/controller'
@@ -0,0 +1,21 @@
1
+ module Builder
2
+
3
+ class XmlBase
4
+ def namespace(ns)
5
+ old_namespace = @namespace
6
+ @namespace = ns
7
+ yield
8
+ @namespace = old_namespace
9
+ self
10
+ end
11
+
12
+ alias_method :method_missing_without_namespace, :method_missing
13
+
14
+ def method_missing(sym, *args, &block)
15
+ sym = "#{@namespace}:#{sym}" if @namespace
16
+ method_missing_without_namespace(sym, *args, &block)
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,495 @@
1
+ module RackDAV
2
+
3
+ class Controller
4
+ include RackDAV::HTTPStatus
5
+
6
+ attr_reader :request, :response, :resource
7
+
8
+ def initialize(request, response, options)
9
+ @request = request
10
+ @response = response
11
+ @options = options
12
+ @resource = resource_class.new(url_unescape(request.path_info), @options)
13
+ raise Forbidden if request.path_info.include?('../')
14
+ end
15
+
16
+ def url_escape(s)
17
+ s.gsub(/([^\/a-zA-Z0-9_.-]+)/n) do
18
+ '%' + $1.unpack('H2' * $1.size).join('%').upcase
19
+ end.tr(' ', '+')
20
+ end
21
+
22
+ def url_unescape(s)
23
+ s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n) do
24
+ [$1.delete('%')].pack('H*')
25
+ end
26
+ end
27
+
28
+ def options
29
+ response["Allow"] = 'OPTIONS,HEAD,GET,PUT,POST,DELETE,PROPFIND,PROPPATCH,MKCOL,COPY,MOVE'
30
+ response["Dav"] = "1"
31
+
32
+ if resource.lockable?
33
+ response["Allow"] << ",LOCK,UNLOCK"
34
+ response["Dav"] << ",2"
35
+ end
36
+
37
+ response["Ms-Author-Via"] = "DAV"
38
+ end
39
+
40
+ def head
41
+ raise NotFound if not resource.exist?
42
+ response['Etag'] = resource.etag
43
+ response['Content-Type'] = resource.content_type
44
+ response['Last-Modified'] = resource.last_modified.httpdate
45
+ end
46
+
47
+ def get
48
+ raise NotFound if not resource.exist?
49
+ response['Etag'] = resource.etag
50
+ response['Content-Type'] = resource.content_type
51
+ response['Content-Length'] = resource.content_length.to_s
52
+ response['Last-Modified'] = resource.last_modified.httpdate
53
+ map_exceptions do
54
+ resource.get(request, response)
55
+ end
56
+ end
57
+
58
+ def put
59
+ raise Forbidden if resource.collection?
60
+ map_exceptions do
61
+ resource.put(request, response)
62
+ end
63
+ end
64
+
65
+ def post
66
+ map_exceptions do
67
+ resource.post(request, response)
68
+ end
69
+ end
70
+
71
+ def delete
72
+ delete_recursive(resource, errors = [])
73
+
74
+ if errors.empty?
75
+ response.status = NoContent
76
+ else
77
+ multistatus do |xml|
78
+ response_errors(xml, errors)
79
+ end
80
+ end
81
+ end
82
+
83
+ def mkcol
84
+ map_exceptions do
85
+ resource.make_collection
86
+ end
87
+ response.status = Created
88
+ end
89
+
90
+ def copy
91
+ raise NotFound if not resource.exist?
92
+
93
+ dest_uri = URI.parse(env['HTTP_DESTINATION'])
94
+ destination = url_unescape(dest_uri.path)
95
+
96
+ raise BadGateway if dest_uri.host and dest_uri.host != request.host
97
+ raise Forbidden if destination == resource.path
98
+
99
+ dest = resource_class.new(destination, @options)
100
+ dest = dest.child(resource.name) if dest.collection?
101
+
102
+ dest_existed = dest.exist?
103
+
104
+ copy_recursive(resource, dest, depth, errors = [])
105
+
106
+ if errors.empty?
107
+ response.status = dest_existed ? NoContent : Created
108
+ else
109
+ multistatus do |xml|
110
+ response_errors(xml, errors)
111
+ end
112
+ end
113
+ rescue URI::InvalidURIError => e
114
+ raise BadRequest.new(e.message)
115
+ end
116
+
117
+ def move
118
+ raise NotFound if not resource.exist?
119
+
120
+ dest_uri = URI.parse(env['HTTP_DESTINATION'])
121
+ destination = url_unescape(dest_uri.path)
122
+
123
+ raise BadGateway if dest_uri.host and dest_uri.host != request.host
124
+ raise Forbidden if destination == resource.path
125
+
126
+ dest = resource_class.new(destination, @options)
127
+ dest = dest.child(resource.name) if dest.collection?
128
+
129
+ dest_existed = dest.exist?
130
+
131
+ raise Conflict if depth <= 1
132
+
133
+ copy_recursive(resource, dest, depth, errors = [])
134
+ delete_recursive(resource, errors)
135
+
136
+ if errors.empty?
137
+ response.status = dest_existed ? NoContent : Created
138
+ else
139
+ multistatus do |xml|
140
+ response_errors(xml, errors)
141
+ end
142
+ end
143
+ rescue URI::InvalidURIError => e
144
+ raise BadRequest.new(e.message)
145
+ end
146
+
147
+ def propfind
148
+ raise NotFound if not resource.exist?
149
+
150
+ if not request_match("/propfind/allprop").empty?
151
+ names = resource.property_names
152
+ else
153
+ names = request_match("/propfind/prop/*").map { |e| e.name }
154
+ names = resource.property_names if names.empty?
155
+ raise BadRequest if names.empty?
156
+ end
157
+
158
+ multistatus do |xml|
159
+ for resource in find_resources
160
+ resource.path.gsub!(/\/\//, '/')
161
+ xml.response do
162
+ xml.href "http://#{host}#{url_escape resource.path}"
163
+ propstats xml, get_properties(resource, names)
164
+ end
165
+ end
166
+ end
167
+ end
168
+
169
+ def proppatch
170
+ raise NotFound if not resource.exist?
171
+
172
+ prop_rem = request_match("/propertyupdate/remove/prop/*").map { |e| [e.name] }
173
+ prop_set = request_match("/propertyupdate/set/prop/*").map { |e| [e.name, e.text] }
174
+
175
+ multistatus do |xml|
176
+ for resource in find_resources
177
+ xml.response do
178
+ xml.href "http://#{host}#{resource.path}"
179
+ propstats xml, set_properties(resource, prop_set)
180
+ end
181
+ end
182
+ end
183
+
184
+ resource.save
185
+ end
186
+
187
+ def lock
188
+ raise MethodNotAllowed unless resource.lockable?
189
+ raise NotFound if not resource.exist?
190
+
191
+ timeout = request_timeout
192
+ if timeout.nil? || timeout.zero?
193
+ timeout = 60
194
+ end
195
+
196
+ if request_document.to_s.empty?
197
+ refresh_lock timeout
198
+ else
199
+ create_lock timeout
200
+ end
201
+ end
202
+
203
+ def unlock
204
+ raise MethodNotAllowed unless resource.lockable?
205
+
206
+ locktoken = request_locktoken('LOCK_TOKEN')
207
+ raise BadRequest if locktoken.nil?
208
+
209
+ response.status = resource.unlock(locktoken) ? NoContent : Forbidden
210
+ end
211
+
212
+ private
213
+
214
+ def env
215
+ @request.env
216
+ end
217
+
218
+ def host
219
+ env['HTTP_HOST']
220
+ end
221
+
222
+ def resource_class
223
+ @options[:resource_class]
224
+ end
225
+
226
+ def depth
227
+ case env['HTTP_DEPTH']
228
+ when '0' then 0
229
+ when '1' then 1
230
+ else 100
231
+ end
232
+ end
233
+
234
+ def overwrite
235
+ env['HTTP_OVERWRITE'].to_s.upcase != 'F'
236
+ end
237
+
238
+ def find_resources
239
+ case env['HTTP_DEPTH']
240
+ when '0'
241
+ [resource]
242
+ when '1'
243
+ [resource] + resource.children
244
+ else
245
+ [resource] + resource.descendants
246
+ end
247
+ end
248
+
249
+ def delete_recursive(res, errors)
250
+ for child in res.children
251
+ delete_recursive(child, errors)
252
+ end
253
+
254
+ begin
255
+ map_exceptions { res.delete } if errors.empty?
256
+ rescue Status
257
+ errors << [res.path, $!]
258
+ end
259
+ end
260
+
261
+ def copy_recursive(res, dest, depth, errors)
262
+ map_exceptions do
263
+ if dest.exist?
264
+ if overwrite
265
+ delete_recursive(dest, errors)
266
+ else
267
+ raise PreconditionFailed
268
+ end
269
+ end
270
+ res.copy(dest)
271
+ end
272
+ rescue Status
273
+ errors << [res.path, $!]
274
+ else
275
+ if depth > 0
276
+ for child in res.children
277
+ dest_child = dest.child(child.name)
278
+ copy_recursive(child, dest_child, depth - 1, errors)
279
+ end
280
+ end
281
+ end
282
+
283
+ def map_exceptions
284
+ yield
285
+ rescue
286
+ case $!
287
+ when URI::InvalidURIError then raise BadRequest
288
+ when Errno::EACCES then raise Forbidden
289
+ when Errno::ENOENT then raise Conflict
290
+ when Errno::EEXIST then raise Conflict
291
+ when Errno::ENOSPC then raise InsufficientStorage
292
+ else
293
+ raise
294
+ end
295
+ end
296
+
297
+ def request_document
298
+ @request_document ||= REXML::Document.new(request.body.read)
299
+ rescue REXML::ParseException
300
+ raise BadRequest
301
+ end
302
+
303
+ def request_match(pattern)
304
+ REXML::XPath::match(request_document, pattern, '' => 'DAV:')
305
+ end
306
+
307
+ # Quick and dirty parsing of the WEBDAV Timeout header.
308
+ # Refuses infinity, rejects anything but Second- timeouts
309
+ #
310
+ # @return [nil] or [Fixnum]
311
+ #
312
+ # @api internal
313
+ #
314
+ def request_timeout
315
+ timeout = request.env['HTTP_TIMEOUT']
316
+ return if timeout.nil? || timeout.empty?
317
+
318
+ timeout = timeout.split /,\s*/
319
+ timeout.reject! {|t| t !~ /^Second-/}
320
+ timeout.first.sub('Second-', '').to_i
321
+ end
322
+
323
+ def request_locktoken(header)
324
+ token = request.env["HTTP_#{header}"]
325
+ return if token.nil? || token.empty?
326
+ token.scan /^\(?<?(.+?)>?\)?$/
327
+ return $1
328
+ end
329
+
330
+ # Creates a new XML document, yields given block
331
+ # and sets the response.body with the final XML content.
332
+ # The response length is updated accordingly.
333
+ #
334
+ # @return [void]
335
+ #
336
+ # @yield [xml] Yields the Builder XML instance.
337
+ #
338
+ # @api internal
339
+ #
340
+ def render_xml
341
+ xml = Builder::XmlMarkup.new(:indent => 2)
342
+ xml.instruct! :xml, :version => "1.0", :encoding => "UTF-8"
343
+
344
+ xml.namespace('D') do
345
+ yield xml
346
+ end
347
+
348
+ content = xml.target!
349
+ response.body = [content]
350
+ response["Content-Type"] = 'text/xml; charset="utf-8"'
351
+ response["Content-Length"] = Rack::Utils.bytesize(content).to_s
352
+ end
353
+
354
+ def multistatus
355
+ render_xml do |xml|
356
+ xml.multistatus('xmlns:D' => "DAV:") do
357
+ yield xml
358
+ end
359
+ end
360
+
361
+ response.status = MultiStatus
362
+ end
363
+
364
+ def response_errors(xml, errors)
365
+ for path, status in errors
366
+ xml.response do
367
+ xml.href "http://#{host}#{path}"
368
+ xml.status "#{request.env['HTTP_VERSION']} #{status.status_line}"
369
+ end
370
+ end
371
+ end
372
+
373
+ def get_properties(resource, names)
374
+ stats = Hash.new { |h, k| h[k] = [] }
375
+ for name in names
376
+ begin
377
+ map_exceptions do
378
+ stats[OK] << [name, resource.get_property(name)]
379
+ end
380
+ rescue Status
381
+ stats[$!] << name
382
+ end
383
+ end
384
+ stats
385
+ end
386
+
387
+ def set_properties(resource, pairs)
388
+ stats = Hash.new { |h, k| h[k] = [] }
389
+ for name, value in pairs
390
+ begin
391
+ map_exceptions do
392
+ stats[OK] << [name, resource.set_property(name, value)]
393
+ end
394
+ rescue Status
395
+ stats[$!] << name
396
+ end
397
+ end
398
+ stats
399
+ end
400
+
401
+ def propstats(xml, stats)
402
+ return if stats.empty?
403
+ for status, props in stats
404
+ xml.propstat do
405
+ xml.prop do
406
+ for name, value in props
407
+ if value.is_a?(REXML::Element)
408
+ xml.tag!(name) do
409
+ rexml_convert(xml, value)
410
+ end
411
+ else
412
+ xml.tag!(name, value)
413
+ end
414
+ end
415
+ end
416
+ xml.status "#{request.env['HTTP_VERSION']} #{status.status_line}"
417
+ end
418
+ end
419
+ end
420
+
421
+ def create_lock(timeout)
422
+ lockscope = request_match("/lockinfo/lockscope/*")[0].name
423
+ locktype = request_match("/lockinfo/locktype/*")[0].name
424
+ owner = request_match("/lockinfo/owner/href")[0]
425
+ owner = owner.text if owner
426
+ locktoken = "opaquelocktoken:" + sprintf('%x-%x-%s', Time.now.to_i, Time.now.sec, resource.etag)
427
+
428
+ # Quick & Dirty - FIXME: Lock should become a new Class
429
+ # and this dirty parameter passing refactored.
430
+ unless resource.lock(locktoken, timeout, lockscope, locktype, owner)
431
+ raise Forbidden
432
+ end
433
+
434
+ response['Lock-Token'] = locktoken
435
+
436
+ render_lockdiscovery locktoken, lockscope, locktype, timeout, owner
437
+ end
438
+
439
+ def refresh_lock(timeout)
440
+ locktoken = request_locktoken('IF')
441
+ raise BadRequest if locktoken.nil?
442
+
443
+ timeout, lockscope, locktype, owner = resource.lock(locktoken, timeout)
444
+ unless lockscope && locktype && timeout
445
+ raise Forbidden
446
+ end
447
+
448
+ render_lockdiscovery locktoken, lockscope, locktype, timeout, owner
449
+ end
450
+
451
+ # FIXME add multiple locks support
452
+ def render_lockdiscovery(locktoken, lockscope, locktype, timeout, owner)
453
+ render_xml do |xml|
454
+ xml.prop('xmlns:D' => "DAV:") do
455
+ xml.lockdiscovery do
456
+ render_lock(xml, locktoken, lockscope, locktype, timeout, owner)
457
+ end
458
+ end
459
+ end
460
+ end
461
+
462
+ def render_lock(xml, locktoken, lockscope, locktype, timeout, owner)
463
+ xml.activelock do
464
+ xml.lockscope { xml.tag! lockscope }
465
+ xml.locktype { xml.tag! locktype }
466
+ xml.depth 'Infinity'
467
+ if owner
468
+ xml.owner { xml.href owner }
469
+ end
470
+ xml.timeout "Second-#{timeout}"
471
+ xml.locktoken do
472
+ xml.href locktoken
473
+ end
474
+ end
475
+ end
476
+
477
+ def rexml_convert(xml, element)
478
+ if element.elements.empty?
479
+ if element.text
480
+ xml.tag!(element.name, element.text, element.attributes)
481
+ else
482
+ xml.tag!(element.name, element.attributes)
483
+ end
484
+ else
485
+ xml.tag!(element.name, element.attributes) do
486
+ element.elements.each do |child|
487
+ rexml_convert(xml, child)
488
+ end
489
+ end
490
+ end
491
+ end
492
+
493
+ end
494
+
495
+ end