class RackWebDAV::Controller

Attributes

request[R]
resource[R]
response[R]

Public Class Methods

new(request, response, options) click to toggle source
# File lib/rack-webdav/controller.rb, line 12
def initialize(request, response, options)
  @request  = request
  @response = response
  @options  = options
  @resource = resource_class.new(url_unescape(request.path_info), @request, @response, @options)
  raise Forbidden if request.path_info.include?('../')
end

Public Instance Methods

copy() click to toggle source
# File lib/rack-webdav/controller.rb, line 106
def copy
  raise NotFound if not resource.exist?
  # Source Lock Check
  locktoken = request_locktoken('LOCK_TOKEN')
  locktoken ||= request_locktoken('IF')
  raise Locked if resource.locked?(locktoken) && !overwrite

  dest_uri = URI.parse(env['HTTP_DESTINATION'])
  destination = parse_destination(dest_uri)

  raise BadGateway if dest_uri.host and dest_uri.host != request.host
  raise Forbidden if destination == resource.path

  dest = resource_class.new(destination, @request, @response, @options)
  raise PreconditionFailed if dest.exist? && !overwrite
  # Destination Lock Check
  locktoken = request_locktoken('LOCK_TOKEN')
  locktoken ||= request_locktoken('IF')
  raise Locked if dest.locked?(locktoken)

  dest = dest.child(resource.name) if dest.collection?

  dest_existed = dest.exist?

  copy_recursive(resource, dest, depth, errors = [])

  if errors.empty?
    response.status = dest_existed ? NoContent : Created
  else
    multistatus do |xml|
      response_errors(xml, errors)
    end
  end
rescue URI::InvalidURIError => e
  raise BadRequest.new(e.message)
end
delete() click to toggle source
# File lib/rack-webdav/controller.rb, line 80
def delete
  raise NotFound if not resource.exist?
  raise Locked if resource.locked?(request_locktoken('LOCK_TOKEN'))

  delete_recursive(resource, errors = [])

  if errors.empty?
    response.status = NoContent
  else
    multistatus do |xml|
      response_errors(xml, errors)
    end
  end
end
get() click to toggle source
# File lib/rack-webdav/controller.rb, line 48
def get
  raise NotFound if not resource.exist?
  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
  map_exceptions do
    resource.get
  end
end
head() click to toggle source
# File lib/rack-webdav/controller.rb, line 40
def head
  raise NotFound if not resource.exist?
  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
lock() click to toggle source
# File lib/rack-webdav/controller.rb, line 242
def lock
  raise MethodNotAllowed unless resource.lockable?
  raise NotFound if not resource.exist?

  timeout = request_timeout
  if timeout.nil? || timeout.zero?
    timeout = 60
  end

  if request_document.content.empty?
    refresh_lock timeout
  else
    create_lock timeout
  end
end
mkcol() click to toggle source
# File lib/rack-webdav/controller.rb, line 95
def mkcol
  # Reject message bodies - RFC2518:8.3.1
  body = @request.body.read(8)
  fail UnsupportedMediaType if !body.nil? && body.length > 0

  map_exceptions do
    resource.make_collection
  end
  response.status = Created
end
move() click to toggle source
# File lib/rack-webdav/controller.rb, line 143
def move
  raise NotFound if not resource.exist?
  raise Locked if resource.locked?(request_locktoken('LOCK_TOKEN'))

  dest_uri = URI.parse(env['HTTP_DESTINATION'])
  destination = parse_destination(dest_uri)

  raise BadGateway if dest_uri.host and dest_uri.host != request.host
  raise Forbidden if destination == resource.path

  dest = resource_class.new(destination, @request, @response, @options)
  raise PreconditionFailed if dest.exist? && !overwrite

  dest_existed = dest.exist?
  dest = dest.child(resource.name) if dest.collection?

  raise Conflict if depth <= 1

  copy_recursive(resource, dest, depth, errors = [])
  delete_recursive(resource, errors)

  if errors.empty?
    response.status = dest_existed ? NoContent : Created
  else
    multistatus do |xml|
      response_errors(xml, errors)
    end
  end
rescue URI::InvalidURIError => e
  raise BadRequest.new(e.message)
end
options() click to toggle source
# File lib/rack-webdav/controller.rb, line 28
def options
  response["Allow"] = 'OPTIONS,HEAD,GET,PUT,POST,DELETE,PROPFIND,PROPPATCH,MKCOL,COPY,MOVE'
  response["Dav"] = "1"

  if resource.lockable?
    response["Allow"] << ",LOCK,UNLOCK"
    response["Dav"]   << ",2"
  end

  response["Ms-Author-Via"] = "DAV"
end
post() click to toggle source
# File lib/rack-webdav/controller.rb, line 74
def post
  map_exceptions do
    resource.post
  end
end
propfind() click to toggle source
# File lib/rack-webdav/controller.rb, line 175
def propfind
  raise NotFound if not resource.exist?

  if not request_match("/d:propfind/d:allprop").empty?
    nodes = all_prop_nodes
  else
    nodes = request_match("/d:propfind/d:prop/*")
    nodes = all_prop_nodes if nodes.empty?
  end

  nodes.each do |n|
    # Don't allow empty namespace declarations
    # See litmus props test 3
    raise BadRequest if n.namespace.nil? && n.namespace_definitions.empty?

    # Set a blank namespace if one is included in the request
    # See litmus props test 16
    # <propfind xmlns="DAV:"><prop><nonamespace xmlns=""/></prop></propfind>
    if n.namespace.nil?
      nd = n.namespace_definitions.first
      if nd.prefix.nil? && nd.href.empty?
        n.add_namespace(nil, '')
      end
    end
  end

  multistatus do |xml|
    for resource in find_resources
      resource.path.gsub!(/\/\//, '/')
      xml.response do
        xml.href "http://#{host}#{@request.script_name}#{url_escape resource.path}"
        propstats xml, get_properties(resource, nodes)
      end
    end
  end
end
proppatch() click to toggle source
# File lib/rack-webdav/controller.rb, line 212
def proppatch
  raise NotFound if not resource.exist?
  locktoken = request_locktoken('LOCK_TOKEN')
  locktoken ||= request_locktoken('IF')
  raise Locked if resource.locked?(locktoken)

  nodes = request_match("/d:propertyupdate[d:remove/d:prop/* or d:set/d:prop/*]//d:prop/*")

  # Set a blank namespace if one is included in the request
  # See litmus props test 15
  # <propertyupdate xmlns="DAV:"><set>
  #   <prop><nonamespace xmlns="">randomvalue</nonamespace></prop>
  # </set></propertyupdate>
  nodes.each do |n|
    nd = n.namespace_definitions.first
    if !nd.nil? && nd.prefix.nil? && nd.href.empty?
      n.add_namespace(nil, '')
    end
  end

  multistatus do |xml|
    for resource in find_resources
      xml.response do
        xml.href "http://#{host}#{@request.script_name}#{resource.path}"
        propstats xml, set_properties(resource, nodes)
      end
    end
  end
end
put() click to toggle source
# File lib/rack-webdav/controller.rb, line 59
def put
  raise Forbidden if resource.collection?
  locktoken = request_locktoken('LOCK_TOKEN')
  locktoken ||= request_locktoken('IF')
  locketag = request_locketag('IF')
  raise PreconditionFailed if locketag && locketag != resource.etag
  raise Locked if resource.locked?(locktoken, locketag)

  map_exceptions do
    resource.put
  end
  response.status = Created
  response['Location'] = "#{request.scheme}://#{request.host}:#{request.port}#{url_format_for_response(resource)}"
end
unlock() click to toggle source
# File lib/rack-webdav/controller.rb, line 258
def unlock
  raise MethodNotAllowed unless resource.lockable?

  locktoken = request_locktoken('LOCK_TOKEN')
  raise BadRequest if locktoken.nil?

  response.status = resource.unlock(locktoken) ? NoContent : Forbidden
end
url_escape(s) click to toggle source
# File lib/rack-webdav/controller.rb, line 20
def url_escape(s)
  URI.escape(s)
end
url_unescape(s) click to toggle source
# File lib/rack-webdav/controller.rb, line 24
def url_unescape(s)
  URI.unescape(s).force_valid_encoding
end

Private Instance Methods

all_prop_nodes() click to toggle source
# File lib/rack-webdav/controller.rb, line 375
def all_prop_nodes
  resource.property_names.map do |n|
    node = Nokogiri::XML::Element.new(n, request_document)
    node.add_namespace(nil, 'DAV:')
    node
  end
end
copy_recursive(res, dest, depth, errors) click to toggle source
# File lib/rack-webdav/controller.rb, line 316
def copy_recursive(res, dest, depth, errors)
  map_exceptions do
    if dest.exist?
      if overwrite
        delete_recursive(dest, errors)
      else
        raise PreconditionFailed
      end
    end
    res.copy(dest)
  end
rescue Status
  errors << [res.path, $!]
else
  if depth > 0
    for child in res.children
      dest_child = dest.child(child.name)
      copy_recursive(child, dest_child, depth - 1, errors)
    end
  end
end
create_lock(timeout) click to toggle source
# File lib/rack-webdav/controller.rb, line 508
def create_lock(timeout)
  lockscope = request_match("/d:lockinfo/d:lockscope/d:*").first
  lockscope = lockscope.name if lockscope
  locktype = request_match("/d:lockinfo/d:locktype/d:*").first
  locktype = locktype.name if locktype
  owner = request_match("/d:lockinfo/d:owner/d:href").first
  owner ||= request_match("/d:lockinfo/d:owner").first
  owner = owner.text if owner
  locktoken = "opaquelocktoken:" + sprintf('%x-%x-%s', Time.now.to_i, Time.now.sec, resource.etag)

  raise Locked if resource.other_owner_locked?(locktoken, owner)

  # Quick & Dirty - FIXME: Lock should become a new Class
  # and this dirty parameter passing refactored.
  unless resource.lock(locktoken, timeout, lockscope, locktype, owner)
    raise Forbidden
  end

  response['Lock-Token'] = locktoken

  render_lockdiscovery locktoken, lockscope, locktype, timeout, owner
end
delete_recursive(res, errors) click to toggle source
# File lib/rack-webdav/controller.rb, line 304
def delete_recursive(res, errors)
  for child in res.children
    delete_recursive(child, errors)
  end

  begin
    map_exceptions { res.delete } if errors.empty?
  rescue Status
    errors << [res.path, $!]
  end
end
depth() click to toggle source
# File lib/rack-webdav/controller.rb, line 281
def depth
  case env['HTTP_DEPTH']
  when '0' then 0
  when '1' then 1
  else 100
  end
end
env() click to toggle source
# File lib/rack-webdav/controller.rb, line 269
def env
  @request.env
end
find_resources() click to toggle source
# File lib/rack-webdav/controller.rb, line 293
def find_resources
  case env['HTTP_DEPTH']
  when '0'
    [resource]
  when '1'
    [resource] + resource.children
  else
    [resource] + resource.descendants
  end
end
get_properties(resource, nodes) click to toggle source
# File lib/rack-webdav/controller.rb, line 451
def get_properties(resource, nodes)
  stats = Hash.new { |h, k| h[k] = [] }
  for node in nodes
    begin
      map_exceptions do
        stats[OK] << [node, resource.get_property(qualified_property_name(node))]
      end
    rescue Status
      stats[$!] << node
    end
  end
  stats
end
host() click to toggle source
# File lib/rack-webdav/controller.rb, line 273
def host
  @request.host
end
map_exceptions() { || ... } click to toggle source
# File lib/rack-webdav/controller.rb, line 338
def map_exceptions
  yield
rescue
  case $!
  when URI::InvalidURIError then raise BadRequest
  when Errno::EACCES then raise Forbidden
  when Errno::ENOENT then raise Conflict
  when Errno::EEXIST then raise Conflict
  when Errno::ENOSPC then raise InsufficientStorage
  else
    raise
  end
end
multistatus() { |xml| ... } click to toggle source
# File lib/rack-webdav/controller.rb, line 432
def multistatus
  render_xml do |xml|
    xml.multistatus('xmlns' => "DAV:") do
      yield xml
    end
  end

  response.status = MultiStatus
end
overwrite() click to toggle source
# File lib/rack-webdav/controller.rb, line 289
def overwrite
  env['HTTP_OVERWRITE'].to_s.upcase != 'F'
end
parse_destination(dest_uri) click to toggle source
# File lib/rack-webdav/controller.rb, line 585
def parse_destination dest_uri
  destination = url_unescape(dest_uri.path)
  destination.slice!(1..@request.script_name.length) if @request.script_name.length > 0
  destination
end
propstats(xml, stats) click to toggle source
# File lib/rack-webdav/controller.rb, line 479
def propstats(xml, stats)
  return if stats.empty?
  for status, props in stats
    xml.propstat do
      xml.prop do
        for node, value in props
          if value.is_a?(Nokogiri::XML::Node)
            xml.send(qualified_node_name(node).to_sym) do
              rexml_convert(xml, value)
            end
          else
            attrs = {}
            unless node.namespace.nil?
              unless node.namespace.prefix.nil?
                attrs = { "xmlns:#{node.namespace.prefix}" => node.namespace.href }
              else
                attrs = { 'xmlns' => node.namespace.href }
              end
            end

            xml.send(qualified_node_name(node).to_sym, value, attrs)
          end
        end
      end
      xml.status "#{request.env['HTTP_VERSION']} #{status.status_line}"
    end
  end
end
qualified_node_name(node) click to toggle source
# File lib/rack-webdav/controller.rb, line 367
def qualified_node_name(node)
  node.namespace.nil? || node.namespace.prefix.nil? ? node.name : "#{node.namespace.prefix}:#{node.name}"
end
qualified_property_name(node) click to toggle source
# File lib/rack-webdav/controller.rb, line 371
def qualified_property_name(node)
  node.namespace.nil? || node.namespace.href == 'DAV:' ? node.name : "{#{node.namespace.href}}#{node.name}"
end
refresh_lock(timeout) click to toggle source
# File lib/rack-webdav/controller.rb, line 531
def refresh_lock(timeout)
  locktoken = request_locktoken('IF')
  raise BadRequest if locktoken.nil?

  timeout, lockscope, locktype, owner = resource.lock(locktoken, timeout)
  unless lockscope && locktype && timeout
    raise Forbidden
  end

  render_lockdiscovery locktoken, lockscope, locktype, timeout, owner
end
render_lock(xml, locktoken, lockscope, locktype, timeout, owner) click to toggle source
# File lib/rack-webdav/controller.rb, line 554
def render_lock(xml, locktoken, lockscope, locktype, timeout, owner)
  xml.activelock do
    xml.lockscope { xml.tag! lockscope }
    xml.locktype { xml.tag! locktype }
    xml.depth 'Infinity'
    if owner
      xml.owner { xml.href owner }
    end
    xml.timeout "Second-#{timeout}"
    xml.locktoken do
      xml.href locktoken
    end
  end
end
render_lockdiscovery(locktoken, lockscope, locktype, timeout, owner) click to toggle source

FIXME add multiple locks support

# File lib/rack-webdav/controller.rb, line 544
def render_lockdiscovery(locktoken, lockscope, locktype, timeout, owner)
  render_xml do |xml|
    xml.prop('xmlns' => "DAV:") do
      xml.lockdiscovery do
        render_lock(xml, locktoken, lockscope, locktype, timeout, owner)
      end
    end
  end
end
render_xml() { |xml| ... } click to toggle source

Creates a new XML document, yields given block and sets the response.body with the final XML content. The response length is updated accordingly.

@return [void]

@yield [xml] Yields the Builder XML instance.

@api internal

# File lib/rack-webdav/controller.rb, line 423
def render_xml
  content = Nokogiri::XML::Builder.new(:encoding => "UTF-8") do |xml|
    yield xml
  end.to_xml
  response.body = [content]
  response["Content-Type"] = 'text/xml; charset=utf-8'
  response["Content-Length"] = content.bytesize.to_s
end
request_document() click to toggle source
# File lib/rack-webdav/controller.rb, line 352
def request_document
  @request_document ||= if (body = request.body.read).empty?
    Nokogiri::XML::Document.new
  else
    Nokogiri::XML(body, &:strict)
  end

rescue Nokogiri::XML::SyntaxError, RuntimeError # Nokogiri raise RuntimeError :-(
  raise BadRequest
end
request_locketag(header) click to toggle source
# File lib/rack-webdav/controller.rb, line 406
def request_locketag(header)
  etag = request.env["HTTP_#{header}"]
  return if etag.nil? || etag.empty?
  etag.scan /\[(.+?)\]/
  return $1
end
request_locktoken(header) click to toggle source
# File lib/rack-webdav/controller.rb, line 399
def request_locktoken(header)
  token = request.env["HTTP_#{header}"]
  return if token.nil? || token.empty?
  token.scan /<(opaquelocktoken:.+?)>/
  return $1
end
request_match(pattern) click to toggle source
# File lib/rack-webdav/controller.rb, line 363
def request_match(pattern)
  request_document.xpath(pattern, 'd' => 'DAV:')
end
request_timeout() click to toggle source

Quick and dirty parsing of the WEBDAV Timeout header. Refuses infinity, rejects anything but Second- timeouts

@return [nil] or [Fixnum]

@api internal

# File lib/rack-webdav/controller.rb, line 390
def request_timeout
  timeout = request.env['HTTP_TIMEOUT']
  return if timeout.nil? || timeout.empty?

  timeout = timeout.split /,\s*/
  timeout.reject! {|t| t !~ /^Second-/}
  timeout.first.sub('Second-', '').to_i
end
resource_class() click to toggle source
# File lib/rack-webdav/controller.rb, line 277
def resource_class
  @options[:resource_class]
end
response_errors(xml, errors) click to toggle source
# File lib/rack-webdav/controller.rb, line 442
def response_errors(xml, errors)
  for path, status in errors
    xml.response do
      xml.href "http://#{host}#{path}"
      xml.status "#{request.env['HTTP_VERSION']} #{status.status_line}"
    end
  end
end
rexml_convert(xml, element) click to toggle source
# File lib/rack-webdav/controller.rb, line 569
def rexml_convert(xml, element)
  if element.elements.empty?
    if element.text
      xml.send(element.name.to_sym, element.text, element.attributes)
    else
      xml.send(element.name.to_sym, element.attributes)
    end
  else
    xml.send(element.name.to_sym, element.attributes) do
      element.elements.each do |child|
        rexml_convert(xml, child)
      end
    end
  end
end
set_properties(resource, nodes) click to toggle source
# File lib/rack-webdav/controller.rb, line 465
def set_properties(resource, nodes)
  stats = Hash.new { |h, k| h[k] = [] }
  for node in nodes
    begin
      map_exceptions do
        stats[OK] << [node, resource.set_property(qualified_property_name(node), node.text)]
      end
    rescue Status
      stats[$!] << node
    end
  end
  stats
end
url_format_for_response(resource) click to toggle source
# File lib/rack-webdav/controller.rb, line 591
def url_format_for_response(resource)
  ret = URI.escape(resource.path)
  if resource.collection? and ret[-1,1] != '/'
    ret += '/'
  end
  ret
end