rack-webdav 0.4.3 → 0.4.4

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.
@@ -1,427 +1,181 @@
1
- require 'uuidtools'
2
- require 'rack-webdav/http_status'
3
-
4
1
  module RackWebDAV
5
-
6
- class LockFailure < RuntimeError
7
- attr_reader :path_status
8
- def initialize(*args)
9
- super(*args)
10
- @path_status = {}
11
- end
12
-
13
- def add_failure(path, status)
14
- @path_status[path] = status
15
- end
16
- end
17
-
2
+
18
3
  class Resource
19
- attr_reader :path, :options, :public_path, :request,
20
- :response, :propstat_relative_path, :root_xml_attributes
21
- attr_accessor :user
22
- @@blocks = {}
23
-
24
- class << self
25
-
26
- # This lets us define a bunch of before and after blocks that are
27
- # either called before all methods on the resource, or only specific
28
- # methods on the resource
29
- def method_missing(*args, &block)
30
- class_sym = self.name.to_sym
31
- @@blocks[class_sym] ||= {:before => {}, :after => {}}
32
- m = args.shift
33
- parts = m.to_s.split('_')
34
- type = parts.shift.to_s.to_sym
35
- method = parts.empty? ? nil : parts.join('_').to_sym
36
- if(@@blocks[class_sym][type] && block_given?)
37
- if(method)
38
- @@blocks[class_sym][type][method] ||= []
39
- @@blocks[class_sym][type][method] << block
40
- else
41
- @@blocks[class_sym][type][:'__all__'] ||= []
42
- @@blocks[class_sym][type][:'__all__'] << block
43
- end
44
- else
45
- raise NoMethodError.new("Undefined method #{m} for class #{self}")
46
- end
47
- end
48
-
49
- end
50
-
51
- include RackWebDAV::HTTPStatus
52
-
53
- # public_path:: Path received via request
54
- # path:: Internal resource path (Only different from public path when using root_uri's for webdav)
55
- # request:: Rack::Request
56
- # options:: Any options provided for this resource
57
- # Creates a new instance of the resource.
58
- # NOTE: path and public_path will only differ if the root_uri has been set for the resource. The
59
- # controller will strip out the starting path so the resource can easily determine what
60
- # it is working on. For example:
61
- # request -> /my/webdav/directory/actual/path
62
- # public_path -> /my/webdav/directory/actual/path
63
- # path -> /actual/path
64
- # NOTE: Customized Resources should not use initialize for setup. Instead
65
- # use the #setup method
66
- def initialize(public_path, path, request, response, options)
67
- @skip_alias = [
68
- :authenticate, :authentication_error_msg,
69
- :authentication_realm, :path, :options,
70
- :public_path, :request, :response, :user,
71
- :user=, :setup
72
- ]
73
- @public_path = public_path.dup
74
- @path = path.dup
75
- @propstat_relative_path = !!options.delete(:propstat_relative_path)
76
- @root_xml_attributes = options.delete(:root_xml_attributes) || {}
4
+
5
+ attr_reader :path, :options
6
+
7
+ def initialize(path, request, response, options)
8
+ @path = path
77
9
  @request = request
78
10
  @response = response
79
- unless(options.has_key?(:lock_class))
80
- require 'rack-webdav/lock_store'
81
- @lock_class = LockStore
82
- else
83
- @lock_class = options[:lock_class]
84
- raise NameError.new("Unknown lock type constant provided: #{@lock_class}") unless @lock_class.nil? || defined?(@lock_class)
85
- end
86
- @options = options.dup
87
- @max_timeout = options[:max_timeout] || 86400
88
- @default_timeout = options[:default_timeout] || 60
89
- @user = @options[:user] || request.ip
90
- setup if respond_to?(:setup)
91
- public_methods(false).each do |method|
92
- next if @skip_alias.include?(method.to_sym) || method[0,4] == 'DAV_' || method[0,5] == '_DAV_'
93
- self.class.class_eval "alias :'_DAV_#{method}' :'#{method}'"
94
- self.class.class_eval "undef :'#{method}'"
95
- end
96
- @runner = lambda do |class_sym, kind, method_name|
97
- [:'__all__', method_name.to_sym].each do |sym|
98
- if(@@blocks[class_sym] && @@blocks[class_sym][kind] && @@blocks[class_sym][kind][sym])
99
- @@blocks[class_sym][kind][sym].each do |b|
100
- args = [self, sym == :'__all__' ? method_name : nil].compact
101
- b.call(*args)
102
- end
103
- end
104
- end
105
- end
106
- end
107
-
108
- # This allows us to call before and after blocks
109
- def method_missing(*args)
110
- result = nil
111
- orig = args.shift
112
- class_sym = self.class.name.to_sym
113
- m = orig.to_s[0,5] == '_DAV_' ? orig : "_DAV_#{orig}" # If hell is doing the same thing over and over and expecting a different result this is a hell preventer
114
- raise NoMethodError.new("Undefined method: #{orig} for class #{self}.") unless respond_to?(m)
115
- @runner.call(class_sym, :before, orig)
116
- result = send m, *args
117
- @runner.call(class_sym, :after, orig)
118
- result
119
- end
120
-
121
- # Returns if resource supports locking
122
- def supports_locking?
123
- false #true
11
+ @options = options
124
12
  end
125
13
 
126
14
  # If this is a collection, return the child resources.
127
15
  def children
128
- NotImplemented
16
+ raise NotImplementedError
129
17
  end
130
18
 
131
19
  # Is this resource a collection?
132
20
  def collection?
133
- NotImplemented
21
+ raise NotImplementedError
134
22
  end
135
23
 
136
- # Does this resource exist?
24
+ # Does this recource exist?
137
25
  def exist?
138
- NotImplemented
26
+ raise NotImplementedError
139
27
  end
140
-
141
- # Does the parent resource exist?
142
- def parent_exists?
143
- parent.exist?
144
- end
145
-
146
- # Is the parent resource a collection?
147
- def parent_collection?
148
- parent.collection?
149
- end
150
-
28
+
151
29
  # Return the creation time.
152
30
  def creation_date
153
- raise NotImplemented
31
+ raise NotImplementedError
154
32
  end
155
33
 
156
34
  # Return the time of last modification.
157
35
  def last_modified
158
- raise NotImplemented
36
+ raise NotImplementedError
159
37
  end
160
-
38
+
161
39
  # Set the time of last modification.
162
40
  def last_modified=(time)
163
- # Is this correct?
164
- raise NotImplemented
41
+ raise NotImplementedError
165
42
  end
166
43
 
167
44
  # Return an Etag, an unique hash value for this resource.
168
45
  def etag
169
- raise NotImplemented
46
+ raise NotImplementedError
170
47
  end
171
48
 
172
- # Return the resource type. Generally only used to specify
173
- # resource is a collection.
49
+ # Return the resource type.
50
+ #
51
+ # If this is a collection, return a collection element
174
52
  def resource_type
175
- :collection if collection?
53
+ if collection?
54
+ Nokogiri::XML::fragment('<D:collection xmlns:D="DAV:"/>').children.first
55
+ end
176
56
  end
177
57
 
178
58
  # Return the mime type of this resource.
179
59
  def content_type
180
- raise NotImplemented
60
+ raise NotImplementedError
181
61
  end
182
62
 
183
63
  # Return the size in bytes for this resource.
184
64
  def content_length
185
- raise NotImplemented
65
+ raise NotImplementedError
186
66
  end
187
67
 
188
68
  # HTTP GET request.
189
69
  #
190
70
  # Write the content of the resource to the response.body.
191
- def get(request, response)
192
- NotImplemented
71
+ def get
72
+ raise NotImplementedError
193
73
  end
194
74
 
195
75
  # HTTP PUT request.
196
76
  #
197
77
  # Save the content of the request.body.
198
- def put(request, response)
199
- NotImplemented
78
+ def put
79
+ raise NotImplementedError
200
80
  end
201
-
81
+
202
82
  # HTTP POST request.
203
83
  #
204
84
  # Usually forbidden.
205
- def post(request, response)
206
- NotImplemented
85
+ def post
86
+ raise NotImplementedError
207
87
  end
208
-
88
+
209
89
  # HTTP DELETE request.
210
90
  #
211
91
  # Delete this resource.
212
92
  def delete
213
- NotImplemented
93
+ raise NotImplementedError
214
94
  end
215
-
95
+
216
96
  # HTTP COPY request.
217
97
  #
218
98
  # Copy this resource to given destination resource.
219
- def copy(dest, overwrite=false)
220
- NotImplemented
99
+ def copy(dest)
100
+ raise NotImplementedError
221
101
  end
222
-
102
+
223
103
  # HTTP MOVE request.
224
104
  #
225
105
  # Move this resource to given destination resource.
226
- def move(dest, overwrite=false)
227
- NotImplemented
228
- end
229
-
230
- # args:: Hash of lock arguments
231
- # Request for a lock on the given resource. A valid lock should lock
232
- # all descendents. Failures should be noted and returned as an exception
233
- # using LockFailure.
234
- # Valid args keys: :timeout -> requested timeout
235
- # :depth -> lock depth
236
- # :scope -> lock scope
237
- # :type -> lock type
238
- # :owner -> lock owner
239
- # Should return a tuple: [lock_time, locktoken] where lock_time is the
240
- # given timeout
241
- # NOTE: See section 9.10 of RFC 4918 for guidance about
242
- # how locks should be generated and the expected responses
243
- # (http://www.webdav.org/specs/rfc4918.html#rfc.section.9.10)
244
-
245
- def lock(args)
246
- unless(@lock_class)
247
- NotImplemented
248
- else
249
- unless(parent_exists?)
250
- Conflict
251
- else
252
- lock_check(args[:scope])
253
- lock = @lock_class.explicit_locks(@path).find{|l| l.scope == args[:scope] && l.kind == args[:type] && l.user == @user}
254
- unless(lock)
255
- token = UUIDTools::UUID.random_create.to_s
256
- lock = @lock_class.generate(@path, @user, token)
257
- lock.scope = args[:scope]
258
- lock.kind = args[:type]
259
- lock.owner = args[:owner]
260
- lock.depth = args[:depth].is_a?(Symbol) ? args[:depth] : args[:depth].to_i
261
- if(args[:timeout])
262
- lock.timeout = args[:timeout] <= @max_timeout && args[:timeout] > 0 ? args[:timeout] : @max_timeout
263
- else
264
- lock.timeout = @default_timeout
265
- end
266
- lock.save if lock.respond_to? :save
267
- end
268
- begin
269
- lock_check(args[:type])
270
- rescue RackWebDAV::LockFailure => lock_failure
271
- lock.destroy
272
- raise lock_failure
273
- rescue HTTPStatus::Status => status
274
- status
275
- end
276
- [lock.remaining_timeout, lock.token]
277
- end
278
- end
106
+ def move(dest)
107
+ copy(dest)
108
+ delete
279
109
  end
280
110
 
281
- # lock_scope:: scope of lock
282
- # Check if resource is locked. Raise RackWebDAV::LockFailure if locks are in place.
283
- def lock_check(lock_scope=nil)
284
- return unless @lock_class
285
- if(@lock_class.explicitly_locked?(@path))
286
- raise Locked if @lock_class.explicit_locks(@path).find_all{|l|l.scope == 'exclusive' && l.user != @user}.size > 0
287
- elsif(@lock_class.implicitly_locked?(@path))
288
- if(lock_scope.to_s == 'exclusive')
289
- locks = @lock_class.implicit_locks(@path)
290
- failure = RackWebDAV::LockFailure.new("Failed to lock: #{@path}")
291
- locks.each do |lock|
292
- failure.add_failure(@path, Locked)
293
- end
294
- raise failure
295
- else
296
- locks = @lock_class.implict_locks(@path).find_all{|l| l.scope == 'exclusive' && l.user != @user}
297
- if(locks.size > 0)
298
- failure = LockFailure.new("Failed to lock: #{@path}")
299
- locks.each do |lock|
300
- failure.add_failure(@path, Locked)
301
- end
302
- raise failure
303
- end
304
- end
305
- end
306
- end
307
-
308
- # token:: Lock token
309
- # Remove the given lock
310
- def unlock(token)
311
- unless(@lock_class)
312
- NotImplemented
313
- else
314
- token = token.slice(1, token.length - 2)
315
- if(token.nil? || token.empty?)
316
- BadRequest
317
- else
318
- lock = @lock_class.find_by_token(token)
319
- if(lock.nil? || lock.user != @user)
320
- Forbidden
321
- elsif(lock.path !~ /^#{Regexp.escape(@path)}.*$/)
322
- Conflict
323
- else
324
- lock.destroy
325
- NoContent
326
- end
327
- end
328
- end
329
- end
330
-
331
-
111
+ # HTTP MKCOL request.
112
+ #
332
113
  # Create this resource as collection.
333
114
  def make_collection
334
- NotImplemented
115
+ raise NotImplementedError
335
116
  end
336
117
 
337
- # other:: Resource
338
- # Returns if current resource is equal to other resource
339
118
  def ==(other)
340
119
  path == other.path
341
120
  end
342
121
 
343
- # Name of the resource
344
122
  def name
345
123
  File.basename(path)
346
124
  end
347
125
 
348
- # Name of the resource to be displayed to the client
349
126
  def display_name
350
127
  name
351
128
  end
352
-
353
- # Available properties
354
- def properties
355
- %w(creationdate displayname getlastmodified getetag resourcetype getcontenttype getcontentlength).collect do |prop|
356
- {:name => prop, :ns_href => 'DAV:'}
357
- end
129
+
130
+ def child(name, option={})
131
+ self.class.new(path + '/' + name, @request, @response, options)
132
+ end
133
+
134
+ def lockable?
135
+ self.respond_to?(:lock) && self.respond_to?(:unlock)
136
+ end
137
+
138
+ def property_names
139
+ %w(creationdate displayname getlastmodified getetag resourcetype getcontenttype getcontentlength)
358
140
  end
359
-
360
- # name:: String - Property name
361
- # Returns the value of the given property
362
- def get_property(element)
363
- raise NotImplemented if (element[:ns_href] != 'DAV:')
364
- case element[:name]
141
+
142
+ def get_property(name)
143
+ case name
365
144
  when 'resourcetype' then resource_type
366
145
  when 'displayname' then display_name
367
- when 'creationdate' then use_ms_compat_creationdate? ? creation_date.httpdate : creation_date.xmlschema
146
+ when 'creationdate' then creation_date.xmlschema
368
147
  when 'getcontentlength' then content_length.to_s
369
148
  when 'getcontenttype' then content_type
370
149
  when 'getetag' then etag
371
150
  when 'getlastmodified' then last_modified.httpdate
372
- else raise NotImplemented
151
+ else self.get_custom_property(name) if self.respond_to?(:get_custom_property)
373
152
  end
374
153
  end
375
154
 
376
- # name:: String - Property name
377
- # value:: New value
378
- # Set the property to the given value
379
- def set_property(element, value)
380
- raise NotImplemented if (element[:ns_href] != 'DAV:')
381
- case element[:name]
155
+ def set_property(name, value)
156
+ case name
382
157
  when 'resourcetype' then self.resource_type = value
383
158
  when 'getcontenttype' then self.content_type = value
384
159
  when 'getetag' then self.etag = value
385
160
  when 'getlastmodified' then self.last_modified = Time.httpdate(value)
386
- else raise NotImplemented
161
+ else self.set_custom_property(name, value) if self.respond_to?(:set_custom_property)
387
162
  end
163
+ rescue Errno::EOPNOTSUPP
164
+ # nothing done
165
+ rescue ArgumentError
166
+ raise HTTPStatus::Conflict
388
167
  end
389
168
 
390
- # name:: Property name
391
- # Remove the property from the resource
392
- def remove_property(element)
393
- Forbidden
169
+ def remove_property(name)
170
+ raise HTTPStatus::Forbidden if property_names.include?(name)
394
171
  end
395
172
 
396
- # name:: Name of child
397
- # Create a new child with the given name
398
- # NOTE:: Include trailing '/' if child is collection
399
- def child(name)
400
- new_public = public_path.dup
401
- new_public = new_public + '/' unless new_public[-1,1] == '/'
402
- new_public = '/' + new_public unless new_public[0,1] == '/'
403
- new_path = path.dup
404
- new_path = new_path + '/' unless new_path[-1,1] == '/'
405
- new_path = '/' + new_path unless new_path[0,1] == '/'
406
- self.class.new("#{new_public}#{name}", "#{new_path}#{name}", request, response, options.merge(:user => @user))
407
- end
408
-
409
- # Return parent of this resource
410
173
  def parent
411
- unless(@path.to_s.empty?)
412
- self.class.new(
413
- File.split(@public_path).first,
414
- File.split(@path).first,
415
- @request,
416
- @response,
417
- @options.merge(
418
- :user => @user
419
- )
420
- )
421
- end
174
+ elements = @path.scan(/[^\/]+/)
175
+ return nil if elements.empty?
176
+ self.class.new('/' + elements[0..-2].to_a.join('/'), @options)
422
177
  end
423
-
424
- # Return list of descendants
178
+
425
179
  def descendants
426
180
  list = []
427
181
  children.each do |child|
@@ -430,55 +184,6 @@ module RackWebDAV
430
184
  end
431
185
  list
432
186
  end
433
-
434
- # Index page template for GETs on collection
435
- def index_page
436
- '<html><head> <title>%s</title>
437
- <meta http-equiv="content-type" content="text/html; charset=utf-8" /></head>
438
- <body> <h1>%s</h1> <hr /> <table> <tr> <th class="name">Name</th>
439
- <th class="size">Size</th> <th class="type">Type</th>
440
- <th class="mtime">Last Modified</th> </tr> %s </table> <hr /> </body></html>'
441
- end
442
-
443
- # Does client allow GET redirection
444
- # TODO: Get a comprehensive list in here.
445
- # TODO: Allow this to be dynamic so users can add regexes to match if they know of a client
446
- # that can be supported that is not listed.
447
- def allows_redirect?
448
- [
449
- %r{cyberduck}i,
450
- %r{konqueror}i
451
- ].any? do |regexp|
452
- (request.respond_to?(:user_agent) ? request.user_agent : request.env['HTTP_USER_AGENT']).to_s =~ regexp
453
- end
454
- end
455
-
456
- def use_compat_mkcol_response?
457
- @options[:compat_mkcol] || @options[:compat_all]
458
- end
459
-
460
- # Returns true if using an MS client
461
- def use_ms_compat_creationdate?
462
- if(@options[:compat_ms_mangled_creationdate] || @options[:compat_all])
463
- is_ms_client?
464
- end
465
- end
466
-
467
- # Basic user agent testing for MS authored client
468
- def is_ms_client?
469
- [%r{microsoft-webdav}i, %r{microsoft office}i].any? do |regexp|
470
- (request.respond_to?(:user_agent) ? request.user_agent : request.env['HTTP_USER_AGENT']).to_s =~ regexp
471
- end
472
- end
473
-
474
- protected
475
-
476
- # Returns authentication credentials if available in form of [username,password]
477
- # TODO: Add support for digest
478
- def auth_credentials
479
- auth = Rack::Auth::Basic::Request.new(request.env)
480
- auth.provided? && auth.basic? ? auth.credentials : [nil,nil]
481
- end
482
187
 
483
188
  end
484
189
 
@@ -0,0 +1,28 @@
1
+ class String
2
+
3
+ if RUBY_VERSION >= "1.9"
4
+ def force_valid_encoding
5
+ find_encoding(Encoding.list.to_enum)
6
+ end
7
+ else
8
+ def force_valid_encoding
9
+ self
10
+ end
11
+ end
12
+
13
+ private
14
+
15
+ def find_encoding(encodings)
16
+ if valid_encoding?
17
+ self
18
+ else
19
+ force_next_encoding(encodings)
20
+ end
21
+ end
22
+
23
+ def force_next_encoding(encodings)
24
+ force_encoding(encodings.next)
25
+ find_encoding(encodings)
26
+ end
27
+
28
+ end
@@ -1,17 +1,16 @@
1
1
  module RackWebDAV
2
- class Version
3
2
 
4
- attr_reader :major, :minor, :tiny
3
+ # Holds information about library version.
4
+ module Version
5
+ MAJOR = 0
6
+ MINOR = 4
7
+ PATCH = 4
8
+ BUILD = nil
5
9
 
6
- def initialize(version)
7
- version = version.split('.')
8
- @major, @minor, @tiny = [version[0].to_i, version[1].to_i, version[2].to_i]
9
- end
10
-
11
- def to_s
12
- "#{@major}.#{@minor}.#{@tiny}"
13
- end
10
+ STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join(".")
14
11
  end
15
12
 
16
- VERSION = Version.new('0.4.3')
13
+ # The current library version.
14
+ VERSION = Version::STRING
15
+
17
16
  end
data/lib/rack-webdav.rb CHANGED
@@ -1,11 +1,15 @@
1
+
2
+ require 'rubygems'
1
3
  require 'time'
2
4
  require 'uri'
5
+ require 'rexml/document'
3
6
  require 'nokogiri'
7
+ require 'webrick/httputils'
4
8
 
5
9
  require 'rack'
6
- require 'rack-webdav/utils'
7
10
  require 'rack-webdav/http_status'
8
11
  require 'rack-webdav/resource'
12
+ require 'rack-webdav/file_resource'
9
13
  require 'rack-webdav/handler'
10
14
  require 'rack-webdav/controller'
11
15
 
data/rack-webdav.gemspec CHANGED
@@ -23,6 +23,7 @@ Gem::Specification.new do |s|
23
23
  s.add_dependency 'nokogiri', '>= 1.4.2'
24
24
  s.add_dependency 'uuidtools', '~> 2.1.1'
25
25
  s.add_dependency 'rack', '>= 1.1.0'
26
+ s.add_dependency("ffi-xattr", "~> 0.1")
26
27
 
27
28
  s.add_development_dependency 'rspec'
28
29
 
@@ -32,4 +33,6 @@ Gem::Specification.new do |s|
32
33
  s.add_development_dependency 'awesome_print'
33
34
  s.add_development_dependency 'hirb'
34
35
  s.add_development_dependency 'hirb-unicode'
36
+ s.add_development_dependency 'rubocop'
37
+ s.add_development_dependency 'rack-timeout'
35
38
  end