rack_dav_sp 0.2.dev

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.
@@ -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