rack_dav 0.1.3 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,4 +1,6 @@
1
1
  *~
2
+ /.bundle
2
3
  doc/*
3
4
  pkg/*
4
5
  test/repo
6
+ /vendor/bundle
data/CHANGELOG.md ADDED
@@ -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
data/Gemfile.lock ADDED
@@ -0,0 +1,31 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rack_dav (0.3.0)
5
+ nokogiri
6
+ rack (~> 1.4.0)
7
+
8
+ GEM
9
+ remote: http://rubygems.org/
10
+ specs:
11
+ diff-lcs (1.1.3)
12
+ nokogiri (1.5.5)
13
+ rack (1.4.1)
14
+ rake (0.9.2.2)
15
+ rspec (2.11.0)
16
+ rspec-core (~> 2.11.0)
17
+ rspec-expectations (~> 2.11.0)
18
+ rspec-mocks (~> 2.11.0)
19
+ rspec-core (2.11.1)
20
+ rspec-expectations (2.11.2)
21
+ diff-lcs (~> 1.1.3)
22
+ rspec-mocks (2.11.1)
23
+
24
+ PLATFORMS
25
+ java
26
+ ruby
27
+
28
+ DEPENDENCIES
29
+ rack_dav!
30
+ rake (~> 0.9.0)
31
+ rspec (~> 2.11.0)
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  ---
2
- title: RackDAV - Web Authoring for Rack
2
+ RackDAV - Web Authoring for Rack
3
3
  ---
4
4
 
5
5
  RackDAV is Handler for [Rack][1], which allows content authoring over
@@ -8,10 +8,9 @@ possible by subclassing RackDAV::Resource.
8
8
 
9
9
  ## Install
10
10
 
11
- Just install the gem from github:
11
+ Just install the gem from RubyGems:
12
12
 
13
- $ gem sources -a http://gems.github.com
14
- $ sudo gem install georgi-rack_dav
13
+ $ gem install rack_dav
15
14
 
16
15
  ## Quickstart
17
16
 
@@ -28,14 +27,12 @@ to without authentication.
28
27
  Using RackDAV inside a rack application is quite easy. A simple rackup
29
28
  script looks like this:
30
29
 
31
- @@ruby
32
-
33
30
  require 'rubygems'
34
31
  require 'rack_dav'
35
-
32
+
36
33
  use Rack::CommonLogger
37
-
38
- run RackDAV::Handler.new('/path/to/docs')
34
+
35
+ run RackDAV::Handler.new(:root => '/path/to/docs')
39
36
 
40
37
  ## Implementing your own WebDAV resource
41
38
 
@@ -47,23 +44,21 @@ find the real resource.
47
44
 
48
45
  RackDAV::Handler needs to be initialized with the actual resource class:
49
46
 
50
- @@ruby
51
-
52
47
  RackDAV::Handler.new(:resource_class => MyResource)
53
48
 
54
49
  RackDAV needs some information about the resources, so you have to
55
50
  implement following methods:
56
-
51
+
57
52
  * __children__: If this is a collection, return the child resources.
58
53
 
59
54
  * __collection?__: Is this resource a collection?
60
55
 
61
56
  * __exist?__: Does this recource exist?
62
-
57
+
63
58
  * __creation\_date__: Return the creation time.
64
59
 
65
60
  * __last\_modified__: Return the time of last modification.
66
-
61
+
67
62
  * __last\_modified=(time)__: Set the time of last modification.
68
63
 
69
64
  * __etag__: Return an Etag, an unique hash value for this resource.
@@ -87,9 +82,13 @@ to retrieve and change the resources:
87
82
  * __copy(dest)__: Copy this resource to given destination resource.
88
83
 
89
84
  * __move(dest)__: Move this resource to given destination resource.
90
-
85
+
91
86
  * __make\_collection__: Create this resource as collection.
92
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
93
92
 
94
93
  Note, that it is generally possible, that a resource object is
95
94
  instantiated for a not yet existing resource.
data/Rakefile ADDED
@@ -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
data/bin/rack_dav CHANGED
@@ -1,20 +1,24 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'rubygems'
3
+ $:.unshift(File.expand_path("../../lib", __FILE__))
4
+
4
5
  require 'rack_dav'
5
6
 
6
- app = Rack::Builder.new do
7
+ root=ARGV[1] || Dir.pwd
8
+ port = ARGV[0] || 3000
9
+
10
+ app = Rack::Builder.new do
7
11
  use Rack::ShowExceptions
8
12
  use Rack::CommonLogger
9
13
  use Rack::Reloader
10
14
  use Rack::Lint
11
-
12
- run RackDAV::Handler.new
15
+
16
+ run RackDAV::Handler.new(:root => root)
13
17
 
14
18
  end.to_app
15
19
 
16
20
  begin
17
- Rack::Handler::Mongrel.run(app, :Port => 3000)
18
- rescue
19
- Rack::Handler::WEBrick.run(app, :Port => 3000)
21
+ Rack::Handler::Mongrel.run(app, :Port => port)
22
+ rescue LoadError
23
+ Rack::Handler::WEBrick.run(app, :Port => port)
20
24
  end
data/lib/rack_dav.rb CHANGED
@@ -1,15 +1,13 @@
1
1
  require 'rubygems'
2
- require 'builder'
3
2
  require 'time'
4
3
  require 'uri'
5
4
  require 'rexml/document'
5
+ require 'nokogiri'
6
6
  require 'webrick/httputils'
7
7
 
8
8
  require 'rack'
9
- require 'rack_dav/builder_namespace'
10
9
  require 'rack_dav/http_status'
11
10
  require 'rack_dav/resource'
12
11
  require 'rack_dav/file_resource'
13
12
  require 'rack_dav/handler'
14
13
  require 'rack_dav/controller'
15
-
@@ -1,49 +1,56 @@
1
+ require 'uri'
2
+
3
+ require_relative 'string'
4
+
1
5
  module RackDAV
2
-
6
+
3
7
  class Controller
4
8
  include RackDAV::HTTPStatus
5
-
9
+
6
10
  attr_reader :request, :response, :resource
7
-
11
+
8
12
  def initialize(request, response, options)
9
- @request = request
13
+ @request = request
10
14
  @response = response
11
- @options = options
15
+ @options = options
12
16
  @resource = resource_class.new(url_unescape(request.path_info), @options)
13
17
  raise Forbidden if request.path_info.include?('../')
14
18
  end
15
-
19
+
16
20
  def url_escape(s)
17
- s.gsub(/([^\/a-zA-Z0-9_.-]+)/n) do
18
- '%' + $1.unpack('H2' * $1.size).join('%').upcase
19
- end.tr(' ', '+')
21
+ URI.escape(s)
20
22
  end
21
23
 
22
24
  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
-
25
+ URI.unescape(s).force_valid_encoding
26
+ end
27
+
28
28
  def options
29
- response["Allow"] = 'OPTIONS,HEAD,GET,PUT,POST,DELETE,PROPFIND,PROPPATCH,MKCOL,COPY,MOVE,LOCK,UNLOCK'
30
- response["Dav"] = "1,2"
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
+
31
37
  response["Ms-Author-Via"] = "DAV"
32
38
  end
33
-
39
+
34
40
  def head
35
41
  raise NotFound if not resource.exist?
36
42
  response['Etag'] = resource.etag
37
43
  response['Content-Type'] = resource.content_type
44
+ response['Content-Length'] = resource.content_length.to_s
38
45
  response['Last-Modified'] = resource.last_modified.httpdate
39
46
  end
40
-
47
+
41
48
  def get
42
49
  raise NotFound if not resource.exist?
43
50
  response['Etag'] = resource.etag
44
51
  response['Content-Type'] = resource.content_type
45
52
  response['Content-Length'] = resource.content_length.to_s
46
- response['Last-Modified'] = resource.last_modified.httpdate
53
+ response['Last-Modified'] = resource.last_modified.httpdate
47
54
  map_exceptions do
48
55
  resource.get(request, response)
49
56
  end
@@ -64,7 +71,7 @@ module RackDAV
64
71
 
65
72
  def delete
66
73
  delete_recursive(resource, errors = [])
67
-
74
+
68
75
  if errors.empty?
69
76
  response.status = NoContent
70
77
  else
@@ -73,30 +80,30 @@ module RackDAV
73
80
  end
74
81
  end
75
82
  end
76
-
83
+
77
84
  def mkcol
78
85
  map_exceptions do
79
86
  resource.make_collection
80
87
  end
81
88
  response.status = Created
82
89
  end
83
-
90
+
84
91
  def copy
85
92
  raise NotFound if not resource.exist?
86
-
93
+
87
94
  dest_uri = URI.parse(env['HTTP_DESTINATION'])
88
95
  destination = url_unescape(dest_uri.path)
89
96
 
90
97
  raise BadGateway if dest_uri.host and dest_uri.host != request.host
91
98
  raise Forbidden if destination == resource.path
92
-
99
+
93
100
  dest = resource_class.new(destination, @options)
94
101
  dest = dest.child(resource.name) if dest.collection?
95
-
102
+
96
103
  dest_existed = dest.exist?
97
-
104
+
98
105
  copy_recursive(resource, dest, depth, errors = [])
99
-
106
+
100
107
  if errors.empty?
101
108
  response.status = dest_existed ? NoContent : Created
102
109
  else
@@ -113,20 +120,20 @@ module RackDAV
113
120
 
114
121
  dest_uri = URI.parse(env['HTTP_DESTINATION'])
115
122
  destination = url_unescape(dest_uri.path)
116
-
123
+
117
124
  raise BadGateway if dest_uri.host and dest_uri.host != request.host
118
125
  raise Forbidden if destination == resource.path
119
-
126
+
120
127
  dest = resource_class.new(destination, @options)
121
128
  dest = dest.child(resource.name) if dest.collection?
122
-
129
+
123
130
  dest_existed = dest.exist?
124
-
131
+
125
132
  raise Conflict if depth <= 1
126
-
133
+
127
134
  copy_recursive(resource, dest, depth, errors = [])
128
135
  delete_recursive(resource, errors)
129
-
136
+
130
137
  if errors.empty?
131
138
  response.status = dest_existed ? NoContent : Created
132
139
  else
@@ -137,14 +144,14 @@ module RackDAV
137
144
  rescue URI::InvalidURIError => e
138
145
  raise BadRequest.new(e.message)
139
146
  end
140
-
147
+
141
148
  def propfind
142
149
  raise NotFound if not resource.exist?
143
150
 
144
- if not request_match("/propfind/allprop").empty?
151
+ if not request_match("/d:propfind/d:allprop").empty?
145
152
  names = resource.property_names
146
153
  else
147
- names = request_match("/propfind/prop/*").map { |e| e.name }
154
+ names = request_match("/d:propfind/d:prop/d:*").map { |e| e.name }
148
155
  names = resource.property_names if names.empty?
149
156
  raise BadRequest if names.empty?
150
157
  end
@@ -153,18 +160,18 @@ module RackDAV
153
160
  for resource in find_resources
154
161
  resource.path.gsub!(/\/\//, '/')
155
162
  xml.response do
156
- xml.href "http://#{host}#{url_escape resource.path}"
163
+ xml.href "http://#{host}#{url_escape resource.path}"
157
164
  propstats xml, get_properties(resource, names)
158
165
  end
159
166
  end
160
167
  end
161
168
  end
162
-
169
+
163
170
  def proppatch
164
171
  raise NotFound if not resource.exist?
165
172
 
166
- prop_rem = request_match("/propertyupdate/remove/prop/*").map { |e| [e.name] }
167
- prop_set = request_match("/propertyupdate/set/prop/*").map { |e| [e.name, e.text] }
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] }
168
175
 
169
176
  multistatus do |xml|
170
177
  for resource in find_resources
@@ -179,233 +186,308 @@ module RackDAV
179
186
  end
180
187
 
181
188
  def lock
189
+ raise MethodNotAllowed unless resource.lockable?
182
190
  raise NotFound if not resource.exist?
183
191
 
184
- lockscope = request_match("/lockinfo/lockscope/*")[0].name
185
- locktype = request_match("/lockinfo/locktype/*")[0].name
186
- owner = request_match("/lockinfo/owner/href")[0]
187
- locktoken = "opaquelocktoken:" + sprintf('%x-%x-%s', Time.now.to_i, Time.now.sec, resource.etag)
188
-
189
- response['Lock-Token'] = locktoken
190
-
191
- render_xml do |xml|
192
- xml.prop('xmlns:D' => "DAV:") do
193
- xml.lockdiscovery do
194
- xml.activelock do
195
- xml.lockscope { xml.tag! lockscope }
196
- xml.locktype { xml.tag! locktype }
197
- xml.depth 'Infinity'
198
- if owner
199
- xml.owner { xml.href owner.text }
200
- end
201
- xml.timeout "Second-60"
202
- xml.locktoken do
203
- xml.href locktoken
204
- end
205
- end
206
- end
207
- end
192
+ timeout = request_timeout
193
+ if timeout.nil? || timeout.zero?
194
+ timeout = 60
195
+ end
196
+
197
+ if request_document.content.empty?
198
+ refresh_lock timeout
199
+ else
200
+ create_lock timeout
208
201
  end
209
202
  end
210
203
 
211
204
  def unlock
212
- raise NoContent
205
+ raise MethodNotAllowed unless resource.lockable?
206
+
207
+ locktoken = request_locktoken('LOCK_TOKEN')
208
+ raise BadRequest if locktoken.nil?
209
+
210
+ response.status = resource.unlock(locktoken) ? NoContent : Forbidden
213
211
  end
214
212
 
215
- # ************************************************************
216
- # private methods
217
-
218
213
  private
219
214
 
220
- def env
221
- @request.env
222
- end
223
-
224
- def host
225
- env['HTTP_HOST']
226
- end
227
-
228
- def resource_class
229
- @options[:resource_class]
230
- end
215
+ def env
216
+ @request.env
217
+ end
231
218
 
232
- def depth
233
- case env['HTTP_DEPTH']
234
- when '0' then 0
235
- when '1' then 1
236
- else 100
219
+ def host
220
+ env['HTTP_HOST']
237
221
  end
238
- end
239
222
 
240
- def overwrite
241
- env['HTTP_OVERWRITE'].to_s.upcase != 'F'
242
- end
223
+ def resource_class
224
+ @options[:resource_class]
225
+ end
243
226
 
244
- def find_resources
245
- case env['HTTP_DEPTH']
246
- when '0'
247
- [resource]
248
- when '1'
249
- [resource] + resource.children
250
- else
251
- [resource] + resource.descendants
227
+ def depth
228
+ case env['HTTP_DEPTH']
229
+ when '0' then 0
230
+ when '1' then 1
231
+ else 100
232
+ end
252
233
  end
253
- end
254
234
 
255
- def delete_recursive(res, errors)
256
- for child in res.children
257
- delete_recursive(child, errors)
235
+ def overwrite
236
+ env['HTTP_OVERWRITE'].to_s.upcase != 'F'
258
237
  end
259
-
260
- begin
261
- map_exceptions { res.delete } if errors.empty?
238
+
239
+ def find_resources
240
+ case env['HTTP_DEPTH']
241
+ when '0'
242
+ [resource]
243
+ when '1'
244
+ [resource] + resource.children
245
+ else
246
+ [resource] + resource.descendants
247
+ end
248
+ end
249
+
250
+ def delete_recursive(res, errors)
251
+ for child in res.children
252
+ delete_recursive(child, errors)
253
+ end
254
+
255
+ begin
256
+ map_exceptions { res.delete } if errors.empty?
257
+ rescue Status
258
+ errors << [res.path, $!]
259
+ end
260
+ end
261
+
262
+ def copy_recursive(res, dest, depth, errors)
263
+ map_exceptions do
264
+ if dest.exist?
265
+ if overwrite
266
+ delete_recursive(dest, errors)
267
+ else
268
+ raise PreconditionFailed
269
+ end
270
+ end
271
+ res.copy(dest)
272
+ end
262
273
  rescue Status
263
274
  errors << [res.path, $!]
264
- end
265
- end
266
-
267
- def copy_recursive(res, dest, depth, errors)
268
- map_exceptions do
269
- if dest.exist?
270
- if overwrite
271
- delete_recursive(dest, errors)
272
- else
273
- raise PreconditionFailed
275
+ else
276
+ if depth > 0
277
+ for child in res.children
278
+ dest_child = dest.child(child.name)
279
+ copy_recursive(child, dest_child, depth - 1, errors)
274
280
  end
275
281
  end
276
- res.copy(dest)
277
282
  end
278
- rescue Status
279
- errors << [res.path, $!]
280
- else
281
- if depth > 0
282
- for child in res.children
283
- dest_child = dest.child(child.name)
284
- copy_recursive(child, dest_child, depth - 1, errors)
283
+
284
+ def map_exceptions
285
+ yield
286
+ rescue
287
+ case $!
288
+ when URI::InvalidURIError then raise BadRequest
289
+ when Errno::EACCES then raise Forbidden
290
+ when Errno::ENOENT then raise Conflict
291
+ when Errno::EEXIST then raise Conflict
292
+ when Errno::ENOSPC then raise InsufficientStorage
293
+ else
294
+ raise
285
295
  end
286
296
  end
287
- end
288
-
289
- def map_exceptions
290
- yield
291
- rescue
292
- case $!
293
- when URI::InvalidURIError then raise BadRequest
294
- when Errno::EACCES then raise Forbidden
295
- when Errno::ENOENT then raise Conflict
296
- when Errno::EEXIST then raise Conflict
297
- when Errno::ENOSPC then raise InsufficientStorage
298
- else
299
- raise
297
+
298
+ def request_document
299
+ @request_document ||= Nokogiri::XML(request.body.read) {|config| config.strict }
300
+ rescue Nokogiri::XML::SyntaxError
301
+ raise BadRequest
300
302
  end
301
- end
302
-
303
- def request_document
304
- @request_document ||= REXML::Document.new(request.body.read)
305
- rescue REXML::ParseException
306
- raise BadRequest
307
- end
308
303
 
309
- def request_match(pattern)
310
- REXML::XPath::match(request_document, pattern, '' => 'DAV:')
311
- end
304
+ def request_match(pattern)
305
+ request_document.xpath(pattern, 'd' => 'DAV:')
306
+ end
312
307
 
313
- def render_xml
314
- xml = Builder::XmlMarkup.new(:indent => 2)
315
- xml.instruct! :xml, :version => "1.0", :encoding => "UTF-8"
316
-
317
- xml.namespace('D') do
318
- yield xml
319
- end
320
-
321
- response.body = xml.target!
322
- response["Content-Type"] = 'text/xml; charset="utf-8"'
323
- response["Content-Length"] = response.body.size.to_s
324
- end
325
-
326
- def multistatus
327
- render_xml do |xml|
328
- xml.multistatus('xmlns:D' => "DAV:") do
308
+ # Quick and dirty parsing of the WEBDAV Timeout header.
309
+ # Refuses infinity, rejects anything but Second- timeouts
310
+ #
311
+ # @return [nil] or [Fixnum]
312
+ #
313
+ # @api internal
314
+ #
315
+ def request_timeout
316
+ timeout = request.env['HTTP_TIMEOUT']
317
+ return if timeout.nil? || timeout.empty?
318
+
319
+ timeout = timeout.split /,\s*/
320
+ timeout.reject! {|t| t !~ /^Second-/}
321
+ timeout.first.sub('Second-', '').to_i
322
+ end
323
+
324
+ def request_locktoken(header)
325
+ token = request.env["HTTP_#{header}"]
326
+ return if token.nil? || token.empty?
327
+ token.scan /^\(?<?(.+?)>?\)?$/
328
+ return $1
329
+ end
330
+
331
+ # Creates a new XML document, yields given block
332
+ # and sets the response.body with the final XML content.
333
+ # The response length is updated accordingly.
334
+ #
335
+ # @return [void]
336
+ #
337
+ # @yield [xml] Yields the Builder XML instance.
338
+ #
339
+ # @api internal
340
+ #
341
+ def render_xml
342
+ content = Nokogiri::XML::Builder.new(:encoding => "UTF-8") do |xml|
329
343
  yield xml
344
+ end.to_xml
345
+ response.body = [content]
346
+ response["Content-Type"] = 'text/xml; charset=utf-8'
347
+ response["Content-Length"] = Rack::Utils.bytesize(content).to_s
348
+ end
349
+
350
+ def multistatus
351
+ render_xml do |xml|
352
+ xml.multistatus('xmlns' => "DAV:") do
353
+ yield xml
354
+ end
330
355
  end
356
+
357
+ response.status = MultiStatus
331
358
  end
332
-
333
- response.status = MultiStatus
334
- end
335
-
336
- def response_errors(xml, errors)
337
- for path, status in errors
338
- xml.response do
339
- xml.href "http://#{host}#{path}"
340
- xml.status "#{request.env['HTTP_VERSION']} #{status.status_line}"
359
+
360
+ def response_errors(xml, errors)
361
+ for path, status in errors
362
+ xml.response do
363
+ xml.href "http://#{host}#{path}"
364
+ xml.status "#{request.env['HTTP_VERSION']} #{status.status_line}"
365
+ end
341
366
  end
342
367
  end
343
- end
344
368
 
345
- def get_properties(resource, names)
346
- stats = Hash.new { |h, k| h[k] = [] }
347
- for name in names
348
- begin
349
- map_exceptions do
350
- stats[OK] << [name, resource.get_property(name)]
369
+ def get_properties(resource, names)
370
+ stats = Hash.new { |h, k| h[k] = [] }
371
+ for name in names
372
+ begin
373
+ map_exceptions do
374
+ stats[OK] << [name, resource.get_property(name)]
375
+ end
376
+ rescue Status
377
+ stats[$!] << name
351
378
  end
352
- rescue Status
353
- stats[$!] << name
354
379
  end
380
+ stats
355
381
  end
356
- stats
357
- end
358
382
 
359
- def set_properties(resource, pairs)
360
- stats = Hash.new { |h, k| h[k] = [] }
361
- for name, value in pairs
362
- begin
363
- map_exceptions do
364
- stats[OK] << [name, resource.set_property(name, value)]
383
+ def set_properties(resource, pairs)
384
+ stats = Hash.new { |h, k| h[k] = [] }
385
+ for name, value in pairs
386
+ begin
387
+ map_exceptions do
388
+ stats[OK] << [name, resource.set_property(name, value)]
389
+ end
390
+ rescue Status
391
+ stats[$!] << name
365
392
  end
366
- rescue Status
367
- stats[$!] << name
368
393
  end
394
+ stats
369
395
  end
370
- stats
371
- end
372
-
373
- def propstats(xml, stats)
374
- return if stats.empty?
375
- for status, props in stats
376
- xml.propstat do
377
- xml.prop do
378
- for name, value in props
379
- if value.is_a?(REXML::Element)
380
- xml.tag!(name) do
381
- rexml_convert(xml, value)
396
+
397
+ def propstats(xml, stats)
398
+ return if stats.empty?
399
+ for status, props in stats
400
+ xml.propstat do
401
+ xml.prop do
402
+ for name, value in props
403
+ if value.is_a?(Nokogiri::XML::Node)
404
+ xml.send(name) do
405
+ rexml_convert(xml, value)
406
+ end
407
+ else
408
+ xml.send(name, value)
382
409
  end
383
- else
384
- xml.tag!(name, value)
385
410
  end
386
411
  end
412
+ xml.status "#{request.env['HTTP_VERSION']} #{status.status_line}"
387
413
  end
388
- xml.status "#{request.env['HTTP_VERSION']} #{status.status_line}"
389
414
  end
390
415
  end
391
- end
392
-
393
- def rexml_convert(xml, element)
394
- if element.elements.empty?
395
- if element.text
396
- xml.tag!(element.name, element.text, element.attributes)
397
- else
398
- xml.tag!(element.name, element.attributes)
416
+
417
+ def create_lock(timeout)
418
+ lockscope = request_match("/d:lockinfo/d:lockscope/d:*").first
419
+ lockscope = lockscope.name if lockscope
420
+ locktype = request_match("/d:lockinfo/d:locktype/d:*").first
421
+ locktype = locktype.name if locktype
422
+ owner = request_match("/d:lockinfo/d:owner/d:href").first
423
+ owner = owner.text if owner
424
+ locktoken = "opaquelocktoken:" + sprintf('%x-%x-%s', Time.now.to_i, Time.now.sec, resource.etag)
425
+
426
+ # Quick & Dirty - FIXME: Lock should become a new Class
427
+ # and this dirty parameter passing refactored.
428
+ unless resource.lock(locktoken, timeout, lockscope, locktype, owner)
429
+ raise Forbidden
399
430
  end
400
- else
401
- xml.tag!(element.name, element.attributes) do
402
- element.elements.each do |child|
403
- rexml_convert(xml, child)
431
+
432
+ response['Lock-Token'] = locktoken
433
+
434
+ render_lockdiscovery locktoken, lockscope, locktype, timeout, owner
435
+ end
436
+
437
+ def refresh_lock(timeout)
438
+ locktoken = request_locktoken('IF')
439
+ raise BadRequest if locktoken.nil?
440
+
441
+ timeout, lockscope, locktype, owner = resource.lock(locktoken, timeout)
442
+ unless lockscope && locktype && timeout
443
+ raise Forbidden
444
+ end
445
+
446
+ render_lockdiscovery locktoken, lockscope, locktype, timeout, owner
447
+ end
448
+
449
+ # FIXME add multiple locks support
450
+ def render_lockdiscovery(locktoken, lockscope, locktype, timeout, owner)
451
+ render_xml do |xml|
452
+ xml.prop('xmlns' => "DAV:") do
453
+ xml.lockdiscovery do
454
+ render_lock(xml, locktoken, lockscope, locktype, timeout, owner)
455
+ end
404
456
  end
405
457
  end
406
458
  end
407
- end
408
-
459
+
460
+ def render_lock(xml, locktoken, lockscope, locktype, timeout, owner)
461
+ xml.activelock do
462
+ xml.lockscope { xml.tag! lockscope }
463
+ xml.locktype { xml.tag! locktype }
464
+ xml.depth 'Infinity'
465
+ if owner
466
+ xml.owner { xml.href owner }
467
+ end
468
+ xml.timeout "Second-#{timeout}"
469
+ xml.locktoken do
470
+ xml.href locktoken
471
+ end
472
+ end
473
+ end
474
+
475
+ def rexml_convert(xml, element)
476
+ if element.elements.empty?
477
+ if element.text
478
+ xml.send(element.name.to_sym, element.text, element.attributes)
479
+ else
480
+ xml.send(element.name.to_sym, element.attributes)
481
+ end
482
+ else
483
+ xml.send(element.name.to_sym, element.attributes) do
484
+ element.elements.each do |child|
485
+ rexml_convert(xml, child)
486
+ end
487
+ end
488
+ end
489
+ end
490
+
409
491
  end
410
492
 
411
- end
493
+ end