calligraphy 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +64 -0
- data/lib/calligraphy.rb +58 -0
- data/lib/calligraphy/copy.rb +37 -0
- data/lib/calligraphy/delete.rb +17 -0
- data/lib/calligraphy/file_resource.rb +487 -0
- data/lib/calligraphy/get.rb +12 -0
- data/lib/calligraphy/lock.rb +52 -0
- data/lib/calligraphy/mkcol.rb +20 -0
- data/lib/calligraphy/move.rb +31 -0
- data/lib/calligraphy/propfind.rb +18 -0
- data/lib/calligraphy/proppatch.rb +20 -0
- data/lib/calligraphy/put.rb +12 -0
- data/lib/calligraphy/rails/mapper.rb +120 -0
- data/lib/calligraphy/rails/web_dav_requests_controller.rb +208 -0
- data/lib/calligraphy/resource.rb +93 -0
- data/lib/calligraphy/unlock.rb +15 -0
- data/lib/calligraphy/utils.rb +38 -0
- data/lib/calligraphy/version.rb +3 -0
- data/lib/calligraphy/web_dav_request.rb +31 -0
- data/lib/calligraphy/xml/builder.rb +147 -0
- data/lib/calligraphy/xml/namespace.rb +10 -0
- data/lib/calligraphy/xml/node.rb +23 -0
- data/lib/calligraphy/xml/utils.rb +18 -0
- data/spec/spec_helper.rb +46 -0
- metadata +97 -0
@@ -0,0 +1,93 @@
|
|
1
|
+
module Calligraphy
|
2
|
+
class Resource
|
3
|
+
attr_accessor :client_nonce, :contents, :updated_at
|
4
|
+
attr_reader :full_request_path, :mount_point, :request_body, :request_path, :root_dir
|
5
|
+
|
6
|
+
def initialize(resource: nil, req: nil, mount: nil, root_dir: nil)
|
7
|
+
@full_request_path = req&.original_url
|
8
|
+
@mount_point = mount || req&.path&.tap { |s| s.slice! resource }
|
9
|
+
@request_body = req&.body&.read || ''
|
10
|
+
@request_path = mount.nil? ? resource : resource.split(mount)[-1]
|
11
|
+
end
|
12
|
+
|
13
|
+
def ancestor_exist?
|
14
|
+
raise NotImplemented
|
15
|
+
end
|
16
|
+
|
17
|
+
def can_copy?(options)
|
18
|
+
raise NotImplemented
|
19
|
+
end
|
20
|
+
|
21
|
+
def collection?
|
22
|
+
raise NotImplemented
|
23
|
+
end
|
24
|
+
|
25
|
+
def copy(options)
|
26
|
+
raise NotImplemented
|
27
|
+
end
|
28
|
+
|
29
|
+
def create_collection
|
30
|
+
raise NotImplemented
|
31
|
+
end
|
32
|
+
|
33
|
+
def delete_collection
|
34
|
+
raise NotImplemented
|
35
|
+
end
|
36
|
+
|
37
|
+
def etag
|
38
|
+
raise NotImplemented
|
39
|
+
end
|
40
|
+
|
41
|
+
def exists?
|
42
|
+
raise NotImplemented
|
43
|
+
end
|
44
|
+
|
45
|
+
def lock(nodes, depth='infinity')
|
46
|
+
raise NotImplemented
|
47
|
+
end
|
48
|
+
|
49
|
+
def lock_is_exclusive?
|
50
|
+
raise NotImplemented
|
51
|
+
end
|
52
|
+
|
53
|
+
def lock_tokens
|
54
|
+
raise NotImplemented
|
55
|
+
end
|
56
|
+
|
57
|
+
def locked?
|
58
|
+
raise NotImplemented
|
59
|
+
end
|
60
|
+
|
61
|
+
def locked_to_user?(headers=nil)
|
62
|
+
raise NotImplemented
|
63
|
+
end
|
64
|
+
|
65
|
+
def propfind(nodes)
|
66
|
+
raise NotImplemented
|
67
|
+
end
|
68
|
+
|
69
|
+
def proppatch(nodes)
|
70
|
+
raise NotImplemented
|
71
|
+
end
|
72
|
+
|
73
|
+
def read
|
74
|
+
raise NotImplemented
|
75
|
+
end
|
76
|
+
|
77
|
+
def readable?
|
78
|
+
exists? && !collection?
|
79
|
+
end
|
80
|
+
|
81
|
+
def refresh_lock
|
82
|
+
raise NotImplemented
|
83
|
+
end
|
84
|
+
|
85
|
+
def unlock(token)
|
86
|
+
raise NotImplemented
|
87
|
+
end
|
88
|
+
|
89
|
+
def write(contents=@request_body.to_s)
|
90
|
+
raise NotImplemented
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Calligraphy
|
2
|
+
class Unlock < WebDavRequest
|
3
|
+
def request
|
4
|
+
return :bad_request if @headers['Lock-Token'].nil?
|
5
|
+
|
6
|
+
@resource.unlock lock_token_header
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def lock_token_header
|
12
|
+
@headers['Lock-Token'].gsub Calligraphy::LOCK_TOKEN_ANGLE_REGEX, ''
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Calligraphy
|
2
|
+
module Utils
|
3
|
+
TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE']
|
4
|
+
FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE']
|
5
|
+
|
6
|
+
def is_true?(val)
|
7
|
+
TRUE_VALUES.include? val
|
8
|
+
end
|
9
|
+
|
10
|
+
def is_false?(val)
|
11
|
+
FALSE_VALUES.include? val
|
12
|
+
end
|
13
|
+
|
14
|
+
def join_paths(*paths)
|
15
|
+
paths.join '/'
|
16
|
+
end
|
17
|
+
|
18
|
+
def split_and_pop(path:, separator: '/')
|
19
|
+
path.split(separator)[0..-2]
|
20
|
+
end
|
21
|
+
|
22
|
+
def obj_exists_and_is_not_type?(obj:, type:)
|
23
|
+
obj.nil? ? false : obj != type
|
24
|
+
end
|
25
|
+
|
26
|
+
def map_array_of_hashes(arr_hashes)
|
27
|
+
[].tap do |output_array|
|
28
|
+
arr_hashes.each do |hash|
|
29
|
+
output_array.push hash.map { |k, v| v }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def extract_lock_token(if_header)
|
35
|
+
if_header.scan(Calligraphy::LOCK_TOKEN_REGEX)&.flatten[0]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Calligraphy
|
2
|
+
class WebDavRequest
|
3
|
+
attr_accessor :resource, :response
|
4
|
+
attr_reader :headers, :request
|
5
|
+
|
6
|
+
def initialize(headers:, request:, response:, resource:)
|
7
|
+
@headers = headers
|
8
|
+
@request = request
|
9
|
+
@response = response
|
10
|
+
@resource = resource
|
11
|
+
end
|
12
|
+
|
13
|
+
def request
|
14
|
+
raise NotImplemented
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def body
|
20
|
+
@resource.request_body
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_xml_content_type
|
24
|
+
@response.content_type = 'application/xml'
|
25
|
+
end
|
26
|
+
|
27
|
+
def xml_builder
|
28
|
+
Calligraphy::XML::Builder.new server_protocol: @request.env['SERVER_PROTOCOL']
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
module Calligraphy::XML
|
2
|
+
class Builder
|
3
|
+
SUPPORTED_NS_TAGS = %w(
|
4
|
+
creationdate displayname exclusive getcontentlanguage getcontentlength
|
5
|
+
getcontenttype getetag getlastmodified href lockdiscovery lockscope
|
6
|
+
locktype owner write
|
7
|
+
)
|
8
|
+
|
9
|
+
attr_reader :dav_ns, :default_ns, :server_protocol
|
10
|
+
|
11
|
+
def initialize(dav_ns: 'D', server_protocol: 'HTTP/1.1')
|
12
|
+
@dav_ns = dav_ns
|
13
|
+
@default_ns = { "xmlns:#{@dav_ns}" => 'DAV:' }
|
14
|
+
@server_protocol = server_protocol
|
15
|
+
end
|
16
|
+
|
17
|
+
def lock_res(activelock_properties)
|
18
|
+
build :prop do |xml|
|
19
|
+
xml.lockdiscovery do
|
20
|
+
activelock_properties.each do |properties|
|
21
|
+
activelock xml, properties
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def propfind_res(path, properties)
|
28
|
+
multistatus do |xml|
|
29
|
+
href xml, path
|
30
|
+
propstat xml, properties[:found], :ok
|
31
|
+
propstat xml, properties[:not_found], :not_found
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def proppatch_res(path, actions)
|
36
|
+
multistatus do |xml|
|
37
|
+
href xml, path
|
38
|
+
propstat xml, actions[:set]
|
39
|
+
propstat xml, actions[:remove]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def build(tag)
|
46
|
+
Nokogiri::XML::Builder.new do |xml|
|
47
|
+
xml[@dav_ns].send(tag, @default_ns) do
|
48
|
+
yield xml
|
49
|
+
end
|
50
|
+
end.to_xml
|
51
|
+
end
|
52
|
+
|
53
|
+
def activelock(xml, property_set)
|
54
|
+
xml.activelock do
|
55
|
+
property_set.each do |property|
|
56
|
+
property_drilldown xml, property
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def href(xml, path)
|
62
|
+
xml.href path
|
63
|
+
end
|
64
|
+
|
65
|
+
def multistatus
|
66
|
+
build :multistatus do |xml|
|
67
|
+
xml.response do
|
68
|
+
yield xml
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def prop(xml, property_set)
|
74
|
+
xml.prop do
|
75
|
+
property_set.each do |property|
|
76
|
+
property_drilldown xml, property
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def propstat(xml, property_set, status=:ok)
|
82
|
+
return unless property_set.length > 0
|
83
|
+
|
84
|
+
xml.propstat do
|
85
|
+
prop xml, property_set
|
86
|
+
status xml, status
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def resourcetype(xml, property)
|
91
|
+
if property.children.text == 'collection'
|
92
|
+
xml[@dav_ns].resourcetype do
|
93
|
+
xml.send 'collection'
|
94
|
+
end
|
95
|
+
else
|
96
|
+
xml[@dav_ns].resourcetype
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def status(xml, status)
|
101
|
+
xml.status status_message status
|
102
|
+
end
|
103
|
+
|
104
|
+
# NOTE: `xml[@dav_ns].send timeout` results in Timeout being called, so
|
105
|
+
# we have this timeout method for convenience
|
106
|
+
def timeout(xml, property)
|
107
|
+
xml[@dav_ns].timeout do
|
108
|
+
xml.text property.text
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def property_drilldown(xml, property)
|
113
|
+
if property.is_a? Array
|
114
|
+
property.each do |prop|
|
115
|
+
property_drilldown xml, prop
|
116
|
+
end
|
117
|
+
elsif property.children && property.text.nil?
|
118
|
+
xml.send property.name do
|
119
|
+
property.children.each do |child|
|
120
|
+
property_drilldown xml, child
|
121
|
+
end
|
122
|
+
end
|
123
|
+
elsif property.name == 'resourcetype'
|
124
|
+
resourcetype xml, property
|
125
|
+
elsif property.name == 'timeout'
|
126
|
+
timeout xml, property
|
127
|
+
elsif SUPPORTED_NS_TAGS.include? property.name
|
128
|
+
xml[@dav_ns].send property.name do
|
129
|
+
xml.text property.text
|
130
|
+
end
|
131
|
+
elsif property.namespace && property.namespace.href
|
132
|
+
xml.send property.name, xmlns: property.namespace.href do
|
133
|
+
xml.text property.text
|
134
|
+
end
|
135
|
+
else
|
136
|
+
xml.send property.name, property.text do
|
137
|
+
xml.parent.namespace = nil
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def status_message(status)
|
143
|
+
status_code = Rack::Utils.status_code status
|
144
|
+
[@server_protocol, status_code, Rack::Utils::HTTP_STATUS_CODES[status_code]].join ' '
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Calligraphy::XML
|
2
|
+
class Node
|
3
|
+
attr_accessor :children, :name, :namespace, :text
|
4
|
+
|
5
|
+
def initialize(node=nil)
|
6
|
+
unless node.nil?
|
7
|
+
@name = node.name
|
8
|
+
@text = node.text unless node.text.empty?
|
9
|
+
|
10
|
+
if node.namespace
|
11
|
+
@namespace = Calligraphy::XML::Namespace.new node.namespace
|
12
|
+
end
|
13
|
+
|
14
|
+
if node.children&.length > 0
|
15
|
+
@children = []
|
16
|
+
node.children.each do |child|
|
17
|
+
@children.push Calligraphy::XML::Node.new child
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Calligraphy::XML
|
2
|
+
module Utils
|
3
|
+
def xml_for(body:, node:)
|
4
|
+
xml = Nokogiri::XML body
|
5
|
+
return :bad_request unless xml.errors.empty?
|
6
|
+
|
7
|
+
namespace = nil
|
8
|
+
xml.root.namespace_definitions.each do |n|
|
9
|
+
namespace = "#{n.prefix}|" if n&.href == Calligraphy::DAV_NS && !n.prefix.nil?
|
10
|
+
end
|
11
|
+
namespace
|
12
|
+
|
13
|
+
node = node.split(' ').map! { |n| namespace + n }.join(' ') if namespace
|
14
|
+
|
15
|
+
xml.css(node).children
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
RSpec.configure do |config|
|
2
|
+
config.expect_with :rspec do |expectations|
|
3
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
4
|
+
end
|
5
|
+
|
6
|
+
config.mock_with :rspec do |mocks|
|
7
|
+
mocks.verify_partial_doubles = true
|
8
|
+
end
|
9
|
+
|
10
|
+
config.shared_context_metadata_behavior = :apply_to_host_groups
|
11
|
+
|
12
|
+
config.filter_run_when_matching :focus
|
13
|
+
|
14
|
+
# Allows RSpec to persist some state between runs in order to support
|
15
|
+
# the `--only-failures` and `--next-failure` CLI options. We recommend
|
16
|
+
# you configure your source control system to ignore this file.
|
17
|
+
# config.example_status_persistence_file_path = "spec/examples.txt"
|
18
|
+
|
19
|
+
# Limits the available syntax to the non-monkey patched syntax that is
|
20
|
+
# recommended. For more details, see:
|
21
|
+
# - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
|
22
|
+
# - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
23
|
+
# - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
|
24
|
+
config.disable_monkey_patching!
|
25
|
+
|
26
|
+
config.warnings = true
|
27
|
+
|
28
|
+
# Many RSpec users commonly either run the entire suite or an individual
|
29
|
+
# file, and it's useful to allow more verbose output when running an
|
30
|
+
# individual spec file.
|
31
|
+
if config.files_to_run.one?
|
32
|
+
# Use the documentation formatter for detailed output,
|
33
|
+
# unless a formatter has already been configured
|
34
|
+
# (e.g. via a command-line flag).
|
35
|
+
config.default_formatter = "doc"
|
36
|
+
end
|
37
|
+
|
38
|
+
# Print the 10 slowest examples and example groups at the
|
39
|
+
# end of the spec run, to help surface which specs are running
|
40
|
+
# particularly slow.
|
41
|
+
config.profile_examples = 10
|
42
|
+
|
43
|
+
config.order = :random
|
44
|
+
|
45
|
+
Kernel.srand config.seed
|
46
|
+
end
|
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: calligraphy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Brandon Robins
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-12-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: WebDAV framework and extension for Rails 5
|
42
|
+
email: brandon@onebnottwo.com
|
43
|
+
executables: []
|
44
|
+
extensions: []
|
45
|
+
extra_rdoc_files: []
|
46
|
+
files:
|
47
|
+
- LICENSE
|
48
|
+
- README.md
|
49
|
+
- lib/calligraphy.rb
|
50
|
+
- lib/calligraphy/copy.rb
|
51
|
+
- lib/calligraphy/delete.rb
|
52
|
+
- lib/calligraphy/file_resource.rb
|
53
|
+
- lib/calligraphy/get.rb
|
54
|
+
- lib/calligraphy/lock.rb
|
55
|
+
- lib/calligraphy/mkcol.rb
|
56
|
+
- lib/calligraphy/move.rb
|
57
|
+
- lib/calligraphy/propfind.rb
|
58
|
+
- lib/calligraphy/proppatch.rb
|
59
|
+
- lib/calligraphy/put.rb
|
60
|
+
- lib/calligraphy/rails/mapper.rb
|
61
|
+
- lib/calligraphy/rails/web_dav_requests_controller.rb
|
62
|
+
- lib/calligraphy/resource.rb
|
63
|
+
- lib/calligraphy/unlock.rb
|
64
|
+
- lib/calligraphy/utils.rb
|
65
|
+
- lib/calligraphy/version.rb
|
66
|
+
- lib/calligraphy/web_dav_request.rb
|
67
|
+
- lib/calligraphy/xml/builder.rb
|
68
|
+
- lib/calligraphy/xml/namespace.rb
|
69
|
+
- lib/calligraphy/xml/node.rb
|
70
|
+
- lib/calligraphy/xml/utils.rb
|
71
|
+
- spec/spec_helper.rb
|
72
|
+
homepage: http://www.github.com/eanlain/calligraphy
|
73
|
+
licenses:
|
74
|
+
- MIT
|
75
|
+
metadata: {}
|
76
|
+
post_install_message:
|
77
|
+
rdoc_options: []
|
78
|
+
require_paths:
|
79
|
+
- lib
|
80
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
requirements: []
|
91
|
+
rubyforge_project:
|
92
|
+
rubygems_version: 2.6.14
|
93
|
+
signing_key:
|
94
|
+
specification_version: 4
|
95
|
+
summary: WebDAV framework and extension for Rails 5
|
96
|
+
test_files:
|
97
|
+
- spec/spec_helper.rb
|