rack_dav_sp 0.2.dev

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