calligraphy 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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