Class: DAV4Rack::Resource

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

Direct Known Subclasses

FileResource, InterceptorResource, MongoResource

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



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

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)


447
448
449
450
451
452
453
454
# File 'lib/dav4rack/resource.rb', line 447

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



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

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.



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

def children
  NotImplemented
end

#collection?Boolean

Is this resource a collection?

Returns:

  • (Boolean)


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

def collection?
  NotImplemented
end

#content_lengthObject

Return the size in bytes for this resource.

Raises:

  • (NotImplemented)


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

def content_length
  raise NotImplemented
end

#content_typeObject

Return the mime type of this resource.

Raises:

  • (NotImplemented)


179
180
181
# File 'lib/dav4rack/resource.rb', line 179

def content_type
  raise NotImplemented
end

#copy(dest, overwrite = false) ⇒ Object

HTTP COPY request.

Copy this resource to given destination resource.



219
220
221
# File 'lib/dav4rack/resource.rb', line 219

def copy(dest, overwrite=false)
  NotImplemented
end

#creation_dateObject

Return the creation time.

Raises:

  • (NotImplemented)


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

def creation_date
  raise NotImplemented
end

#deleteObject

HTTP DELETE request.

Delete this resource.



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

def delete
  NotImplemented
end

#descendantsObject

Return list of descendants



425
426
427
428
429
430
431
432
# File 'lib/dav4rack/resource.rb', line 425

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



349
350
351
# File 'lib/dav4rack/resource.rb', line 349

def display_name
  name
end

#etagObject

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

Raises:

  • (NotImplemented)


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

def etag
  raise NotImplemented
end

#exist?Boolean

Does this resource exist?

Returns:

  • (Boolean)


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

def exist?
  NotImplemented
end

#get(request, response) ⇒ Object

HTTP GET request.

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



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

def get(request, response)
  NotImplemented
end

#get_property(element) ⇒ Object

name

String - Property name

Returns the value of the given property

Raises:

  • (NotImplemented)


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

def get_property(element)
  raise NotImplemented if (element[:ns_href] != 'DAV:')
  case element[: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
  else                    raise NotImplemented
  end
end

#index_pageObject

Index page template for GETs on collection



435
436
437
438
439
440
441
# File 'lib/dav4rack/resource.rb', line 435

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)


468
469
470
471
472
# File 'lib/dav4rack/resource.rb', line 468

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)


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

def last_modified
  raise NotImplemented
end

#last_modified=(time) ⇒ Object

Set the time of last modification.

Raises:

  • (NotImplemented)


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

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)



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
270
271
272
273
274
275
276
277
278
279
# File 'lib/dav4rack/resource.rb', line 245

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.



283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/dav4rack/resource.rb', line 283

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.



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

def make_collection
  NotImplemented
end

#move(dest, overwrite = false) ⇒ Object

HTTP MOVE request.

Move this resource to given destination resource.



226
227
228
# File 'lib/dav4rack/resource.rb', line 226

def move(dest, overwrite=false)
  NotImplemented
end

#nameObject

Name of the resource



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

def name
  File.basename(path)
end

#parentObject

Return parent of this resource



410
411
412
413
414
415
416
417
418
419
420
421
422
# File 'lib/dav4rack/resource.rb', line 410

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_collection?Boolean

Is the parent resource a collection?

Returns:

  • (Boolean)


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

def parent_collection?
  parent.collection?
end

#parent_exists?Boolean

Does the parent resource exist?

Returns:

  • (Boolean)


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

def parent_exists?
  parent.exist?
end

#post(request, response) ⇒ Object

HTTP POST request.

Usually forbidden.



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

def post(request, response)
  NotImplemented
end

#propertiesObject

Available properties



354
355
356
357
358
# File 'lib/dav4rack/resource.rb', line 354

def properties
  %w(creationdate displayname getlastmodified getetag resourcetype getcontenttype getcontentlength).collect do |prop|
    {:name => prop, :ns_href => 'DAV:'}
  end
end

#put(request, response) ⇒ Object

HTTP PUT request.

Save the content of the request.body.



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

def put(request, response)
  NotImplemented
end

#remove_property(element) ⇒ Object

name

Property name

Remove the property from the resource



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

def remove_property(element)
  Forbidden
end

#resource_typeObject

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



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

def resource_type
  :collection if collection?
end

#set_property(element, value) ⇒ Object

name

String - Property name

value

New value

Set the property to the given value

Raises:

  • (NotImplemented)


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

def set_property(element, value)
  raise NotImplemented if (element[:ns_href] != 'DAV:')
  case element[: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)
  else                   raise NotImplemented
  end
end

#supports_locking?Boolean

Returns if resource supports locking

Returns:

  • (Boolean)


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

def supports_locking?
  false #true
end

#unlock(token) ⇒ Object

token

Lock token

Remove the given lock



310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
# File 'lib/dav4rack/resource.rb', line 310

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)


456
457
458
# File 'lib/dav4rack/resource.rb', line 456

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)


461
462
463
464
465
# File 'lib/dav4rack/resource.rb', line 461

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