Class: DAV4Rack::Controller

Inherits:
Object
  • Object
show all
Includes:
HTTPStatus, Utils
Defined in:
lib/dav4rack/controller.rb

Constant Summary

Constants included from HTTPStatus

HTTPStatus::StatusMessage

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Utils

#to_element_hash, #to_element_key

Constructor Details

#initialize(request, response, options = {}) ⇒ Controller

request

Rack::Request

response

Rack::Response

options

Options hash

Create a new Controller. NOTE: options will be passed to Resource

Raises:

  • (Forbidden)


16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/dav4rack/controller.rb', line 16

def initialize(request, response, options={})
  raise Forbidden if request.path_info.include?('..')
  @request = request
  @response = response
  @options = options
  
  @dav_extensions = options.delete(:dav_extensions) || []
  @always_include_dav_header = options.delete(:always_include_dav_header)
  
  @resource = resource_class.new(actual_path, implied_path, @request, @response, @options)
  
  if(@always_include_dav_header)
    add_dav_header
  end
end

Instance Attribute Details

#requestObject (readonly)

Returns the value of attribute request.



9
10
11
# File 'lib/dav4rack/controller.rb', line 9

def request
  @request
end

#resourceObject (readonly)

Returns the value of attribute resource.



9
10
11
# File 'lib/dav4rack/controller.rb', line 9

def resource
  @resource
end

#responseObject (readonly)

Returns the value of attribute response.



9
10
11
# File 'lib/dav4rack/controller.rb', line 9

def response
  @response
end

Instance Method Details

#add_dav_headerObject



48
49
50
51
52
53
# File 'lib/dav4rack/controller.rb', line 48

def add_dav_header
  unless(response['Dav'])
    dav_support = %w(1 2) + @dav_extensions
    response['Dav'] = dav_support.join(', ')
  end
end

#authenticateObject

Perform authentication NOTE: Authentication will only be performed if the Resource has defined an #authenticate method

Raises:

  • (Unauthorized)


328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
# File 'lib/dav4rack/controller.rb', line 328

def authenticate
  authed = true
  if(resource.respond_to?(:authenticate, true))
    authed = false
    uname = nil
    password = nil
    if(request.env['HTTP_AUTHORIZATION'])
      auth = Rack::Auth::Basic::Request.new(request.env)
      if(auth.basic? && auth.credentials)
        uname = auth.credentials[0]
        password = auth.credentials[1]
      end
    end
    authed = resource.send(:authenticate, uname, password)
  end
  raise Unauthorized unless authed
end

#copyObject

Return response to COPY



140
141
142
# File 'lib/dav4rack/controller.rb', line 140

def copy
  move(:copy)
end

#deleteObject

Return response to DELETE



112
113
114
115
116
117
118
119
# File 'lib/dav4rack/controller.rb', line 112

def delete
  if(resource.exist?)
    resource.lock_check if resource.supports_locking?
    resource.delete
  else
    NotFound
  end
end

#getObject

Return response to GET



76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/dav4rack/controller.rb', line 76

def get
  if(resource.exist?)
    res = resource.get(request, response)
    if(res == OK && !resource.collection?)
      response['Etag'] = resource.etag
      response['Content-Type'] = resource.content_type
      response['Content-Length'] = resource.content_length.to_s
      response['Last-Modified'] = resource.last_modified.httpdate
    end
    res
  else
    NotFound
  end
end

#headObject

Return response to HEAD



64
65
66
67
68
69
70
71
72
73
# File 'lib/dav4rack/controller.rb', line 64

def head
  if(resource.exist?)
    response['Etag'] = resource.etag
    response['Content-Type'] = resource.content_type
    response['Last-Modified'] = resource.last_modified.httpdate
    OK
  else
    NotFound
  end
end

#lockObject

Lock current resource NOTE: This will pass an argument hash to Resource#lock and wait for a success/failure response.



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'lib/dav4rack/controller.rb', line 268

def lock
  lockinfo = request_document.xpath("//#{ns}lockinfo")
  asked = {}
  asked[:timeout] = request.env['Timeout'].split(',').map{|x|x.strip} if request.env['Timeout']
  asked[:depth] = depth
  unless([0, :infinity].include?(asked[:depth]))
    BadRequest
  else
    asked[:scope] = lockinfo.xpath("//#{ns}lockscope").children.find_all{|n|n.element?}.map{|n|n.name}.first
    asked[:type] = lockinfo.xpath("#{ns}locktype").children.find_all{|n|n.element?}.map{|n|n.name}.first
    asked[:owner] = lockinfo.xpath("//#{ns}owner/#{ns}href").children.map{|n|n.text}.first
    begin
      lock_time, locktoken = resource.lock(asked)
      render_xml(:prop) do |xml|
        xml.lockdiscovery do
          xml.activelock do
            if(asked[:scope])
              xml.lockscope do
                xml.send(asked[:scope])
              end
            end
            if(asked[:type])
              xml.locktype do
                xml.send(asked[:type])
              end
            end
            xml.depth asked[:depth].to_s
            xml.timeout lock_time ? "Second-#{lock_time}" : 'infinity'
            xml.locktoken do
              xml.href locktoken
            end
            if(asked[:owner])
              xml.owner asked[:owner]
            end
          end
        end
      end
      response.headers['Lock-Token'] = locktoken
      response.status = resource.exist? ? OK : Created
    rescue LockFailure => e
      multistatus do |xml|
        e.path_status.each_pair do |path, status|
          xml.response do
            xml.href path
            xml.status "#{http_version} #{status.status_line}"
          end
        end
      end
    end
  end
end

#mkcolObject

Return response to MKCOL



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/dav4rack/controller.rb', line 122

def mkcol
  resource.lock_check if resource.supports_locking?
  status = resource.make_collection
  gen_url = "#{scheme}://#{host}:#{port}#{url_format(resource)}" if status == Created
  if(resource.use_compat_mkcol_response?)
    multistatus do |xml|
      xml.response do
        xml.href gen_url
        xml.status "#{http_version} #{status.status_line}"
      end
    end
  else
    response['Location'] = gen_url
    status
  end
end

#move(*args) ⇒ Object

args

Only argument used: :copy

Move Resource to new location. If :copy is provided, Resource will be copied (implementation ease)



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/dav4rack/controller.rb', line 147

def move(*args)
  unless(resource.exist?)
    NotFound
  else
    resource.lock_check if resource.supports_locking? && !args.include(:copy)
    destination = url_unescape(env['HTTP_DESTINATION'].sub(%r{https?://([^/]+)}, ''))
    dest_host = $1
    if(dest_host && dest_host.gsub(/:\d{2,5}$/, '') != request.host)
      BadGateway
    elsif(destination == resource.public_path)
      Forbidden
    else
      collection = resource.collection?
      dest = resource_class.new(destination, clean_path(destination), @request, @response, @options.merge(:user => resource.user))
      status = nil
      if(args.include?(:copy))
        status = resource.copy(dest, overwrite)
      else
        return Conflict unless depth.is_a?(Symbol) || depth > 1
        status = resource.move(dest, overwrite)
      end
      response['Location'] = "#{scheme}://#{host}:#{port}#{url_format(dest)}" if status == Created
      # RFC 2518
      if collection
        multistatus do |xml|
          xml.response do
            xml.href "#{scheme}://#{host}:#{port}#{url_format(status == Created ? dest : resource)}"
            xml.status "#{http_version} #{status.status_line}"
          end
        end
      else
        status
      end
    end
  end
end

#optionsObject

Return response to OPTIONS



56
57
58
59
60
61
# File 'lib/dav4rack/controller.rb', line 56

def options
  add_dav_header
  response['Allow'] = 'OPTIONS,HEAD,GET,PUT,POST,DELETE,PROPFIND,PROPPATCH,MKCOL,COPY,MOVE,LOCK,UNLOCK'
  response['Ms-Author-Via'] = 'DAV'
  OK
end

#postObject

Return response to POST



107
108
109
# File 'lib/dav4rack/controller.rb', line 107

def post
  resource.post(request, response)
end

#propfindObject

Return response to PROPFIND



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/dav4rack/controller.rb', line 185

def propfind
  unless(resource.exist?)
    NotFound
  else
    unless(request_document.xpath("//#{ns}propfind/#{ns}allprop").empty?)
      properties = resource.properties
    else
      check = request_document.xpath("//#{ns}propfind")
      if(check && !check.empty?)
        properties = request_document.xpath(
          "//#{ns}propfind/#{ns}prop"
        ).children.find_all{ |item|
          item.element?
        }.map{ |item|
          # We should do this, but Nokogiri transforms prefix w/ null href into
          # something valid.  Oops.
          # TODO: Hacky grep fix that's horrible
          hsh = to_element_hash(item)
          if(hsh.namespace.nil? && !ns.empty?)
            raise BadRequest if request_document.to_s.scan(%r{<#{item.name}[^>]+xmlns=""}).empty?
          end
          hsh
        }.compact
      else
        raise BadRequest
      end
    end
    multistatus do |xml|
      find_resources.each do |resource|
        xml.response do
          unless(resource.propstat_relative_path)
            xml.href "#{scheme}://#{host}:#{port}#{url_format(resource)}"
          else
            xml.href url_format(resource)
          end
          propstats(xml, get_properties(resource, properties.empty? ? resource.properties : properties))
        end
      end
    end
  end
end

#proppatchObject

Return response to PROPPATCH



228
229
230
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
# File 'lib/dav4rack/controller.rb', line 228

def proppatch
  unless(resource.exist?)
    NotFound
  else
    resource.lock_check if resource.supports_locking?
    prop_actions = []
    request_document.xpath("/#{ns}propertyupdate").children.each do |element|
      case element.name
      when 'set', 'remove'
        prp = element.children.detect{|e|e.name == 'prop'}
        if(prp)
          prp.children.each do |elm|
            next if elm.name == 'text'
            prop_actions << {:type => element.name, :name => to_element_hash(elm), :value => elm.text}
          end
        end
      end
    end
    multistatus do |xml|
      find_resources.each do |resource|
        xml.response do
          xml.href "#{scheme}://#{host}:#{port}#{url_format(resource)}"
          prop_actions.each do |action|
            case action[:type]
            when 'set'
              propstats(xml, set_properties(resource, action[:name] => action[:value]))
            when 'remove'
              rm_properties(resource, action[:name] => action[:value])
            end
          end
        end
      end
    end
  end
end

#putObject

Return response to PUT



92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/dav4rack/controller.rb', line 92

def put
  if(resource.collection?)
    Forbidden
  elsif(!resource.parent_exists? || !resource.parent_collection?)
    Conflict
  else
    resource.lock_check if resource.supports_locking?
    status = resource.put(request, response)
    response['Location'] = "#{scheme}://#{host}:#{port}#{url_format(resource)}" if status == Created
    response.body = response['Location']
    status
  end
end

#unlockObject

Unlock current resource



321
322
323
# File 'lib/dav4rack/controller.rb', line 321

def unlock
  resource.unlock(lock_token)
end

#url_format(resource) ⇒ Object

s

string

Escape URL string



34
35
36
37
38
39
40
# File 'lib/dav4rack/controller.rb', line 34

def url_format(resource)
  ret = URI.escape(resource.public_path)
  if resource.collection? and ret[-1,1] != '/'
    ret += '/'
  end
  ret
end

#url_unescape(s) ⇒ Object

s

string

Unescape URL string



44
45
46
# File 'lib/dav4rack/controller.rb', line 44

def url_unescape(s)
  URI.unescape(s)
end