rack_dav_sp 0.2.dev

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,168 @@
1
+ module RackDAV
2
+
3
+ class FileResource < Resource
4
+ include WEBrick::HTTPUtils
5
+
6
+ # If this is a collection, return the child resources.
7
+ def children
8
+ Dir[file_path + '/*'].map do |path|
9
+ child File.basename(path)
10
+ end
11
+ end
12
+
13
+ # Is this resource a collection?
14
+ def collection?
15
+ File.directory?(file_path)
16
+ end
17
+
18
+ # Does this recource exist?
19
+ def exist?
20
+ File.exist?(file_path)
21
+ end
22
+
23
+ # Return the creation time.
24
+ def creation_date
25
+ stat.ctime
26
+ end
27
+
28
+ # Return the time of last modification.
29
+ def last_modified
30
+ stat.mtime
31
+ end
32
+
33
+ # Set the time of last modification.
34
+ def last_modified=(time)
35
+ File.utime(Time.now, time, file_path)
36
+ end
37
+
38
+ # Return an Etag, an unique hash value for this resource.
39
+ def etag
40
+ sprintf('%x-%x-%x', stat.ino, stat.size, stat.mtime.to_i)
41
+ end
42
+
43
+ # Return the resource type.
44
+ #
45
+ # If this is a collection, return
46
+ # REXML::Element.new('D:collection')
47
+ def resource_type
48
+ if collection?
49
+ REXML::Element.new('D:collection')
50
+ end
51
+ end
52
+
53
+ # Return the mime type of this resource.
54
+ def content_type
55
+ if stat.directory?
56
+ "text/html"
57
+ else
58
+ mime_type(file_path, DefaultMimeTypes)
59
+ end
60
+ end
61
+
62
+ # Return the size in bytes for this resource.
63
+ def content_length
64
+ stat.size
65
+ end
66
+
67
+ # HTTP GET request.
68
+ #
69
+ # Write the content of the resource to the response.body.
70
+ def get(request, response)
71
+ if stat.directory?
72
+ content = ""
73
+ Rack::Directory.new(root).call(request.env)[2].each { |line| content << line }
74
+ response.body = [content]
75
+ response['Content-Length'] = content.size.to_s
76
+ else
77
+ file = Rack::File.new(nil)
78
+ file.path = file_path
79
+ response.body = file
80
+ end
81
+ end
82
+
83
+ # HTTP PUT request.
84
+ #
85
+ # Save the content of the request.body.
86
+ def put(request, response)
87
+ write(request.body)
88
+ end
89
+
90
+ # HTTP POST request.
91
+ #
92
+ # Usually forbidden.
93
+ def post(request, response)
94
+ raise HTTPStatus::Forbidden
95
+ end
96
+
97
+ # HTTP DELETE request.
98
+ #
99
+ # Delete this resource.
100
+ def delete
101
+ if stat.directory?
102
+ Dir.rmdir(file_path)
103
+ else
104
+ File.unlink(file_path)
105
+ end
106
+ end
107
+
108
+ # HTTP COPY request.
109
+ #
110
+ # Copy this resource to given destination resource.
111
+ def copy(dest)
112
+ if stat.directory?
113
+ dest.make_collection
114
+ else
115
+ open(file_path, "rb") do |file|
116
+ dest.write(file)
117
+ end
118
+ end
119
+ end
120
+
121
+ # HTTP MOVE request.
122
+ #
123
+ # Move this resource to given destination resource.
124
+ def move(dest)
125
+ copy(dest)
126
+ delete
127
+ end
128
+
129
+ # HTTP MKCOL request.
130
+ #
131
+ # Create this resource as collection.
132
+ def make_collection
133
+ Dir.mkdir(file_path)
134
+ end
135
+
136
+ # Write to this resource from given IO.
137
+ def write(io)
138
+ tempfile = "#{file_path}.#{Process.pid}.#{object_id}"
139
+
140
+ open(tempfile, "wb") do |file|
141
+ while part = io.read(8192)
142
+ file << part
143
+ end
144
+ end
145
+
146
+ File.rename(tempfile, file_path)
147
+ ensure
148
+ File.unlink(tempfile) rescue nil
149
+ end
150
+
151
+
152
+ private
153
+
154
+ def root
155
+ @options[:root]
156
+ end
157
+
158
+ def file_path
159
+ root + '/' + path
160
+ end
161
+
162
+ def stat
163
+ @stat ||= File.stat(file_path)
164
+ end
165
+
166
+ end
167
+
168
+ end
@@ -0,0 +1,41 @@
1
+ module RackDAV
2
+
3
+ class Handler
4
+
5
+ # @return [Hash] The hash of options.
6
+ attr_reader :options
7
+
8
+
9
+ # Initializes a new instance with given options.
10
+ #
11
+ # @param [Hash] options Hash of options to customize the handler behavior.
12
+ # @option options [Class] :resource_class (FileResource)
13
+ # The resource class.
14
+ # @option options [String] :root (".")
15
+ # The root resource folder.
16
+ #
17
+ def initialize(options = {})
18
+ @options = {
19
+ :resource_class => FileResource,
20
+ :root => Dir.pwd
21
+ }.merge(options)
22
+ end
23
+
24
+ def call(env)
25
+ request = Rack::Request.new(env)
26
+ response = Rack::Response.new
27
+
28
+ begin
29
+ controller = Controller.new(request, response, @options)
30
+ controller.send(request.request_method.downcase)
31
+
32
+ rescue HTTPStatus::Status => status
33
+ response.status = status.code
34
+ end
35
+
36
+ response.finish
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -0,0 +1,108 @@
1
+ module RackDAV
2
+
3
+ module HTTPStatus
4
+
5
+ class Status < Exception
6
+
7
+ class << self
8
+ attr_accessor :code, :reason_phrase
9
+ alias_method :to_i, :code
10
+
11
+ def status_line
12
+ "#{code} #{reason_phrase}"
13
+ end
14
+
15
+ end
16
+
17
+ def code
18
+ self.class.code
19
+ end
20
+
21
+ def reason_phrase
22
+ self.class.reason_phrase
23
+ end
24
+
25
+ def status_line
26
+ self.class.status_line
27
+ end
28
+
29
+ def to_i
30
+ self.class.to_i
31
+ end
32
+
33
+ end
34
+
35
+ StatusMessage = {
36
+ 100 => 'Continue',
37
+ 101 => 'Switching Protocols',
38
+ 102 => 'Processing',
39
+ 200 => 'OK',
40
+ 201 => 'Created',
41
+ 202 => 'Accepted',
42
+ 203 => 'Non-Authoritative Information',
43
+ 204 => 'No Content',
44
+ 205 => 'Reset Content',
45
+ 206 => 'Partial Content',
46
+ 207 => 'Multi-Status',
47
+ 300 => 'Multiple Choices',
48
+ 301 => 'Moved Permanently',
49
+ 302 => 'Found',
50
+ 303 => 'See Other',
51
+ 304 => 'Not Modified',
52
+ 305 => 'Use Proxy',
53
+ 307 => 'Temporary Redirect',
54
+ 400 => 'Bad Request',
55
+ 401 => 'Unauthorized',
56
+ 402 => 'Payment Required',
57
+ 403 => 'Forbidden',
58
+ 404 => 'Not Found',
59
+ 405 => 'Method Not Allowed',
60
+ 406 => 'Not Acceptable',
61
+ 407 => 'Proxy Authentication Required',
62
+ 408 => 'Request Timeout',
63
+ 409 => 'Conflict',
64
+ 410 => 'Gone',
65
+ 411 => 'Length Required',
66
+ 412 => 'Precondition Failed',
67
+ 413 => 'Request Entity Too Large',
68
+ 414 => 'Request-URI Too Large',
69
+ 415 => 'Unsupported Media Type',
70
+ 416 => 'Request Range Not Satisfiable',
71
+ 417 => 'Expectation Failed',
72
+ 422 => 'Unprocessable Entity',
73
+ 423 => 'Locked',
74
+ 424 => 'Failed Dependency',
75
+ 500 => 'Internal Server Error',
76
+ 501 => 'Not Implemented',
77
+ 502 => 'Bad Gateway',
78
+ 503 => 'Service Unavailable',
79
+ 504 => 'Gateway Timeout',
80
+ 505 => 'HTTP Version Not Supported',
81
+ 507 => 'Insufficient Storage'
82
+ }
83
+
84
+ StatusMessage.each do |code, reason_phrase|
85
+ klass = Class.new(Status)
86
+ klass.code = code
87
+ klass.reason_phrase = reason_phrase
88
+ klass_name = reason_phrase.gsub(/[ \-]/,'')
89
+ const_set(klass_name, klass)
90
+ end
91
+
92
+ end
93
+
94
+ end
95
+
96
+
97
+ module Rack
98
+ class Response
99
+ module Helpers
100
+ RackDAV::HTTPStatus::StatusMessage.each do |code, reason_phrase|
101
+ name = reason_phrase.gsub(/[ \-]/,'_').downcase
102
+ define_method(name + '?') do
103
+ @status == code
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,185 @@
1
+ module RackDAV
2
+
3
+ class Resource
4
+
5
+ attr_reader :path, :options
6
+
7
+ def initialize(path, options)
8
+ @path = path
9
+ @options = options
10
+ end
11
+
12
+ # If this is a collection, return the child resources.
13
+ def children
14
+ raise NotImplementedError
15
+ end
16
+
17
+ # Is this resource a collection?
18
+ def collection?
19
+ raise NotImplementedError
20
+ end
21
+
22
+ # Does this recource exist?
23
+ def exist?
24
+ raise NotImplementedError
25
+ end
26
+
27
+ # Return the creation time.
28
+ def creation_date
29
+ raise NotImplementedError
30
+ end
31
+
32
+ # Return the time of last modification.
33
+ def last_modified
34
+ raise NotImplementedError
35
+ end
36
+
37
+ # Set the time of last modification.
38
+ def last_modified=(time)
39
+ raise NotImplementedError
40
+ end
41
+
42
+ # Return an Etag, an unique hash value for this resource.
43
+ def etag
44
+ raise NotImplementedError
45
+ end
46
+
47
+ # Return the resource type.
48
+ #
49
+ # If this is a collection, return
50
+ # REXML::Element.new('D:collection')
51
+ def resource_type
52
+ if collection?
53
+ REXML::Element.new('D:collection')
54
+ end
55
+ end
56
+
57
+ # Return the mime type of this resource.
58
+ def content_type
59
+ raise NotImplementedError
60
+ end
61
+
62
+ # Return the size in bytes for this resource.
63
+ def content_length
64
+ raise NotImplementedError
65
+ end
66
+
67
+ # HTTP GET request.
68
+ #
69
+ # Write the content of the resource to the response.body.
70
+ def get(request, response)
71
+ raise NotImplementedError
72
+ end
73
+
74
+ # HTTP PUT request.
75
+ #
76
+ # Save the content of the request.body.
77
+ def put(request, response)
78
+ raise NotImplementedError
79
+ end
80
+
81
+ # HTTP POST request.
82
+ #
83
+ # Usually forbidden.
84
+ def post(request, response)
85
+ raise NotImplementedError
86
+ end
87
+
88
+ # HTTP DELETE request.
89
+ #
90
+ # Delete this resource.
91
+ def delete
92
+ raise NotImplementedError
93
+ end
94
+
95
+ # HTTP COPY request.
96
+ #
97
+ # Copy this resource to given destination resource.
98
+ def copy(dest)
99
+ raise NotImplementedError
100
+ end
101
+
102
+ # HTTP MOVE request.
103
+ #
104
+ # Move this resource to given destination resource.
105
+ def move(dest)
106
+ copy(dest)
107
+ delete
108
+ end
109
+
110
+ # HTTP MKCOL request.
111
+ #
112
+ # Create this resource as collection.
113
+ def make_collection
114
+ raise NotImplementedError
115
+ end
116
+
117
+ def ==(other)
118
+ path == other.path
119
+ end
120
+
121
+ def name
122
+ File.basename(path)
123
+ end
124
+
125
+ def display_name
126
+ name
127
+ end
128
+
129
+ def child(name, option={})
130
+ self.class.new(path + '/' + name, options)
131
+ end
132
+
133
+ def lockable?
134
+ self.respond_to?(:lock) && self.respond_to?(:unlock)
135
+ end
136
+
137
+ def property_names
138
+ %w(creationdate displayname getlastmodified getetag resourcetype getcontenttype getcontentlength)
139
+ end
140
+
141
+ def get_property(name)
142
+ case name
143
+ when 'resourcetype' then resource_type
144
+ when 'displayname' then display_name
145
+ when 'creationdate' then creation_date.xmlschema
146
+ when 'getcontentlength' then content_length.to_s
147
+ when 'getcontenttype' then content_type
148
+ when 'getetag' then etag
149
+ when 'getlastmodified' then last_modified.httpdate
150
+ end
151
+ end
152
+
153
+ def set_property(name, value)
154
+ case name
155
+ when 'resourcetype' then self.resource_type = value
156
+ when 'getcontenttype' then self.content_type = value
157
+ when 'getetag' then self.etag = value
158
+ when 'getlastmodified' then self.last_modified = Time.httpdate(value)
159
+ end
160
+ rescue ArgumentError
161
+ raise HTTPStatus::Conflict
162
+ end
163
+
164
+ def remove_property(name)
165
+ raise HTTPStatus::Forbidden
166
+ end
167
+
168
+ def parent
169
+ elements = @path.scan(/[^\/]+/)
170
+ return nil if elements.empty?
171
+ self.class.new('/' + elements[0..-2].to_a.join('/'), @options)
172
+ end
173
+
174
+ def descendants
175
+ list = []
176
+ children.each do |child|
177
+ list << child
178
+ list.concat(child.descendants)
179
+ end
180
+ list
181
+ end
182
+
183
+ end
184
+
185
+ end