From 5b7d67ee575b29dc762670dd9a0b04fcb66a2aff Mon Sep 17 00:00:00 2001 From: Brandon Robins Date: Thu, 28 Dec 2017 20:30:17 -0600 Subject: [PATCH] Rubocop and update files --- .rubocop.yml | 14 + lib/calligraphy.rb | 15 +- lib/calligraphy/rails/mapper.rb | 306 +++++++++++----------- lib/calligraphy/resource/file_resource.rb | 5 +- lib/calligraphy/resource/resource.rb | 141 ++++++++-- lib/calligraphy/utils.rb | 20 +- lib/calligraphy/version.rb | 2 + lib/calligraphy/web_dav_request/move.rb | 2 +- spec/resource/resource_spec.rb | 4 +- 9 files changed, 323 insertions(+), 186 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 8e510ba..afc0240 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -2,3 +2,17 @@ AllCops: TargetRubyVersion: 2.3 Exclude: - 'spec/dummy/**/*' +Metrics/ClassLength: + Exclude: + - 'lib/calligraphy/resource/resource.rb' +Metrics/AbcSize: + Exclude: + - 'lib/calligraphy/rails/mapper.rb' +Metrics/LineLength: + Exclude: + - 'lib/calligraphy/rails/mapper.rb' +Metrics/MethodLength: + Exclude: + - 'lib/calligraphy/rails/mapper.rb' +Style/ClassVars: + Enabled: False diff --git a/lib/calligraphy.rb b/lib/calligraphy.rb index a93aabf..f1ec613 100644 --- a/lib/calligraphy.rb +++ b/lib/calligraphy.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'calligraphy/rails/mapper' require 'calligraphy/rails/web_dav_methods' require 'calligraphy/rails/web_dav_preconditions' @@ -24,6 +26,7 @@ require 'calligraphy/web_dav_request/proppatch' require 'calligraphy/web_dav_request/put' require 'calligraphy/web_dav_request/unlock' +#:nodoc: module Calligraphy # Constants used throughout Calligraphy. DAV_NS = 'DAV:' @@ -39,17 +42,17 @@ module Calligraphy # HTTP methods allowed by the WebDavRequests controller. mattr_accessor :allowed_http_methods - @@allowed_http_methods = %w( + @@allowed_http_methods = %w[ options get put delete copy move mkcol propfind proppatch lock unlock - ) + ] # Proc responsible for returning the user's password, API key, # or HA1 digest hash so that Rails can check user credentials. # Should be overridden to handle your particular application's # user and/or authentication setup. mattr_accessor :digest_password_procedure - @@digest_password_procedure = Proc.new { |username| 'changeme!' } + @@digest_password_procedure = proc { |_username| 'changeme!' } # If Digest Authentication is enabled by default. mattr_accessor :enable_digest_authentication @@ -61,15 +64,15 @@ module Calligraphy # Maximum lock lifetime in seconds. mattr_accessor :lock_timeout_period - @@lock_timeout_period = 86400 + @@lock_timeout_period = 86_400 # The HTTP actions Calligraphy uses to create mappings between WebDAV # HTTP verbs and URLs and WebDAV controller actions. mattr_accessor :web_dav_actions - @@web_dav_actions = %i( + @@web_dav_actions = %i[ options get put delete copy move mkcol propfind proppatch lock unlock - ) + ] # Default way to set up Calligraphy. # Run `rails generate calligraphy:install` to generate a diff --git a/lib/calligraphy/rails/mapper.rb b/lib/calligraphy/rails/mapper.rb index 2ac42c9..e2460a5 100644 --- a/lib/calligraphy/rails/mapper.rb +++ b/lib/calligraphy/rails/mapper.rb @@ -1,166 +1,174 @@ -module ActionDispatch::Routing - class Mapper - module HttpHelpers - # Define a Calligraphy route that only recognizes HTTP COPY. - # copy 'bacon', to: 'food#bacon' - def copy(*args, &block) - args = set_web_dav_args args - map_method :copy, args, &block +# frozen_string_literal: true + +module ActionDispatch + module Routing + class Mapper + #:nodoc: + module HttpHelpers + # Define a Calligraphy route that only recognizes HTTP COPY. + # copy 'bacon', to: 'food#bacon' + def copy(*args, &block) + args = web_dav_args args + map_method :copy, args, &block + end + + # Define a Calligraphy route that only recognizes HTTP HEAD. + # head 'bacon', to: 'food#bacon' + def head(*args, &block) + args = web_dav_args args + map_method :head, args, &block + end + + # Define a Calligraphy route that only recognizes HTTP LOCK. + # lock 'bacon', to: 'food#bacon' + def lock(*args, &block) + args = web_dav_args args + map_method :lock, args, &block + end + + # Define a Calligraphy route that only recognizes HTTP MKCOL. + # mkcol 'bacon', to: 'food#bacon' + def mkcol(*args, &block) + args = web_dav_args args + map_method :mkcol, args, &block + end + + # Define a Calligraphy route that only recognizes HTTP MOVE. + # move 'bacon', to: 'food#bacon' + def move(*args, &block) + args = web_dav_args args + map_method :move, args, &block + end + + # Define a Calligraphy route that only recognizes HTTP OPTIONS. + # options 'bacon', to: 'food#bacon' + def options(*args, &block) + args = web_dav_args args + map_method :options, args, &block + end + + # Define a Calligraphy route that only recognizes HTTP PROPFIND. + # propfind 'bacon', to: 'food#bacon' + def propfind(*args, &block) + args = web_dav_args args + map_method :propfind, args, &block + end + + # Define a Calligraphy route that only recognizes HTTP PROPPATCH. + # proppatch 'bacon', to: 'food#bacon' + def proppatch(*args, &block) + args = web_dav_args args + map_method :proppatch, args, &block + end + + # Define a Calligraphy route that only recognizes HTTP UNLOCK. + # unlock 'bacon', to: 'food#bacon' + def unlock(*args, &block) + args = web_dav_args args + map_method :unlock, args, &block + end + + # Define a Calligraphy route that only recognizes HTTP DELETE. + # web_dav_delete 'broccoli', to: 'food#broccoli' + def web_dav_delete(*args, &block) + args = web_dav_args args + map_method :delete, args, &block + end + + # Define a Calligraphy route that only recognizes HTTP GET. + # web_dav_get 'bacon', to: 'food#bacon' + def web_dav_get(*args, &block) + args = web_dav_args args + map_method :get, args, &block + end + + # Define a Calligraphy route that only recognizes HTTP PUT. + # web_dav_put 'bacon', to: 'food#bacon' + def web_dav_put(*args, &block) + args = web_dav_args args + map_method :put, args, &block + end + + private + + def web_dav_args(args) + options = { + controller: 'calligraphy/rails/web_dav_requests', + action: 'invoke_method' + } + [args[0], options] + end end - # Define a Calligraphy route that only recognizes HTTP HEAD. - # head 'bacon', to: 'food#bacon' - def head(*args, &block) - args = set_web_dav_args args - map_method :head, args, &block - end - - # Define a Calligraphy route that only recognizes HTTP LOCK. - # lock 'bacon', to: 'food#bacon' - def lock(*args, &block) - args = set_web_dav_args args - map_method :lock, args, &block - end - - # Define a Calligraphy route that only recognizes HTTP MKCOL. - # mkcol 'bacon', to: 'food#bacon' - def mkcol(*args, &block) - args = set_web_dav_args args - map_method :mkcol, args, &block - end - - # Define a Calligraphy route that only recognizes HTTP MOVE. - # move 'bacon', to: 'food#bacon' - def move(*args, &block) - args = set_web_dav_args args - map_method :move, args, &block - end - - # Define a Calligraphy route that only recognizes HTTP OPTIONS. - # options 'bacon', to: 'food#bacon' - def options(*args, &block) - args = set_web_dav_args args - map_method :options, args, &block - end - - # Define a Calligraphy route that only recognizes HTTP PROPFIND. - # propfind 'bacon', to: 'food#bacon' - def propfind(*args, &block) - args = set_web_dav_args args - map_method :propfind, args, &block - end - - # Define a Calligraphy route that only recognizes HTTP PROPPATCH. - # proppatch 'bacon', to: 'food#bacon' - def proppatch(*args, &block) - args = set_web_dav_args args - map_method :proppatch, args, &block - end - - # Define a Calligraphy route that only recognizes HTTP UNLOCK. - # unlock 'bacon', to: 'food#bacon' - def unlock(*args, &block) - args = set_web_dav_args args - map_method :unlock, args, &block - end - - # Define a Calligraphy route that only recognizes HTTP DELETE. - # web_dav_delete 'broccoli', to: 'food#broccoli' - def web_dav_delete(*args, &block) - args = set_web_dav_args args - map_method :delete, args, &block - end - - # Define a Calligraphy route that only recognizes HTTP GET. - # web_dav_get 'bacon', to: 'food#bacon' - def web_dav_get(*args, &block) - args = set_web_dav_args args - map_method :get, args, &block - end - - # Define a Calligraphy route that only recognizes HTTP PUT. - # web_dav_put 'bacon', to: 'food#bacon' - def web_dav_put(*args, &block) - args = set_web_dav_args args - map_method :put, args, &block - end - - private - - def set_web_dav_args(args) - options = { - controller: 'calligraphy/rails/web_dav_requests', - action: 'invoke_method' - } - [args[0], options] - end - end - - module Resources - class Resource - def web_dav_actions - if @only - Array(@only).map(&:to_sym) - elsif @except - Calligraphy.web_dav_actions - Array(@except).map(&:to_sym) - else - Calligraphy.web_dav_actions + #:nodoc: + module Resources + #:nodoc: + class Resource + # Returns the available WebDAV HTTP verbs based on Calligraphy + # configuration and Rails routing options. + def web_dav_actions + if @only + Array(@only).map(&:to_sym) + elsif @except + Calligraphy.web_dav_actions - Array(@except).map(&:to_sym) + else + Calligraphy.web_dav_actions + end end end - end - # With Calligraphy, a resourceful route provides mappings between WebDAV - # HTTP verbs and URLs and WebDAV controller actions. A single entry in - # the routing file, such as: - # - # calligraphy_resource :photos - # - # creates eleven different routes in your application, all mapping to the - # WebDavRequests controller: - # - # OPTIONS /photos/*resource - # GET /photos/*resource - # PUT /photos/*resource - # DELETE /photos/*resource - # COPY /photos/*resource - # MOVE /photos/*resource - # MKCOL /photos/*resource - # PROPFIND /photos/*resource - # PROPPATCH /photos/*resource - # LOCK /photos/*resource - # UNLOCK /photos/*resource - def calligraphy_resource(*resources, &block) - options = resources.extract_options!.dup + # With Calligraphy, a resourceful route provides mappings between WebDAV + # HTTP verbs and URLs and WebDAV controller actions. A single entry in + # the routing file, such as: + # + # calligraphy_resource :photos + # + # creates eleven different routes in your application, all mapping to + # the WebDavRequests controller: + # + # OPTIONS /photos/*resource + # GET /photos/*resource + # PUT /photos/*resource + # DELETE /photos/*resource + # COPY /photos/*resource + # MOVE /photos/*resource + # MKCOL /photos/*resource + # PROPFIND /photos/*resource + # PROPPATCH /photos/*resource + # LOCK /photos/*resource + # UNLOCK /photos/*resource + def calligraphy_resource(*resources, &block) + options = resources.extract_options!.dup - if apply_common_behavior_for :calligraphy_resource, resources, options, &block - return self - end + if apply_common_behavior_for :calligraphy_resource, resources, options, &block + return self + end - with_scope_level(:resource) do - options = apply_action_options options - singleton_resoure = ActionDispatch::Routing::Mapper::SingletonResource + with_scope_level(:resource) do + options = apply_action_options options - resource_scope(singleton_resoure.new resources.pop, api_only?, @scope[:shallow], options) do - yield if block_given? + resource_scope(SingletonResource.new(resources.pop, api_only?, @scope[:shallow], options)) do + yield if block_given? - concerns(options[:concerns]) if options[:concerns] + concerns(options[:concerns]) if options[:concerns] - set_mappings_for_web_dav_resources + set_mappings_for_web_dav_resources + end end end - end - private + private - def set_mappings_for_web_dav_resources - parent_resource.web_dav_actions.each do |action| - # Rails already defines GET, PUT, and DELETE actions which we don't - # want to override. Instead, we map WebDAV GET, PUT, and DELETE - # HTTP actions to 'web_dav_' prefixed methods. - if [:get, :put, :delete].include? action - send "web_dav_#{action.to_s}", '*resource' - else - send action, '*resource' + def set_mappings_for_web_dav_resources + parent_resource.web_dav_actions.each do |action| + # Rails already defines GET, PUT, and DELETE actions which we don't + # want to override. Instead, we map WebDAV GET, PUT, and DELETE + # HTTP actions to 'web_dav_' prefixed methods. + if %i[get put delete].include? action + send "web_dav_#{action}", '*resource' + else + send action, '*resource' + end end end end diff --git a/lib/calligraphy/resource/file_resource.rb b/lib/calligraphy/resource/file_resource.rb index d94aef0..724eef4 100644 --- a/lib/calligraphy/resource/file_resource.rb +++ b/lib/calligraphy/resource/file_resource.rb @@ -26,7 +26,7 @@ module Calligraphy def can_copy?(options) copy_options = { can_copy: false, ancestor_exist: false, locked: false } - overwrite = is_true? options[:overwrite] + overwrite = true? options[:overwrite] destination = options[:destination].tap { |s| s.slice! @mount_point } copy_options[:ancestor_exist] = File.exist? parent_path(destination) @@ -62,9 +62,10 @@ module Calligraphy File.directory? @src_path end + # Creates a duplicate of the resource in `options[:destination]`. def copy(options) destination = options[:destination].tap { |s| s.slice! @mount_point } - preserve_existing = is_false? options[:overwrite] + preserve_existing = false? options[:overwrite] to_path = join_paths @root_dir, destination to_path_exists = File.exist? to_path diff --git a/lib/calligraphy/resource/resource.rb b/lib/calligraphy/resource/resource.rb index 5c75214..89e5dc6 100644 --- a/lib/calligraphy/resource/resource.rb +++ b/lib/calligraphy/resource/resource.rb @@ -1,143 +1,248 @@ +# frozen_string_literal: true + module Calligraphy + # Resource base class. + # + # All custom resource classes should be inherited from Resource and should + # implement the relevant methods needed for the desired level of WebDAV + # support. class Resource attr_accessor :client_nonce, :contents, :updated_at - attr_reader :full_request_path, :mount_point, :request_body, :request_path, :root_dir + attr_reader :full_request_path, :mount_point, :request_body, :request_path, + :root_dir def initialize(resource: nil, req: nil, mount: nil, root_dir: nil) @full_request_path = req&.original_url @mount_point = mount || req&.path&.tap { |s| s.slice! resource } @request_body = req&.body&.read || '' @request_path = mount.nil? ? resource : resource.split(mount)[-1] + @root_dir = root_dir end + # Responsible for returning a boolean value indicating if an ancestor + # exists for the resource. + # + # Used in COPY and MKCOL requests. def ancestor_exist? raise NotImplementedError end - def can_copy?(options) + # Responsible for returning a boolean value indicating if the resource + # can be copied. + # + # Used in COPY and MOVE (which inherits from COPY) requests. + def can_copy?(_options) raise NotImplementedError end + # Responsible for returning a boolean value indicating if the resource + # is a collection. + # + # Used in DELETE, MKCOL, MOVE, and PUT requests. def collection? raise NotImplementedError end - def copy(options) + # Responsible for creating a duplicate of the resource in + # `options[:destination]` (see section 9.8 of RFC4918). + # + # Used in COPY and MOVE (which inherits from COPY) requests. + def copy(_options) raise NotImplementedError end + # Responsible for creating a new collection based on the resource (see + # section 9.3 of RFC4918). + # + # Used in MKCOL requests. def create_collection raise NotImplementedError end + # A DAV-compliant resource can advertise several classes of compliance. + # `dav_compliance` is responsible for returning the classes of WebDAV + # compliance that the resource supports (see section 18 of RFC4918). + # + # Used in OPTIONS requests. def dav_compliance '1, 2, 3' end + # Responsible for deleting a resource collection (see section 9.6 of + # RFC4918). + # + # Used in DELETE and MOVE requests. def delete_collection raise NotImplementedError end + # Responsible for returning unique identifier used to create an etag. + # + # Used in precondition validation, as well as GET, HEAD, and PROPFIND + # requests. def etag raise NotImplementedError end + # Responsible for indicating if the resource already exists. + # + # Used in DELETE, LOCK, MKCOL, and MOVE requests. def exists? raise NotImplementedError end - def lock(nodes, depth='infinity') + # Responsible for creating a lock on the resource (see section 9.10 of + # RFC4918). + # + # Used in LOCK requests. + def lock(_nodes, _depth = 'infinity') raise NotImplementedError end + # Responsible for indicating if a resource lock is exclusive. + # + # Used in LOCK requests. def lock_is_exclusive? raise NotImplementedError end - def lock_tokens - raise NotImplementedError - end - + # Responsible for indicating if a resource is current locked. + # + # Used in LOCK requests. def locked? raise NotImplementedError end - def locked_to_user?(headers=nil) + # Responsible for indicating if a resource is locked to the current user. + # + # Used in DELETE, LOCK, MOVE, PROPPATCH, and PUT requests. + def locked_to_user?(_headers = nil) raise NotImplementedError end - def propfind(nodes) + # Responsible for handling the retrieval of properties defined on the + # resource (see section 9.1 of RFC4918). + # + # Used in PROPFIND requests. + def propfind(_nodes) raise NotImplementedError end - def proppatch(nodes) + # Responsible for handling the addition and/or removal of properties + # defined on the resource through a PROPPATCH request (see section 9.2 of + # RFC4918). + # + # Used in PROPPATCH requests. + def proppatch(_nodes) raise NotImplementedError end + # Responsible for setting and returning the contents of a resource + # if it is readable (see section 9.4 of RFC4918). + # + # Used in GET requests. def read raise NotImplementedError end + # Responsible for indicating if a resource is readable. + # + # Used in GET and HEAD requests. def readable? exists? && !collection? end + # Responsible for refreshing locks (see section 9.10.2 of RFC4918). + # + # Used in LOCK requests. def refresh_lock raise NotImplementedError end - def unlock(token) + # Responsible for unlocking a resource lock (see section 9.11 of RFC4918). + # + # Used in UNLOCK requests. + def unlock(_token) raise NotImplementedError end - def write(contents=@request_body.to_s) + # Responsible for writing contents to a resource (see section 9.7 of + # RFC4918). + # + # Used in PUT requests. + def write(_contents = @request_body.to_s) raise NotImplementedError end private + # DAV property which can be retrieved by a PROPFIND request. `creationdate` + # records the time and date the resource was created (see section 15.1 of + # RFC4918). def creationdate raise NotImplementedError end + # DAV property which can be retrieved by a PROPFIND request. `displayname` + # returns a name for the resource that is suitable for presentation to the + # user (see section 15.2 of RFC4918). def displayname raise NotImplementedError end + # DAV property which can be retrieved by a PROPFIND request. + # `getcontentlanguage` returns the Content-Language header value (see + # section 15.3 of RFC4918). def getcontentlanguage raise NotImplementedError end + # DAV property which can be retrieved by a PROPFIND request. + # `getcontentlength` returns the Content-Length header value (see section + # 15.4 of RFC4918). def getcontentlength raise NotImplementedError end + # DAV property which can be retrieved by a PROPFIND request. + # `getcontenttype` returns the Content-Type header value (see section + # 15.5 of RFC4918). def getcontenttype raise NotImplementedError end + # DAV property which can be retrieved by a PROPFIND request. + # `getetag` returns the ETag header value (see section 15.6 of RFC4918). def getetag raise NotImplementedError end + # DAV property which can be retrieved by a PROPFIND request. + # `getlastmodified` returns the Last-Modified header value (see section + # 15.7 of RFC4918). def getlastmodified raise NotImplementedError end + # DAV property which can be retrieved by a PROPFIND request. + # `lockdiscovery` describes the active locks on a resource (see section + # 15.8 of RFC4918). def lockdiscovery raise NotImplementedError end + # DAV property which can be retrieved by a PROPFIND request. + # `resourcetype` specifies the nature of the resource (see section 15.9 of + # RFC4918). def resourcetype raise NotImplementedError end + # DAV property which can be retrieved by a PROPFIND request. + # `supportedlock` provides a listing of the lock capabilities supported by + # the resource (see section 15.10 of RFC4918). def supportedlock raise NotImplementedError end - - def get_custom_property(prop) - raise NotImplementedError - end - end + end end diff --git a/lib/calligraphy/utils.rb b/lib/calligraphy/utils.rb index 757160b..827367f 100644 --- a/lib/calligraphy/utils.rb +++ b/lib/calligraphy/utils.rb @@ -1,13 +1,16 @@ -module Calligraphy - module Utils - TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE'] - FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE'] +# frozen_string_literal: true - def is_true?(val) +module Calligraphy + # Miscellaneous convenience methods. + module Utils + TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE'].freeze + FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE'].freeze + + def true?(val) TRUE_VALUES.include? val end - def is_false?(val) + def false?(val) FALSE_VALUES.include? val end @@ -26,13 +29,14 @@ module Calligraphy def map_array_of_hashes(arr_hashes) [].tap do |output_array| arr_hashes.each do |hash| - output_array.push hash.map { |k, v| v } + output_array.push(hash.map { |_k, v| v }) end end end def extract_lock_token(if_header) - if_header.scan(Calligraphy::LOCK_TOKEN_REGEX)&.flatten[0] + token = if_header.scan(Calligraphy::LOCK_TOKEN_REGEX) + token.flatten.first if token.is_a? Array end def lockentry_hash(scope, type) diff --git a/lib/calligraphy/version.rb b/lib/calligraphy/version.rb index 5a39f5c..c51aa89 100644 --- a/lib/calligraphy/version.rb +++ b/lib/calligraphy/version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Calligraphy VERSION = '0.2.1' end diff --git a/lib/calligraphy/web_dav_request/move.rb b/lib/calligraphy/web_dav_request/move.rb index 3976d9d..9e848f3 100644 --- a/lib/calligraphy/web_dav_request/move.rb +++ b/lib/calligraphy/web_dav_request/move.rb @@ -7,7 +7,7 @@ module Calligraphy def execute return :locked if @resource.locked_to_user? @headers - if @resource.is_true? options[:overwrite] + if @resource.true? options[:overwrite] previous_resource_existed = overwrite_destination end diff --git a/spec/resource/resource_spec.rb b/spec/resource/resource_spec.rb index 092ba2b..7aab791 100644 --- a/spec/resource/resource_spec.rb +++ b/spec/resource/resource_spec.rb @@ -4,12 +4,12 @@ RSpec.describe 'Resource' do context 'base method' do resource_methods_without_inputs = %w( ancestor_exist? collection? create_collection delete_collection etag - exists? lock_is_exclusive? lock_tokens locked? read readable? refresh_lock + exists? lock_is_exclusive? locked? read readable? refresh_lock creationdate displayname getcontentlanguage getcontentlength getcontenttype getetag getlastmodified lockdiscovery resourcetype supportedlock ) resource_methods_with_inputs = %w( - can_copy? copy lock locked_to_user? propfind proppatch unlock write get_custom_property + can_copy? copy lock locked_to_user? propfind proppatch unlock write ) resource_methods_without_inputs.each do |method|