dav4rack 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,176 @@
1
+ require 'webrick/httputils'
2
+
3
+ module DAV4Rack
4
+
5
+ class FileResource < Resource
6
+
7
+ include WEBrick::HTTPUtils
8
+
9
+ def authenticate(user, pass)
10
+ if(options[:username])
11
+ options[:username] == user && options[:password] == pass
12
+ else
13
+ true
14
+ end
15
+ end
16
+
17
+ # If this is a collection, return the child resources.
18
+ def children
19
+ Dir[file_path + '/*'].map do |path|
20
+ child File.basename(path)
21
+ end
22
+ end
23
+
24
+ # Is this resource a collection?
25
+ def collection?
26
+ File.directory?(file_path)
27
+ end
28
+
29
+ # Does this recource exist?
30
+ def exist?
31
+ File.exist?(file_path)
32
+ end
33
+
34
+ # Return the creation time.
35
+ def creation_date
36
+ stat.ctime
37
+ end
38
+
39
+ # Return the time of last modification.
40
+ def last_modified
41
+ stat.mtime
42
+ end
43
+
44
+ # Set the time of last modification.
45
+ def last_modified=(time)
46
+ File.utime(Time.now, time, file_path)
47
+ end
48
+
49
+ # Return an Etag, an unique hash value for this resource.
50
+ def etag
51
+ sprintf('%x-%x-%x', stat.ino, stat.size, stat.mtime.to_i)
52
+ end
53
+
54
+ # Return the mime type of this resource.
55
+ def content_type
56
+ if stat.directory?
57
+ "text/html"
58
+ else
59
+ mime_type(file_path, DefaultMimeTypes)
60
+ end
61
+ end
62
+
63
+ # Return the size in bytes for this resource.
64
+ def content_length
65
+ stat.size
66
+ end
67
+
68
+ # HTTP GET request.
69
+ #
70
+ # Write the content of the resource to the response.body.
71
+ def get(request, response)
72
+ raise NotFound unless exist?
73
+ if stat.directory?
74
+ response.body = ""
75
+ Rack::Directory.new(root).call(request.env)[2].each do |line|
76
+ response.body << line
77
+ end
78
+ response['Content-Length'] = response.body.size.to_s
79
+ else
80
+ file = Rack::File.new(nil)
81
+ file.path = file_path
82
+ response.body = file
83
+ end
84
+ OK
85
+ end
86
+
87
+ # HTTP PUT request.
88
+ #
89
+ # Save the content of the request.body.
90
+ def put(request, response)
91
+ write(request.body)
92
+ Created
93
+ end
94
+
95
+ # HTTP POST request.
96
+ #
97
+ # Usually forbidden.
98
+ def post(request, response)
99
+ raise HTTPStatus::Forbidden
100
+ end
101
+
102
+ # HTTP DELETE request.
103
+ #
104
+ # Delete this resource.
105
+ def delete
106
+ if stat.directory?
107
+ Dir.rmdir(file_path)
108
+ else
109
+ File.unlink(file_path)
110
+ end
111
+ NoContent
112
+ end
113
+
114
+ # HTTP COPY request.
115
+ #
116
+ # Copy this resource to given destination resource.
117
+ def copy(dest)
118
+ if stat.directory?
119
+ dest.make_collection
120
+ else
121
+ open(file_path, "rb") do |file|
122
+ dest.write(file)
123
+ end
124
+ end
125
+ OK
126
+ end
127
+
128
+ # HTTP MOVE request.
129
+ #
130
+ # Move this resource to given destination resource.
131
+ def move(dest)
132
+ copy(dest)
133
+ delete
134
+ OK
135
+ end
136
+
137
+ # HTTP MKCOL request.
138
+ #
139
+ # Create this resource as collection.
140
+ def make_collection
141
+ Dir.mkdir(file_path)
142
+ NoContent
143
+ end
144
+
145
+ # Write to this resource from given IO.
146
+ def write(io)
147
+ tempfile = "#{file_path}.#{Process.pid}.#{object_id}"
148
+
149
+ open(tempfile, "wb") do |file|
150
+ while part = io.read(8192)
151
+ file << part
152
+ end
153
+ end
154
+
155
+ File.rename(tempfile, file_path)
156
+ ensure
157
+ File.unlink(tempfile) rescue nil
158
+ end
159
+
160
+ private
161
+
162
+ def root
163
+ @options[:root]
164
+ end
165
+
166
+ def file_path
167
+ root + '/' + path
168
+ end
169
+
170
+ def stat
171
+ @stat ||= File.stat(file_path)
172
+ end
173
+
174
+ end
175
+
176
+ end
@@ -0,0 +1,44 @@
1
+ module DAV4Rack
2
+
3
+ class Handler
4
+ include DAV4Rack::HTTPStatus
5
+ def initialize(options={})
6
+ @options = options.dup
7
+ unless(@options[:resource_class])
8
+ require 'dav4rack/file_resource'
9
+ @options[:resource_class] = FileResource
10
+ @options[:root] ||= Dir.pwd
11
+ end
12
+ end
13
+
14
+ def call(env)
15
+
16
+ request = Rack::Request.new(env)
17
+ response = Rack::Response.new
18
+
19
+ begin
20
+ controller = Controller.new(request, response, @options.dup)
21
+ res = controller.send(request.request_method.downcase)
22
+ response.status = res.code if res.respond_to?(:code)
23
+ rescue HTTPStatus::Status => status
24
+ response.status = status.code
25
+ end
26
+
27
+ # Strings in Ruby 1.9 are no longer enumerable. Rack still expects the response.body to be
28
+ # enumerable, however.
29
+
30
+ response['Content-Length'] = response.body.to_s.length unless response['Content-Length'] || response.body.empty?
31
+ response.body = [response.body] if not response.body.respond_to? :each
32
+ response.status = response.status ? response.status.to_i : 200
33
+ response.headers.each_pair{|k,v| response[k] = v.to_s}
34
+
35
+ # Apache wants the body dealt with, so just read it and junk it
36
+ buf = true
37
+ buf = request.body.read(8192) while buf
38
+
39
+ response.finish
40
+ end
41
+
42
+ end
43
+
44
+ end
@@ -0,0 +1,108 @@
1
+ module DAV4Rack
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
+ DAV4Rack::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,20 @@
1
+ require 'dav4rack/interceptor_resource'
2
+ module DAV4Rack
3
+ class Interceptor
4
+ def initialize(app, args={})
5
+ @roots = args[:mappings].keys
6
+ @args = args
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ path = env['PATH_INFO'].downcase
12
+ method = env['REQUEST_METHOD'].upcase
13
+ app = nil
14
+ if(@roots.detect{|x| path =~ /^#{Regexp.escape(x.downcase)}\/?/}.nil? && %w(OPTIONS PUT PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK).include?(method))
15
+ app = DAV4Rack::Handler.new(:resource_class => InterceptorResource, :mappings => @args[:mappings])
16
+ end
17
+ app ? app.call(env) : @app.call(env)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,119 @@
1
+ require 'digest/sha1'
2
+
3
+ module DAV4Rack
4
+
5
+ class InterceptorResource < Resource
6
+ attr_reader :path, :options
7
+
8
+ def initialize(*args)
9
+ super
10
+ @root_paths = @options[:mappings].keys
11
+ @mappings = @options[:mappings]
12
+ end
13
+
14
+ def children
15
+ childs = @root_paths.find_all{|x|x =~ /^#{Regexp.escape(@path)}/}
16
+ childs = childs.map{|a| child a.gsub(/^#{Regexp.escape(@path)}/, '').split('/').delete_if{|x|x.empty?}.first }.flatten
17
+ end
18
+
19
+ def collection?
20
+ true if exist?
21
+ end
22
+
23
+ def exist?
24
+ !@root_paths.find_all{|x| x =~ /^#{Regexp.escape(@path)}/}.empty?
25
+ end
26
+
27
+ def creation_date
28
+ Time.now
29
+ end
30
+
31
+ def last_modified
32
+ Time.now
33
+ end
34
+
35
+ def last_modified=(time)
36
+ Time.now
37
+ end
38
+
39
+ def etag
40
+ Digest::SHA1.hexdigest(@path)
41
+ end
42
+
43
+ def content_type
44
+ 'text/html'
45
+ end
46
+
47
+ def content_length
48
+ 0
49
+ end
50
+
51
+ def get(request, response)
52
+ raise Forbidden
53
+ end
54
+
55
+ def put(request, response)
56
+ raise Forbidden
57
+ end
58
+
59
+ def post(request, response)
60
+ raise Forbidden
61
+ end
62
+
63
+ def delete
64
+ raise Forbidden
65
+ end
66
+
67
+ def copy(dest)
68
+ raise Forbidden
69
+ end
70
+
71
+ def move(dest)
72
+ raise Forbidden
73
+ end
74
+
75
+ def make_collection
76
+ raise Forbidden
77
+ end
78
+
79
+ def ==(other)
80
+ path == other.path
81
+ end
82
+
83
+ def name
84
+ File.basename(path)
85
+ end
86
+
87
+ def display_name
88
+ File.basename(path.to_s)
89
+ end
90
+
91
+ def child(name, option={})
92
+ new_path = path.dup
93
+ new_path = '/' + new_path unless new_path[0,1] == '/'
94
+ new_path.slice!(-1) if new_path[-1,1] == '/'
95
+ name = '/' + name unless name[-1,1] == '/'
96
+ new_path = "#{new_path}#{name}"
97
+ new_public = public_path.dup
98
+ new_public = '/' + new_public unless new_public[0,1] == '/'
99
+ new_public.slice!(-1) if new_public[-1,1] == '/'
100
+ new_public = "#{new_public}#{name}"
101
+ if(key = @root_paths.find{|x| new_path =~ /^#{Regexp.escape(x.downcase)}\/?/})
102
+ @mappings[key][:resource_class].new(new_public, new_path.gsub(key, ''), request, {:root_uri_path => key}.merge(@mappings[key][:options] ? @mappings[key][:options] : options))
103
+ else
104
+ self.class.new(new_public, new_path, request, options)
105
+ end
106
+ end
107
+
108
+ def descendants
109
+ list = []
110
+ children.each do |child|
111
+ list << child
112
+ list.concat(child.descendants)
113
+ end
114
+ list
115
+ end
116
+
117
+ end
118
+
119
+ end