dav4rack 0.0.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.
@@ -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