calligraphy 0.2.1 → 0.3.1

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.
Files changed (38) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +1 -1
  3. data/lib/calligraphy.rb +23 -17
  4. data/lib/calligraphy/rails/mapper.rb +150 -142
  5. data/lib/calligraphy/rails/web_dav_methods.rb +67 -0
  6. data/lib/calligraphy/rails/web_dav_preconditions.rb +114 -0
  7. data/lib/calligraphy/rails/web_dav_requests_controller.rb +72 -180
  8. data/lib/calligraphy/resource/file_resource.rb +377 -194
  9. data/lib/calligraphy/resource/resource.rb +192 -32
  10. data/lib/calligraphy/utils.rb +23 -6
  11. data/lib/calligraphy/version.rb +3 -1
  12. data/lib/calligraphy/{copy.rb → web_dav_request/copy.rb} +13 -8
  13. data/lib/calligraphy/{delete.rb → web_dav_request/delete.rb} +6 -2
  14. data/lib/calligraphy/web_dav_request/get.rb +18 -0
  15. data/lib/calligraphy/web_dav_request/lock.rb +89 -0
  16. data/lib/calligraphy/{mkcol.rb → web_dav_request/mkcol.rb} +7 -2
  17. data/lib/calligraphy/web_dav_request/move.rb +56 -0
  18. data/lib/calligraphy/web_dav_request/propfind.rb +24 -0
  19. data/lib/calligraphy/web_dav_request/proppatch.rb +29 -0
  20. data/lib/calligraphy/web_dav_request/put.rb +16 -0
  21. data/lib/calligraphy/{unlock.rb → web_dav_request/unlock.rb} +6 -1
  22. data/lib/calligraphy/web_dav_request/web_dav_request.rb +43 -0
  23. data/lib/calligraphy/xml/builder.rb +83 -117
  24. data/lib/calligraphy/xml/namespace.rb +12 -6
  25. data/lib/calligraphy/xml/node.rb +24 -10
  26. data/lib/calligraphy/xml/utils.rb +22 -11
  27. data/lib/calligraphy/xml/web_dav_elements.rb +92 -0
  28. data/lib/generators/calligraphy/install_generator.rb +4 -0
  29. data/lib/generators/templates/calligraphy.rb +2 -0
  30. metadata +109 -22
  31. data/lib/calligraphy/get.rb +0 -12
  32. data/lib/calligraphy/lock.rb +0 -52
  33. data/lib/calligraphy/move.rb +0 -31
  34. data/lib/calligraphy/propfind.rb +0 -18
  35. data/lib/calligraphy/proppatch.rb +0 -20
  36. data/lib/calligraphy/put.rb +0 -12
  37. data/lib/calligraphy/web_dav_request.rb +0 -31
  38. data/spec/spec_helper.rb +0 -46
@@ -1,6 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Calligraphy
4
+ # Responsible for creating a new collection resource at the location
5
+ # specified by the request.
2
6
  class Mkcol < WebDavRequest
3
- def request
7
+ # Executes the WebDAV request for a particular resource.
8
+ def execute
4
9
  return :method_not_allowed if @resource.exists?
5
10
  return :conflict unless @resource.ancestor_exist?
6
11
  return :unsupported_media_type unless @resource.request_body.blank?
@@ -8,7 +13,7 @@ module Calligraphy
8
13
  @resource.create_collection
9
14
  set_content_location_header
10
15
 
11
- return :created
16
+ :created
12
17
  end
13
18
 
14
19
  private
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Calligraphy
4
+ # Responsible for copying a resource then deleting the original source.
5
+ class Move < Copy
6
+ # Executes the WebDAV request for a particular resource.
7
+ def execute
8
+ return :locked if @resource.locked_to_user? @headers
9
+
10
+ if @resource.true? options[:overwrite]
11
+ previous_resource_existed = overwrite_destination
12
+ end
13
+
14
+ status = super
15
+ return status if %i[precondition_failed conflict].include? status
16
+
17
+ @resource.delete_collection
18
+
19
+ response_status status, previous_resource_existed
20
+ end
21
+
22
+ private
23
+
24
+ def options
25
+ copy_move_options
26
+ end
27
+
28
+ def overwrite_destination
29
+ to_path = options[:destination].tap { |s| s.slice! @resource.mount_point }
30
+ to_resource = destination_resource to_path
31
+
32
+ if to_resource.exists?
33
+ to_resource.delete_collection
34
+ previous_resource_existed = true
35
+ end
36
+
37
+ previous_resource_existed
38
+ end
39
+
40
+ def destination_resource(to_path)
41
+ @resource.class.new(
42
+ resource: to_path,
43
+ req: @request,
44
+ root_dir: @resource.root_dir
45
+ )
46
+ end
47
+
48
+ def response_status(status, previous_resource)
49
+ return :no_content if status == :created && previous_resource
50
+
51
+ response.headers['Location'] = options[:destination] if status == :created
52
+
53
+ status
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Calligraphy
4
+ # Responsible for retrieving properties defined on the resource.
5
+ class Propfind < WebDavRequest
6
+ include Calligraphy::XML::Utils
7
+
8
+ # Executes the WebDAV request for a particular resource.
9
+ def execute
10
+ xml = xml_for body: body, node: 'propfind'
11
+ return :bad_request if xml == :bad_request
12
+
13
+ properties = @resource.propfind xml
14
+
15
+ builder = xml_builder
16
+ xml_res = builder.propfind_response(@resource.full_request_path,
17
+ properties)
18
+
19
+ set_xml_content_type
20
+
21
+ [:multi_status, xml_res]
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Calligraphy
4
+ # Responsible for processing instructions specified in the request body
5
+ # to set and/or remove properties defined on the resource.
6
+ class Proppatch < WebDavRequest
7
+ include Calligraphy::XML::Utils
8
+
9
+ # Executes the WebDAV request for a particular resource.
10
+ def execute
11
+ return :locked if @resource.locked_to_user? @headers
12
+
13
+ # The `propertyupdate` tag contains the request to alter properties
14
+ # on a resource.
15
+ xml = xml_for body: body, node: 'propertyupdate'
16
+ return :bad_request if xml == :bad_request
17
+
18
+ actions = @resource.proppatch xml
19
+
20
+ builder = xml_builder
21
+ xml_res = builder.proppatch_response(@resource.full_request_path,
22
+ actions)
23
+
24
+ set_xml_content_type
25
+
26
+ [:multi_status, xml_res]
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Calligraphy
4
+ # Responsible for replacing the `Get` response entity of the resource.
5
+ class Put < WebDavRequest
6
+ # Executes the WebDAV request for a particular resource.
7
+ def execute
8
+ return :locked if @resource.locked_to_user? @headers
9
+ return :method_not_allowed if @resource.collection?
10
+
11
+ @resource.write
12
+
13
+ [:created, @resource.contents]
14
+ end
15
+ end
16
+ end
@@ -1,6 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Calligraphy
4
+ # Responsible for removing the lock identified by the lock token in the
5
+ # request header.
2
6
  class Unlock < WebDavRequest
3
- def request
7
+ # Executes the WebDAV request for a particular resource.
8
+ def execute
4
9
  return :bad_request if @headers['Lock-Token'].nil?
5
10
 
6
11
  @resource.unlock lock_token_header
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Calligraphy
4
+ # Base class for WebDavRequests.
5
+ #
6
+ # `WebDavRequest` exposes the `request` method, which is used by the
7
+ # `WebDavRequestsController` to execute the WebDAV request for a particular
8
+ # resource. This class also provides private methods for accessing the
9
+ # request body, setting response headers, and creating XML responses.
10
+ class WebDavRequest
11
+ attr_accessor :resource, :response
12
+ attr_reader :headers, :request
13
+
14
+ #:nodoc:
15
+ def initialize(headers:, request:, response:, resource:)
16
+ @headers = headers
17
+ @request = request
18
+ @response = response
19
+ @resource = resource
20
+ end
21
+
22
+ # Executes the WebDAV request for a particular resource.
23
+ def execute
24
+ raise NotImplemented
25
+ end
26
+
27
+ private
28
+
29
+ def body
30
+ @resource.request_body
31
+ end
32
+
33
+ def set_xml_content_type
34
+ @response.content_type = 'application/xml'
35
+ end
36
+
37
+ def xml_builder
38
+ protocol = @request.env['SERVER_PROTOCOL']
39
+
40
+ Calligraphy::XML::Builder.new server_protocol: protocol
41
+ end
42
+ end
43
+ end
@@ -1,152 +1,118 @@
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
- DAV_PROPERTIES = %w(
9
- creationdate displayname getcontentlanguage getcontentlength
10
- getcontenttype getetag getlastmodified lockdiscovery
11
- resourcetype supportedlock
12
- )
13
-
14
- attr_reader :dav_ns, :default_ns, :server_protocol
15
-
16
- def initialize(dav_ns: 'D', server_protocol: 'HTTP/1.1')
17
- @dav_ns = dav_ns
18
- @default_ns = { "xmlns:#{@dav_ns}" => 'DAV:' }
19
- @server_protocol = server_protocol
20
- end
1
+ # frozen_string_literal: true
21
2
 
22
- def lock_res(activelock_properties)
23
- build :prop do |xml|
24
- xml.lockdiscovery do
25
- activelock_properties.each do |properties|
26
- activelock xml, properties
27
- end
28
- end
29
- end
30
- end
3
+ module Calligraphy
4
+ module XML
5
+ # Responsible for building XML responses for WebDAV requests.
6
+ class Builder
7
+ include Calligraphy::XML::WebDavElements
8
+
9
+ attr_reader :dav_ns, :default_ns, :server_protocol
31
10
 
32
- def propfind_res(path, properties)
33
- multistatus do |xml|
34
- href xml, path
35
- propstat xml, properties[:found], :ok
36
- propstat xml, properties[:not_found], :not_found
11
+ #:nodoc:
12
+ def initialize(dav_ns: 'D', server_protocol: 'HTTP/1.1')
13
+ @dav_ns = dav_ns
14
+ @default_ns = { "xmlns:#{@dav_ns}" => 'DAV:' }
15
+ @server_protocol = server_protocol
37
16
  end
38
- end
39
17
 
40
- def proppatch_res(path, actions)
41
- multistatus do |xml|
42
- href xml, path
43
- propstat xml, actions[:set]
44
- propstat xml, actions[:remove]
18
+ private
19
+
20
+ def build(tag)
21
+ Nokogiri::XML::Builder.new do |xml|
22
+ xml[@dav_ns].send(tag, @default_ns) { yield xml }
23
+ end.to_xml
45
24
  end
46
- end
47
25
 
48
- private
26
+ def multistatus
27
+ build :multistatus do |xml|
28
+ xml.response { yield xml }
29
+ end
30
+ end
49
31
 
50
- def build(tag)
51
- Nokogiri::XML::Builder.new do |xml|
52
- xml[@dav_ns].send(tag, @default_ns) do
53
- yield xml
32
+ def property_drilldown(xml, property)
33
+ if property.is_a? Array
34
+ iterate_and_drilldown xml, property
35
+ elsif DAV_NS_TAGS.include? property.name
36
+ supported_ns_tag xml, property
37
+ elsif property.namespace&.href
38
+ non_supported_ns_tag xml, property
39
+ else
40
+ nil_ns_tag xml, property
54
41
  end
55
- end.to_xml
56
- end
42
+ end
57
43
 
58
- def activelock(xml, property_set)
59
- xml.activelock do
44
+ def iterate_and_drilldown(xml, property_set)
60
45
  property_set.each do |property|
61
46
  property_drilldown xml, property
62
47
  end
63
48
  end
64
- end
65
49
 
66
- def href(xml, path)
67
- xml.href path
68
- end
50
+ def supported_ns_tag(xml, property)
51
+ if DAV_NS_METHODS.include? property.name
52
+ return send property.name, xml, property
53
+ end
69
54
 
70
- def multistatus
71
- build :multistatus do |xml|
72
- xml.response do
73
- yield xml
55
+ xml[@dav_ns].send property.name do
56
+ if property.children
57
+ iterate_and_drilldown xml, property.children
58
+ else
59
+ xml.text property.text
60
+ end
74
61
  end
75
62
  end
76
- end
77
63
 
78
- def prop(xml, property_set)
79
- xml.prop do
80
- property_set.each do |property|
81
- property_drilldown xml, property
64
+ def non_supported_ns_tag(xml, property)
65
+ xml.send property.name, xmlns: property.namespace.href do
66
+ if property.children
67
+ iterate_and_drilldown xml, property.children
68
+ else
69
+ xml.text property.text
70
+ end
82
71
  end
83
72
  end
84
- end
85
73
 
86
- def propstat(xml, property_set, status=:ok)
87
- return unless property_set.length > 0
74
+ def nil_ns_tag(xml, property)
75
+ xml.send property.name, property.text do
76
+ xml.parent.namespace = nil
77
+ end
78
+ end
88
79
 
89
- xml.propstat do
90
- prop xml, property_set
91
- status xml, status
80
+ def self_closing_tag(xml, text)
81
+ xml.send text
82
+ end
83
+
84
+ def href(xml, path)
85
+ xml[@dav_ns].href path
92
86
  end
93
- end
94
87
 
95
- def resourcetype(xml, property)
96
- if property.children.text == 'collection'
97
- xml[@dav_ns].resourcetype do
98
- xml.send 'collection'
88
+ def prop(xml, property_set)
89
+ xml[@dav_ns].prop do
90
+ iterate_and_drilldown xml, property_set
99
91
  end
100
- else
101
- xml[@dav_ns].resourcetype
102
92
  end
103
- end
104
93
 
105
- def status(xml, status)
106
- xml.status status_message status
107
- end
94
+ def propstat(xml, property_set, status = :ok)
95
+ return if property_set.empty?
108
96
 
109
- # NOTE: `xml[@dav_ns].send timeout` results in Timeout being called, so
110
- # we have this timeout method for convenience
111
- def timeout(xml, property)
112
- xml[@dav_ns].timeout do
113
- xml.text property.text
97
+ xml[@dav_ns].propstat do
98
+ prop xml, property_set
99
+ status xml, status
100
+ end
114
101
  end
115
- end
116
102
 
117
- def property_drilldown(xml, property)
118
- if property.is_a? Array
119
- property.each do |prop|
120
- property_drilldown xml, prop
121
- end
122
- elsif property.children && property.text.nil?
123
- xml.send property.name do
124
- property.children.each do |child|
125
- property_drilldown xml, child
126
- end
127
- end
128
- elsif property.name == 'resourcetype'
129
- resourcetype xml, property
130
- elsif property.name == 'timeout'
131
- timeout xml, property
132
- elsif SUPPORTED_NS_TAGS.include? property.name
133
- xml[@dav_ns].send property.name do
134
- xml.text property.text
135
- end
136
- elsif property.namespace && property.namespace.href
137
- xml.send property.name, xmlns: property.namespace.href do
138
- xml.text property.text
139
- end
140
- else
141
- xml.send property.name, property.text do
142
- xml.parent.namespace = nil
143
- end
103
+ def status(xml, status)
104
+ xml[@dav_ns].status status_message status
144
105
  end
145
- end
146
106
 
147
- def status_message(status)
148
- status_code = Rack::Utils.status_code status
149
- [@server_protocol, status_code, Rack::Utils::HTTP_STATUS_CODES[status_code]].join ' '
107
+ def status_message(status)
108
+ status_code = Rack::Utils.status_code status
109
+
110
+ [
111
+ @server_protocol,
112
+ status_code,
113
+ Rack::Utils::HTTP_STATUS_CODES[status_code]
114
+ ].join ' '
115
+ end
150
116
  end
151
117
  end
152
118
  end
@@ -1,10 +1,16 @@
1
- module Calligraphy::XML
2
- class Namespace
3
- attr_accessor :href, :prefix
1
+ # frozen_string_literal: true
4
2
 
5
- def initialize(namespace)
6
- @href = namespace.href if namespace.href
7
- @prefix = namespace.prefix if namespace.prefix
3
+ module Calligraphy
4
+ module XML
5
+ # Simple XML namespace, used to store a namespace's href and prefix values.
6
+ class Namespace
7
+ attr_accessor :href, :prefix
8
+
9
+ #:nodoc:
10
+ def initialize(namespace)
11
+ @href = namespace.href if namespace.href
12
+ @prefix = namespace.prefix if namespace.prefix
13
+ end
8
14
  end
9
15
  end
10
16
  end