lib/net/dav.rb in net_dav-0.2.2 vs lib/net/dav.rb in net_dav-0.3.0
- old
+ new
@@ -1,10 +1,11 @@
require 'net/https'
require 'uri'
require 'nokogiri'
require 'net/dav/item'
require 'base64'
+require 'digest/md5'
begin
require 'curb'
rescue LoadError
end
@@ -13,19 +14,22 @@
class DAV
MAX_REDIRECTS = 10
class NetHttpHandler
attr_writer :user, :pass
+ attr_accessor :disable_basic_auth
+
def verify_callback=(callback)
@http.verify_callback = callback
end
def verify_server=(value)
@http.verify_mode = value ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
end
def initialize(uri)
+ @disable_basic_auth = false
@uri = uri
case @uri.scheme
when "http"
@http = Net::HTTP.new(@uri.host, @uri.port)
when "https"
@@ -67,13 +71,10 @@
end
req.body_stream = stream
req.content_length = length
headers.each_pair { |key, value| req[key] = value } if headers
req.content_type = 'text/xml; charset="utf-8"'
- if (@user)
- req.basic_auth @user, @pass
- end
res = handle_request(req, headers)
res
end
def request_sending_body(verb, path, body, headers)
@@ -85,13 +86,10 @@
raise "unkown sending_body verb #{verb}"
end
req.body = body
headers.each_pair { |key, value| req[key] = value } if headers
req.content_type = 'text/xml; charset="utf-8"'
- if (@user)
- req.basic_auth @user, @pass
- end
res = handle_request(req, headers)
res
end
def request_returning_body(verb, path, headers, &block)
@@ -101,13 +99,10 @@
Net::HTTP::Get.new(path)
else
raise "unkown returning_body verb #{verb}"
end
headers.each_pair { |key, value| req[key] = value } if headers
- if (@user)
- req.basic_auth @user, @pass
- end
res = handle_request(req, headers, MAX_REDIRECTS, &block)
res.body
end
def request(verb, path, body, headers)
@@ -121,13 +116,10 @@
raise "unkown verb #{verb}"
end
req.body = body
headers.each_pair { |key, value| req[key] = value } if headers
req.content_type = 'text/xml; charset="utf-8"'
- if (@user)
- req.basic_auth @user, @pass
- end
res = handle_request(req, headers)
res
end
def handle_request(req, headers, limit = MAX_REDIRECTS, &block)
@@ -142,30 +134,82 @@
}
else
response = @http.request(req)
end
case response
- when Net::HTTPSuccess then response
+ when Net::HTTPSuccess then
+ return response
+ when Net::HTTPUnauthorized then
+ response.error! unless @user
+ response.error! if req['authorization']
+ new_req = clone_req(req.path, req, headers)
+ if response['www-authenticate'] =~ /^Basic/
+ if disable_basic_auth
+ raise "server requested basic auth, but that is disabled"
+ end
+ new_req.basic_auth @user, @pass
+ else
+ digest_auth(new_req, @user, @pass, response)
+ end
+ return handle_request(new_req, headers, limit - 1, &block)
when Net::HTTPRedirection then
location = URI.parse(response['location'])
if (@uri.scheme != location.scheme ||
@uri.host != location.host ||
@uri.port != location.port)
raise "cannot redirect to a different host #{@uri} => #{location}"
end
- new_req = req.class.new(location.path)
- new_req.body = req.body
- new_req.body_stream = req.body_stream
- headers.each_pair { |key, value| new_req[key] = value } if headers
- if (@user)
- new_req.basic_auth @user, @pass
- end
- handle_request(new_req, limit - 1, &block)
+ new_req = clone_req(location.path, req, headers)
+ return handle_request(new_req, headers, limit - 1, &block)
else
response.error!
end
end
+ def clone_req(path, req, headers)
+ new_req = req.class.new(path)
+ new_req.body = req.body
+ new_req.body_stream = req.body_stream
+ headers.each_pair { |key, value| new_req[key] = value } if headers
+ return new_req
+ end
+
+ CNONCE = Digest::MD5.hexdigest("%x" % (Time.now.to_i + rand(65535))).slice(0, 8)
+
+ def digest_auth(request, user, password, response)
+ # based on http://segment7.net/projects/ruby/snippets/digest_auth.rb
+ @nonce_count = 0 if @nonce_count.nil?
+ @nonce_count += 1
+
+ raise "bad www-authenticate header" unless (response['www-authenticate'] =~ /^(\w+) (.*)/)
+
+ params = {}
+ $2.gsub(/(\w+)="(.*?)"/) { params[$1] = $2 }
+
+ a_1 = "#{user}:#{params['realm']}:#{password}"
+ a_2 = "#{request.method}:#{request.path}"
+ request_digest = ''
+ request_digest << Digest::MD5.hexdigest(a_1)
+ request_digest << ':' << params['nonce']
+ request_digest << ':' << ('%08x' % @nonce_count)
+ request_digest << ':' << CNONCE
+ request_digest << ':' << params['qop']
+ request_digest << ':' << Digest::MD5.hexdigest(a_2)
+
+ header = []
+ header << "Digest username=\"#{user}\""
+ header << "realm=\"#{params['realm']}\""
+ header << "nonce=\"#{params['nonce']}\""
+ header << "uri=\"#{request.path}\""
+ header << "cnonce=\"#{CNONCE}\""
+ header << "nc=#{'%08x' % @nonce_count}"
+ header << "qop=#{params['qop']}"
+ header << "response=\"#{Digest::MD5.hexdigest(request_digest)}\""
+ header << "algorithm=\"MD5\""
+
+ header = header.join(', ')
+ request['Authorization'] = header
+ end
end
class CurlHandler < NetHttpHandler
def verify_callback=(callback)
@@ -185,10 +229,13 @@
unless @curl
@curl = Curl::Easy.new
@curl.timeout = @http.read_timeout
@curl.follow_location = true
@curl.max_redirects = MAX_REDIRECTS
+ if disable_basic_auth
+ @curl.http_auth_types = Curl::CURLAUTH_DIGEST
+ end
end
@curl
end
def request_returning_body(verb, path, headers)
@@ -208,12 +255,26 @@
yield frag
frag.length
end
end
curl.perform
+ unless curl.response_code >= 200 && curl.response_code < 300
+ headers = curl.header_str.split(/\r?\n/)
+ raise Exception.new("Curl response #{headers[0]}")
+ end
curl.body_str
end
+ end
+
+ # Disable basic auth - to protect passwords from going in the clear
+ # through a man-in-the-middle attack.
+ def disable_basic_auth?
+ @handler.disable_basic_auth
+ end
+
+ def disable_basic_auth=(value)
+ @handler.disable_basic_auth = value
end
# Seconds to wait until reading one block (by one system call).
# If the DAV object cannot read a block in this many seconds,
# it raises a TimeoutError exception.