diff --git a/lib/calligraphy/web_dav_request/copy.rb b/lib/calligraphy/web_dav_request/copy.rb index 0bbf54c..e9d1353 100644 --- a/lib/calligraphy/web_dav_request/copy.rb +++ b/lib/calligraphy/web_dav_request/copy.rb @@ -1,19 +1,24 @@ +# frozen_string_literal: true + module Calligraphy + # Responsible for creating a duplicate of the source resource identified + # by the request to the destination resource identified by the URI in + # the Destination header. class Copy < WebDavRequest - def request + # Executes the WebDAV request for a particular resource. + def execute options = copy_move_options can_copy = @resource.can_copy? options - if can_copy[:ancestor_exist] - return :precondition_failed - else + unless can_copy[:can_copy] + return :precondition_failed if can_copy[:ancestor_exist] return :conflict - end unless can_copy[:can_copy] + end return :locked if can_copy[:locked] overwritten = @resource.copy options - return overwritten ? :no_content : :created + overwritten ? :no_content : :created end private diff --git a/lib/calligraphy/web_dav_request/delete.rb b/lib/calligraphy/web_dav_request/delete.rb index d1455fb..04d51e0 100644 --- a/lib/calligraphy/web_dav_request/delete.rb +++ b/lib/calligraphy/web_dav_request/delete.rb @@ -1,6 +1,10 @@ +# frozen_string_literal: true + module Calligraphy + # Responsible for deleting the resource identified by the request. class Delete < WebDavRequest - def request + # Executes the WebDAV request for a particular resource. + def execute return :locked if @resource.locked_to_user? @headers if @resource.collection? @@ -11,7 +15,7 @@ module Calligraphy return :not_found unless @resource.exists? end - return :no_content + :no_content end end end diff --git a/lib/calligraphy/web_dav_request/get.rb b/lib/calligraphy/web_dav_request/get.rb index b675ffb..a53f9ee 100644 --- a/lib/calligraphy/web_dav_request/get.rb +++ b/lib/calligraphy/web_dav_request/get.rb @@ -1,11 +1,17 @@ +# frozen_string_literal: true + module Calligraphy + # Responsible for retrieving whatever information is identified by the + # request. class Get < WebDavRequest - def request(head: false) + # Executes the WebDAV request for a particular resource. + def execute(head: false) if @resource.readable? return :ok if head - return :ok, @resource.read + + [:ok, @resource.read] else - return :not_found + :not_found end end end diff --git a/lib/calligraphy/web_dav_request/lock.rb b/lib/calligraphy/web_dav_request/lock.rb index 16917ac..f247a4e 100644 --- a/lib/calligraphy/web_dav_request/lock.rb +++ b/lib/calligraphy/web_dav_request/lock.rb @@ -1,45 +1,53 @@ +# frozen_string_literal: true + module Calligraphy + # Responsible for taking out a lock of any access type and refreshing + # existing locks. class Lock < WebDavRequest include Calligraphy::XML::Utils - def request - if @resource.request_body.blank? && !@resource.locked_to_user?(@headers) + attr_reader :resource_exists + + def initialize(headers:, request:, response:, resource:) + super + + # Determine is resource already exists before lock operation. + @resource_exists = @resource.exists? + end + + # Executes the WebDAV request for a particular resource. + def execute + if refresh_lock? lock_properties = @resource.refresh_lock - elsif (@resource.locked? && @resource.lock_is_exclusive?) || - (@resource.locked_to_user?(@headers) && !xml_contains_shared_lock?) + elsif resource_locked? return :locked else - resource_exists_beforehand = @resource.exists? - + # The `lockinfo` tag is used to specify the type of lock the client + # wishes to have created. xml = xml_for body: body, node: 'lockinfo' return :bad_request if xml == :bad_request lock_properties = @resource.lock xml, @headers['Depth'] end - builder = xml_builder - xml_res = builder.lock_res lock_properties - - lock_token = lock_properties[-1] - .select { |x| x.name == 'locktoken' }[0] - .children[0] - .text - - response.headers['Lock-Token'] = "<#{lock_token}>" - set_xml_content_type - - if resource_exists_beforehand - return :ok, xml_res - else - return :created, xml_res - end + build_response lock_properties end private + def refresh_lock? + @resource.request_body.blank? && !@resource.locked_to_user?(@headers) + end + + def resource_locked? + (@resource.locked? && @resource.lock_is_exclusive?) || + (@resource.locked_to_user?(@headers) && !xml_contains_shared_lock?) + end + def xml_contains_shared_lock? lock_type = nil xml = xml_for body: body, node: 'lockinfo' + xml.each do |node| next unless node.is_a? Nokogiri::XML::Element @@ -48,5 +56,33 @@ module Calligraphy lock_type == 'shared' end + + def build_response(lock_properties) + builder = xml_builder + xml_res = builder.lock_res lock_properties + + lock_token = extract_lock_token lock_properties + prepare_response_headers lock_token + + response_status xml_res + end + + def extract_lock_token(properties) + properties[-1] + .select { |x| x.name == 'locktoken' }[0] + .children[0] + .text + end + + def prepare_response_headers(lock_token) + response.headers['Lock-Token'] = "<#{lock_token}>" + + set_xml_content_type + end + + def response_status(xml_res) + return :ok, xml_res if @resource_exists + [:created, xml_res] + end end end diff --git a/lib/calligraphy/web_dav_request/mkcol.rb b/lib/calligraphy/web_dav_request/mkcol.rb index ddb30d8..c1587ba 100644 --- a/lib/calligraphy/web_dav_request/mkcol.rb +++ b/lib/calligraphy/web_dav_request/mkcol.rb @@ -1,6 +1,11 @@ +# frozen_string_literal: true + module Calligraphy + # Responsible for creating a new collection resource at the location + # specified by the request. class Mkcol < WebDavRequest - def request + # Executes the WebDAV request for a particular resource. + def execute return :method_not_allowed if @resource.exists? return :conflict unless @resource.ancestor_exist? return :unsupported_media_type unless @resource.request_body.blank? @@ -8,7 +13,7 @@ module Calligraphy @resource.create_collection set_content_location_header - return :created + :created end private diff --git a/lib/calligraphy/web_dav_request/move.rb b/lib/calligraphy/web_dav_request/move.rb index d58043b..3976d9d 100644 --- a/lib/calligraphy/web_dav_request/move.rb +++ b/lib/calligraphy/web_dav_request/move.rb @@ -1,31 +1,56 @@ +# frozen_string_literal: true + module Calligraphy + # Responsible for copying a resource then deleting the original source. class Move < Copy - def request + # Executes the WebDAV request for a particular resource. + def execute return :locked if @resource.locked_to_user? @headers - options = copy_move_options - if @resource.is_true? options[:overwrite] - to_path = options[:destination].tap { |s| s.slice! @resource.mount_point } - to_resource = @resource.class.new resource: to_path, req: @request, root_dir: @resource.root_dir - - if to_resource.exists? - to_resource.delete_collection - to_resource_existed = true - end + previous_resource_existed = overwrite_destination end - copy_status = super - return copy_status if [:precondition_failed, :conflict].include? copy_status + status = super + return status if %i[precondition_failed conflict].include? status @resource.delete_collection - if copy_status == :created && to_resource_existed - return :no_content - else - response.headers['Location'] = options[:destination] if copy_status == :created - return copy_status + response_status status, previous_resource_existed + end + + private + + def options + copy_move_options + end + + def overwrite_destination + to_path = options[:destination].tap { |s| s.slice! @resource.mount_point } + to_resource = destination_resource to_path + + if to_resource.exists? + to_resource.delete_collection + previous_resource_existed = true end + + previous_resource_existed + end + + def destination_resource(to_path) + @resource.class.new( + resource: to_path, + req: @request, + root_dir: @resource.root_dir + ) + end + + def response_status(status, previous_resource) + return :no_content if status == :created && previous_resource + + response.headers['Location'] = options[:destination] if status == :created + + status end end end diff --git a/lib/calligraphy/web_dav_request/propfind.rb b/lib/calligraphy/web_dav_request/propfind.rb index fdc30a9..8cc2091 100644 --- a/lib/calligraphy/web_dav_request/propfind.rb +++ b/lib/calligraphy/web_dav_request/propfind.rb @@ -1,8 +1,12 @@ +# frozen_string_literal: true + module Calligraphy + # Responsible for retrieving properties defined on the resource. class Propfind < WebDavRequest include Calligraphy::XML::Utils - def request + # Executes the WebDAV request for a particular resource. + def execute xml = xml_for body: body, node: 'propfind' return :bad_request if xml == :bad_request @@ -12,7 +16,8 @@ module Calligraphy xml_res = builder.propfind_res @resource.full_request_path, properties set_xml_content_type - return :multi_status, xml_res + + [:multi_status, xml_res] end end end diff --git a/lib/calligraphy/web_dav_request/proppatch.rb b/lib/calligraphy/web_dav_request/proppatch.rb index 6d7cb67..c8e3a7c 100644 --- a/lib/calligraphy/web_dav_request/proppatch.rb +++ b/lib/calligraphy/web_dav_request/proppatch.rb @@ -1,10 +1,17 @@ +# frozen_string_literal: true + module Calligraphy + # Responsible for processing instructions specified in the request body + # to set and/or remove properties defined on the resource. class Proppatch < WebDavRequest include Calligraphy::XML::Utils - def request + # Executes the WebDAV request for a particular resource. + def execute return :locked if @resource.locked_to_user? @headers + # The `propertyupdate` tag contains the request to alter properties + # on a resource. xml = xml_for body: body, node: 'propertyupdate' return :bad_request if xml == :bad_request @@ -14,7 +21,8 @@ module Calligraphy xml_res = builder.proppatch_res @resource.full_request_path, actions set_xml_content_type - return :multi_status, xml_res + + [:multi_status, xml_res] end end end diff --git a/lib/calligraphy/web_dav_request/put.rb b/lib/calligraphy/web_dav_request/put.rb index 05564e5..a7ab46d 100644 --- a/lib/calligraphy/web_dav_request/put.rb +++ b/lib/calligraphy/web_dav_request/put.rb @@ -1,12 +1,16 @@ +# frozen_string_literal: true + module Calligraphy + # Responsible for replacing the `Get` response entity of the resource. class Put < WebDavRequest - def request + # Executes the WebDAV request for a particular resource. + def execute return :locked if @resource.locked_to_user? @headers return :method_not_allowed if @resource.collection? @resource.write - return :created, @resource.contents + [:created, @resource.contents] end end end diff --git a/lib/calligraphy/web_dav_request/unlock.rb b/lib/calligraphy/web_dav_request/unlock.rb index 6ffc38c..1152fd9 100644 --- a/lib/calligraphy/web_dav_request/unlock.rb +++ b/lib/calligraphy/web_dav_request/unlock.rb @@ -1,6 +1,11 @@ +# frozen_string_literal: true + module Calligraphy + # Responsible for removing the lock identified by the lock token in the + # request header. class Unlock < WebDavRequest - def request + # Executes the WebDAV request for a particular resource. + def execute return :bad_request if @headers['Lock-Token'].nil? @resource.unlock lock_token_header diff --git a/lib/calligraphy/web_dav_request/web_dav_request.rb b/lib/calligraphy/web_dav_request/web_dav_request.rb index 887cda9..e4efd92 100644 --- a/lib/calligraphy/web_dav_request/web_dav_request.rb +++ b/lib/calligraphy/web_dav_request/web_dav_request.rb @@ -1,4 +1,12 @@ +# frozen_string_literal: true + module Calligraphy + # Base class for WebDavRequests. + # + # `WebDavRequest` exposes the `request` method, which is used by the + # `WebDavRequestsController` to execute the WebDAV request for a particular + # resource. This class also provides private methods for accessing the + # request body, setting response headers, and creating XML responses. class WebDavRequest attr_accessor :resource, :response attr_reader :headers, :request @@ -10,7 +18,8 @@ module Calligraphy @resource = resource end - def request + # Executes the WebDAV request for a particular resource. + def execute raise NotImplemented end @@ -25,7 +34,9 @@ module Calligraphy end def xml_builder - Calligraphy::XML::Builder.new server_protocol: @request.env['SERVER_PROTOCOL'] + protocol = @request.env['SERVER_PROTOCOL'] + + Calligraphy::XML::Builder.new server_protocol: protocol end end end