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


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
104
105
106
# File 'lib/dav4rack/resource.rb', line 66

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
  @propstat_relative_path = !!options.delete(:propstat_relative_path)
  @root_xml_attributes = options.delete(:root_xml_attributes) || {}
  @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)


109
110
111
112
113
114
115
116
117
118
119
# File 'lib/dav4rack/resource.rb', line 109

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

#propstat_relative_pathObject (readonly)

Returns the value of attribute propstat_relative_path.



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

def propstat_relative_path
  @propstat_relative_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

#root_xml_attributesObject (readonly)

Returns the value of attribute root_xml_attributes.



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

def root_xml_attributes
  @root_xml_attributes
end

#userObject

Returns the value of attribute user.



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

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



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

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



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

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

#allows_redirect?Boolean

Does client allow GET redirection TODO: Get a comprehensive list in here. TODO: Allow this to be dynamic so users can add regexes to match if they know of a client that can be supported that is not listed.

Returns:

  • (Boolean)


431
432
433
434
435
436
437
438
# File 'lib/dav4rack/resource.rb', line 431

def allows_redirect?
  [
    %r{cyberduck}i,
    %r{konqueror}i
  ].any? do |regexp|
    (request.respond_to?(:user_agent) ? request.user_agent : request.env['HTTP_USER_AGENT']).to_s =~ regexp
  end
end

#child(name) ⇒ Object

name

Name of child

Create a new child with the given name

NOTE

Include trailing ‘/’ if child is collection



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

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.



122
123
124
# File 'lib/dav4rack/resource.rb', line 122

def children
  NotImplemented
end

#collection?Boolean

Is this resource a collection?

Returns:

  • (Boolean)


127
128
129
# File 'lib/dav4rack/resource.rb', line 127

def collection?
  NotImplemented
end

#content_lengthObject

Return the size in bytes for this resource.

Raises:

  • (NotImplemented)


174
175
176
# File 'lib/dav4rack/resource.rb', line 174

def content_length
  raise NotImplemented
end

#content_typeObject

Return the mime type of this resource.

Raises:

  • (NotImplemented)


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

def content_type
  raise NotImplemented
end

#copy(dest, overwrite = false) ⇒ Object

HTTP COPY request.

Copy this resource to given destination resource.



209
210
211
# File 'lib/dav4rack/resource.rb', line 209

def copy(dest, overwrite=false)
  NotImplemented
end

#creation_dateObject

Return the creation time.

Raises:

  • (NotImplemented)


142
143
144
# File 'lib/dav4rack/resource.rb', line 142

def creation_date
  raise NotImplemented
end

#deleteObject

HTTP DELETE request.

Delete this resource.



202
203
204
# File 'lib/dav4rack/resource.rb', line 202

def delete
  NotImplemented
end

#descendantsObject

Return list of descendants



409
410
411
412
413
414
415
416
# File 'lib/dav4rack/resource.rb', line 409

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



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

def display_name
  name
end

#etagObject

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

Raises:

  • (NotImplemented)


158
159
160
# File 'lib/dav4rack/resource.rb', line 158

def etag
  raise NotImplemented
end

#exist?Boolean

Does this resource exist?

Returns:

  • (Boolean)


132
133
134
# File 'lib/dav4rack/resource.rb', line 132

def exist?
  NotImplemented
end

#get(request, response) ⇒ Object

HTTP GET request.

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



181
182
183
# File 'lib/dav4rack/resource.rb', line 181

def get(request, response)
  NotImplemented
end

#get_property(name) ⇒ Object

name

String - Property name

Returns the value of the given property



350
351
352
353
354
355
356
357
358
359
360
# File 'lib/dav4rack/resource.rb', line 350

def get_property(name)
  case name
  when 'resourcetype'     then resource_type
  when 'displayname'      then display_name
  when 'creationdate'     then use_ms_compat_creationdate? ? creation_date.httpdate : 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

#index_pageObject

Index page template for GETs on collection



419
420
421
422
423
424
425
# File 'lib/dav4rack/resource.rb', line 419

def index_page
  '<html><head> <title>%s</title>
  <meta http-equiv="content-type" content="text/html; charset=utf-8" /></head>
  <body> <h1>%s</h1> <hr /> <table> <tr> <th class="name">Name</th>
  <th class="size">Size</th> <th class="type">Type</th> 
  <th class="mtime">Last Modified</th> </tr> %s </table> <hr /> </body></html>'
end

#is_ms_client?Boolean

Basic user agent testing for MS authored client

Returns:

  • (Boolean)


452
453
454
455
456
# File 'lib/dav4rack/resource.rb', line 452

def is_ms_client?
  [%r{microsoft-webdav}i, %r{microsoft office}i].any? do |regexp| 
    (request.respond_to?(:user_agent) ? request.user_agent : request.env['HTTP_USER_AGENT']).to_s =~ regexp
  end
end

#last_modifiedObject

Return the time of last modification.

Raises:

  • (NotImplemented)


147
148
149
# File 'lib/dav4rack/resource.rb', line 147

def last_modified
  raise NotImplemented
end

#last_modified=(time) ⇒ Object

Set the time of last modification.

Raises:

  • (NotImplemented)


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

def last_modified=(time)
  # Is this correct?
  raise 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)



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
266
267
268
269
# File 'lib/dav4rack/resource.rb', line 235

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.



273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
# File 'lib/dav4rack/resource.rb', line 273

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.



323
324
325
# File 'lib/dav4rack/resource.rb', line 323

def make_collection
  NotImplemented
end

#move(dest, overwrite = false) ⇒ Object

HTTP MOVE request.

Move this resource to given destination resource.



216
217
218
# File 'lib/dav4rack/resource.rb', line 216

def move(dest, overwrite=false)
  NotImplemented
end

#nameObject

Name of the resource



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

def name
  File.basename(path)
end

#parentObject

Return parent of this resource



394
395
396
397
398
399
400
401
402
403
404
405
406
# File 'lib/dav4rack/resource.rb', line 394

def parent
  unless(@path.to_s.empty?)
    self.class.new(
      File.split(@public_path).first,
      File.split(@path).first,
      @request,
      @response,
      @options.merge(
        :user => @user
      )
    )
  end
end

#parent_exists?Boolean

Does the parent resource exist?

Returns:

  • (Boolean)


137
138
139
# File 'lib/dav4rack/resource.rb', line 137

def parent_exists?
  parent.exist?
end

#post(request, response) ⇒ Object

HTTP POST request.

Usually forbidden.



195
196
197
# File 'lib/dav4rack/resource.rb', line 195

def post(request, response)
  NotImplemented
end

#property_namesObject

Available properties



344
345
346
# File 'lib/dav4rack/resource.rb', line 344

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.



188
189
190
# File 'lib/dav4rack/resource.rb', line 188

def put(request, response)
  NotImplemented
end

#remove_property(name) ⇒ Object

name

Property name

Remove the property from the resource



376
377
378
# File 'lib/dav4rack/resource.rb', line 376

def remove_property(name)
  Forbidden
end

#resource_typeObject

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



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

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



365
366
367
368
369
370
371
372
# File 'lib/dav4rack/resource.rb', line 365

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
end

#unlock(token) ⇒ Object

token

Lock token

Remove the given lock



300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
# File 'lib/dav4rack/resource.rb', line 300

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

#use_compat_mkcol_response?Boolean

Returns:

  • (Boolean)


440
441
442
# File 'lib/dav4rack/resource.rb', line 440

def use_compat_mkcol_response?
  @options[:compat_mkcol] || @options[:compat_all]
end

#use_ms_compat_creationdate?Boolean

Returns true if using an MS client

Returns:

  • (Boolean)


445
446
447
448
449
# File 'lib/dav4rack/resource.rb', line 445

def use_ms_compat_creationdate?
  if(@options[:compat_ms_mangled_creationdate] || @options[:compat_all])
    is_ms_client?
  end
end