rubysl-webrick 1.0.0

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.
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