class Railsdav::Renderer

Attributes

depth[RW]

Public Class Methods

new(controller) click to toggle source

Create a new Renderer instance that will render an XML multistatus response.

Arguments:

controller: the current controller instance
# File lib/railsdav/renderer.rb, line 35
    def initialize(controller)
      @controller = controller
      @request    = controller.request
#       @depth      = (@request.headers['Depth'].to_i > 0) ? 1 : 0 # depth "infinite" is not yet supported
      @depth      = (@request.headers['Depth'].to_i)
      Rails.logger.debug "Depth:#{@request.headers['Depth']}"
    end
webdav_metadata_for_url(routing_data) click to toggle source

Return the webdav metadata for a given URL/path

If the given route is recognized, a controller and action can be identified. To get the properties for that URI, we lookup the metadata specified in the target controller (see enable_webdav_for)

# File lib/railsdav/renderer.rb, line 23
def self.webdav_metadata_for_url(routing_data)
  controller = "#{routing_data[:controller]}_controller".camelize.constantize
  controller ? controller.webdav_metadata_for_action(routing_data[:action]) : nil
end

Public Instance Methods

respond_with(response_type, options = {}) click to toggle source

Render the requested response_type.

Arguments:

response_type: currently either :propstat or :response.
options:
  - for response: :href, :status, :error (see #response)
  - for propstat: :size, :format, :created_at, :updated_at, :resource_layout, ... (see #propstat)
# File lib/railsdav/renderer.rb, line 50
def respond_with(response_type, options = {})
  self.send response_type, options
end

Protected Instance Methods

not_found(options = {}) click to toggle source

Allows you to `render :webdav => :not_found` and get a 404 status properly embedded inside multi-status

# File lib/railsdav/renderer.rb, line 107
def not_found(options = {})
  render do |dav|
    status_for 404
  end
end
propstat(options = {}) click to toggle source

Render a WebDAV multistatus response with a “response” element per resource TODO: explain the magic implemented here Arguments:

TODO: describe valid arguments
# File lib/railsdav/renderer.rb, line 118
def propstat(options = {})
  response_collector = ResponseCollector.new(@controller, self.request_format)
  # retrieve properties for this resource and all subresources if this is a collection
  if options[:respond_to_block]
    options[:respond_to_block].call(response_collector)
  end

  render do |dav|
    propstat_for response_collector.resource
    propstat_for response_collector.subresources if @depth >= 0
  end
end
render() { |dav| ... } click to toggle source
# File lib/railsdav/renderer.rb, line 68
def render
  @dav = Builder::XmlMarkup.new :indent => 2
  @dav.instruct!
  @dav.tag!("D:multistatus", "xmlns:D" => 'DAV:') do
    yield @dav
  end
  @dav.target!
end
request_format() click to toggle source
# File lib/railsdav/renderer.rb, line 56
def request_format
  if @controller.params[:format].blank?
    if @controller.webdav_metadata_for_current_action[:collection]
      return :collection
    else
      return @controller.request_format
    end
  else
    return @controller.params[:format]
  end
end
response(options = {}) click to toggle source

Render a WebDAV multistatus response with a single “response” element. This is primarily intended vor responding to single resource errors.

Arguments:

options:
  - href: the requested resource URL, usually request.url
  - status: the response status, something like :unprocessable_entity or 204.
  - error: an Error description, if any.
# File lib/railsdav/renderer.rb, line 86
def response(options = {})
  elements = options.slice(:error)

  render do
    response_for options[:href] do |dav|
      elements.each do |name, value|
        status_for options[:status]
        dav.__send__ name, value
      end
    end
  end
end
status_for(status) click to toggle source
# File lib/railsdav/renderer.rb, line 99
def status_for(status)
  code   = Rack::Utils.status_code(status || :ok)
  status = "HTTP/1.1 #{code} #{Rack::Utils::HTTP_STATUS_CODES[code]}"
  @dav.tag! "D:status", status
end

Private Instance Methods

propstat_for(*resources) click to toggle source
# File lib/railsdav/renderer.rb, line 133
def propstat_for(*resources)
  params = @controller.params.dup
  if params[:propfind]
    # OK
  elsif @controller.request.body.size > 0 # rails version without automatic XML body params parsing, so do it by hand here:
    @controller.request.body.rewind
    params.merge! Hash.from_xml(@controller.request.body.read)
  end

  params[:propfind] ||= {:prop => []}

  if params[:propfind].respond_to? :to_unsafe_h
    propfind_params = params[:propfind].to_unsafe_h
  else
    propfind_params = params[:propfind]
  end

  if propfind_params.has_key? 'allprop'
    requested_properties = nil # fill it later, see below.
  else
    requested_properties = propfind_params[:prop]
  end

  resources.flatten.each do |resource|
    hash = resource.props

    case hash[:updated_at]
    when Time, DateTime
      updated_at = hash[:updated_at]
    when String
      updated_at = Time.parse(hash[:updated_at])
    else
      updated_at = Time.now
    end

    # note: all of these are assumed to be from the DAV:
    # namespace!
    response_hash = {
      :quota_used_bytes      => 0,
      :quota_available_bytes => 10.gigabytes,
      :creationdate          => updated_at.iso8601,
      :getlastmodified       => updated_at.rfc2822,
      :getcontentlength      => hash[:size],
      :getcontenttype        => hash[:format].to_s
    }

    if resource.collection?
      response_hash[:resourcetype]   = proc { @dav.tag! "D:collection" } # must be block to render <collection></collection> instead of "collection"
      response_hash[:getcontenttype] = nil
    end

    requested_properties ||= response_hash.keys

    # as a workaround for another bug in Flycode's WebDAV module,
    # which expects (because of how it does element name matching)
    # that the properties are always returned with a specified
    # namespace prefix.  Now, the original Railsdav makes a
    # slightly careless assumption of its own; it just directly
    # matches element names in its fixed list, counting on (and
    # there are none so far) collisions between property names
    # from various name spaces.  As such, it just assumes that if
    # the property name is discrimated by a namespace, the client
    # has only done so by putting a non-prefixed `xmlns` attribute
    # on the propfind properties (ie., it did not try to use a
    # prefix itself).  Railsdav really should track and match
    # properties by both name *and* namespace, in order to be
    # properly compliant.

    # So, back to the Flycode WebDAV bug: we work around this here
    # by collecting all of the requested properties, gathering the
    # namespaces from them, and defining them *all* as prefixes.

    # all of our built-in properties are in the DAV namespace
    # anyway:
    gathered_namespaces = {}
    @namespace_counter ||= 1

    namespaced_requested_properties = {}

    Rails.logger.debug requested_properties.inspect
    requested_properties.each do |prop_name, opts|
      if prop_name.to_s.include?(':')
        # see first paragraph of long comment above
        Rails.logger.warn 'DAV propfind prop request contains a namespace prefix; we do NOT handle these properly yet!'
      end

      if opts and opts['xmlns']
        gathered_namespaces["xmlns:lp#{@namespace_counter}"] = opts['xmlns']
        opts.delete 'xmlns'
        namespaced_requested_properties["lp#{@namespace_counter}:#{prop_name}"] = opts
        @namespace_counter +=1
      else
        # just copy it through; however, we'll assume it's in the
        # DAV namespace (which we hardcoded to the `D` prefix).
        namespaced_requested_properties["D:#{prop_name}"] = opts
      end
    end

    response_for(resource.url) do |dav|
      dav.tag! 'D:propstat', gathered_namespaces do
        status_for hash[:status]
        dav.tag! 'D:prop' do
          namespaced_requested_properties.each do |both_prop_name, opts|
            prop_space_and_name_pair = both_prop_name.split(':')
            prop_name = prop_space_and_name_pair[1] || prop_space_and_name_pair[0]
            if prop_val = response_hash[prop_name.to_sym]
              if prop_val.respond_to? :call
                if opts
                  dav.tag! both_prop_name, opts.to_h, &prop_val
                else
                  dav.tag! both_prop_name, &prop_val
                end
              else
                dav.tag! both_prop_name, prop_val, opts.to_h
              end
            elsif opts
              dav.tag! both_prop_name, opts
            else
              dav.tag! both_prop_name
            end
          end # requested_properties.each
        end # dav.prop
      end # dav.propstat
    end # dav.response_for
  end # resource_layout.keys.each
end
response_for(href) { |dav| ... } click to toggle source
# File lib/railsdav/renderer.rb, line 260
def response_for(href)
  @dav.tag! 'D:response' do
    @dav.tag! 'D:href', href
    yield @dav
  end
end