calligraphy 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,12 @@
1
+ module Calligraphy
2
+ class Get < WebDavRequest
3
+ def request(head: false)
4
+ if @resource.readable?
5
+ return :ok if head
6
+ return :ok, @resource.read
7
+ else
8
+ return :not_found
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,52 @@
1
+ module Calligraphy
2
+ class Lock < WebDavRequest
3
+ include Calligraphy::XML::Utils
4
+
5
+ def request
6
+ if @resource.request_body.blank? && !@resource.locked_to_user?(@headers)
7
+ lock_properties = @resource.refresh_lock
8
+ elsif (@resource.locked? && @resource.lock_is_exclusive?) ||
9
+ (@resource.locked_to_user?(@headers) && !xml_contains_shared_lock?)
10
+ return :locked
11
+ else
12
+ resource_exists_beforehand = @resource.exists?
13
+
14
+ xml = xml_for body: body, node: 'lockinfo'
15
+ return :bad_request if xml == :bad_request
16
+
17
+ lock_properties = @resource.lock xml, @headers['Depth']
18
+ end
19
+
20
+ builder = xml_builder
21
+ xml_res = builder.lock_res lock_properties
22
+
23
+ lock_token = lock_properties[-1]
24
+ .select { |x| x.name == 'locktoken' }[0]
25
+ .children[0]
26
+ .text
27
+
28
+ response.headers['Lock-Token'] = "<#{lock_token}>"
29
+ set_xml_content_type
30
+
31
+ if resource_exists_beforehand
32
+ return :ok, xml_res
33
+ else
34
+ return :created, xml_res
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def xml_contains_shared_lock?
41
+ lock_type = nil
42
+ xml = xml_for body: body, node: 'lockinfo'
43
+ xml.each do |node|
44
+ next unless node.is_a? Nokogiri::XML::Element
45
+
46
+ lock_type = node.children[0].name if node.name == 'lockscope'
47
+ end
48
+
49
+ lock_type == 'shared'
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,20 @@
1
+ module Calligraphy
2
+ class Mkcol < WebDavRequest
3
+ def request
4
+ return :method_not_allowed if @resource.exists?
5
+ return :conflict unless @resource.ancestor_exist?
6
+ return :unsupported_media_type unless @resource.request_body.blank?
7
+
8
+ @resource.create_collection
9
+ set_content_location_header
10
+
11
+ return :created
12
+ end
13
+
14
+ private
15
+
16
+ def set_content_location_header
17
+ @response.headers['Content-Location'] = @resource.full_request_path
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,31 @@
1
+ module Calligraphy
2
+ class Move < Copy
3
+ def request
4
+ return :locked if @resource.locked_to_user? @headers
5
+
6
+ options = copy_move_options
7
+
8
+ if @resource.is_true? options[:overwrite]
9
+ to_path = options[:destination].tap { |s| s.slice! @resource.mount_point }
10
+ to_resource = @resource.class.new resource: to_path, req: @request, root_dir: @resource.root_dir
11
+
12
+ if to_resource.exists?
13
+ to_resource.delete_collection
14
+ to_resource_existed = true
15
+ end
16
+ end
17
+
18
+ copy_status = super
19
+ return copy_status if [:precondition_failed, :conflict].include? copy_status
20
+
21
+ @resource.delete_collection
22
+
23
+ if copy_status == :created && to_resource_existed
24
+ return :no_content
25
+ else
26
+ response.headers['Location'] = options[:destination] if copy_status == :created
27
+ return copy_status
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,18 @@
1
+ module Calligraphy
2
+ class Propfind < WebDavRequest
3
+ include Calligraphy::XML::Utils
4
+
5
+ def request
6
+ xml = xml_for body: body, node: 'propfind'
7
+ return :bad_request if xml == :bad_request
8
+
9
+ properties = @resource.propfind xml
10
+
11
+ builder = xml_builder
12
+ xml_res = builder.propfind_res @resource.full_request_path, properties
13
+
14
+ set_xml_content_type
15
+ return :multi_status, xml_res
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,20 @@
1
+ module Calligraphy
2
+ class Proppatch < WebDavRequest
3
+ include Calligraphy::XML::Utils
4
+
5
+ def request
6
+ return :locked if @resource.locked_to_user? @headers
7
+
8
+ xml = xml_for body: body, node: 'propertyupdate'
9
+ return :bad_request if xml == :bad_request
10
+
11
+ actions = @resource.proppatch xml
12
+
13
+ builder = xml_builder
14
+ xml_res = builder.proppatch_res @resource.full_request_path, actions
15
+
16
+ set_xml_content_type
17
+ return :multi_status, xml_res
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,12 @@
1
+ module Calligraphy
2
+ class Put < WebDavRequest
3
+ def request
4
+ return :locked if @resource.locked_to_user? @headers
5
+ return :method_not_allowed if @resource.collection?
6
+
7
+ @resource.write
8
+
9
+ return :created, @resource.contents
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,120 @@
1
+ module ActionDispatch::Routing
2
+ class Mapper
3
+ module HttpHelpers
4
+ def copy(*args, &block)
5
+ args = set_web_dav_args args
6
+ map_method :copy, args, &block
7
+ end
8
+
9
+ def head(*args, &block)
10
+ args = set_web_dav_args args
11
+ map_method :head, args, &block
12
+ end
13
+
14
+ def lock(*args, &block)
15
+ args = set_web_dav_args args
16
+ map_method :lock, args, &block
17
+ end
18
+
19
+ def mkcol(*args, &block)
20
+ args = set_web_dav_args args
21
+ map_method :mkcol, args, &block
22
+ end
23
+
24
+ def move(*args, &block)
25
+ args = set_web_dav_args args
26
+ map_method :move, args, &block
27
+ end
28
+
29
+ def options(*args, &block)
30
+ args = set_web_dav_args args
31
+ map_method :options, args, &block
32
+ end
33
+
34
+ def propfind(*args, &block)
35
+ args = set_web_dav_args args
36
+ map_method :propfind, args, &block
37
+ end
38
+
39
+ def proppatch(*args, &block)
40
+ args = set_web_dav_args args
41
+ map_method :proppatch, args, &block
42
+ end
43
+
44
+ def unlock(*args, &block)
45
+ args = set_web_dav_args args
46
+ map_method :unlock, args, &block
47
+ end
48
+
49
+ def web_dav_delete(*args, &block)
50
+ args = set_web_dav_args args
51
+ map_method :delete, args, &block
52
+ end
53
+
54
+ def web_dav_get(*args, &block)
55
+ args = set_web_dav_args args
56
+ map_method :get, args, &block
57
+ end
58
+
59
+ def web_dav_put(*args, &block)
60
+ args = set_web_dav_args args
61
+ map_method :put, args, &block
62
+ end
63
+
64
+ private
65
+
66
+ def set_web_dav_args(args)
67
+ options = {}
68
+ options[:controller] = 'calligraphy/rails/web_dav_requests'
69
+ options[:action] = 'invoke_method'
70
+ [args[0], options]
71
+ end
72
+ end
73
+
74
+ module Resources
75
+ class Resource
76
+ def web_dav_actions
77
+ if @only
78
+ Array(@only).map(&:to_sym)
79
+ elsif @except
80
+ Calligraphy.web_dav_actions - Array(@except).map(&:to_sym)
81
+ else
82
+ Calligraphy.web_dav_actions
83
+ end
84
+ end
85
+ end
86
+
87
+ def calligraphy_resource(*resources, &block)
88
+ options = resources.extract_options!.dup
89
+
90
+ if apply_common_behavior_for :calligraphy_resource, resources, options, &block
91
+ return self
92
+ end
93
+
94
+ with_scope_level(:resource) do
95
+ options = apply_action_options options
96
+ singleton_resoure = ActionDispatch::Routing::Mapper::SingletonResource
97
+ resource_scope(singleton_resoure.new resources.pop, api_only?, @scope[:shallow], options) do
98
+ yield if block_given?
99
+
100
+ concerns(options[:concerns]) if options[:concerns]
101
+
102
+ set_mappings_for_web_dav_resources
103
+ end
104
+ end
105
+ end
106
+
107
+ private
108
+
109
+ def set_mappings_for_web_dav_resources
110
+ parent_resource.web_dav_actions.each do |action|
111
+ if [:get, :put, :delete].include? action
112
+ send "web_dav_#{action.to_s}", '*resource'
113
+ else
114
+ send action, '*resource'
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,208 @@
1
+ module Calligraphy::Rails
2
+ class WebDavRequestsController < ActionController::Base
3
+ before_action :verify_resource_scope
4
+ before_action :authenticate_with_digest_authentiation
5
+ before_action :set_resource
6
+
7
+ def invoke_method
8
+ method = request.request_method.downcase
9
+
10
+ if check_preconditions
11
+ if method == 'head'
12
+ status = get head: true
13
+ elsif Calligraphy.allowed_methods.include? method
14
+ set_resource_client_nonce(method) if Calligraphy.enable_digest_authentication
15
+
16
+ status, body = send method
17
+ else
18
+ status = :method_not_allowed
19
+ end
20
+
21
+ send_response status: status, body: body
22
+ else
23
+ send_response status: :precondition_failed
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def verify_resource_scope
30
+ head :forbidden if params[:resource].include? '..'
31
+ end
32
+
33
+ def authenticate_with_digest_authentiation
34
+ if Calligraphy.enable_digest_authentication
35
+ authenticate_or_request_with_http_digest do |username|
36
+ Calligraphy.digest_password_procedure.call(username)
37
+ end
38
+ end
39
+ end
40
+
41
+ def set_resource
42
+ resource_id = if params[:format]
43
+ [params[:resource], params[:format]].join '.'
44
+ else
45
+ params[:resource]
46
+ end
47
+
48
+ @resource_class = params[:resource_class]
49
+ @resource_root_path = params[:resource_root_path]
50
+ @resource = @resource_class.new resource: resource_id, req: request, root_dir: @resource_root_path
51
+ end
52
+
53
+ def check_preconditions
54
+ return true unless request.headers['If'].present?
55
+
56
+ evaluate_if_header
57
+ end
58
+
59
+ def evaluate_if_header
60
+ conditions_met = false
61
+
62
+ condition_lists = get_if_conditions
63
+ condition_lists.each do |list|
64
+ conditions = parse_preconditions list
65
+
66
+ conditions_met = evaluate_preconditions conditions
67
+ break if conditions_met
68
+ end
69
+
70
+ conditions_met
71
+ end
72
+
73
+ def get_if_conditions
74
+ lists = if request.headers['If'][0] == '<'
75
+ request.headers['If'].split Calligraphy::TAGGED_LIST_REGEX
76
+ else
77
+ request.headers['If'].split Calligraphy::UNTAGGAGED_LIST_REGEX
78
+ end
79
+
80
+ lists
81
+ end
82
+
83
+ def parse_preconditions(list)
84
+ conditions = { dav_no_lock: nil, etag: nil, lock_token: nil, resource: nil }
85
+
86
+ conditions[:dav_no_lock] = if list =~ Calligraphy::DAV_NO_LOCK_REGEX
87
+ list =~ Calligraphy::DAV_NOT_NO_LOCK_REGEX ? nil : true
88
+ end
89
+
90
+ if list =~ Calligraphy::RESOURCE_REGEX
91
+ conditions[:resource] = list.scan(Calligraphy::RESOURCE_REGEX).flatten[0]
92
+ end
93
+
94
+ if list =~ Calligraphy::LOCK_TOKEN_REGEX
95
+ conditions[:lock_token] = list.scan(Calligraphy::LOCK_TOKEN_REGEX).flatten[0]
96
+ end
97
+
98
+ if list =~ Calligraphy::ETAG_IF_REGEX
99
+ conditions[:etag] = list.scan(Calligraphy::ETAG_IF_REGEX).flatten[0]
100
+ end
101
+
102
+ conditions
103
+ end
104
+
105
+ def evaluate_preconditions(conditions)
106
+ conditions_met = true
107
+ target = if conditions[:resource]
108
+ @resource_class.new(
109
+ resource: conditions[:resource],
110
+ mount: @resource.mount_point
111
+ )
112
+ else
113
+ @resource
114
+ end
115
+
116
+ if conditions[:lock_token]
117
+ if target.locked?
118
+ conditions_met = false unless target.lock_tokens&.include? conditions[:lock_token]
119
+ else
120
+ conditions_met = false if target.locked_to_user? request.headers
121
+ end
122
+ end
123
+
124
+ if conditions[:etag]
125
+ validators = [@resource.etag, ""]
126
+ conditions_met = false unless validate_etag validators, conditions[:etag]
127
+ end
128
+
129
+ conditions_met = false if conditions[:dav_no_lock]
130
+ conditions_met
131
+ end
132
+
133
+ def validate_etag(etag_validators, validate_against)
134
+ cache_key = ActiveSupport::Cache.expand_cache_key etag_validators
135
+ "W/\"#{Digest::MD5.hexdigest(cache_key)}\"" == validate_against
136
+ end
137
+
138
+ def web_dav_request
139
+ { headers: request.headers, request: request, resource: @resource, response: response }
140
+ end
141
+
142
+ def options
143
+ response.headers['DAV'] = '1, 2, 3'
144
+ :ok
145
+ end
146
+
147
+ def get(head: false)
148
+ fresh_when(@resource, etag: @resource.etag) if @resource.readable?
149
+
150
+ Calligraphy::Get.new(web_dav_request).request(head: head)
151
+ end
152
+
153
+ def put
154
+ Calligraphy::Put.new(web_dav_request).request
155
+ end
156
+
157
+ def delete
158
+ Calligraphy::Delete.new(web_dav_request).request
159
+ end
160
+
161
+ def copy
162
+ Calligraphy::Copy.new(web_dav_request).request
163
+ end
164
+
165
+ def move
166
+ Calligraphy::Move.new(web_dav_request).request
167
+ end
168
+
169
+ def mkcol
170
+ Calligraphy::Mkcol.new(web_dav_request).request
171
+ end
172
+
173
+ def propfind
174
+ Calligraphy::Propfind.new(web_dav_request).request
175
+ end
176
+
177
+ def proppatch
178
+ Calligraphy::Proppatch.new(web_dav_request).request
179
+ end
180
+
181
+ def lock
182
+ Calligraphy::Lock.new(web_dav_request).request
183
+ end
184
+
185
+ def unlock
186
+ Calligraphy::Unlock.new(web_dav_request).request
187
+ end
188
+
189
+ def send_response(status:, body: nil)
190
+ if body.nil?
191
+ head status
192
+ else
193
+ render body: body, status: status
194
+ end
195
+ end
196
+
197
+ def set_resource_client_nonce(method)
198
+ @resource.client_nonce = get_client_nonce
199
+ end
200
+
201
+ def get_client_nonce
202
+ auth_header = request.headers["HTTP_AUTHORIZATION"]
203
+
204
+ auth = ::ActionController::HttpAuthentication::Digest.decode_credentials auth_header
205
+ auth[:cnonce]
206
+ end
207
+ end
208
+ end