Class: DAV4Rack::Resource

Inherits:
Object
  • Object
show all
Includes:
HTTPStatus
Defined in:
lib/dav4rack/resource.rb

Direct Known Subclasses

FileResource, InterceptorResource

Constant Summary collapse

@@blocks =
{}

Constants included from HTTPStatus

HTTPStatus::StatusMessage

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(public_path, path, request, response, options) ⇒ Resource

public_path

Path received via request

path

Internal resource path (Only different from public path when using root_uri’s for webdav)

request

Rack::Request

options

Any options provided for this resource

Creates a new instance of the resource. NOTE: path and public_path will only differ if the root_uri has been set for the resource. The

controller will strip out the starting path so the resource can easily determine what
it is working on. For example:
request -> /my/webdav/directory/actual/path
public_path -> /my/webdav/directory/actual/path
path -> /actual/path

NOTE: Customized Resources should not use initialize for setup. Instead

use the #setup method


65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/dav4rack/resource.rb', line 65

def initialize(public_path, path, request, response, options)
  @skip_alias = [
    :authenticate, :authentication_error_msg, 
    :authentication_realm, :path, :options, 
    :public_path, :request, :response, :user, 
    :user=, :setup
  ]
  @public_path = public_path.dup
  @path = path.dup
  @request = request
  @response = response
  unless(options.has_key?(:lock_class))
    require 'dav4rack/lock_store'
    @lock_class = LockStore
  else
    @lock_class = options[:lock_class]
    raise NameError.new("Unknown lock type constant provided: #{@lock_class}") unless @lock_class.nil? || defined?(@lock_class)
  end
  @options = options.dup
  @max_timeout = options[:max_timeout] || 86400
  @default_timeout = options[:default_timeout] || 60
  @user = @options[:user] || request.ip
  setup if respond_to?(:setup)
  public_methods(false).each do |method|
    next if @skip_alias.include?(method.to_sym) || method[0,4] == 'DAV_' || method[0,5] == '_DAV_'
    self.class.class_eval "alias :'_DAV_#{method}' :'#{method}'"
    self.class.class_eval "undef :'#{method}'"
  end
  @runner = lambda do |class_sym, kind, method_name|
    [:'__all__', method_name.to_sym].each do |sym|
      if(@@blocks[class_sym] && @@blocks[class_sym][kind] && @@blocks[class_sym][kind][sym])
        @@blocks[class_sym][kind][sym].each do |b|
          args = [self, sym == :'__all__' ? method_name : nil].compact
          b.call(*args)
        end
      end
    end
  end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(*args) ⇒ Object

This allows us to call before and after blocks

Raises:

  • (NoMethodError)


106
107
108
109
110
111
112
113
114
115
116
# File 'lib/dav4rack/resource.rb', line 106

def method_missing(*args)
  result = nil
  orig = args.shift
  class_sym = self.class.name.to_sym
  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
  raise NoMethodError.new("Undefined method: #{orig} for class #{self}.") unless respond_to?(m)
  @runner.call(class_sym, :before, orig)
  result = send m, *args
  @runner.call(class_sym, :after, orig)
  result
end

Instance Attribute Details

#optionsObject (readonly)

Returns the value of attribute options.



19
20
21
# File 'lib/dav4rack/resource.rb', line 19

def options
  @options
end

#pathObject (readonly)

Returns the value of attribute path.



19
20
21
# File 'lib/dav4rack/resource.rb', line 19

def path
  @path
end

#public_pathObject (readonly)

Returns the value of attribute public_path.



19
20
21
# File 'lib/dav4rack/resource.rb', line 19

def public_path
  @public_path
end

#requestObject (readonly)

Returns the value of attribute request.



19
20
21
# File 'lib/dav4rack/resource.rb', line 19

def request
  @request
end

#responseObject (readonly)

Returns the value of attribute response.



19
20
21
# File 'lib/dav4rack/resource.rb', line 19

def response
  @response
end

#userObject

Returns the value of attribute user.



20
21
22
# File 'lib/dav4rack/resource.rb', line 20

def user
  @user
end

Class Method Details

.method_missing(*args, &block) ⇒ Object

This lets us define a bunch of before and after blocks that are either called before all methods on the resource, or only specific methods on the resource



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/dav4rack/resource.rb', line 28

def method_missing(*args, &block)
  class_sym = self.name.to_sym
  @@blocks[class_sym] ||= {:before => {}, :after => {}}
  m = args.shift
  parts = m.to_s.split('_')
  type = parts.shift.to_s.to_sym
  method = parts.empty? ? nil : parts.join('_').to_sym
  if(@@blocks[class_sym][type] && block_given?)
    if(method)
      @@blocks[class_sym][type][method] ||= []
      @@blocks[class_sym][type][method] << block
    else
      @@blocks[class_sym][type][:'__all__'] ||= []
      @@blocks[class_sym][type][:'__all__'] << block
    end
  else
    raise NoMethodError.new("Undefined method #{m} for class #{self}")
  end
end

Instance Method Details

#==(other) ⇒ Object

other

Resource

Returns if current resource is equal to other resource



325
326
327
# File 'lib/dav4rack/resource.rb', line 325

def ==(other)
  path == other.path
end

#child(name) ⇒ Object

name

Name of child

Create a new child with the given name

NOTE

Include trailing ‘/’ if child is collection



381
382
383
384
385
386
387
388
389
# File 'lib/dav4rack/resource.rb', line 381

def child(name)
  new_public = public_path.dup
  new_public = new_public + '/' unless new_public[-1,1] == '/'
  new_public = '/' + new_public unless new_public[0,1] == '/'
  new_path = path.dup
  new_path = new_path + '/' unless new_path[-1,1] == '/'
  new_path = '/' + new_path unless new_path[0,1] == '/'
  self.class.new("#{new_public}#{name}", "#{new_path}#{name}", request, response, options.merge(:user => @user))
end

#childrenObject

If this is a collection, return the child resources.



119
120
121
# File 'lib/dav4rack/resource.rb', line 119

def children
  NotImplemented
end

#collection?Boolean

Is this resource a collection?

Returns:

  • (Boolean)


124
125
126
# File 'lib/dav4rack/resource.rb', line 124

def collection?
  NotImplemented
end

#content_lengthObject

Return the size in bytes for this resource.



170
171
172
# File 'lib/dav4rack/resource.rb', line 170

def content_length
  NotImplemented
end

#content_typeObject

Return the mime type of this resource.



165
166
167
# File 'lib/dav4rack/resource.rb', line 165

def content_type
  NotImplemented
end

#copy(dest, overwrite = false) ⇒ Object

HTTP COPY request.

Copy this resource to given destination resource.



205
206
207
# File 'lib/dav4rack/resource.rb', line 205

def copy(dest, overwrite=false)
  NotImplemented
end

#creation_dateObject

Return the creation time.



139
140
141
# File 'lib/dav4rack/resource.rb', line 139

def creation_date
  NotImplemented
end

#deleteObject

HTTP DELETE request.

Delete this resource.



198
199
200
# File 'lib/dav4rack/resource.rb', line 198

def delete
  NotImplemented
end

#descendantsObject

Return list of descendants



399
400
401
402
403
404
405
406
# File 'lib/dav4rack/resource.rb', line 399

def descendants
  list = []
  children.each do |child|
    list << child
    list.concat(child.descendants)
  end
  list
end

#display_nameObject

Name of the resource to be displayed to the client



335
336
337
# File 'lib/dav4rack/resource.rb', line 335

def display_name
  name
end

#etagObject

Return an Etag, an unique hash value for this resource.



154
155
156
# File 'lib/dav4rack/resource.rb', line 154

def etag
  NotImplemented
end

#exist?Boolean

Does this resource exist?

Returns:

  • (Boolean)


129
130
131
# File 'lib/dav4rack/resource.rb', line 129

def exist?
  NotImplemented
end

#get(request, response) ⇒ Object

HTTP GET request.

Write the content of the resource to the response.body.



177
178
179
# File 'lib/dav4rack/resource.rb', line 177

def get(request, response)
  NotImplemented
end

#get_property(name) ⇒ Object

name

String - Property name

Returns the value of the given property



346
347
348
349
350
351
352
353
354
355
356
# File 'lib/dav4rack/resource.rb', line 346

def get_property(name)
  case name
  when 'resourcetype'     then resource_type
  when 'displayname'      then display_name
  when 'creationdate'     then creation_date.xmlschema 
  when 'getcontentlength' then content_length.to_s
  when 'getcontenttype'   then content_type
  when 'getetag'          then etag
  when 'getlastmodified'  then last_modified.httpdate
  end
end

#last_modifiedObject

Return the time of last modification.



144
145
146
# File 'lib/dav4rack/resource.rb', line 144

def last_modified
  NotImplemented
end

#last_modified=(time) ⇒ Object

Set the time of last modification.



149
150
151
# File 'lib/dav4rack/resource.rb', line 149

def last_modified=(time)
  NotImplemented
end

#lock(args) ⇒ Object

args

Hash of lock arguments

Request for a lock on the given resource. A valid lock should lock all descendents. Failures should be noted and returned as an exception using LockFailure. Valid args keys: :timeout -> requested timeout

:depth -> lock depth
:scope -> lock scope
:type -> lock type
:owner -> lock owner

Should return a tuple: [lock_time, locktoken] where lock_time is the given timeout NOTE: See section 9.10 of RFC 4918 for guidance about how locks should be generated and the expected responses (www.webdav.org/specs/rfc4918.html#rfc.section.9.10)



231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/dav4rack/resource.rb', line 231

def lock(args)
  unless(@lock_class)
    NotImplemented
  else
    unless(parent_exists?)
      Conflict
    else
      lock_check(args[:scope])
      lock = @lock_class.explicit_locks(@path).find{|l| l.scope == args[:scope] && l.kind == args[:type] && l.user == @user}
      unless(lock)
        token = UUIDTools::UUID.random_create.to_s
        lock = @lock_class.generate(@path, @user, token)
        lock.scope = args[:scope]
        lock.kind = args[:type]
        lock.owner = args[:owner]
        lock.depth = args[:depth].is_a?(Symbol) ? args[:depth] : args[:depth].to_i
        if(args[:timeout])
          lock.timeout = args[:timeout] <= @max_timeout && args[:timeout] > 0 ? args[:timeout] : @max_timeout
        else
          lock.timeout = @default_timeout
        end
        lock.save if lock.respond_to? :save
      end
      begin
        lock_check(args[:type])
      rescue DAV4Rack::LockFailure => lock_failure
        lock.destroy
        raise lock_failure
      rescue HTTPStatus::Status => status
        status
      end
      [lock.remaining_timeout, lock.token]
    end
  end
end

#lock_check(lock_scope = nil) ⇒ Object

lock_scope

scope of lock

Check if resource is locked. Raise DAV4Rack::LockFailure if locks are in place.



269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
# File 'lib/dav4rack/resource.rb', line 269

def lock_check(lock_scope=nil)
  return unless @lock_class
  if(@lock_class.explicitly_locked?(@path))
    raise Locked if @lock_class.explicit_locks(@path).find_all{|l|l.scope == 'exclusive' && l.user != @user}.size > 0
  elsif(@lock_class.implicitly_locked?(@path))
    if(lock_scope.to_s == 'exclusive')
      locks = @lock_class.implicit_locks(@path)
      failure = DAV4Rack::LockFailure.new("Failed to lock: #{@path}")
      locks.each do |lock|
        failure.add_failure(@path, Locked)
      end
      raise failure
    else
      locks = @lock_class.implict_locks(@path).find_all{|l| l.scope == 'exclusive' && l.user != @user}
      if(locks.size > 0)
        failure = LockFailure.new("Failed to lock: #{@path}")
        locks.each do |lock|
          failure.add_failure(@path, Locked)
        end
        raise failure
      end
    end
  end
end

#make_collectionObject

Create this resource as collection.



319
320
321
# File 'lib/dav4rack/resource.rb', line 319

def make_collection
  NotImplemented
end

#move(dest, overwrite = false) ⇒ Object

HTTP MOVE request.

Move this resource to given destination resource.



212
213
214
# File 'lib/dav4rack/resource.rb', line 212

def move(dest, overwrite=false)
  NotImplemented
end

#nameObject

Name of the resource



330
331
332
# File 'lib/dav4rack/resource.rb', line 330

def name
  File.basename(path)
end

#parentObject

Return parent of this resource



392
393
394
395
396
# File 'lib/dav4rack/resource.rb', line 392

def parent
  elements = @path.scan(/[^\/]+/)
  return nil if elements.empty?
  self.class.new(('/' + @public_path.scan(/[^\/]+/)[0..-2].join('/')), ('/' + elements[0..-2].to_a.join('/')), @request, @response, @options.merge(:user => @user))
end

#parent_exists?Boolean

Does the parent resource exist?

Returns:

  • (Boolean)


134
135
136
# File 'lib/dav4rack/resource.rb', line 134

def parent_exists?
  parent.exist?
end

#post(request, response) ⇒ Object

HTTP POST request.

Usually forbidden.



191
192
193
# File 'lib/dav4rack/resource.rb', line 191

def post(request, response)
  NotImplemented
end

#property_namesObject

Available properties



340
341
342
# File 'lib/dav4rack/resource.rb', line 340

def property_names
  %w(creationdate displayname getlastmodified getetag resourcetype getcontenttype getcontentlength)
end

#put(request, response) ⇒ Object

HTTP PUT request.

Save the content of the request.body.



184
185
186
# File 'lib/dav4rack/resource.rb', line 184

def put(request, response)
  NotImplemented
end

#remove_property(name) ⇒ Object

name

Property name

Remove the property from the resource



374
375
376
# File 'lib/dav4rack/resource.rb', line 374

def remove_property(name)
  Forbidden
end

#resource_typeObject

Return the resource type. Generally only used to specify resource is a collection.



160
161
162
# File 'lib/dav4rack/resource.rb', line 160

def resource_type
  :collection if collection?
end

#set_property(name, value) ⇒ Object

name

String - Property name

value

New value

Set the property to the given value



361
362
363
364
365
366
367
368
369
370
# File 'lib/dav4rack/resource.rb', line 361

def set_property(name, value)
  case name
  when 'resourcetype'    then self.resource_type = value
  when 'getcontenttype'  then self.content_type = value
  when 'getetag'         then self.etag = value
  when 'getlastmodified' then self.last_modified = Time.httpdate(value)
  end
rescue ArgumentError
  Conflict
end

#unlock(token) ⇒ Object

token

Lock token

Remove the given lock



296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
# File 'lib/dav4rack/resource.rb', line 296

def unlock(token)
  unless(@lock_class)
    NotImplemented
  else
    token = token.slice(1, token.length - 2)
    if(token.nil? || token.empty?)
      BadRequest
    else
      lock = @lock_class.find_by_token(token)
      if(lock.nil? || lock.user != @user)
        Forbidden
      elsif(lock.path !~ /^#{Regexp.escape(@path)}.*$/)
        Conflict
      else
        lock.destroy
        NoContent
      end
    end
  end
end