diff --git a/lib/calligraphy/resource/file_resource.rb b/lib/calligraphy/resource/file_resource.rb
index 91321f9..0e24186 100644
--- a/lib/calligraphy/resource/file_resource.rb
+++ b/lib/calligraphy/resource/file_resource.rb
@@ -6,9 +6,9 @@ module Calligraphy
# Resource responsible for writing and deleting directories and files to disk.
class FileResource < Resource
DAV_PROPERTY_METHODS = %w[
- creationdate displayname getcontentlanguage getcontentlength
- getcontenttype getetag getlastmodified lockdiscovery resourcetype
- supportedlock
+ allprop creationdate displayname getcontentlanguage getcontentlength
+ getcontenttype getetag getlastmodified lockdiscovery propname
+ resourcetype supportedlock
].freeze
include Calligraphy::Utils
@@ -174,15 +174,7 @@ module Calligraphy
def propfind(nodes)
properties = { found: [], not_found: [] }
- nodes.each do |node|
- node.children.each do |prop|
- next unless prop.is_a? Nokogiri::XML::Element
-
- value = get_property prop
-
- update_found_properties properties, prop, value
- end
- end
+ find_properties_from_xml_elements nodes, properties
properties[:found] = properties[:found].uniq.flatten if properties[:found]
properties
@@ -507,6 +499,30 @@ module Calligraphy
(lock_info[:check_creator] && (lock_info[:creator] == client_nonce))
end
+ def find_properties_from_xml_elements(nodes, properties)
+ nodes.each do |node|
+ next unless node.is_a? Nokogiri::XML::Element
+
+ if node.children.length.positive?
+ find_properties_from_property_nodes node, properties
+ else
+ value = get_property node
+
+ update_found_properties properties, node, value
+ end
+ end
+ end
+
+ def find_properties_from_property_nodes(node, properties)
+ node.children.each do |prop|
+ next unless prop.is_a? Nokogiri::XML::Element
+
+ value = get_property prop
+
+ update_found_properties properties, prop, value
+ end
+ end
+
def ancestor_lock_tokens(lock_info)
lock_info[:lock].each { |x| x }.map { |k| k[:locktoken].children[0].text }
end
@@ -521,55 +537,94 @@ module Calligraphy
def get_property(prop)
case prop.name
when *DAV_PROPERTY_METHODS
- prop.content = send prop.name
- prop
+ send prop.name, prop
else
get_custom_property prop.name, deserialize: true
end
end
- def creationdate
- @stats[:created_at]
+ def allprop(_prop)
+ get_custom_property nil, deserialize: true
+
+ {}.tap do |properties|
+ @store_properties.each_value do |node|
+ next unless node.is_a? Nokogiri::XML::Element
+
+ properties[node.name.to_sym] = node
+ end
+ end
end
- def displayname
- get_custom_property(:displayname) || @name
+ def creationdate(prop)
+ prop.content = @stats[:created_at]
+ prop
end
- def getcontentlanguage
- get_custom_property :contentlanguage
+ def displayname(prop)
+ prop.content = get_custom_property(:displayname) || @name
+ prop
end
- def getcontentlength
- @stats[:size]
+ def getcontentlanguage(prop)
+ prop.content = get_custom_property :contentlanguage
+ prop
end
- def getcontenttype
- get_custom_property :contenttype
+ def getcontentlength(prop)
+ prop.content = @stats[:size]
+ prop
end
- def getetag
+ def getcontenttype(prop)
+ prop.content = get_custom_property :contenttype
+ prop
+ end
+
+ def getetag(prop)
cache_key = ActiveSupport::Cache.expand_cache_key [@resource.etag, '']
- "W/\"#{Digest::MD5.hexdigest(cache_key)}\""
+
+ prop.content = "W/\"#{Digest::MD5.hexdigest(cache_key)}\""
+ prop
end
- def getlastmodified
- @updated_at
+ def getlastmodified(prop)
+ prop.content = @updated_at
+ prop
end
- def lockdiscovery
- fetch_lock_info
+ # def include(prop)
+ # # TODO: Implement
+ # prop
+ # end
+
+ def lockdiscovery(prop)
+ prop.content = fetch_lock_info
+ prop
end
- def resourcetype
- 'collection' if collection?
+ def propname(_prop)
+ get_custom_property nil, deserialize: true
+
+ {}.tap do |properties|
+ @store_properties.each_value do |node|
+ next unless node.is_a? Nokogiri::XML::Element
+
+ properties[node.name.to_sym] = xml_node node.name
+ end
+ end
end
- def supportedlock
+ def resourcetype(prop)
+ prop.content = 'collection' if collection?
+ prop
+ end
+
+ def supportedlock(prop)
exclusive_write = lockentry_hash('exclusive', 'write')
shared_write = lockentry_hash('shared', 'write')
- JSON.generate [exclusive_write, shared_write]
+ prop.content = JSON.generate [exclusive_write, shared_write]
+ prop
end
def get_custom_property(prop, deserialize: false)
@@ -609,11 +664,10 @@ module Calligraphy
def add_properties(node, actions)
node.children.each do |prop|
prop.children.each do |property|
- next unless node.is_a? Nokogiri::XML::Element
+ next unless property.is_a? Nokogiri::XML::Element
prop_sym = property.name.to_sym
-
- store_property_node property.serialize, prop_sym
+ store_property_node property.clone.serialize, prop_sym
actions[:set].push property
end
diff --git a/spec/requests/propfind_spec.rb b/spec/requests/propfind_spec.rb
new file mode 100644
index 0000000..5faa422
--- /dev/null
+++ b/spec/requests/propfind_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: false
+
+require 'rails_helper'
+require 'support/request_helpers'
+require 'support/examples/propfind'
+require 'support/examples/proppatch'
+
+RSpec.describe 'PROPFIND', 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
+
+ context 'with xml defintiion' do
+ before(:each) do
+ put '/webdav/bar.html', headers: {
+ RAW_POST_DATA: 'hello world'
+ }
+ proppatch '/webdav/bar.html', headers: {
+ RAW_POST_DATA: Support::Examples::Proppatch.rfc4918_9_2_2
+ }
+ end
+
+ describe 'allprop' do
+ it 'returns all property names and values' do
+ propfind '/webdav/bar.html', headers: {
+ RAW_POST_DATA: Support::Examples::Propfind.allprop
+ }
+
+ expect(response.status).to eq(207)
+ expect(response.body).to include('Authors')
+ expect(response.body).to include('Author>')
+ expect(response.body).to include('Jim')
+ expect(response.body).to include('Roy')
+ end
+ end
+
+ describe 'propname' do
+ it 'returns all property names' do
+ propfind '/webdav/bar.html', headers: {
+ RAW_POST_DATA: Support::Examples::Propfind.propname
+ }
+
+ expect(response.status).to eq(207)
+ expect(response.body).to include('Authors/')
+ expect(response.body).to_not include('Author/')
+ end
+ end
+ end
+end
diff --git a/spec/support/examples/propfind.rb b/spec/support/examples/propfind.rb
new file mode 100644
index 0000000..0bd0c98
--- /dev/null
+++ b/spec/support/examples/propfind.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: false
+
+module Support
+ module Examples
+ module Propfind
+ def self.allprop
+ <<~XML
+
+
+
+
+XML
+ end
+
+ def self.propname
+ <<~XML
+
+
+
+
+XML
+ end
+ end
+ end
+end
diff --git a/spec/support/examples/proppatch.rb b/spec/support/examples/proppatch.rb
new file mode 100644
index 0000000..191bdbf
--- /dev/null
+++ b/spec/support/examples/proppatch.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: false
+
+module Support
+ module Examples
+ module Proppatch
+ # RFC4918: 9.2.2
+ def self.rfc4918_9_2_2
+ <<~XML
+
+
+
+
+
+ Jim Whitehead
+ Roy Fielding
+
+
+
+
+
+
+
+XML
+ end
+ end
+ end
+end