net_dav 0.3.3 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +3 -1
- data/VERSION +1 -1
- data/bin/dav +8 -5
- data/lib/net/dav/item.rb +21 -11
- data/lib/net/dav.rb +314 -209
- data/net_dav.gemspec +12 -6
- data/spec/fixtures/file.html +1 -0
- data/spec/integration/net_dav_spec.rb +96 -0
- data/spec/integration/webdav_server.rb +87 -0
- data/spec/spec_helper.rb +10 -1
- metadata +18 -4
- data/spec/net_dav_spec.rb +0 -7
data/Rakefile
CHANGED
@@ -9,9 +9,11 @@ begin
|
|
9
9
|
gem.description = %Q{WebDAV client library in the style of Net::HTTP, using Net::HTTP and libcurl, if installed}
|
10
10
|
gem.email = "c1.github@niftybox.net"
|
11
11
|
gem.homepage = "http://github.com/devrandom/net_dav"
|
12
|
-
gem.authors = ["Miron Cuperman"]
|
12
|
+
gem.authors = ["Miron Cuperman","Thomas Flemming"]
|
13
|
+
gem.executables = ["dav"]
|
13
14
|
gem.add_dependency "nokogiri", ">= 1.3.0"
|
14
15
|
gem.add_development_dependency "rspec", ">= 1.2.0"
|
16
|
+
gem.add_development_dependency "webrick-webdav", ">= 1.0"
|
15
17
|
|
16
18
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
17
19
|
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.0
|
data/bin/dav
CHANGED
@@ -21,6 +21,7 @@ def print_usage
|
|
21
21
|
puts " put Put file from FILE to URL"
|
22
22
|
puts " mkdir Create directory at URL"
|
23
23
|
puts " gsub Replace content at URL from REGEXP to VALUE"
|
24
|
+
puts " props Display xml properties for file or directory at URL"
|
24
25
|
exit
|
25
26
|
end
|
26
27
|
|
@@ -62,13 +63,13 @@ res = dav.start { |dav|
|
|
62
63
|
when 'get'
|
63
64
|
if file.nil?
|
64
65
|
dav.get(url.path) do |str|
|
65
|
-
|
66
|
+
$stdout.print str
|
66
67
|
end
|
67
68
|
else
|
68
69
|
File.open(file, "w") do |stream|
|
69
|
-
|
70
|
-
|
71
|
-
|
70
|
+
dav.get(url.path) do |str|
|
71
|
+
stream.print str
|
72
|
+
end
|
72
73
|
end
|
73
74
|
end
|
74
75
|
when 'lsr'
|
@@ -86,9 +87,11 @@ res = dav.start { |dav|
|
|
86
87
|
val = $*[3]
|
87
88
|
dav.find(url.path) do |item|
|
88
89
|
if (item.type == :file)
|
89
|
-
|
90
|
+
item.content = item.content.gsub(re, val)
|
90
91
|
end
|
91
92
|
end
|
93
|
+
when 'props'
|
94
|
+
puts dav.propfind(url.path).to_s
|
92
95
|
else
|
93
96
|
print_usage
|
94
97
|
end
|
data/lib/net/dav/item.rb
CHANGED
@@ -13,34 +13,44 @@ module Net
|
|
13
13
|
|
14
14
|
# Synonym for uri
|
15
15
|
def url
|
16
|
-
|
16
|
+
@uri
|
17
17
|
end
|
18
18
|
|
19
19
|
def initialize(dav, uri, type, size) #:nodoc:
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
20
|
+
@uri = uri
|
21
|
+
@size = size.to_i rescue nil
|
22
|
+
@type = type
|
23
|
+
@dav = dav
|
24
24
|
end
|
25
25
|
|
26
26
|
# Get content from server if needed and return as string
|
27
27
|
def content
|
28
|
-
|
29
|
-
|
28
|
+
return @content unless @content.nil?
|
29
|
+
@content = @dav.get(@uri.path)
|
30
30
|
end
|
31
31
|
|
32
32
|
# Put content to server
|
33
33
|
def content=(str)
|
34
|
-
|
35
|
-
|
34
|
+
@dav.put_string(@uri.path, str)
|
35
|
+
@content = str
|
36
|
+
end
|
37
|
+
|
38
|
+
# Proppatch item
|
39
|
+
def proppatch(xml_snippet)
|
40
|
+
@dav.proppatch(@uri.path,xml_snippet)
|
41
|
+
end
|
42
|
+
|
43
|
+
#Properties for this item
|
44
|
+
def propfind
|
45
|
+
return @dav.propfind(@uri.path)
|
36
46
|
end
|
37
47
|
|
38
48
|
def to_s #:nodoc:
|
39
|
-
|
49
|
+
"#<Net::DAV::Item URL:#{@uri.to_s} type:#{@type}>"
|
40
50
|
end
|
41
51
|
|
42
52
|
def inspect #:nodoc:
|
43
|
-
|
53
|
+
"#<Net::DAV::Item URL:#{@uri.to_s} type:#{@type}>"
|
44
54
|
end
|
45
55
|
end
|
46
56
|
end
|
data/lib/net/dav.rb
CHANGED
@@ -19,252 +19,261 @@ module Net #:nodoc:
|
|
19
19
|
attr_accessor :disable_basic_auth
|
20
20
|
|
21
21
|
def verify_callback=(callback)
|
22
|
-
|
22
|
+
@http.verify_callback = callback
|
23
23
|
end
|
24
24
|
|
25
25
|
def verify_server=(value)
|
26
|
-
|
26
|
+
@http.verify_mode = value ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
|
27
27
|
end
|
28
28
|
|
29
29
|
def initialize(uri)
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
30
|
+
@disable_basic_auth = false
|
31
|
+
@uri = uri
|
32
|
+
case @uri.scheme
|
33
|
+
when "http"
|
34
|
+
@http = Net::HTTP.new(@uri.host, @uri.port)
|
35
|
+
when "https"
|
36
|
+
@http = Net::HTTP.new(@uri.host, @uri.port)
|
37
|
+
@http.use_ssl = true
|
38
|
+
self.verify_server = true
|
39
|
+
else
|
40
|
+
raise "unknown uri scheme"
|
41
|
+
end
|
42
42
|
end
|
43
43
|
|
44
44
|
def start(&block)
|
45
|
-
|
45
|
+
@http.start(&block)
|
46
46
|
end
|
47
47
|
|
48
48
|
def read_timeout
|
49
|
-
|
49
|
+
@http.read_timeout
|
50
50
|
end
|
51
51
|
|
52
52
|
def read_timeout=(sec)
|
53
|
-
|
53
|
+
@http.read_timeout = sec
|
54
54
|
end
|
55
55
|
|
56
56
|
def open_timeout
|
57
|
-
|
57
|
+
@http.read_timeout
|
58
58
|
end
|
59
59
|
|
60
60
|
def open_timeout=(sec)
|
61
|
-
|
61
|
+
@http.read_timeout = sec
|
62
62
|
end
|
63
63
|
|
64
64
|
def request_sending_stream(verb, path, stream, length, headers)
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
65
|
+
req =
|
66
|
+
case verb
|
67
|
+
when :put
|
68
|
+
Net::HTTP::Put.new(path)
|
69
|
+
else
|
70
|
+
raise "unkown sending_stream verb #{verb}"
|
71
|
+
end
|
72
|
+
req.body_stream = stream
|
73
|
+
req.content_length = length
|
74
|
+
headers.each_pair { |key, value| req[key] = value } if headers
|
75
|
+
req.content_type = 'text/xml; charset="utf-8"'
|
76
|
+
res = handle_request(req, headers)
|
77
|
+
res
|
78
78
|
end
|
79
79
|
|
80
80
|
def request_sending_body(verb, path, body, headers)
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
81
|
+
req =
|
82
|
+
case verb
|
83
|
+
when :put
|
84
|
+
Net::HTTP::Put.new(path)
|
85
|
+
else
|
86
|
+
raise "unkown sending_body verb #{verb}"
|
87
|
+
end
|
88
|
+
req.body = body
|
89
|
+
headers.each_pair { |key, value| req[key] = value } if headers
|
90
|
+
req.content_type = 'text/xml; charset="utf-8"'
|
91
|
+
res = handle_request(req, headers)
|
92
|
+
res
|
93
93
|
end
|
94
94
|
|
95
95
|
def request_returning_body(verb, path, headers, &block)
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
96
|
+
req =
|
97
|
+
case verb
|
98
|
+
when :get
|
99
|
+
Net::HTTP::Get.new(path)
|
100
|
+
else
|
101
|
+
raise "unkown returning_body verb #{verb}"
|
102
|
+
end
|
103
|
+
headers.each_pair { |key, value| req[key] = value } if headers
|
104
|
+
res = handle_request(req, headers, MAX_REDIRECTS, &block)
|
105
|
+
res.body
|
106
106
|
end
|
107
107
|
|
108
108
|
def request(verb, path, body, headers)
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
109
|
+
req =
|
110
|
+
case verb
|
111
|
+
when :propfind
|
112
|
+
Net::HTTP::Propfind.new(path)
|
113
|
+
when :mkcol
|
114
|
+
Net::HTTP::Mkcol.new(path)
|
115
|
+
when :delete
|
116
|
+
Net::HTTP::Delete.new(path)
|
117
|
+
when :move
|
118
|
+
Net::HTTP::Move.new(path)
|
119
|
+
when :copy
|
120
|
+
Net::HTTP::Copy.new(path)
|
121
|
+
when :proppatch
|
122
|
+
Net::HTTP::Proppatch.new(path)
|
123
|
+
else
|
124
|
+
raise "unkown verb #{verb}"
|
125
|
+
end
|
126
|
+
req.body = body
|
127
|
+
headers.each_pair { |key, value| req[key] = value } if headers
|
128
|
+
req.content_type = 'text/xml; charset="utf-8"'
|
129
|
+
res = handle_request(req, headers)
|
130
|
+
res
|
123
131
|
end
|
124
132
|
|
125
133
|
def handle_request(req, headers, limit = MAX_REDIRECTS, &block)
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
134
|
+
# You should choose better exception.
|
135
|
+
raise ArgumentError, 'HTTP redirect too deep' if limit == 0
|
136
|
+
|
137
|
+
response = nil
|
138
|
+
if block
|
139
|
+
@http.request(req) {|res|
|
140
|
+
# Only start returning a body if we will not retry
|
141
|
+
res.read_body nil, &block if !res.is_a?(Net::HTTPUnauthorized) && !res.is_a?(Net::HTTPRedirection)
|
142
|
+
response = res
|
143
|
+
}
|
144
|
+
else
|
145
|
+
response = @http.request(req)
|
146
|
+
end
|
147
|
+
case response
|
148
|
+
when Net::HTTPSuccess then
|
149
|
+
return response
|
150
|
+
when Net::HTTPUnauthorized then
|
151
|
+
response.error! unless @user
|
152
|
+
response.error! if req['authorization']
|
153
|
+
new_req = clone_req(req.path, req, headers)
|
154
|
+
if response['www-authenticate'] =~ /^Basic/
|
155
|
+
if disable_basic_auth
|
156
|
+
raise "server requested basic auth, but that is disabled"
|
157
|
+
end
|
158
|
+
new_req.basic_auth @user, @pass
|
159
|
+
else
|
160
|
+
digest_auth(new_req, @user, @pass, response)
|
161
|
+
end
|
162
|
+
return handle_request(new_req, headers, limit - 1, &block)
|
163
|
+
when Net::HTTPRedirection then
|
164
|
+
location = URI.parse(response['location'])
|
165
|
+
if (@uri.scheme != location.scheme ||
|
166
|
+
@uri.host != location.host ||
|
167
|
+
@uri.port != location.port)
|
168
|
+
raise ArgumentError, "cannot redirect to a different host #{@uri} => #{location}"
|
169
|
+
end
|
170
|
+
new_req = clone_req(location.path, req, headers)
|
171
|
+
return handle_request(new_req, headers, limit - 1, &block)
|
172
|
+
else
|
173
|
+
response.error!
|
174
|
+
end
|
167
175
|
end
|
176
|
+
|
168
177
|
def clone_req(path, req, headers)
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
178
|
+
new_req = req.class.new(path)
|
179
|
+
new_req.body = req.body if req.body
|
180
|
+
new_req.body_stream = req.body_stream if req.body_stream
|
181
|
+
headers.each_pair { |key, value| new_req[key] = value } if headers
|
182
|
+
return new_req
|
174
183
|
end
|
175
184
|
|
176
185
|
CNONCE = Digest::MD5.hexdigest("%x" % (Time.now.to_i + rand(65535))).slice(0, 8)
|
177
186
|
|
178
187
|
def digest_auth(request, user, password, response)
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
188
|
+
# based on http://segment7.net/projects/ruby/snippets/digest_auth.rb
|
189
|
+
@nonce_count = 0 if @nonce_count.nil?
|
190
|
+
@nonce_count += 1
|
191
|
+
|
192
|
+
raise "bad www-authenticate header" unless (response['www-authenticate'] =~ /^(\w+) (.*)/)
|
193
|
+
|
194
|
+
params = {}
|
195
|
+
$2.gsub(/(\w+)="(.*?)"/) { params[$1] = $2 }
|
196
|
+
|
197
|
+
a_1 = "#{user}:#{params['realm']}:#{password}"
|
198
|
+
a_2 = "#{request.method}:#{request.path}"
|
199
|
+
request_digest = ''
|
200
|
+
request_digest << Digest::MD5.hexdigest(a_1)
|
201
|
+
request_digest << ':' << params['nonce']
|
202
|
+
request_digest << ':' << ('%08x' % @nonce_count)
|
203
|
+
request_digest << ':' << CNONCE
|
204
|
+
request_digest << ':' << params['qop']
|
205
|
+
request_digest << ':' << Digest::MD5.hexdigest(a_2)
|
206
|
+
|
207
|
+
header = []
|
208
|
+
header << "Digest username=\"#{user}\""
|
209
|
+
header << "realm=\"#{params['realm']}\""
|
210
|
+
header << "nonce=\"#{params['nonce']}\""
|
211
|
+
header << "uri=\"#{request.path}\""
|
212
|
+
header << "cnonce=\"#{CNONCE}\""
|
213
|
+
header << "nc=#{'%08x' % @nonce_count}"
|
214
|
+
header << "qop=#{params['qop']}"
|
215
|
+
header << "response=\"#{Digest::MD5.hexdigest(request_digest)}\""
|
216
|
+
header << "algorithm=\"MD5\""
|
217
|
+
|
218
|
+
header = header.join(', ')
|
219
|
+
request['Authorization'] = header
|
211
220
|
end
|
212
221
|
end
|
213
222
|
|
214
223
|
|
215
224
|
class CurlHandler < NetHttpHandler
|
216
225
|
def verify_callback=(callback)
|
217
|
-
|
218
|
-
|
219
|
-
|
226
|
+
super
|
227
|
+
curl = make_curl
|
228
|
+
$stderr.puts "verify_callback not implemented in Curl::Easy"
|
220
229
|
end
|
221
230
|
|
222
231
|
def verify_server=(value)
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
232
|
+
super
|
233
|
+
curl = make_curl
|
234
|
+
curl.ssl_verify_peer = value
|
235
|
+
curl.ssl_verify_host = value
|
227
236
|
end
|
228
237
|
|
229
238
|
def make_curl
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
239
|
+
unless @curl
|
240
|
+
@curl = Curl::Easy.new
|
241
|
+
@curl.timeout = @http.read_timeout
|
242
|
+
@curl.follow_location = true
|
243
|
+
@curl.max_redirects = MAX_REDIRECTS
|
244
|
+
if disable_basic_auth
|
245
|
+
@curl.http_auth_types = Curl::CURLAUTH_DIGEST
|
246
|
+
end
|
247
|
+
end
|
248
|
+
@curl
|
240
249
|
end
|
241
250
|
|
242
251
|
def request_returning_body(verb, path, headers)
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
252
|
+
raise "unkown returning_body verb #{verb}" unless verb == :get
|
253
|
+
url = @uri.merge(path)
|
254
|
+
curl = make_curl
|
255
|
+
curl.url = url.to_s
|
256
|
+
headers.each_pair { |key, value| curl.headers[key] = value } if headers
|
257
|
+
if (@user)
|
258
|
+
curl.userpwd = "#{@user}:#{@pass}"
|
259
|
+
else
|
260
|
+
curl.userpwd = nil
|
261
|
+
end
|
262
|
+
res = nil
|
263
|
+
if block_given?
|
264
|
+
curl.on_body do |frag|
|
265
|
+
yield frag
|
266
|
+
frag.length
|
267
|
+
end
|
268
|
+
end
|
269
|
+
curl.perform
|
270
|
+
unless curl.response_code >= 200 && curl.response_code < 300
|
271
|
+
header_block = curl.header_str.split(/\r?\n\r?\n/)[-1]
|
272
|
+
msg = header_block.split(/\r?\n/)[0]
|
273
|
+
msg.gsub!(/^HTTP\/\d+.\d+ /, '')
|
274
|
+
raise Net::HTTPError.new(msg, nil)
|
275
|
+
end
|
276
|
+
curl.body_str
|
268
277
|
end
|
269
278
|
|
270
279
|
end
|
@@ -325,7 +334,7 @@ module Net #:nodoc:
|
|
325
334
|
def initialize(uri, options = nil)
|
326
335
|
@have_curl = Curl rescue nil
|
327
336
|
if options && options.has_key?(:curl) && !options[:curl]
|
328
|
-
|
337
|
+
@have_curl = false
|
329
338
|
end
|
330
339
|
@uri = uri
|
331
340
|
@uri = URI.parse(@uri) if @uri.is_a? String
|
@@ -343,7 +352,7 @@ module Net #:nodoc:
|
|
343
352
|
# end
|
344
353
|
def start(&block) # :yield: dav
|
345
354
|
@handler.start do
|
346
|
-
|
355
|
+
return yield(self)
|
347
356
|
end
|
348
357
|
end
|
349
358
|
|
@@ -362,6 +371,16 @@ module Net #:nodoc:
|
|
362
371
|
|
363
372
|
# Find files and directories, yields Net::DAV::Item
|
364
373
|
#
|
374
|
+
# The :filename option can be a regexp or string, and is used
|
375
|
+
# to filter the yielded items.
|
376
|
+
#
|
377
|
+
# If :suppress_errors is passed, exceptions that occurs when
|
378
|
+
# reading directory information is ignored, and a warning is
|
379
|
+
# printed out stderr instead.
|
380
|
+
#
|
381
|
+
# The default is to not traverse recursively, unless the :recursive
|
382
|
+
# options is passed.
|
383
|
+
#
|
365
384
|
# Examples:
|
366
385
|
#
|
367
386
|
# res = Net::DAV.start(url) do |dav|
|
@@ -370,29 +389,63 @@ module Net #:nodoc:
|
|
370
389
|
# puts item.content
|
371
390
|
# end
|
372
391
|
# end
|
392
|
+
#
|
393
|
+
# dav = Net::DAV.new(url)
|
394
|
+
# dav.find(url.path, :filename => /\.html/, :suppress_errors => true)
|
395
|
+
# puts item.url.to_s
|
396
|
+
# end
|
373
397
|
def find(path, options = {})
|
374
398
|
path = @uri.merge(path).path
|
375
399
|
namespaces = {'x' => "DAV:"}
|
376
|
-
|
400
|
+
begin
|
401
|
+
doc = propfind(path)
|
402
|
+
rescue Net::HTTPServerException => e
|
403
|
+
msg = e.to_s + ": " + path.to_s
|
404
|
+
if(options[:suppress_errors])then
|
405
|
+
# Ignore dir if propfind returns an error
|
406
|
+
warn("Warning: " + msg)
|
407
|
+
return nil
|
408
|
+
else
|
409
|
+
raise Net::HTTPServerException.new(msg, nil)
|
410
|
+
end
|
411
|
+
end
|
377
412
|
path.sub!(/\/$/, '')
|
378
413
|
doc./('.//x:response', namespaces).each do |item|
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
414
|
+
uri = @uri.merge(item.xpath("x:href", namespaces).inner_text)
|
415
|
+
size = item.%(".//x:getcontentlength", namespaces).inner_text rescue nil
|
416
|
+
type = item.%(".//x:collection", namespaces) ? :directory : :file
|
417
|
+
res = Item.new(self, uri, type, size)
|
418
|
+
if type == :file then
|
419
|
+
|
420
|
+
if(options[:filename])then
|
421
|
+
search_term = options[:filename]
|
422
|
+
filename = File.basename(uri.path)
|
423
|
+
if(search_term.class == Regexp and search_term.match(filename))then
|
424
|
+
yield res
|
425
|
+
elsif(search_term.class == String and search_term == filename)then
|
426
|
+
yield res
|
427
|
+
end
|
428
|
+
else
|
429
|
+
yield res
|
430
|
+
end
|
431
|
+
|
432
|
+
elsif uri.path == path || uri.path == path + "/"
|
433
|
+
# This is the top-level dir, skip it
|
434
|
+
elsif options[:recursive] && type == :directory
|
435
|
+
|
436
|
+
if(!options[:filename])then
|
437
|
+
yield res
|
438
|
+
end
|
439
|
+
|
440
|
+
# This is a subdir, recurse
|
441
|
+
find(uri.path, options) do |sub_res|
|
442
|
+
yield sub_res
|
443
|
+
end
|
444
|
+
else
|
445
|
+
if(!options[:filename])then
|
446
|
+
yield res
|
447
|
+
end
|
448
|
+
end
|
396
449
|
end
|
397
450
|
end
|
398
451
|
|
@@ -400,7 +453,7 @@ module Net #:nodoc:
|
|
400
453
|
def cd(url)
|
401
454
|
new_uri = @uri.merge(url)
|
402
455
|
if new_uri.host != @uri.host || new_uri.port != @uri.port || new_uri.scheme != @uri.scheme
|
403
|
-
|
456
|
+
raise Exception , "uri must have same scheme, host and port"
|
404
457
|
end
|
405
458
|
@uri = new_uri
|
406
459
|
end
|
@@ -441,6 +494,58 @@ module Net #:nodoc:
|
|
441
494
|
res.body
|
442
495
|
end
|
443
496
|
|
497
|
+
# Delete request
|
498
|
+
#
|
499
|
+
# Example:
|
500
|
+
# dav.delete(uri.path)
|
501
|
+
def delete(path)
|
502
|
+
path = @uri.merge(path).path
|
503
|
+
res = @handler.request(:delete, path, nil, nil)
|
504
|
+
res.body
|
505
|
+
end
|
506
|
+
|
507
|
+
# Send a move request to the server.
|
508
|
+
#
|
509
|
+
# Example:
|
510
|
+
# dav.move(original_path, new_path)
|
511
|
+
def move(path,destination)
|
512
|
+
path = @uri.merge(path).path
|
513
|
+
destination = @uri.merge(destination).to_s
|
514
|
+
headers = {'Destination' => destination}
|
515
|
+
res = @handler.request(:move, path, nil, headers)
|
516
|
+
res.body
|
517
|
+
end
|
518
|
+
|
519
|
+
# Send a copy request to the server.
|
520
|
+
#
|
521
|
+
# Example:
|
522
|
+
# dav.copy(original_path, destination)
|
523
|
+
def copy(path,destination)
|
524
|
+
path = @uri.merge(path).path
|
525
|
+
destination = @uri.merge(destination).to_s
|
526
|
+
headers = {'Destination' => destination}
|
527
|
+
res = @handler.request(:copy, path, nil, headers)
|
528
|
+
res.body
|
529
|
+
end
|
530
|
+
|
531
|
+
# Do a proppatch request to the server to
|
532
|
+
# update properties on resources or collections.
|
533
|
+
#
|
534
|
+
# Example:
|
535
|
+
# dav.proppatch(uri.path,"<d:creationdate>#{new_date}</d:creationdate>")
|
536
|
+
def proppatch(path, xml_snippet)
|
537
|
+
headers = {'Depth' => '1'}
|
538
|
+
body = '<?xml version="1.0"?>' +
|
539
|
+
'<d:propertyupdate xmlns:d="DAV:">' +
|
540
|
+
'<d:set>' +
|
541
|
+
'<d:prop>' +
|
542
|
+
xml_snippet +
|
543
|
+
'</d:prop>' +
|
544
|
+
'</d:set>' +
|
545
|
+
'</d:propertyupdate>'
|
546
|
+
res = @handler.request(:proppatch, path, body, headers)
|
547
|
+
Nokogiri::XML.parse(res.body)
|
548
|
+
end
|
444
549
|
|
445
550
|
# Makes a new directory (collection)
|
446
551
|
def mkdir(path)
|
data/net_dav.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{net_dav}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.4.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
-
s.authors = ["Miron Cuperman"]
|
12
|
-
s.date = %q{2009-
|
11
|
+
s.authors = ["Miron Cuperman", "Thomas Flemming"]
|
12
|
+
s.date = %q{2009-12-10}
|
13
13
|
s.default_executable = %q{dav}
|
14
14
|
s.description = %q{WebDAV client library in the style of Net::HTTP, using Net::HTTP and libcurl, if installed}
|
15
15
|
s.email = %q{c1.github@niftybox.net}
|
@@ -30,7 +30,9 @@ Gem::Specification.new do |s|
|
|
30
30
|
"lib/net/dav/item.rb",
|
31
31
|
"net_dav.gemspec",
|
32
32
|
"script/multi-test",
|
33
|
-
"spec/
|
33
|
+
"spec/fixtures/file.html",
|
34
|
+
"spec/integration/net_dav_spec.rb",
|
35
|
+
"spec/integration/webdav_server.rb",
|
34
36
|
"spec/spec.opts",
|
35
37
|
"spec/spec_helper.rb",
|
36
38
|
"tmp/.gitignore"
|
@@ -41,8 +43,9 @@ Gem::Specification.new do |s|
|
|
41
43
|
s.rubygems_version = %q{1.3.5}
|
42
44
|
s.summary = %q{WebDAV client library in the style of Net::HTTP}
|
43
45
|
s.test_files = [
|
44
|
-
"spec/
|
45
|
-
"spec/
|
46
|
+
"spec/spec_helper.rb",
|
47
|
+
"spec/integration/webdav_server.rb",
|
48
|
+
"spec/integration/net_dav_spec.rb"
|
46
49
|
]
|
47
50
|
|
48
51
|
if s.respond_to? :specification_version then
|
@@ -52,13 +55,16 @@ Gem::Specification.new do |s|
|
|
52
55
|
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
53
56
|
s.add_runtime_dependency(%q<nokogiri>, [">= 1.3.0"])
|
54
57
|
s.add_development_dependency(%q<rspec>, [">= 1.2.0"])
|
58
|
+
s.add_development_dependency(%q<webrick-webdav>, [">= 1.0"])
|
55
59
|
else
|
56
60
|
s.add_dependency(%q<nokogiri>, [">= 1.3.0"])
|
57
61
|
s.add_dependency(%q<rspec>, [">= 1.2.0"])
|
62
|
+
s.add_dependency(%q<webrick-webdav>, [">= 1.0"])
|
58
63
|
end
|
59
64
|
else
|
60
65
|
s.add_dependency(%q<nokogiri>, [">= 1.3.0"])
|
61
66
|
s.add_dependency(%q<rspec>, [">= 1.2.0"])
|
67
|
+
s.add_dependency(%q<webrick-webdav>, [">= 1.0"])
|
62
68
|
end
|
63
69
|
end
|
64
70
|
|
@@ -0,0 +1 @@
|
|
1
|
+
Content
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/webdav_server')
|
3
|
+
|
4
|
+
describe "Net::Dav" do
|
5
|
+
|
6
|
+
before(:all) do
|
7
|
+
# Start webdav server in subprocess
|
8
|
+
@pid = fork do
|
9
|
+
webdav_server(:port => 10080,:authentication => false)
|
10
|
+
end
|
11
|
+
# Wait for webdavserver to start
|
12
|
+
sleep(10)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should create a Net::Dav object" do
|
16
|
+
Net::DAV.new("http://localhost.localdomain/").should_not be_nil
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should read properties from webdav server" do
|
20
|
+
dav = Net::DAV.new("http://localhost:10080/")
|
21
|
+
@props = dav.propfind("/").to_s
|
22
|
+
@props.should match(/200 OK/)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should write files to webdav server" do
|
26
|
+
dav = Net::DAV.new("http://localhost:10080/")
|
27
|
+
@props = find_props_or_error(dav, "/new_file.html")
|
28
|
+
@props.should match(/404.*Not found/i)
|
29
|
+
|
30
|
+
dav.put_string("/new_file.html","File contents")
|
31
|
+
|
32
|
+
@props = find_props_or_error(dav, "/new_file.html")
|
33
|
+
@props.should match(/200 OK/i)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should delete files from webdav server" do
|
37
|
+
dav = Net::DAV.new("http://localhost:10080/")
|
38
|
+
|
39
|
+
@props = find_props_or_error(dav, "/new_file.html")
|
40
|
+
@props.should match(/200 OK/i)
|
41
|
+
|
42
|
+
dav.delete("/new_file.html")
|
43
|
+
@props = find_props_or_error(dav, "/new_file.html")
|
44
|
+
@props.should match(/404.*Not found/i)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should copy files on webdav server" do
|
48
|
+
dav = Net::DAV.new("http://localhost:10080/")
|
49
|
+
|
50
|
+
@props = find_props_or_error(dav, "/file.html")
|
51
|
+
@props.should match(/200 OK/i)
|
52
|
+
|
53
|
+
dav.copy("/file.html","/copied_file.html")
|
54
|
+
@props = find_props_or_error(dav, "/copied_file.html")
|
55
|
+
@props.should match(/200 OK/i)
|
56
|
+
|
57
|
+
dav.delete("/copied_file.html")
|
58
|
+
|
59
|
+
@props = find_props_or_error(dav, "/copied_file.html")
|
60
|
+
@props.should match(/404.*Not found/i)
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should move files on webdav server" do
|
64
|
+
dav = Net::DAV.new("http://localhost:10080/")
|
65
|
+
|
66
|
+
@props = find_props_or_error(dav, "/file.html")
|
67
|
+
@props.should match(/200 OK/i)
|
68
|
+
|
69
|
+
dav.move("/file.html","/moved_file.html")
|
70
|
+
@props = find_props_or_error(dav, "/moved_file.html")
|
71
|
+
@props.should match(/200 OK/i)
|
72
|
+
|
73
|
+
@props = find_props_or_error(dav, "/file.html")
|
74
|
+
@props.should match(/404.*Not found/i)
|
75
|
+
|
76
|
+
dav.move("/moved_file.html","/file.html")
|
77
|
+
@props = find_props_or_error(dav, "/file.html")
|
78
|
+
@props.should match(/200 OK/i)
|
79
|
+
end
|
80
|
+
|
81
|
+
# proppatch seems to work, but our simple webdav server don't update properties
|
82
|
+
# it "should alter properties on resources on webdav server" do
|
83
|
+
# dav = Net::DAV.new("http://localhost:10080/")
|
84
|
+
# @props = find_props_or_error(dav, "/file.html")
|
85
|
+
# puts @props
|
86
|
+
# dav.proppatch("/file.html", "<d:resourcetype>static-file</d:resourcetype>")
|
87
|
+
# @props = find_props_or_error(dav, "/file.html")
|
88
|
+
# puts @props
|
89
|
+
# end
|
90
|
+
|
91
|
+
after(:all) do
|
92
|
+
# Shut down webdav server
|
93
|
+
Process.kill('SIGKILL', @pid) rescue nil
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
require 'rubygems'
|
4
|
+
require 'webrick'
|
5
|
+
require 'webrick/httpservlet/webdavhandler'
|
6
|
+
|
7
|
+
# Web server with WebDAV extensions
|
8
|
+
#
|
9
|
+
# Usage: ruby webdav_server.rb
|
10
|
+
|
11
|
+
# Code based on:
|
12
|
+
# http://github.com/aslakhellesoy/webdavjs/blob/master/spec/webdav_server.rb
|
13
|
+
|
14
|
+
|
15
|
+
# Monkey patch REXML to always nil-indent. The indentation is broken in REXML
|
16
|
+
# on Ruby 1.8.6 and even when fixed it confuses OS-X.
|
17
|
+
module REXML
|
18
|
+
module Node
|
19
|
+
alias old_to_s to_s
|
20
|
+
def to_s(indent=nil)
|
21
|
+
old_to_s(nil)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/223386
|
27
|
+
# http://gmarrone.objectblues.net/cgi-bin/wiki/WebDAV_-_Linux_server%2c_Mac_OS_X_client
|
28
|
+
module WEBrick
|
29
|
+
module HTTPServlet
|
30
|
+
class WebDAVHandlerVersion2 < WebDAVHandler
|
31
|
+
|
32
|
+
def do_OPTIONS(req, res)
|
33
|
+
super
|
34
|
+
res["DAV"] = "1,2"
|
35
|
+
end
|
36
|
+
|
37
|
+
def do_LOCK(req, res)
|
38
|
+
res.body << "<XXX-#{Time.now.to_s}/>"
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
class WebDAVHandlerVersion3 < WebDAVHandlerVersion2
|
44
|
+
|
45
|
+
# Enable authentication
|
46
|
+
$REALM = "WebDav share"
|
47
|
+
$USER = "myuser"
|
48
|
+
$PASS = "mypass"
|
49
|
+
|
50
|
+
def service(req, res)
|
51
|
+
HTTPAuth.basic_auth(req, res, $REALM) {|user, pass|
|
52
|
+
# this block returns true if
|
53
|
+
# authentication token is valid
|
54
|
+
user == $USER && pass == $PASS
|
55
|
+
}
|
56
|
+
super
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def webdav_server(*options)
|
65
|
+
port = 10080
|
66
|
+
if(options and options[0][:port])
|
67
|
+
port = options[0][:port]
|
68
|
+
end
|
69
|
+
log = WEBrick::Log.new
|
70
|
+
log.level = WEBrick::Log::DEBUG if $DEBUG
|
71
|
+
serv = WEBrick::HTTPServer.new({:Port => port, :Logger => log})
|
72
|
+
|
73
|
+
dir = File.expand_path(File.dirname(__FILE__)) + '/../fixtures'
|
74
|
+
if(options and options[0][:authentication])
|
75
|
+
serv.mount("/", WEBrick::HTTPServlet::WebDAVHandlerVersion3, dir)
|
76
|
+
else
|
77
|
+
serv.mount("/", WEBrick::HTTPServlet::WebDAVHandlerVersion2, dir)
|
78
|
+
end
|
79
|
+
|
80
|
+
trap(:INT){ serv.shutdown }
|
81
|
+
serv.start
|
82
|
+
end
|
83
|
+
|
84
|
+
if($0 == __FILE__)
|
85
|
+
|
86
|
+
webdav_server(:port => 10080,:authentication => false)
|
87
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -6,5 +6,14 @@ require 'spec'
|
|
6
6
|
require 'spec/autorun'
|
7
7
|
|
8
8
|
Spec::Runner.configure do |config|
|
9
|
-
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
# Profind helper. Returns properties or error
|
13
|
+
def find_props_or_error(dav, path)
|
14
|
+
begin
|
15
|
+
return dav.propfind(path).to_s
|
16
|
+
rescue Net::HTTPServerException => e
|
17
|
+
return e.to_s
|
18
|
+
end
|
10
19
|
end
|
metadata
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: net_dav
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Miron Cuperman
|
8
|
+
- Thomas Flemming
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
12
|
|
12
|
-
date: 2009-
|
13
|
+
date: 2009-12-10 00:00:00 -08:00
|
13
14
|
default_executable: dav
|
14
15
|
dependencies:
|
15
16
|
- !ruby/object:Gem::Dependency
|
@@ -32,6 +33,16 @@ dependencies:
|
|
32
33
|
- !ruby/object:Gem::Version
|
33
34
|
version: 1.2.0
|
34
35
|
version:
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: webrick-webdav
|
38
|
+
type: :development
|
39
|
+
version_requirement:
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: "1.0"
|
45
|
+
version:
|
35
46
|
description: WebDAV client library in the style of Net::HTTP, using Net::HTTP and libcurl, if installed
|
36
47
|
email: c1.github@niftybox.net
|
37
48
|
executables:
|
@@ -53,7 +64,9 @@ files:
|
|
53
64
|
- lib/net/dav/item.rb
|
54
65
|
- net_dav.gemspec
|
55
66
|
- script/multi-test
|
56
|
-
- spec/
|
67
|
+
- spec/fixtures/file.html
|
68
|
+
- spec/integration/net_dav_spec.rb
|
69
|
+
- spec/integration/webdav_server.rb
|
57
70
|
- spec/spec.opts
|
58
71
|
- spec/spec_helper.rb
|
59
72
|
- tmp/.gitignore
|
@@ -86,5 +99,6 @@ signing_key:
|
|
86
99
|
specification_version: 3
|
87
100
|
summary: WebDAV client library in the style of Net::HTTP
|
88
101
|
test_files:
|
89
|
-
- spec/net_dav_spec.rb
|
90
102
|
- spec/spec_helper.rb
|
103
|
+
- spec/integration/webdav_server.rb
|
104
|
+
- spec/integration/net_dav_spec.rb
|