rack_dav 0.1.3 → 0.3.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.
@@ -1,8 +1,10 @@
1
+ require 'digest'
2
+
1
3
  module RackDAV
2
4
 
3
5
  class FileResource < Resource
4
6
  include WEBrick::HTTPUtils
5
-
7
+
6
8
  # If this is a collection, return the child resources.
7
9
  def children
8
10
  Dir[file_path + '/*'].map do |path|
@@ -19,7 +21,7 @@ module RackDAV
19
21
  def exist?
20
22
  File.exist?(file_path)
21
23
  end
22
-
24
+
23
25
  # Return the creation time.
24
26
  def creation_date
25
27
  stat.ctime
@@ -29,7 +31,7 @@ module RackDAV
29
31
  def last_modified
30
32
  stat.mtime
31
33
  end
32
-
34
+
33
35
  # Set the time of last modification.
34
36
  def last_modified=(time)
35
37
  File.utime(Time.now, time, file_path)
@@ -42,11 +44,10 @@ module RackDAV
42
44
 
43
45
  # Return the resource type.
44
46
  #
45
- # If this is a collection, return
46
- # REXML::Element.new('D:collection')
47
+ # If this is a collection, return a collection element
47
48
  def resource_type
48
49
  if collection?
49
- REXML::Element.new('D:collection')
50
+ Nokogiri::XML::fragment('<D:collection xmlns:D="DAV:"/>').children.first
50
51
  end
51
52
  end
52
53
 
@@ -54,7 +55,7 @@ module RackDAV
54
55
  def content_type
55
56
  if stat.directory?
56
57
  "text/html"
57
- else
58
+ else
58
59
  mime_type(file_path, DefaultMimeTypes)
59
60
  end
60
61
  end
@@ -69,14 +70,12 @@ module RackDAV
69
70
  # Write the content of the resource to the response.body.
70
71
  def get(request, response)
71
72
  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
73
+ content = ""
74
+ Rack::Directory.new(root).call(request.env)[2].each { |line| content << line }
75
+ response.body = [content]
76
+ response['Content-Length'] = (content.respond_to?(:bytesize) ? content.bytesize : content.size).to_s
77
77
  else
78
- file = Rack::File.new(nil)
79
- file.path = file_path
78
+ file = File.open(file_path)
80
79
  response.body = file
81
80
  end
82
81
  end
@@ -85,16 +84,20 @@ module RackDAV
85
84
  #
86
85
  # Save the content of the request.body.
87
86
  def put(request, response)
87
+ if request.env['HTTP_CONTENT_MD5']
88
+ content_md5_pass?(request.env) or raise HTTPStatus::BadRequest.new('Content-MD5 mismatch')
89
+ end
90
+
88
91
  write(request.body)
89
92
  end
90
-
93
+
91
94
  # HTTP POST request.
92
95
  #
93
96
  # Usually forbidden.
94
97
  def post(request, response)
95
98
  raise HTTPStatus::Forbidden
96
99
  end
97
-
100
+
98
101
  # HTTP DELETE request.
99
102
  #
100
103
  # Delete this resource.
@@ -105,7 +108,7 @@ module RackDAV
105
108
  File.unlink(file_path)
106
109
  end
107
110
  end
108
-
111
+
109
112
  # HTTP COPY request.
110
113
  #
111
114
  # Copy this resource to given destination resource.
@@ -118,7 +121,7 @@ module RackDAV
118
121
  end
119
122
  end
120
123
  end
121
-
124
+
122
125
  # HTTP MOVE request.
123
126
  #
124
127
  # Move this resource to given destination resource.
@@ -126,42 +129,55 @@ module RackDAV
126
129
  copy(dest)
127
130
  delete
128
131
  end
129
-
132
+
130
133
  # HTTP MKCOL request.
131
134
  #
132
135
  # Create this resource as collection.
133
136
  def make_collection
134
137
  Dir.mkdir(file_path)
135
138
  end
136
-
139
+
137
140
  # Write to this resource from given IO.
138
141
  def write(io)
139
142
  tempfile = "#{file_path}.#{Process.pid}.#{object_id}"
140
-
143
+
141
144
  open(tempfile, "wb") do |file|
142
145
  while part = io.read(8192)
143
146
  file << part
144
147
  end
145
148
  end
146
149
 
147
- File.rename(tempfile, file_path)
150
+ File.rename(tempfile, file_path)
148
151
  ensure
149
152
  File.unlink(tempfile) rescue nil
150
153
  end
151
-
154
+
155
+
152
156
  private
153
157
 
154
- def root
155
- @options[:root]
156
- end
158
+ def root
159
+ @options[:root]
160
+ end
157
161
 
158
- def file_path
159
- root + '/' + path
160
- end
162
+ def file_path
163
+ root + '/' + path
164
+ end
161
165
 
162
- def stat
163
- @stat ||= File.stat(file_path)
164
- end
166
+ def stat
167
+ @stat ||= File.stat(file_path)
168
+ end
169
+
170
+ def content_md5_pass?(env)
171
+ expected = env['HTTP_CONTENT_MD5'] or return true
172
+
173
+ body = env['rack.input'].dup
174
+ digest = Digest::MD5.new.digest(body.read)
175
+ actual = [ digest ].pack('m').strip
176
+
177
+ body.rewind
178
+
179
+ expected == actual
180
+ end
165
181
 
166
182
  end
167
183
 
@@ -1,8 +1,20 @@
1
1
  module RackDAV
2
-
2
+
3
3
  class Handler
4
-
5
- def initialize(options={})
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 = {})
6
18
  @options = {
7
19
  :resource_class => FileResource,
8
20
  :root => Dir.pwd
@@ -10,25 +22,20 @@ module RackDAV
10
22
  end
11
23
 
12
24
  def call(env)
13
- request = Rack::Request.new(env)
25
+ request = Rack::Request.new(env)
14
26
  response = Rack::Response.new
15
27
 
16
28
  begin
17
- controller = Controller.new(request, response, @options.dup)
29
+ controller = Controller.new(request, response, @options)
18
30
  controller.send(request.request_method.downcase)
19
-
31
+
20
32
  rescue HTTPStatus::Status => status
21
33
  response.status = status.code
22
34
  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
35
 
28
- response.status = response.status ? response.status.to_i : 200
29
36
  response.finish
30
37
  end
31
-
38
+
32
39
  end
33
40
 
34
41
  end
@@ -1,19 +1,19 @@
1
1
  module RackDAV
2
2
 
3
3
  module HTTPStatus
4
-
4
+
5
5
  class Status < Exception
6
-
6
+
7
7
  class << self
8
8
  attr_accessor :code, :reason_phrase
9
9
  alias_method :to_i, :code
10
-
10
+
11
11
  def status_line
12
12
  "#{code} #{reason_phrase}"
13
13
  end
14
-
14
+
15
15
  end
16
-
16
+
17
17
  def code
18
18
  self.class.code
19
19
  end
@@ -21,17 +21,17 @@ module RackDAV
21
21
  def reason_phrase
22
22
  self.class.reason_phrase
23
23
  end
24
-
24
+
25
25
  def status_line
26
26
  self.class.status_line
27
27
  end
28
-
28
+
29
29
  def to_i
30
30
  self.class.to_i
31
31
  end
32
-
32
+
33
33
  end
34
-
34
+
35
35
  StatusMessage = {
36
36
  100 => 'Continue',
37
37
  101 => 'Switching Protocols',
@@ -71,7 +71,7 @@ module RackDAV
71
71
  417 => 'Expectation Failed',
72
72
  422 => 'Unprocessable Entity',
73
73
  423 => 'Locked',
74
- 424 => 'Failed Dependency',
74
+ 424 => 'Failed Dependency',
75
75
  500 => 'Internal Server Error',
76
76
  501 => 'Not Implemented',
77
77
  502 => 'Bad Gateway',
@@ -1,13 +1,14 @@
1
1
  module RackDAV
2
2
 
3
3
  class Resource
4
+
4
5
  attr_reader :path, :options
5
-
6
+
6
7
  def initialize(path, options)
7
8
  @path = path
8
9
  @options = options
9
10
  end
10
-
11
+
11
12
  # If this is a collection, return the child resources.
12
13
  def children
13
14
  raise NotImplementedError
@@ -22,7 +23,7 @@ module RackDAV
22
23
  def exist?
23
24
  raise NotImplementedError
24
25
  end
25
-
26
+
26
27
  # Return the creation time.
27
28
  def creation_date
28
29
  raise NotImplementedError
@@ -32,7 +33,7 @@ module RackDAV
32
33
  def last_modified
33
34
  raise NotImplementedError
34
35
  end
35
-
36
+
36
37
  # Set the time of last modification.
37
38
  def last_modified=(time)
38
39
  raise NotImplementedError
@@ -45,11 +46,10 @@ module RackDAV
45
46
 
46
47
  # Return the resource type.
47
48
  #
48
- # If this is a collection, return
49
- # REXML::Element.new('D:collection')
49
+ # If this is a collection, return a collection element
50
50
  def resource_type
51
51
  if collection?
52
- REXML::Element.new('D:collection')
52
+ Nokogiri::XML::fragment('<D:collection xmlns:D="DAV:"/>').children.first
53
53
  end
54
54
  end
55
55
 
@@ -76,28 +76,28 @@ module RackDAV
76
76
  def put(request, response)
77
77
  raise NotImplementedError
78
78
  end
79
-
79
+
80
80
  # HTTP POST request.
81
81
  #
82
82
  # Usually forbidden.
83
83
  def post(request, response)
84
84
  raise NotImplementedError
85
85
  end
86
-
86
+
87
87
  # HTTP DELETE request.
88
88
  #
89
89
  # Delete this resource.
90
90
  def delete
91
91
  raise NotImplementedError
92
92
  end
93
-
93
+
94
94
  # HTTP COPY request.
95
95
  #
96
96
  # Copy this resource to given destination resource.
97
97
  def copy(dest)
98
98
  raise NotImplementedError
99
99
  end
100
-
100
+
101
101
  # HTTP MOVE request.
102
102
  #
103
103
  # Move this resource to given destination resource.
@@ -105,7 +105,7 @@ module RackDAV
105
105
  copy(dest)
106
106
  delete
107
107
  end
108
-
108
+
109
109
  # HTTP MKCOL request.
110
110
  #
111
111
  # Create this resource as collection.
@@ -124,20 +124,24 @@ module RackDAV
124
124
  def display_name
125
125
  name
126
126
  end
127
-
127
+
128
128
  def child(name, option={})
129
129
  self.class.new(path + '/' + name, options)
130
130
  end
131
-
131
+
132
+ def lockable?
133
+ self.respond_to?(:lock) && self.respond_to?(:unlock)
134
+ end
135
+
132
136
  def property_names
133
137
  %w(creationdate displayname getlastmodified getetag resourcetype getcontenttype getcontentlength)
134
138
  end
135
-
139
+
136
140
  def get_property(name)
137
141
  case name
138
142
  when 'resourcetype' then resource_type
139
143
  when 'displayname' then display_name
140
- when 'creationdate' then creation_date.xmlschema
144
+ when 'creationdate' then creation_date.xmlschema
141
145
  when 'getcontentlength' then content_length.to_s
142
146
  when 'getcontenttype' then content_type
143
147
  when 'getetag' then etag
@@ -165,7 +169,7 @@ module RackDAV
165
169
  return nil if elements.empty?
166
170
  self.class.new('/' + elements[0..-2].to_a.join('/'), @options)
167
171
  end
168
-
172
+
169
173
  def descendants
170
174
  list = []
171
175
  children.each do |child|
@@ -0,0 +1,28 @@
1
+ class String
2
+
3
+ if RUBY_VERSION >= "1.9"
4
+ def force_valid_encoding
5
+ find_encoding(Encoding.list.to_enum)
6
+ end
7
+ else
8
+ def force_valid_encoding
9
+ self
10
+ end
11
+ end
12
+
13
+ private
14
+
15
+ def find_encoding(encodings)
16
+ if valid_encoding?
17
+ self
18
+ else
19
+ force_next_encoding(encodings)
20
+ end
21
+ end
22
+
23
+ def force_next_encoding(encodings)
24
+ force_encoding(encodings.next)
25
+ find_encoding(encodings)
26
+ end
27
+
28
+ end
@@ -0,0 +1,16 @@
1
+ module RackDAV
2
+
3
+ # Holds information about library version.
4
+ module Version
5
+ MAJOR = 0
6
+ MINOR = 3
7
+ PATCH = 1
8
+ BUILD = nil
9
+
10
+ STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join(".")
11
+ end
12
+
13
+ # The current library version.
14
+ VERSION = Version::STRING
15
+
16
+ end