rack_dav 0.1.2

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
+ response.body = ""
73
+ Rack::Directory.new(root).call(request.env)[2].each do |line|
74
+ response.body << line
75
+ end
76
+ response['Content-Length'] = response.body.size.to_s
77
+ else
78
+ file = Rack::File.new(nil)
79
+ file.path = file_path
80
+ response.body = file
81
+ end
82
+ end
83
+
84
+ # HTTP PUT request.
85
+ #
86
+ # Save the content of the request.body.
87
+ def put(request, response)
88
+ write(request.body)
89
+ end
90
+
91
+ # HTTP POST request.
92
+ #
93
+ # Usually forbidden.
94
+ def post(request, response)
95
+ raise HTTPStatus::Forbidden
96
+ end
97
+
98
+ # HTTP DELETE request.
99
+ #
100
+ # Delete this resource.
101
+ def delete
102
+ if stat.directory?
103
+ Dir.rmdir(file_path)
104
+ else
105
+ File.unlink(file_path)
106
+ end
107
+ end
108
+
109
+ # HTTP COPY request.
110
+ #
111
+ # Copy this resource to given destination resource.
112
+ def copy(dest)
113
+ if stat.directory?
114
+ dest.make_collection
115
+ else
116
+ open(file_path, "rb") do |file|
117
+ dest.write(file)
118
+ end
119
+ end
120
+ end
121
+
122
+ # HTTP MOVE request.
123
+ #
124
+ # Move this resource to given destination resource.
125
+ def move(dest)
126
+ copy(dest)
127
+ delete
128
+ end
129
+
130
+ # HTTP MKCOL request.
131
+ #
132
+ # Create this resource as collection.
133
+ def make_collection
134
+ Dir.mkdir(file_path)
135
+ end
136
+
137
+ # Write to this resource from given IO.
138
+ def write(io)
139
+ tempfile = "#{file_path}.#{Process.pid}.#{object_id}"
140
+
141
+ open(tempfile, "wb") do |file|
142
+ while part = io.read(8192)
143
+ file << part
144
+ end
145
+ end
146
+
147
+ File.rename(tempfile, file_path)
148
+ ensure
149
+ File.unlink(tempfile) rescue nil
150
+ end
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,34 @@
1
+ module RackDAV
2
+
3
+ class Handler
4
+
5
+ def initialize(options={})
6
+ @options = {
7
+ :resource_class => FileResource,
8
+ :root => Dir.pwd
9
+ }.merge(options)
10
+ end
11
+
12
+ def call(env)
13
+ request = Rack::Request.new(env)
14
+ response = Rack::Response.new
15
+
16
+ begin
17
+ controller = Controller.new(request, response, @options.dup)
18
+ controller.send(request.request_method.downcase)
19
+
20
+ rescue HTTPStatus::Status => status
21
+ response.status = status.code
22
+ end
23
+
24
+ # Strings in Ruby 1.9 are no longer enumerable. Rack still expects the response.body to be
25
+ # enumerable, however.
26
+ response.body = [response.body] if not response.body.respond_to? :each
27
+
28
+ response.status = response.status ? response.status.to_i : 200
29
+ response.finish
30
+ end
31
+
32
+ end
33
+
34
+ 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,180 @@
1
+ module RackDAV
2
+
3
+ class Resource
4
+ attr_reader :path, :options
5
+
6
+ def initialize(path, options)
7
+ @path = path
8
+ @options = options
9
+ end
10
+
11
+ # If this is a collection, return the child resources.
12
+ def children
13
+ raise NotImplementedError
14
+ end
15
+
16
+ # Is this resource a collection?
17
+ def collection?
18
+ raise NotImplementedError
19
+ end
20
+
21
+ # Does this recource exist?
22
+ def exist?
23
+ raise NotImplementedError
24
+ end
25
+
26
+ # Return the creation time.
27
+ def creation_date
28
+ raise NotImplementedError
29
+ end
30
+
31
+ # Return the time of last modification.
32
+ def last_modified
33
+ raise NotImplementedError
34
+ end
35
+
36
+ # Set the time of last modification.
37
+ def last_modified=(time)
38
+ raise NotImplementedError
39
+ end
40
+
41
+ # Return an Etag, an unique hash value for this resource.
42
+ def etag
43
+ raise NotImplementedError
44
+ end
45
+
46
+ # Return the resource type.
47
+ #
48
+ # If this is a collection, return
49
+ # REXML::Element.new('D:collection')
50
+ def resource_type
51
+ if collection?
52
+ REXML::Element.new('D:collection')
53
+ end
54
+ end
55
+
56
+ # Return the mime type of this resource.
57
+ def content_type
58
+ raise NotImplementedError
59
+ end
60
+
61
+ # Return the size in bytes for this resource.
62
+ def content_length
63
+ raise NotImplementedError
64
+ end
65
+
66
+ # HTTP GET request.
67
+ #
68
+ # Write the content of the resource to the response.body.
69
+ def get(request, response)
70
+ raise NotImplementedError
71
+ end
72
+
73
+ # HTTP PUT request.
74
+ #
75
+ # Save the content of the request.body.
76
+ def put(request, response)
77
+ raise NotImplementedError
78
+ end
79
+
80
+ # HTTP POST request.
81
+ #
82
+ # Usually forbidden.
83
+ def post(request, response)
84
+ raise NotImplementedError
85
+ end
86
+
87
+ # HTTP DELETE request.
88
+ #
89
+ # Delete this resource.
90
+ def delete
91
+ raise NotImplementedError
92
+ end
93
+
94
+ # HTTP COPY request.
95
+ #
96
+ # Copy this resource to given destination resource.
97
+ def copy(dest)
98
+ raise NotImplementedError
99
+ end
100
+
101
+ # HTTP MOVE request.
102
+ #
103
+ # Move this resource to given destination resource.
104
+ def move(dest)
105
+ copy(dest)
106
+ delete
107
+ end
108
+
109
+ # HTTP MKCOL request.
110
+ #
111
+ # Create this resource as collection.
112
+ def make_collection
113
+ raise NotImplementedError
114
+ end
115
+
116
+ def ==(other)
117
+ path == other.path
118
+ end
119
+
120
+ def name
121
+ File.basename(path)
122
+ end
123
+
124
+ def display_name
125
+ name
126
+ end
127
+
128
+ def child(name, option={})
129
+ self.class.new(path + '/' + name, options)
130
+ end
131
+
132
+ def property_names
133
+ %w(creationdate displayname getlastmodified getetag resourcetype getcontenttype getcontentlength)
134
+ end
135
+
136
+ def get_property(name)
137
+ case name
138
+ when 'resourcetype' then resource_type
139
+ when 'displayname' then display_name
140
+ when 'creationdate' then creation_date.xmlschema
141
+ when 'getcontentlength' then content_length.to_s
142
+ when 'getcontenttype' then content_type
143
+ when 'getetag' then etag
144
+ when 'getlastmodified' then last_modified.httpdate
145
+ end
146
+ end
147
+
148
+ def set_property(name, value)
149
+ case name
150
+ when 'resourcetype' then self.resource_type = value
151
+ when 'getcontenttype' then self.content_type = value
152
+ when 'getetag' then self.etag = value
153
+ when 'getlastmodified' then self.last_modified = Time.httpdate(value)
154
+ end
155
+ rescue ArgumentError
156
+ raise HTTPStatus::Conflict
157
+ end
158
+
159
+ def remove_property(name)
160
+ raise HTTPStatus::Forbidden
161
+ end
162
+
163
+ def parent
164
+ elements = @path.scan(/[^\/]+/)
165
+ return nil if elements.empty?
166
+ self.class.new('/' + elements[0..-2].to_a.join('/'), @options)
167
+ end
168
+
169
+ def descendants
170
+ list = []
171
+ children.each do |child|
172
+ list << child
173
+ list.concat(child.descendants)
174
+ end
175
+ list
176
+ end
177
+
178
+ end
179
+
180
+ end
data/lib/rack_dav.rb ADDED
@@ -0,0 +1,15 @@
1
+ require 'rubygems'
2
+ require 'builder'
3
+ require 'time'
4
+ require 'uri'
5
+ require 'rexml/document'
6
+ require 'webrick/httputils'
7
+
8
+ require 'rack'
9
+ require 'rack_dav/builder_namespace'
10
+ require 'rack_dav/http_status'
11
+ require 'rack_dav/resource'
12
+ require 'rack_dav/file_resource'
13
+ require 'rack_dav/handler'
14
+ require 'rack_dav/controller'
15
+
data/rack_dav.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'rack_dav'
3
+ s.version = '0.1.2'
4
+ s.summary = 'WebDAV handler for Rack'
5
+ s.author = 'Matthias Georgi'
6
+ s.email = 'matti.georgi@gmail.com'
7
+ s.homepage = 'http://www.matthias-georgi.de/rack_dav'
8
+ s.description = 'WebDAV handler for Rack'
9
+ s.require_path = 'lib'
10
+ s.executables << 'rack_dav'
11
+ s.has_rdoc = true
12
+ s.extra_rdoc_files = ['README.md']
13
+ s.files = %w{
14
+ .gitignore
15
+ LICENSE
16
+ rack_dav.gemspec
17
+ lib/rack_dav.rb
18
+ lib/rack_dav/file_resource.rb
19
+ lib/rack_dav/handler.rb
20
+ lib/rack_dav/controller.rb
21
+ lib/rack_dav/builder_namespace.rb
22
+ lib/rack_dav/http_status.rb
23
+ lib/rack_dav/resource.rb
24
+ bin/rack_dav
25
+ spec/handler_spec.rb
26
+ README.md
27
+ }
28
+ end