rubysl-webrick 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.travis.yml +8 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE +25 -0
  6. data/README.md +29 -0
  7. data/Rakefile +1 -0
  8. data/lib/rubysl/webrick.rb +2 -0
  9. data/lib/rubysl/webrick/version.rb +5 -0
  10. data/lib/rubysl/webrick/webrick.rb +29 -0
  11. data/lib/webrick.rb +1 -0
  12. data/lib/webrick/accesslog.rb +67 -0
  13. data/lib/webrick/cgi.rb +257 -0
  14. data/lib/webrick/compat.rb +15 -0
  15. data/lib/webrick/config.rb +97 -0
  16. data/lib/webrick/cookie.rb +110 -0
  17. data/lib/webrick/htmlutils.rb +25 -0
  18. data/lib/webrick/httpauth.rb +45 -0
  19. data/lib/webrick/httpauth/authenticator.rb +79 -0
  20. data/lib/webrick/httpauth/basicauth.rb +65 -0
  21. data/lib/webrick/httpauth/digestauth.rb +343 -0
  22. data/lib/webrick/httpauth/htdigest.rb +91 -0
  23. data/lib/webrick/httpauth/htgroup.rb +61 -0
  24. data/lib/webrick/httpauth/htpasswd.rb +83 -0
  25. data/lib/webrick/httpauth/userdb.rb +29 -0
  26. data/lib/webrick/httpproxy.rb +254 -0
  27. data/lib/webrick/httprequest.rb +365 -0
  28. data/lib/webrick/httpresponse.rb +327 -0
  29. data/lib/webrick/https.rb +63 -0
  30. data/lib/webrick/httpserver.rb +210 -0
  31. data/lib/webrick/httpservlet.rb +22 -0
  32. data/lib/webrick/httpservlet/abstract.rb +71 -0
  33. data/lib/webrick/httpservlet/cgi_runner.rb +45 -0
  34. data/lib/webrick/httpservlet/cgihandler.rb +104 -0
  35. data/lib/webrick/httpservlet/erbhandler.rb +54 -0
  36. data/lib/webrick/httpservlet/filehandler.rb +398 -0
  37. data/lib/webrick/httpservlet/prochandler.rb +33 -0
  38. data/lib/webrick/httpstatus.rb +126 -0
  39. data/lib/webrick/httputils.rb +391 -0
  40. data/lib/webrick/httpversion.rb +49 -0
  41. data/lib/webrick/log.rb +88 -0
  42. data/lib/webrick/server.rb +200 -0
  43. data/lib/webrick/ssl.rb +126 -0
  44. data/lib/webrick/utils.rb +100 -0
  45. data/lib/webrick/version.rb +13 -0
  46. data/rubysl-webrick.gemspec +23 -0
  47. metadata +145 -0
@@ -0,0 +1,15 @@
1
+ #
2
+ # compat.rb -- cross platform compatibility
3
+ #
4
+ # Author: IPR -- Internet Programming with Ruby -- writers
5
+ # Copyright (c) 2002 GOTOU Yuuzou
6
+ # Copyright (c) 2002 Internet Programming with Ruby writers. All rights
7
+ # reserved.
8
+ #
9
+ # $IPR: compat.rb,v 1.6 2002/10/01 17:16:32 gotoyuzo Exp $
10
+
11
+ module Errno
12
+ class EPROTO < SystemCallError; end
13
+ class ECONNRESET < SystemCallError; end
14
+ class ECONNABORTED < SystemCallError; end
15
+ end
@@ -0,0 +1,97 @@
1
+ #
2
+ # config.rb -- Default configurations.
3
+ #
4
+ # Author: IPR -- Internet Programming with Ruby -- writers
5
+ # Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
6
+ # Copyright (c) 2003 Internet Programming with Ruby writers. All rights
7
+ # reserved.
8
+ #
9
+ # $IPR: config.rb,v 1.52 2003/07/22 19:20:42 gotoyuzo Exp $
10
+
11
+ require 'webrick/version'
12
+ require 'webrick/httpversion'
13
+ require 'webrick/httputils'
14
+ require 'webrick/utils'
15
+ require 'webrick/log'
16
+
17
+ module WEBrick
18
+ module Config
19
+ LIBDIR = File::dirname(__FILE__)
20
+
21
+ # for GenericServer
22
+ General = {
23
+ :ServerName => Utils::getservername,
24
+ :BindAddress => nil, # "0.0.0.0" or "::" or nil
25
+ :Port => nil, # users MUST specifiy this!!
26
+ :MaxClients => 100, # maximum number of the concurrent connections
27
+ :ServerType => nil, # default: WEBrick::SimpleServer
28
+ :Logger => nil, # default: WEBrick::Log.new
29
+ :ServerSoftware => "WEBrick/#{WEBrick::VERSION} " +
30
+ "(Ruby/#{RUBY_VERSION}/#{RUBY_RELEASE_DATE})",
31
+ :TempDir => ENV['TMPDIR']||ENV['TMP']||ENV['TEMP']||'/tmp',
32
+ :DoNotListen => false,
33
+ :StartCallback => nil,
34
+ :StopCallback => nil,
35
+ :AcceptCallback => nil,
36
+ }
37
+
38
+ # for HTTPServer, HTTPRequest, HTTPResponse ...
39
+ HTTP = General.dup.update(
40
+ :Port => 80,
41
+ :RequestTimeout => 30,
42
+ :HTTPVersion => HTTPVersion.new("1.1"),
43
+ :AccessLog => nil,
44
+ :MimeTypes => HTTPUtils::DefaultMimeTypes,
45
+ :DirectoryIndex => ["index.html","index.htm","index.cgi","index.rhtml"],
46
+ :DocumentRoot => nil,
47
+ :DocumentRootOptions => { :FancyIndexing => true },
48
+ :RequestHandler => nil,
49
+ :RequestCallback => nil, # alias of :RequestHandler
50
+ :ServerAlias => nil,
51
+
52
+ # for HTTPProxyServer
53
+ :ProxyAuthProc => nil,
54
+ :ProxyContentHandler => nil,
55
+ :ProxyVia => true,
56
+ :ProxyTimeout => true,
57
+ :ProxyURI => nil,
58
+
59
+ :CGIInterpreter => nil,
60
+ :CGIPathEnv => nil,
61
+
62
+ # workaround: if Request-URIs contain 8bit chars,
63
+ # they should be escaped before calling of URI::parse().
64
+ :Escape8bitURI => false
65
+ )
66
+
67
+ FileHandler = {
68
+ :NondisclosureName => [".ht*", "*~"],
69
+ :FancyIndexing => false,
70
+ :HandlerTable => {},
71
+ :HandlerCallback => nil,
72
+ :DirectoryCallback => nil,
73
+ :FileCallback => nil,
74
+ :UserDir => nil, # e.g. "public_html"
75
+ :AcceptableLanguages => [] # ["en", "ja", ... ]
76
+ }
77
+
78
+ BasicAuth = {
79
+ :AutoReloadUserDB => true,
80
+ }
81
+
82
+ DigestAuth = {
83
+ :Algorithm => 'MD5-sess', # or 'MD5'
84
+ :Domain => nil, # an array includes domain names.
85
+ :Qop => [ 'auth' ], # 'auth' or 'auth-int' or both.
86
+ :UseOpaque => true,
87
+ :UseNextNonce => false,
88
+ :CheckNc => false,
89
+ :UseAuthenticationInfoHeader => true,
90
+ :AutoReloadUserDB => true,
91
+ :NonceExpirePeriod => 30*60,
92
+ :NonceExpireDelta => 60,
93
+ :InternetExplorerHack => true,
94
+ :OperaHack => true,
95
+ }
96
+ end
97
+ end
@@ -0,0 +1,110 @@
1
+ #
2
+ # cookie.rb -- Cookie class
3
+ #
4
+ # Author: IPR -- Internet Programming with Ruby -- writers
5
+ # Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
6
+ # Copyright (c) 2002 Internet Programming with Ruby writers. All rights
7
+ # reserved.
8
+ #
9
+ # $IPR: cookie.rb,v 1.16 2002/09/21 12:23:35 gotoyuzo Exp $
10
+
11
+ require 'time'
12
+ require 'webrick/httputils'
13
+
14
+ module WEBrick
15
+ class Cookie
16
+
17
+ attr_reader :name
18
+ attr_accessor :value, :version
19
+ attr_accessor :domain, :path, :secure
20
+ attr_accessor :comment, :max_age
21
+ #attr_accessor :comment_url, :discard, :port
22
+
23
+ def initialize(name, value)
24
+ @name = name
25
+ @value = value
26
+ @version = 0 # Netscape Cookie
27
+
28
+ @domain = @path = @secure = @comment = @max_age =
29
+ @expires = @comment_url = @discard = @port = nil
30
+ end
31
+
32
+ def expires=(t)
33
+ @expires = t && (t.is_a?(Time) ? t.httpdate : t.to_s)
34
+ end
35
+
36
+ def expires
37
+ @expires && Time.parse(@expires)
38
+ end
39
+
40
+ def to_s
41
+ ret = ""
42
+ ret << @name << "=" << @value
43
+ ret << "; " << "Version=" << @version.to_s if @version > 0
44
+ ret << "; " << "Domain=" << @domain if @domain
45
+ ret << "; " << "Expires=" << @expires if @expires
46
+ ret << "; " << "Max-Age=" << @max_age.to_s if @max_age
47
+ ret << "; " << "Comment=" << @comment if @comment
48
+ ret << "; " << "Path=" << @path if @path
49
+ ret << "; " << "Secure" if @secure
50
+ ret
51
+ end
52
+
53
+ # Cookie::parse()
54
+ # It parses Cookie field sent from the user agent.
55
+ def self.parse(str)
56
+ if str
57
+ ret = []
58
+ cookie = nil
59
+ ver = 0
60
+ str.split(/[;,]\s+/).each{|x|
61
+ key, val = x.split(/=/,2)
62
+ val = val ? HTTPUtils::dequote(val) : ""
63
+ case key
64
+ when "$Version"; ver = val.to_i
65
+ when "$Path"; cookie.path = val
66
+ when "$Domain"; cookie.domain = val
67
+ when "$Port"; cookie.port = val
68
+ else
69
+ ret << cookie if cookie
70
+ cookie = self.new(key, val)
71
+ cookie.version = ver
72
+ end
73
+ }
74
+ ret << cookie if cookie
75
+ ret
76
+ end
77
+ end
78
+
79
+ def self.parse_set_cookie(str)
80
+ cookie_elem = str.split(/;/)
81
+ first_elem = cookie_elem.shift
82
+ first_elem.strip!
83
+ key, value = first_elem.split(/=/, 2)
84
+ cookie = new(key, HTTPUtils.dequote(value))
85
+ cookie_elem.each{|pair|
86
+ pair.strip!
87
+ key, value = pair.split(/=/, 2)
88
+ if value
89
+ value = HTTPUtils.dequote(value.strip)
90
+ end
91
+ case key.downcase
92
+ when "domain" then cookie.domain = value
93
+ when "path" then cookie.path = value
94
+ when "expires" then cookie.expires = value
95
+ when "max-age" then cookie.max_age = Integer(value)
96
+ when "comment" then cookie.comment = value
97
+ when "version" then cookie.version = Integer(value)
98
+ when "secure" then cookie.secure = true
99
+ end
100
+ }
101
+ return cookie
102
+ end
103
+
104
+ def self.parse_set_cookies(str)
105
+ return str.split(/,(?=[^;,]*=)|,$/).collect{|c|
106
+ parse_set_cookie(c)
107
+ }
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,25 @@
1
+ #
2
+ # htmlutils.rb -- HTMLUtils Module
3
+ #
4
+ # Author: IPR -- Internet Programming with Ruby -- writers
5
+ # Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
6
+ # Copyright (c) 2002 Internet Programming with Ruby writers. All rights
7
+ # reserved.
8
+ #
9
+ # $IPR: htmlutils.rb,v 1.7 2002/09/21 12:23:35 gotoyuzo Exp $
10
+
11
+ module WEBrick
12
+ module HTMLUtils
13
+
14
+ def escape(string)
15
+ str = string ? string.dup : ""
16
+ str.gsub!(/&/n, '&amp;')
17
+ str.gsub!(/\"/n, '&quot;')
18
+ str.gsub!(/>/n, '&gt;')
19
+ str.gsub!(/</n, '&lt;')
20
+ str
21
+ end
22
+ module_function :escape
23
+
24
+ end
25
+ end
@@ -0,0 +1,45 @@
1
+ #
2
+ # httpauth.rb -- HTTP access authentication
3
+ #
4
+ # Author: IPR -- Internet Programming with Ruby -- writers
5
+ # Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
6
+ # Copyright (c) 2002 Internet Programming with Ruby writers. All rights
7
+ # reserved.
8
+ #
9
+ # $IPR: httpauth.rb,v 1.14 2003/07/22 19:20:42 gotoyuzo Exp $
10
+
11
+ require 'webrick/httpauth/basicauth'
12
+ require 'webrick/httpauth/digestauth'
13
+ require 'webrick/httpauth/htpasswd'
14
+ require 'webrick/httpauth/htdigest'
15
+ require 'webrick/httpauth/htgroup'
16
+
17
+ module WEBrick
18
+ module HTTPAuth
19
+ module_function
20
+
21
+ def _basic_auth(req, res, realm, req_field, res_field, err_type, block)
22
+ user = pass = nil
23
+ if /^Basic\s+(.*)/o =~ req[req_field]
24
+ userpass = $1
25
+ user, pass = userpass.unpack("m*")[0].split(":", 2)
26
+ end
27
+ if block.call(user, pass)
28
+ req.user = user
29
+ return
30
+ end
31
+ res[res_field] = "Basic realm=\"#{realm}\""
32
+ raise err_type
33
+ end
34
+
35
+ def basic_auth(req, res, realm, &block)
36
+ _basic_auth(req, res, realm, "Authorization", "WWW-Authenticate",
37
+ HTTPStatus::Unauthorized, block)
38
+ end
39
+
40
+ def proxy_basic_auth(req, res, realm, &block)
41
+ _basic_auth(req, res, realm, "Proxy-Authorization", "Proxy-Authenticate",
42
+ HTTPStatus::ProxyAuthenticationRequired, block)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,79 @@
1
+ #
2
+ # httpauth/authenticator.rb -- Authenticator mix-in module.
3
+ #
4
+ # Author: IPR -- Internet Programming with Ruby -- writers
5
+ # Copyright (c) 2003 Internet Programming with Ruby writers. All rights
6
+ # reserved.
7
+ #
8
+ # $IPR: authenticator.rb,v 1.3 2003/02/20 07:15:47 gotoyuzo Exp $
9
+
10
+ module WEBrick
11
+ module HTTPAuth
12
+ module Authenticator
13
+ RequestField = "Authorization"
14
+ ResponseField = "WWW-Authenticate"
15
+ ResponseInfoField = "Authentication-Info"
16
+ AuthException = HTTPStatus::Unauthorized
17
+ AuthScheme = nil # must override by the derived class
18
+
19
+ attr_reader :realm, :userdb, :logger
20
+
21
+ private
22
+
23
+ def check_init(config)
24
+ [:UserDB, :Realm].each{|sym|
25
+ unless config[sym]
26
+ raise ArgumentError, "Argument #{sym.inspect} missing."
27
+ end
28
+ }
29
+ @realm = config[:Realm]
30
+ @userdb = config[:UserDB]
31
+ @logger = config[:Logger] || Log::new($stderr)
32
+ @reload_db = config[:AutoReloadUserDB]
33
+ @request_field = self::class::RequestField
34
+ @response_field = self::class::ResponseField
35
+ @resp_info_field = self::class::ResponseInfoField
36
+ @auth_exception = self::class::AuthException
37
+ @auth_scheme = self::class::AuthScheme
38
+ end
39
+
40
+ def check_scheme(req)
41
+ unless credentials = req[@request_field]
42
+ error("no credentials in the request.")
43
+ return nil
44
+ end
45
+ unless match = /^#{@auth_scheme}\s+/.match(credentials)
46
+ error("invalid scheme in %s.", credentials)
47
+ info("%s: %s", @request_field, credentials) if $DEBUG
48
+ return nil
49
+ end
50
+ return match.post_match
51
+ end
52
+
53
+ def log(meth, fmt, *args)
54
+ msg = format("%s %s: ", @auth_scheme, @realm)
55
+ msg << fmt % args
56
+ @logger.send(meth, msg)
57
+ end
58
+
59
+ def error(fmt, *args)
60
+ if @logger.error?
61
+ log(:error, fmt, *args)
62
+ end
63
+ end
64
+
65
+ def info(fmt, *args)
66
+ if @logger.info?
67
+ log(:info, fmt, *args)
68
+ end
69
+ end
70
+ end
71
+
72
+ module ProxyAuthenticator
73
+ RequestField = "Proxy-Authorization"
74
+ ResponseField = "Proxy-Authenticate"
75
+ InfoField = "Proxy-Authentication-Info"
76
+ AuthException = HTTPStatus::ProxyAuthenticationRequired
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,65 @@
1
+ #
2
+ # httpauth/basicauth.rb -- HTTP basic access authentication
3
+ #
4
+ # Author: IPR -- Internet Programming with Ruby -- writers
5
+ # Copyright (c) 2003 Internet Programming with Ruby writers. All rights
6
+ # reserved.
7
+ #
8
+ # $IPR: basicauth.rb,v 1.5 2003/02/20 07:15:47 gotoyuzo Exp $
9
+
10
+ require 'webrick/config'
11
+ require 'webrick/httpstatus'
12
+ require 'webrick/httpauth/authenticator'
13
+
14
+ module WEBrick
15
+ module HTTPAuth
16
+ class BasicAuth
17
+ include Authenticator
18
+
19
+ AuthScheme = "Basic"
20
+
21
+ def self.make_passwd(realm, user, pass)
22
+ pass ||= ""
23
+ pass.crypt(Utils::random_string(2))
24
+ end
25
+
26
+ attr_reader :realm, :userdb, :logger
27
+
28
+ def initialize(config, default=Config::BasicAuth)
29
+ check_init(config)
30
+ @config = default.dup.update(config)
31
+ end
32
+
33
+ def authenticate(req, res)
34
+ unless basic_credentials = check_scheme(req)
35
+ challenge(req, res)
36
+ end
37
+ userid, password = basic_credentials.unpack("m*")[0].split(":", 2)
38
+ password ||= ""
39
+ if userid.empty?
40
+ error("user id was not given.")
41
+ challenge(req, res)
42
+ end
43
+ unless encpass = @userdb.get_passwd(@realm, userid, @reload_db)
44
+ error("%s: the user is not allowed.", userid)
45
+ challenge(req, res)
46
+ end
47
+ if password.crypt(encpass) != encpass
48
+ error("%s: password unmatch.", userid)
49
+ challenge(req, res)
50
+ end
51
+ info("%s: authentication succeeded.", userid)
52
+ req.user = userid
53
+ end
54
+
55
+ def challenge(req, res)
56
+ res[@response_field] = "#{@auth_scheme} realm=\"#{@realm}\""
57
+ raise @auth_exception
58
+ end
59
+ end
60
+
61
+ class ProxyBasicAuth < BasicAuth
62
+ include ProxyAuthenticator
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,343 @@
1
+ #
2
+ # httpauth/digestauth.rb -- HTTP digest access authentication
3
+ #
4
+ # Author: IPR -- Internet Programming with Ruby -- writers
5
+ # Copyright (c) 2003 Internet Programming with Ruby writers.
6
+ # Copyright (c) 2003 H.M.
7
+ #
8
+ # The original implementation is provided by H.M.
9
+ # URL: http://rwiki.jin.gr.jp/cgi-bin/rw-cgi.rb?cmd=view;name=
10
+ # %C7%A7%BE%DA%B5%A1%C7%BD%A4%F2%B2%FE%C2%A4%A4%B7%A4%C6%A4%DF%A4%EB
11
+ #
12
+ # $IPR: digestauth.rb,v 1.5 2003/02/20 07:15:47 gotoyuzo Exp $
13
+
14
+ require 'webrick/config'
15
+ require 'webrick/httpstatus'
16
+ require 'webrick/httpauth/authenticator'
17
+ require 'digest/md5'
18
+ require 'digest/sha1'
19
+
20
+ module WEBrick
21
+ module HTTPAuth
22
+ class DigestAuth
23
+ include Authenticator
24
+
25
+ AuthScheme = "Digest"
26
+ OpaqueInfo = Struct.new(:time, :nonce, :nc)
27
+ attr_reader :algorithm, :qop
28
+
29
+ def self.make_passwd(realm, user, pass)
30
+ pass ||= ""
31
+ Digest::MD5::hexdigest([user, realm, pass].join(":"))
32
+ end
33
+
34
+ def initialize(config, default=Config::DigestAuth)
35
+ check_init(config)
36
+ @config = default.dup.update(config)
37
+ @algorithm = @config[:Algorithm]
38
+ @domain = @config[:Domain]
39
+ @qop = @config[:Qop]
40
+ @use_opaque = @config[:UseOpaque]
41
+ @use_next_nonce = @config[:UseNextNonce]
42
+ @check_nc = @config[:CheckNc]
43
+ @use_auth_info_header = @config[:UseAuthenticationInfoHeader]
44
+ @nonce_expire_period = @config[:NonceExpirePeriod]
45
+ @nonce_expire_delta = @config[:NonceExpireDelta]
46
+ @internet_explorer_hack = @config[:InternetExplorerHack]
47
+ @opera_hack = @config[:OperaHack]
48
+
49
+ case @algorithm
50
+ when 'MD5','MD5-sess'
51
+ @h = Digest::MD5
52
+ when 'SHA1','SHA1-sess' # it is a bonus feature :-)
53
+ @h = Digest::SHA1
54
+ else
55
+ msg = format('Alogrithm "%s" is not supported.', @algorithm)
56
+ raise ArgumentError.new(msg)
57
+ end
58
+
59
+ @instance_key = hexdigest(self.__id__, Time.now.to_i, Process.pid)
60
+ @opaques = {}
61
+ @last_nonce_expire = Time.now
62
+ @mutex = Mutex.new
63
+ end
64
+
65
+ def authenticate(req, res)
66
+ unless result = @mutex.synchronize{ _authenticate(req, res) }
67
+ challenge(req, res)
68
+ end
69
+ if result == :nonce_is_stale
70
+ challenge(req, res, true)
71
+ end
72
+ return true
73
+ end
74
+
75
+ def challenge(req, res, stale=false)
76
+ nonce = generate_next_nonce(req)
77
+ if @use_opaque
78
+ opaque = generate_opaque(req)
79
+ @opaques[opaque].nonce = nonce
80
+ end
81
+
82
+ param = Hash.new
83
+ param["realm"] = HTTPUtils::quote(@realm)
84
+ param["domain"] = HTTPUtils::quote(@domain.to_a.join(" ")) if @domain
85
+ param["nonce"] = HTTPUtils::quote(nonce)
86
+ param["opaque"] = HTTPUtils::quote(opaque) if opaque
87
+ param["stale"] = stale.to_s
88
+ param["algorithm"] = @algorithm
89
+ param["qop"] = HTTPUtils::quote(@qop.to_a.join(",")) if @qop
90
+
91
+ res[@response_field] =
92
+ "#{@auth_scheme} " + param.map{|k,v| "#{k}=#{v}" }.join(", ")
93
+ info("%s: %s", @response_field, res[@response_field]) if $DEBUG
94
+ raise @auth_exception
95
+ end
96
+
97
+ private
98
+
99
+ MustParams = ['username','realm','nonce','uri','response']
100
+ MustParamsAuth = ['cnonce','nc']
101
+
102
+ def _authenticate(req, res)
103
+ unless digest_credentials = check_scheme(req)
104
+ return false
105
+ end
106
+
107
+ auth_req = split_param_value(digest_credentials)
108
+ if auth_req['qop'] == "auth" || auth_req['qop'] == "auth-int"
109
+ req_params = MustParams + MustParamsAuth
110
+ else
111
+ req_params = MustParams
112
+ end
113
+ req_params.each{|key|
114
+ unless auth_req.has_key?(key)
115
+ error('%s: parameter missing. "%s"', auth_req['username'], key)
116
+ raise HTTPStatus::BadRequest
117
+ end
118
+ }
119
+
120
+ if !check_uri(req, auth_req)
121
+ raise HTTPStatus::BadRequest
122
+ end
123
+
124
+ if auth_req['realm'] != @realm
125
+ error('%s: realm unmatch. "%s" for "%s"',
126
+ auth_req['username'], auth_req['realm'], @realm)
127
+ return false
128
+ end
129
+
130
+ auth_req['algorithm'] ||= 'MD5'
131
+ if auth_req['algorithm'] != @algorithm &&
132
+ (@opera_hack && auth_req['algorithm'] != @algorithm.upcase)
133
+ error('%s: algorithm unmatch. "%s" for "%s"',
134
+ auth_req['username'], auth_req['algorithm'], @algorithm)
135
+ return false
136
+ end
137
+
138
+ if (@qop.nil? && auth_req.has_key?('qop')) ||
139
+ (@qop && (! @qop.member?(auth_req['qop'])))
140
+ error('%s: the qop is not allowed. "%s"',
141
+ auth_req['username'], auth_req['qop'])
142
+ return false
143
+ end
144
+
145
+ password = @userdb.get_passwd(@realm, auth_req['username'], @reload_db)
146
+ unless password
147
+ error('%s: the user is not allowd.', auth_req['username'])
148
+ return false
149
+ end
150
+
151
+ nonce_is_invalid = false
152
+ if @use_opaque
153
+ info("@opaque = %s", @opaque.inspect) if $DEBUG
154
+ if !(opaque = auth_req['opaque'])
155
+ error('%s: opaque is not given.', auth_req['username'])
156
+ nonce_is_invalid = true
157
+ elsif !(opaque_struct = @opaques[opaque])
158
+ error('%s: invalid opaque is given.', auth_req['username'])
159
+ nonce_is_invalid = true
160
+ elsif !check_opaque(opaque_struct, req, auth_req)
161
+ @opaques.delete(auth_req['opaque'])
162
+ nonce_is_invalid = true
163
+ end
164
+ elsif !check_nonce(req, auth_req)
165
+ nonce_is_invalid = true
166
+ end
167
+
168
+ if /-sess$/ =~ auth_req['algorithm'] ||
169
+ (@opera_hack && /-SESS$/ =~ auth_req['algorithm'])
170
+ ha1 = hexdigest(password, auth_req['nonce'], auth_req['cnonce'])
171
+ else
172
+ ha1 = password
173
+ end
174
+
175
+ if auth_req['qop'] == "auth" || auth_req['qop'] == nil
176
+ ha2 = hexdigest(req.request_method, auth_req['uri'])
177
+ ha2_res = hexdigest("", auth_req['uri'])
178
+ elsif auth_req['qop'] == "auth-int"
179
+ ha2 = hexdigest(req.request_method, auth_req['uri'],
180
+ hexdigest(req.body))
181
+ ha2_res = hexdigest("", auth_req['uri'], hexdigest(res.body))
182
+ end
183
+
184
+ if auth_req['qop'] == "auth" || auth_req['qop'] == "auth-int"
185
+ param2 = ['nonce', 'nc', 'cnonce', 'qop'].map{|key|
186
+ auth_req[key]
187
+ }.join(':')
188
+ digest = hexdigest(ha1, param2, ha2)
189
+ digest_res = hexdigest(ha1, param2, ha2_res)
190
+ else
191
+ digest = hexdigest(ha1, auth_req['nonce'], ha2)
192
+ digest_res = hexdigest(ha1, auth_req['nonce'], ha2_res)
193
+ end
194
+
195
+ if digest != auth_req['response']
196
+ error("%s: digest unmatch.", auth_req['username'])
197
+ return false
198
+ elsif nonce_is_invalid
199
+ error('%s: digest is valid, but nonce is not valid.',
200
+ auth_req['username'])
201
+ return :nonce_is_stale
202
+ elsif @use_auth_info_header
203
+ auth_info = {
204
+ 'nextnonce' => generate_next_nonce(req),
205
+ 'rspauth' => digest_res
206
+ }
207
+ if @use_opaque
208
+ opaque_struct.time = req.request_time
209
+ opaque_struct.nonce = auth_info['nextnonce']
210
+ opaque_struct.nc = "%08x" % (auth_req['nc'].hex + 1)
211
+ end
212
+ if auth_req['qop'] == "auth" || auth_req['qop'] == "auth-int"
213
+ ['qop','cnonce','nc'].each{|key|
214
+ auth_info[key] = auth_req[key]
215
+ }
216
+ end
217
+ res[@resp_info_field] = auth_info.keys.map{|key|
218
+ if key == 'nc'
219
+ key + '=' + auth_info[key]
220
+ else
221
+ key + "=" + HTTPUtils::quote(auth_info[key])
222
+ end
223
+ }.join(', ')
224
+ end
225
+ info('%s: authentication scceeded.', auth_req['username'])
226
+ req.user = auth_req['username']
227
+ return true
228
+ end
229
+
230
+ def split_param_value(string)
231
+ ret = {}
232
+ while string.size != 0
233
+ case string
234
+ when /^\s*([\w\-\.\*\%\!]+)=\s*\"((\\.|[^\"])*)\"\s*,?/
235
+ key = $1
236
+ matched = $2
237
+ string = $'
238
+ ret[key] = matched.gsub(/\\(.)/, "\\1")
239
+ when /^\s*([\w\-\.\*\%\!]+)=\s*([^,\"]*),?/
240
+ key = $1
241
+ matched = $2
242
+ string = $'
243
+ ret[key] = matched.clone
244
+ when /^s*^,/
245
+ string = $'
246
+ else
247
+ break
248
+ end
249
+ end
250
+ ret
251
+ end
252
+
253
+ def generate_next_nonce(req)
254
+ now = "%012d" % req.request_time.to_i
255
+ pk = hexdigest(now, @instance_key)[0,32]
256
+ nonce = [now + ":" + pk].pack("m*").chop # it has 60 length of chars.
257
+ nonce
258
+ end
259
+
260
+ def check_nonce(req, auth_req)
261
+ username = auth_req['username']
262
+ nonce = auth_req['nonce']
263
+
264
+ pub_time, pk = nonce.unpack("m*")[0].split(":", 2)
265
+ if (!pub_time || !pk)
266
+ error("%s: empty nonce is given", username)
267
+ return false
268
+ elsif (hexdigest(pub_time, @instance_key)[0,32] != pk)
269
+ error("%s: invalid private-key: %s for %s",
270
+ username, hexdigest(pub_time, @instance_key)[0,32], pk)
271
+ return false
272
+ end
273
+
274
+ diff_time = req.request_time.to_i - pub_time.to_i
275
+ if (diff_time < 0)
276
+ error("%s: difference of time-stamp is negative.", username)
277
+ return false
278
+ elsif diff_time > @nonce_expire_period
279
+ error("%s: nonce is expired.", username)
280
+ return false
281
+ end
282
+
283
+ return true
284
+ end
285
+
286
+ def generate_opaque(req)
287
+ @mutex.synchronize{
288
+ now = req.request_time
289
+ if now - @last_nonce_expire > @nonce_expire_delta
290
+ @opaques.delete_if{|key,val|
291
+ (now - val.time) > @nonce_expire_period
292
+ }
293
+ @last_nonce_expire = now
294
+ end
295
+ begin
296
+ opaque = Utils::random_string(16)
297
+ end while @opaques[opaque]
298
+ @opaques[opaque] = OpaqueInfo.new(now, nil, '00000001')
299
+ opaque
300
+ }
301
+ end
302
+
303
+ def check_opaque(opaque_struct, req, auth_req)
304
+ if (@use_next_nonce && auth_req['nonce'] != opaque_struct.nonce)
305
+ error('%s: nonce unmatched. "%s" for "%s"',
306
+ auth_req['username'], auth_req['nonce'], opaque_struct.nonce)
307
+ return false
308
+ elsif !check_nonce(req, auth_req)
309
+ return false
310
+ end
311
+ if (@check_nc && auth_req['nc'] != opaque_struct.nc)
312
+ error('%s: nc unmatched."%s" for "%s"',
313
+ auth_req['username'], auth_req['nc'], opaque_struct.nc)
314
+ return false
315
+ end
316
+ true
317
+ end
318
+
319
+ def check_uri(req, auth_req)
320
+ uri = auth_req['uri']
321
+ if uri != req.request_uri.to_s && uri != req.unparsed_uri &&
322
+ (@internet_explorer_hack && uri != req.path)
323
+ error('%s: uri unmatch. "%s" for "%s"', auth_req['username'],
324
+ auth_req['uri'], req.request_uri.to_s)
325
+ return false
326
+ end
327
+ true
328
+ end
329
+
330
+ def hexdigest(*args)
331
+ @h.hexdigest(args.join(":"))
332
+ end
333
+ end
334
+
335
+ class ProxyDigestAuth < DigestAuth
336
+ include ProxyAuthenticator
337
+
338
+ def check_uri(req, auth_req)
339
+ return true
340
+ end
341
+ end
342
+ end
343
+ end