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