Add support for Extended MKCOL (RFC5689)
This commit is contained in:
committed by
Brandon Robins
parent
46ff7a934f
commit
3b65768e40
@@ -1,9 +1,10 @@
|
|||||||
AllCops:
|
AllCops:
|
||||||
TargetRubyVersion: 2.3
|
TargetRubyVersion: 2.3
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'spec/**/*'
|
- 'spec/dummy/**/*'
|
||||||
Metrics/BlockLength:
|
Metrics/BlockLength:
|
||||||
Exclude:
|
Exclude:
|
||||||
|
- 'spec/**/*'
|
||||||
- 'Rakefile'
|
- 'Rakefile'
|
||||||
Metrics/ClassLength:
|
Metrics/ClassLength:
|
||||||
Exclude:
|
Exclude:
|
||||||
@@ -15,6 +16,8 @@ Metrics/AbcSize:
|
|||||||
Metrics/LineLength:
|
Metrics/LineLength:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'lib/calligraphy/rails/mapper.rb'
|
- 'lib/calligraphy/rails/mapper.rb'
|
||||||
|
- 'spec/spec_helper.rb'
|
||||||
|
- 'spec/rails_helper.rb'
|
||||||
Metrics/MethodLength:
|
Metrics/MethodLength:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'lib/calligraphy/rails/mapper.rb'
|
- 'lib/calligraphy/rails/mapper.rb'
|
||||||
|
|||||||
@@ -44,7 +44,12 @@ module Calligraphy
|
|||||||
end
|
end
|
||||||
|
|
||||||
def mkcol
|
def mkcol
|
||||||
Calligraphy::Mkcol.new(web_dav_request).execute
|
mkcol_request = Calligraphy::Mkcol.new(web_dav_request)
|
||||||
|
|
||||||
|
precondition_response = mkcol_request.preconditions
|
||||||
|
return precondition_response unless precondition_response.nil?
|
||||||
|
|
||||||
|
mkcol_request.execute
|
||||||
end
|
end
|
||||||
|
|
||||||
def propfind
|
def propfind
|
||||||
|
|||||||
@@ -44,6 +44,23 @@ module Calligraphy
|
|||||||
File.directory? @src_path
|
File.directory? @src_path
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
destination = copy_destination options
|
||||||
|
to_path = join_paths @root_dir, destination
|
||||||
|
to_path_exists = File.exist? to_path
|
||||||
|
|
||||||
|
preserve_existing = false? options[:overwrite]
|
||||||
|
|
||||||
|
copy_resource_to_path to_path, preserve_existing
|
||||||
|
copy_pstore_to_path to_path, preserve_existing
|
||||||
|
|
||||||
|
to_path_exists
|
||||||
|
end
|
||||||
|
|
||||||
# Responsible for returning a hash with keys indicating if the resource
|
# Responsible for returning a hash with keys indicating if the resource
|
||||||
# can be copied, if an ancestor exists, or if the copy destinatin is
|
# can be copied, if an ancestor exists, or if the copy destinatin is
|
||||||
# locked.
|
# locked.
|
||||||
@@ -65,23 +82,6 @@ module Calligraphy
|
|||||||
copy_options
|
copy_options
|
||||||
end
|
end
|
||||||
|
|
||||||
# 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)
|
|
||||||
destination = copy_destination options
|
|
||||||
to_path = join_paths @root_dir, destination
|
|
||||||
to_path_exists = File.exist? to_path
|
|
||||||
|
|
||||||
preserve_existing = false? options[:overwrite]
|
|
||||||
|
|
||||||
copy_resource_to_path to_path, preserve_existing
|
|
||||||
copy_pstore_to_path to_path, preserve_existing
|
|
||||||
|
|
||||||
to_path_exists
|
|
||||||
end
|
|
||||||
|
|
||||||
# Responsible for creating a new collection based on the resource (see
|
# Responsible for creating a new collection based on the resource (see
|
||||||
# section 9.3 of RFC4918).
|
# section 9.3 of RFC4918).
|
||||||
#
|
#
|
||||||
@@ -99,6 +99,12 @@ module Calligraphy
|
|||||||
FileUtils.rm_r @store_path if store_exist?
|
FileUtils.rm_r @store_path if store_exist?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Responsible for returning a boolean indicating whether the resource
|
||||||
|
# supports Extended MKCOL (see RFC5689).
|
||||||
|
def enable_extended_mkcol?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
# Responsible for returning unique identifier used to create an etag.
|
# Responsible for returning unique identifier used to create an etag.
|
||||||
#
|
#
|
||||||
# Used in precondition validation, as well as GET, HEAD, and PROPFIND
|
# Used in precondition validation, as well as GET, HEAD, and PROPFIND
|
||||||
@@ -185,6 +191,8 @@ module Calligraphy
|
|||||||
#
|
#
|
||||||
# Used in PROPPATCH requests.
|
# Used in PROPPATCH requests.
|
||||||
def proppatch(nodes)
|
def proppatch(nodes)
|
||||||
|
init_pstore unless exists?
|
||||||
|
|
||||||
actions = { set: [], remove: [] }
|
actions = { set: [], remove: [] }
|
||||||
|
|
||||||
@store.transaction do
|
@store.transaction do
|
||||||
@@ -584,6 +592,8 @@ module Calligraphy
|
|||||||
def add_properties(node, actions)
|
def add_properties(node, actions)
|
||||||
node.children.each do |prop|
|
node.children.each do |prop|
|
||||||
prop.children.each do |property|
|
prop.children.each do |property|
|
||||||
|
next unless node.is_a? Nokogiri::XML::Element
|
||||||
|
|
||||||
node = Calligraphy::XML::Node.new property
|
node = Calligraphy::XML::Node.new property
|
||||||
prop_sym = property.name.to_sym
|
prop_sym = property.name.to_sym
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,14 @@ module Calligraphy
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# 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 returning a hash with keys indicating if the resource
|
# Responsible for returning a hash with keys indicating if the resource
|
||||||
# can be copied, if an ancestor exists, or if the copy destinatin is
|
# can be copied, if an ancestor exists, or if the copy destinatin is
|
||||||
# locked.
|
# locked.
|
||||||
@@ -48,14 +56,6 @@ module Calligraphy
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
end
|
end
|
||||||
|
|
||||||
# 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
|
# Responsible for creating a new collection based on the resource (see
|
||||||
# section 9.3 of RFC4918).
|
# section 9.3 of RFC4918).
|
||||||
#
|
#
|
||||||
@@ -70,7 +70,10 @@ module Calligraphy
|
|||||||
#
|
#
|
||||||
# Used in OPTIONS requests.
|
# Used in OPTIONS requests.
|
||||||
def dav_compliance
|
def dav_compliance
|
||||||
'1, 2, 3'
|
compliance_classes = %w[1 2 3]
|
||||||
|
compliance_classes.push 'extended-mkcol' if enable_extended_mkcol?
|
||||||
|
|
||||||
|
compliance_classes.join ', '
|
||||||
end
|
end
|
||||||
|
|
||||||
# Responsible for deleting a resource collection (see section 9.6 of
|
# Responsible for deleting a resource collection (see section 9.6 of
|
||||||
@@ -81,6 +84,12 @@ module Calligraphy
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Responsible for returning a boolean indicating whether the resource
|
||||||
|
# supports Extended MKCOL (see RFC5689).
|
||||||
|
def enable_extended_mkcol?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
# Responsible for returning unique identifier used to create an etag.
|
# Responsible for returning unique identifier used to create an etag.
|
||||||
#
|
#
|
||||||
# Used in precondition validation, as well as GET, HEAD, and PROPFIND
|
# Used in precondition validation, as well as GET, HEAD, and PROPFIND
|
||||||
@@ -179,6 +188,16 @@ module Calligraphy
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Responsible for declaring the valid `resourcetypes` for a resource. If
|
||||||
|
# an extended MKCOL request is made using an invalid `resourcetype` the
|
||||||
|
# request will fail with a 403 (Forbidden) and will return an XML response
|
||||||
|
# with the `mkcol-response` element (see section 3.3 and 3.5 of RFC5689).
|
||||||
|
#
|
||||||
|
# Used in Extended MKCOL requests.
|
||||||
|
def valid_resourcetypes
|
||||||
|
%w[collection]
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# DAV property which can be retrieved by a PROPFIND request. `creationdate`
|
# DAV property which can be retrieved by a PROPFIND request. `creationdate`
|
||||||
|
|||||||
@@ -4,20 +4,90 @@ module Calligraphy
|
|||||||
# Responsible for creating a new collection resource at the location
|
# Responsible for creating a new collection resource at the location
|
||||||
# specified by the request.
|
# specified by the request.
|
||||||
class Mkcol < WebDavRequest
|
class Mkcol < WebDavRequest
|
||||||
|
include Calligraphy::XML::Utils
|
||||||
|
|
||||||
|
# Responsible for evaluating preconditions for the WebDAV request.
|
||||||
|
def preconditions
|
||||||
|
return :unsupported_media_type unless validate_request_body
|
||||||
|
return [:forbidden, mkcol_response] unless validate_resourcetypes
|
||||||
|
end
|
||||||
|
|
||||||
# Executes the WebDAV request for a particular resource.
|
# Executes the WebDAV request for a particular resource.
|
||||||
def execute
|
def execute
|
||||||
return :method_not_allowed if @resource.exists?
|
return :method_not_allowed if @resource.exists?
|
||||||
return :conflict unless @resource.ancestor_exist?
|
return :conflict unless @resource.ancestor_exist?
|
||||||
return :unsupported_media_type unless @resource.request_body.blank?
|
|
||||||
|
xml = @resource.enable_extended_mkcol? ? extended_mkcol_xml : nil
|
||||||
|
|
||||||
@resource.create_collection
|
@resource.create_collection
|
||||||
set_content_location_header
|
|
||||||
|
post_mkcol_actions xml
|
||||||
|
|
||||||
:created
|
:created
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def validate_request_body
|
||||||
|
xml = @resource.enable_extended_mkcol? ? extended_mkcol_xml : nil
|
||||||
|
|
||||||
|
if xml == :bad_request
|
||||||
|
false
|
||||||
|
elsif @resource.enable_extended_mkcol?
|
||||||
|
true
|
||||||
|
else
|
||||||
|
@resource.request_body.blank? ? false : true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_resourcetypes
|
||||||
|
return true if body.blank?
|
||||||
|
|
||||||
|
xml = search_xml_for(body: body, search: 'resourcetype').first
|
||||||
|
resourcetypes = xml.children.map do |node|
|
||||||
|
next unless node.is_a? Nokogiri::XML::Element
|
||||||
|
|
||||||
|
node.name
|
||||||
|
end.compact
|
||||||
|
|
||||||
|
resourcetypes.each do |rt|
|
||||||
|
return false unless @resource.valid_resourcetypes.include? rt
|
||||||
|
end
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def mkcol_response
|
||||||
|
xml_builder.mkcol_response prepare_mkcol_response_xml
|
||||||
|
end
|
||||||
|
|
||||||
|
def prepare_mkcol_response_xml
|
||||||
|
nodes = search_xml_for(body: body, search: 'prop').first.children
|
||||||
|
|
||||||
|
separate_nodes_by_name nodes, 'resourcetype'
|
||||||
|
end
|
||||||
|
|
||||||
|
def extended_mkcol_xml
|
||||||
|
return nil if body.blank?
|
||||||
|
|
||||||
|
# The `mkcol` tag specifies properties to be set in an extended MKCOL
|
||||||
|
# request, as well as any additional information needed when creating
|
||||||
|
# the resource.
|
||||||
|
xml_for body: body, node: 'mkcol'
|
||||||
|
end
|
||||||
|
|
||||||
|
def post_mkcol_actions(xml)
|
||||||
|
apply_extended_mkcol_properties xml
|
||||||
|
|
||||||
|
set_content_location_header
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_extended_mkcol_properties(xml)
|
||||||
|
return nil if xml.nil?
|
||||||
|
|
||||||
|
@resource.proppatch xml
|
||||||
|
end
|
||||||
|
|
||||||
def set_content_location_header
|
def set_content_location_header
|
||||||
@response.headers['Content-Location'] = @resource.full_request_path
|
@response.headers['Content-Location'] = @resource.full_request_path
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ module Calligraphy
|
|||||||
@resource = resource
|
@resource = resource
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Responsible for evaluating preconditions for the WebDAV request.
|
||||||
|
def preconditions
|
||||||
|
raise NotImplemented
|
||||||
|
end
|
||||||
|
|
||||||
# Executes the WebDAV request for a particular resource.
|
# Executes the WebDAV request for a particular resource.
|
||||||
def execute
|
def execute
|
||||||
raise NotImplemented
|
raise NotImplemented
|
||||||
@@ -35,7 +40,7 @@ module Calligraphy
|
|||||||
end
|
end
|
||||||
|
|
||||||
def xml_builder
|
def xml_builder
|
||||||
protocol = @request.env['SERVER_PROTOCOL']
|
protocol = @request.env['SERVER_PROTOCOL'] || 'HTTP/1.1'
|
||||||
|
|
||||||
Calligraphy::XML::Builder.new server_protocol: protocol
|
Calligraphy::XML::Builder.new server_protocol: protocol
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -86,17 +86,17 @@ module Calligraphy
|
|||||||
end
|
end
|
||||||
|
|
||||||
def prop(xml, property_set)
|
def prop(xml, property_set)
|
||||||
xml[@dav_ns].prop do
|
xml[@dav_ns].prop { iterate_and_drilldown xml, property_set }
|
||||||
iterate_and_drilldown xml, property_set
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def propstat(xml, property_set, status = :ok)
|
def propstat(xml, property_set, status, error_tag: nil, description: nil)
|
||||||
return if property_set.empty?
|
return if property_set.empty?
|
||||||
|
|
||||||
xml[@dav_ns].propstat do
|
xml[@dav_ns].propstat do
|
||||||
prop xml, property_set
|
prop xml, property_set
|
||||||
status xml, status
|
status xml, status
|
||||||
|
error xml, error_tag unless error_tag.nil?
|
||||||
|
responsedescription xml, description unless description.nil?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -113,6 +113,16 @@ module Calligraphy
|
|||||||
Rack::Utils::HTTP_STATUS_CODES[status_code]
|
Rack::Utils::HTTP_STATUS_CODES[status_code]
|
||||||
].join ' '
|
].join ' '
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def error(xml, error)
|
||||||
|
xml.error { self_closing_tag xml, error }
|
||||||
|
end
|
||||||
|
|
||||||
|
def responsedescription(xml, description)
|
||||||
|
xml.responsedescription do
|
||||||
|
xml.text description
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,25 +4,50 @@ module Calligraphy
|
|||||||
module XML
|
module XML
|
||||||
# Miscellaneous XML convenience methods.
|
# Miscellaneous XML convenience methods.
|
||||||
module Utils
|
module Utils
|
||||||
# Returns the XML for a given XML body and node/CSS selector.
|
# Returns the inner XML for a given XML body and node/CSS selector.
|
||||||
def xml_for(body:, node:)
|
def xml_for(body:, node:)
|
||||||
xml = Nokogiri::XML body
|
xml = Nokogiri::XML body
|
||||||
return :bad_request unless xml.errors.empty?
|
return :bad_request unless xml.errors.empty?
|
||||||
|
|
||||||
namespace = nil
|
namespace = dav_namespace xml
|
||||||
xml.root.namespace_definitions.each do |n|
|
|
||||||
namespace = "#{n.prefix}|" if dav_namespace n
|
xml.css("dav|#{node}", 'dav': namespace).children
|
||||||
end
|
end
|
||||||
|
|
||||||
node = node.split(' ').map! { |n| namespace + n }.join(' ') if namespace
|
# Searches XML body for a given node/CSS selector and returns that
|
||||||
|
# node/CSS selector.
|
||||||
|
def search_xml_for(body:, search:)
|
||||||
|
xml = Nokogiri::XML body
|
||||||
|
|
||||||
xml.css(node).children
|
[].tap do |results|
|
||||||
|
xml.namespaces.each_value do |v|
|
||||||
|
results << xml.css("cs|#{search}", 'cs': v)
|
||||||
|
end
|
||||||
|
end.flatten
|
||||||
|
end
|
||||||
|
|
||||||
|
# Iterates through top level nodes, finds node names that match and
|
||||||
|
# separates matching nodes from non-matching nodes.
|
||||||
|
def separate_nodes_by_name(nodes, match_name)
|
||||||
|
{ found: [], not_found: [] }.tap do |property|
|
||||||
|
nodes.each do |node|
|
||||||
|
next unless node.is_a? Nokogiri::XML::Element
|
||||||
|
|
||||||
|
if node.name == match_name
|
||||||
|
property[:found].push Calligraphy::XML::Node.new node
|
||||||
|
else
|
||||||
|
property[:not_found].push Calligraphy::XML::Node.new node
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def dav_namespace(namespace)
|
def dav_namespace(xml)
|
||||||
namespace&.href == Calligraphy::DAV_NS && !namespace.prefix.nil?
|
xml.namespaces.each_value do |v|
|
||||||
|
return v if v == Calligraphy::DAV_NS
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ module Calligraphy
|
|||||||
activelock allprop collection creationdate depth displayname error
|
activelock allprop collection creationdate depth displayname error
|
||||||
exclusive getcontentlanguage getcontentlength getcontenttype getetag
|
exclusive getcontentlanguage getcontentlength getcontenttype getetag
|
||||||
getlastmodified href include location lockdiscovery lockentry lockinfo
|
getlastmodified href include location lockdiscovery lockentry lockinfo
|
||||||
lockroot lockscope locktoken locktype multistatus owner prop
|
lockroot lockscope locktoken locktype mkcol-response multistatus owner
|
||||||
propertyupdate propfind propname propstat remove response
|
prop propertyupdate propfind propname propstat remove response
|
||||||
responsedescription resourcetype set shared status supportedlock
|
responsedescription resourcetype set shared status supportedlock
|
||||||
timeout write
|
timeout valid-resourcetype write
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
DAV_NS_METHODS = %w[resourcetype supportedlock timeout].freeze
|
DAV_NS_METHODS = %w[resourcetype supportedlock timeout].freeze
|
||||||
@@ -29,6 +29,21 @@ module Calligraphy
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Build an XML response for a failed MKCOL request.
|
||||||
|
def mkcol_response(properties)
|
||||||
|
description = 'Resource type is not supported by this server'
|
||||||
|
error = 'valid-resourcetype'
|
||||||
|
|
||||||
|
build 'mkcol-response' do |xml|
|
||||||
|
propstat(xml, properties[:found],
|
||||||
|
:forbidden, error_tag: error, description: description)
|
||||||
|
|
||||||
|
if properties[:not_found].length.positive?
|
||||||
|
propstat xml, properties[:not_found], :failed_dependency
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Build an XML response for a PROPFIND request.
|
# Build an XML response for a PROPFIND request.
|
||||||
def propfind_response(path, properties)
|
def propfind_response(path, properties)
|
||||||
multistatus do |xml|
|
multistatus do |xml|
|
||||||
@@ -42,8 +57,8 @@ module Calligraphy
|
|||||||
def proppatch_response(path, actions)
|
def proppatch_response(path, actions)
|
||||||
multistatus do |xml|
|
multistatus do |xml|
|
||||||
href xml, path
|
href xml, path
|
||||||
propstat xml, actions[:set]
|
propstat xml, actions[:set], :ok
|
||||||
propstat xml, actions[:remove]
|
propstat xml, actions[:remove], :ok
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
51
spec/requests/mkcol_spec.rb
Normal file
51
spec/requests/mkcol_spec.rb
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
require 'support/request_helpers'
|
||||||
|
require 'support/examples/ext_mkcol'
|
||||||
|
|
||||||
|
RSpec.describe 'mkcol', type: :request do
|
||||||
|
before(:all) do
|
||||||
|
tmp_dir = Rails.root.join('../../tmp').to_path
|
||||||
|
Dir.mkdir tmp_dir unless File.exists? tmp_dir
|
||||||
|
|
||||||
|
webdav_dir = Rails.root.join('../../tmp/webdav').to_path
|
||||||
|
FileUtils.rm_r webdav_dir if File.exists? webdav_dir
|
||||||
|
Dir.mkdir webdav_dir
|
||||||
|
end
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
allow(Calligraphy).to receive(:enable_digest_authentication)
|
||||||
|
.and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates a collection with additional properties' do
|
||||||
|
allow_any_instance_of(Calligraphy::FileResource).to receive(
|
||||||
|
:valid_resourcetypes
|
||||||
|
).and_return(%w[collection special-resource])
|
||||||
|
|
||||||
|
expect(Dir).to receive(:mkdir).and_call_original
|
||||||
|
expect_any_instance_of(Calligraphy::FileResource).to receive(
|
||||||
|
:proppatch
|
||||||
|
)
|
||||||
|
|
||||||
|
mkcol '/webdav/special', headers: {
|
||||||
|
RAW_POST_DATA: Support::Examples::ExtMkcol.rfc5689_3_4
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(response.body.empty?).to eq(true)
|
||||||
|
expect(response.status).to eq(201)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with an invalid resource type' do
|
||||||
|
it 'returns an error response' do
|
||||||
|
mkcol '/webdav/special', headers: {
|
||||||
|
RAW_POST_DATA: Support::Examples::ExtMkcol.rfc5689_3_4
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(response.status).to eq(403)
|
||||||
|
expect(response.body).to include('mkcol-response')
|
||||||
|
expect(response.body).to include('valid-resourcetype')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
51
spec/requests/options_spec.rb
Normal file
51
spec/requests/options_spec.rb
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
require 'support/request_helpers'
|
||||||
|
|
||||||
|
RSpec.describe 'OPTIONS', type: :request do
|
||||||
|
before(:each) do
|
||||||
|
allow(Calligraphy).to receive(:enable_digest_authentication)
|
||||||
|
.and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when not using extended MKCOL support' do
|
||||||
|
before(:each) do
|
||||||
|
allow_any_instance_of(Calligraphy::FileResource).to receive(
|
||||||
|
:enable_extended_mkcol?
|
||||||
|
).and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'advertises support for all 3 WebDAV classes' do
|
||||||
|
options '/webdav/special'
|
||||||
|
|
||||||
|
%w[1 2 3].each { |c| expect(response.headers['DAV']).to include(c) }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not advertise support for extended-mkcol' do
|
||||||
|
options '/webdav/special'
|
||||||
|
|
||||||
|
expect(response.headers['DAV']).to_not include('extended-mkcol')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when using extended MKCOL support' do
|
||||||
|
before(:each) do
|
||||||
|
allow_any_instance_of(Calligraphy::FileResource).to receive(
|
||||||
|
:enable_extended_mkcol?
|
||||||
|
).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'advertises support for all 3 WebDAV classes' do
|
||||||
|
options '/webdav/special'
|
||||||
|
|
||||||
|
%w[1 2 3].each { |c| expect(response.headers['DAV']).to include(c) }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'advertises support for extended-mkcol' do
|
||||||
|
options '/webdav/special'
|
||||||
|
|
||||||
|
expect(response.headers['DAV']).to include('extended-mkcol')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,22 +1,29 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
require 'support/request_helpers'
|
||||||
|
|
||||||
RSpec.describe 'Resource' do
|
RSpec.describe 'Resource' do
|
||||||
context 'base method' do
|
context 'base method' do
|
||||||
resource_methods_without_inputs = %w(
|
resource_methods_without_inputs = %w[
|
||||||
ancestor_exist? collection? create_collection delete_collection etag
|
ancestor_exist? collection? create_collection delete_collection etag
|
||||||
exists? lock_is_exclusive? locked? read readable? refresh_lock
|
exists? lock_is_exclusive? locked? read readable? refresh_lock
|
||||||
creationdate displayname getcontentlanguage getcontentlength getcontenttype
|
creationdate displayname getcontentlanguage getcontentlength
|
||||||
getetag getlastmodified lockdiscovery resourcetype supportedlock
|
getcontenttype getetag getlastmodified lockdiscovery resourcetype
|
||||||
)
|
supportedlock
|
||||||
resource_methods_with_inputs = %w(
|
]
|
||||||
|
resource_methods_with_inputs = %w[
|
||||||
copy copy_options lock locked_to_user? propfind proppatch unlock write
|
copy copy_options lock locked_to_user? propfind proppatch unlock write
|
||||||
)
|
]
|
||||||
|
|
||||||
resource_methods_without_inputs.each do |method|
|
resource_methods_without_inputs.each do |method|
|
||||||
describe "##{method}" do
|
describe "##{method}" do
|
||||||
it 'raises NotImplementedError' do
|
it 'raises NotImplementedError' do
|
||||||
resource = Calligraphy::Resource.new
|
resource = Calligraphy::Resource.new
|
||||||
expect{resource.send(method)}.to raise_exception(NotImplementedError)
|
|
||||||
|
expect { resource.send(method) }.to raise_exception(
|
||||||
|
NotImplementedError
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -25,7 +32,10 @@ RSpec.describe 'Resource' do
|
|||||||
describe "##{method}" do
|
describe "##{method}" do
|
||||||
it 'raises NotImplementedError' do
|
it 'raises NotImplementedError' do
|
||||||
resource = Calligraphy::Resource.new
|
resource = Calligraphy::Resource.new
|
||||||
expect{resource.send(method, nil)}.to raise_exception(NotImplementedError)
|
|
||||||
|
expect { resource.send(method, nil) }.to raise_exception(
|
||||||
|
NotImplementedError
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
26
spec/support/examples/ext_mkcol.rb
Normal file
26
spec/support/examples/ext_mkcol.rb
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# frozen_string_literal: false
|
||||||
|
|
||||||
|
module Support
|
||||||
|
module Examples
|
||||||
|
module ExtMkcol
|
||||||
|
# RFC5689: 3.4. Successful Extended MKCOL Request
|
||||||
|
def self.rfc5689_3_4
|
||||||
|
<<~XML
|
||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<D:mkcol xmlns:D="DAV:"
|
||||||
|
xmlns:E="http://example.com/ns/">
|
||||||
|
<D:set>
|
||||||
|
<D:prop>
|
||||||
|
<D:resourcetype>
|
||||||
|
<D:collection/>
|
||||||
|
<E:special-resource/>
|
||||||
|
</D:resourcetype>
|
||||||
|
<D:displayname>Special Resource</D:displayname>
|
||||||
|
</D:prop>
|
||||||
|
</D:set>
|
||||||
|
</D:mkcol>
|
||||||
|
XML
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
module ActionDispatch
|
module ActionDispatch
|
||||||
module Integration
|
module Integration
|
||||||
module RequestHelpers
|
module RequestHelpers
|
||||||
%w[copy move mkcol propfind proppatch lock unlock].each do |method|
|
%w[copy move mkcol options propfind proppatch lock unlock].each do |method|
|
||||||
define_method method do |path, **args|
|
define_method method do |path, **args|
|
||||||
process method.to_sym, path, **args
|
process method.to_sym, path, **args
|
||||||
end
|
end
|
||||||
Reference in New Issue
Block a user