rack_dav 0.1.3 → 0.3.1

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