rubysl-webrick 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +14 -6
  2. data/.travis.yml +5 -6
  3. data/lib/rubysl/webrick/version.rb +1 -1
  4. data/lib/rubysl/webrick/webrick.rb +199 -2
  5. data/lib/webrick/accesslog.rb +96 -5
  6. data/lib/webrick/cgi.rb +80 -29
  7. data/lib/webrick/compat.rb +20 -0
  8. data/lib/webrick/config.rb +59 -5
  9. data/lib/webrick/cookie.rb +66 -5
  10. data/lib/webrick/htmlutils.rb +4 -1
  11. data/lib/webrick/httpauth.rb +53 -3
  12. data/lib/webrick/httpauth/authenticator.rb +53 -16
  13. data/lib/webrick/httpauth/basicauth.rb +45 -2
  14. data/lib/webrick/httpauth/digestauth.rb +82 -17
  15. data/lib/webrick/httpauth/htdigest.rb +38 -1
  16. data/lib/webrick/httpauth/htgroup.rb +32 -0
  17. data/lib/webrick/httpauth/htpasswd.rb +40 -2
  18. data/lib/webrick/httpauth/userdb.rb +27 -4
  19. data/lib/webrick/httpproxy.rb +197 -112
  20. data/lib/webrick/httprequest.rb +268 -50
  21. data/lib/webrick/httpresponse.rb +170 -33
  22. data/lib/webrick/https.rb +26 -3
  23. data/lib/webrick/httpserver.rb +75 -7
  24. data/lib/webrick/httpservlet/abstract.rb +88 -6
  25. data/lib/webrick/httpservlet/cgi_runner.rb +5 -4
  26. data/lib/webrick/httpservlet/cgihandler.rb +37 -18
  27. data/lib/webrick/httpservlet/erbhandler.rb +40 -7
  28. data/lib/webrick/httpservlet/filehandler.rb +116 -28
  29. data/lib/webrick/httpservlet/prochandler.rb +17 -4
  30. data/lib/webrick/httpstatus.rb +86 -18
  31. data/lib/webrick/httputils.rb +131 -23
  32. data/lib/webrick/httpversion.rb +28 -2
  33. data/lib/webrick/log.rb +72 -5
  34. data/lib/webrick/server.rb +158 -33
  35. data/lib/webrick/ssl.rb +78 -9
  36. data/lib/webrick/utils.rb +151 -5
  37. data/lib/webrick/version.rb +5 -1
  38. data/rubysl-webrick.gemspec +0 -1
  39. metadata +12 -24
@@ -13,10 +13,31 @@ require 'webrick/httpauth/authenticator'
13
13
 
14
14
  module WEBrick
15
15
  module HTTPAuth
16
+
17
+ ##
18
+ # Basic Authentication for WEBrick
19
+ #
20
+ # Use this class to add basic authentication to a WEBrick servlet.
21
+ #
22
+ # Here is an example of how to set up a BasicAuth:
23
+ #
24
+ # config = { :Realm => 'BasicAuth example realm' }
25
+ #
26
+ # htpasswd = WEBrick::HTTPAuth::Htpasswd.new 'my_password_file'
27
+ # htpasswd.set_passwd config[:Realm], 'username', 'password'
28
+ # htpasswd.flush
29
+ #
30
+ # config[:UserDB] = htpasswd
31
+ #
32
+ # basic_auth = WEBrick::HTTPAuth::BasicAuth.new config
33
+
16
34
  class BasicAuth
17
35
  include Authenticator
18
36
 
19
- AuthScheme = "Basic"
37
+ AuthScheme = "Basic" # :nodoc:
38
+
39
+ ##
40
+ # Used by UserDB to create a basic password entry
20
41
 
21
42
  def self.make_passwd(realm, user, pass)
22
43
  pass ||= ""
@@ -25,16 +46,31 @@ module WEBrick
25
46
 
26
47
  attr_reader :realm, :userdb, :logger
27
48
 
49
+ ##
50
+ # Creates a new BasicAuth instance.
51
+ #
52
+ # See WEBrick::Config::BasicAuth for default configuration entries
53
+ #
54
+ # You must supply the following configuration entries:
55
+ #
56
+ # :Realm:: The name of the realm being protected.
57
+ # :UserDB:: A database of usernames and passwords.
58
+ # A WEBrick::HTTPAuth::Htpasswd instance should be used.
59
+
28
60
  def initialize(config, default=Config::BasicAuth)
29
61
  check_init(config)
30
62
  @config = default.dup.update(config)
31
63
  end
32
64
 
65
+ ##
66
+ # Authenticates a +req+ and returns a 401 Unauthorized using +res+ if
67
+ # the authentication was not correct.
68
+
33
69
  def authenticate(req, res)
34
70
  unless basic_credentials = check_scheme(req)
35
71
  challenge(req, res)
36
72
  end
37
- userid, password = basic_credentials.unpack("m*")[0].split(":", 2)
73
+ userid, password = basic_credentials.unpack("m*")[0].split(":", 2)
38
74
  password ||= ""
39
75
  if userid.empty?
40
76
  error("user id was not given.")
@@ -52,12 +88,19 @@ module WEBrick
52
88
  req.user = userid
53
89
  end
54
90
 
91
+ ##
92
+ # Returns a challenge response which asks for for authentication
93
+ # information
94
+
55
95
  def challenge(req, res)
56
96
  res[@response_field] = "#{@auth_scheme} realm=\"#{@realm}\""
57
97
  raise @auth_exception
58
98
  end
59
99
  end
60
100
 
101
+ ##
102
+ # Basic authentication for proxy servers. See BasicAuth for details.
103
+
61
104
  class ProxyBasicAuth < BasicAuth
62
105
  include ProxyAuthenticator
63
106
  end
@@ -19,18 +19,70 @@ require 'digest/sha1'
19
19
 
20
20
  module WEBrick
21
21
  module HTTPAuth
22
+
23
+ ##
24
+ # RFC 2617 Digest Access Authentication for WEBrick
25
+ #
26
+ # Use this class to add digest authentication to a WEBrick servlet.
27
+ #
28
+ # Here is an example of how to set up DigestAuth:
29
+ #
30
+ # config = { :Realm => 'DigestAuth example realm' }
31
+ #
32
+ # htdigest = WEBrick::HTTPAuth::Htdigest.new 'my_password_file'
33
+ # htdigest.set_passwd config[:Realm], 'username', 'password'
34
+ # htdigest.flush
35
+ #
36
+ # config[:UserDB] = htdigest
37
+ #
38
+ # digest_auth = WEBrick::HTTPAuth::DigestAuth.new config
39
+ #
40
+ # When using this as with a servlet be sure not to create a new DigestAuth
41
+ # object in the servlet's #initialize. By default WEBrick creates a new
42
+ # servlet instance for every request and the DigestAuth object must be
43
+ # used across requests.
44
+
22
45
  class DigestAuth
23
46
  include Authenticator
24
47
 
25
- AuthScheme = "Digest"
26
- OpaqueInfo = Struct.new(:time, :nonce, :nc)
27
- attr_reader :algorithm, :qop
48
+ AuthScheme = "Digest" # :nodoc:
49
+
50
+ ##
51
+ # Struct containing the opaque portion of the digest authentication
52
+
53
+ OpaqueInfo = Struct.new(:time, :nonce, :nc) # :nodoc:
54
+
55
+ ##
56
+ # Digest authentication algorithm
57
+
58
+ attr_reader :algorithm
59
+
60
+ ##
61
+ # Quality of protection. RFC 2617 defines "auth" and "auth-int"
62
+
63
+ attr_reader :qop
64
+
65
+ ##
66
+ # Used by UserDB to create a digest password entry
28
67
 
29
68
  def self.make_passwd(realm, user, pass)
30
69
  pass ||= ""
31
70
  Digest::MD5::hexdigest([user, realm, pass].join(":"))
32
71
  end
33
72
 
73
+ ##
74
+ # Creates a new DigestAuth instance. Be sure to use the same DigestAuth
75
+ # instance for multiple requests as it saves state between requests in
76
+ # order to perform authentication.
77
+ #
78
+ # See WEBrick::Config::DigestAuth for default configuration entries
79
+ #
80
+ # You must supply the following configuration entries:
81
+ #
82
+ # :Realm:: The name of the realm being protected.
83
+ # :UserDB:: A database of usernames and passwords.
84
+ # A WEBrick::HTTPAuth::Htdigest instance should be used.
85
+
34
86
  def initialize(config, default=Config::DigestAuth)
35
87
  check_init(config)
36
88
  @config = default.dup.update(config)
@@ -44,7 +96,6 @@ module WEBrick
44
96
  @nonce_expire_period = @config[:NonceExpirePeriod]
45
97
  @nonce_expire_delta = @config[:NonceExpireDelta]
46
98
  @internet_explorer_hack = @config[:InternetExplorerHack]
47
- @opera_hack = @config[:OperaHack]
48
99
 
49
100
  case @algorithm
50
101
  when 'MD5','MD5-sess'
@@ -52,7 +103,7 @@ module WEBrick
52
103
  when 'SHA1','SHA1-sess' # it is a bonus feature :-)
53
104
  @h = Digest::SHA1
54
105
  else
55
- msg = format('Alogrithm "%s" is not supported.', @algorithm)
106
+ msg = format('Algorithm "%s" is not supported.', @algorithm)
56
107
  raise ArgumentError.new(msg)
57
108
  end
58
109
 
@@ -62,6 +113,10 @@ module WEBrick
62
113
  @mutex = Mutex.new
63
114
  end
64
115
 
116
+ ##
117
+ # Authenticates a +req+ and returns a 401 Unauthorized using +res+ if
118
+ # the authentication was not correct.
119
+
65
120
  def authenticate(req, res)
66
121
  unless result = @mutex.synchronize{ _authenticate(req, res) }
67
122
  challenge(req, res)
@@ -72,6 +127,10 @@ module WEBrick
72
127
  return true
73
128
  end
74
129
 
130
+ ##
131
+ # Returns a challenge response which asks for for authentication
132
+ # information
133
+
75
134
  def challenge(req, res, stale=false)
76
135
  nonce = generate_next_nonce(req)
77
136
  if @use_opaque
@@ -96,6 +155,8 @@ module WEBrick
96
155
 
97
156
  private
98
157
 
158
+ # :stopdoc:
159
+
99
160
  MustParams = ['username','realm','nonce','uri','response']
100
161
  MustParamsAuth = ['cnonce','nc']
101
162
 
@@ -118,18 +179,17 @@ module WEBrick
118
179
  }
119
180
 
120
181
  if !check_uri(req, auth_req)
121
- raise HTTPStatus::BadRequest
182
+ raise HTTPStatus::BadRequest
122
183
  end
123
184
 
124
- if auth_req['realm'] != @realm
185
+ if auth_req['realm'] != @realm
125
186
  error('%s: realm unmatch. "%s" for "%s"',
126
187
  auth_req['username'], auth_req['realm'], @realm)
127
188
  return false
128
189
  end
129
190
 
130
- auth_req['algorithm'] ||= 'MD5'
131
- if auth_req['algorithm'] != @algorithm &&
132
- (@opera_hack && auth_req['algorithm'] != @algorithm.upcase)
191
+ auth_req['algorithm'] ||= 'MD5'
192
+ if auth_req['algorithm'].upcase != @algorithm.upcase
133
193
  error('%s: algorithm unmatch. "%s" for "%s"',
134
194
  auth_req['username'], auth_req['algorithm'], @algorithm)
135
195
  return false
@@ -165,8 +225,7 @@ module WEBrick
165
225
  nonce_is_invalid = true
166
226
  end
167
227
 
168
- if /-sess$/ =~ auth_req['algorithm'] ||
169
- (@opera_hack && /-SESS$/ =~ auth_req['algorithm'])
228
+ if /-sess$/i =~ auth_req['algorithm']
170
229
  ha1 = hexdigest(password, auth_req['nonce'], auth_req['cnonce'])
171
230
  else
172
231
  ha1 = password
@@ -222,15 +281,15 @@ module WEBrick
222
281
  end
223
282
  }.join(', ')
224
283
  end
225
- info('%s: authentication scceeded.', auth_req['username'])
284
+ info('%s: authentication succeeded.', auth_req['username'])
226
285
  req.user = auth_req['username']
227
286
  return true
228
287
  end
229
288
 
230
289
  def split_param_value(string)
231
290
  ret = {}
232
- while string.size != 0
233
- case string
291
+ while string.bytesize != 0
292
+ case string
234
293
  when /^\s*([\w\-\.\*\%\!]+)=\s*\"((\\.|[^\"])*)\"\s*,?/
235
294
  key = $1
236
295
  matched = $2
@@ -320,7 +379,7 @@ module WEBrick
320
379
  uri = auth_req['uri']
321
380
  if uri != req.request_uri.to_s && uri != req.unparsed_uri &&
322
381
  (@internet_explorer_hack && uri != req.path)
323
- error('%s: uri unmatch. "%s" for "%s"', auth_req['username'],
382
+ error('%s: uri unmatch. "%s" for "%s"', auth_req['username'],
324
383
  auth_req['uri'], req.request_uri.to_s)
325
384
  return false
326
385
  end
@@ -330,12 +389,18 @@ module WEBrick
330
389
  def hexdigest(*args)
331
390
  @h.hexdigest(args.join(":"))
332
391
  end
392
+
393
+ # :startdoc:
333
394
  end
334
395
 
396
+ ##
397
+ # Digest authentication for proxy servers. See DigestAuth for details.
398
+
335
399
  class ProxyDigestAuth < DigestAuth
336
400
  include ProxyAuthenticator
337
401
 
338
- def check_uri(req, auth_req)
402
+ private
403
+ def check_uri(req, auth_req) # :nodoc:
339
404
  return true
340
405
  end
341
406
  end
@@ -13,9 +13,26 @@ require 'tempfile'
13
13
 
14
14
  module WEBrick
15
15
  module HTTPAuth
16
+
17
+ ##
18
+ # Htdigest accesses apache-compatible digest password files. Passwords are
19
+ # matched to a realm where they are valid. For security, the path for a
20
+ # digest password database should be stored outside of the paths available
21
+ # to the HTTP server.
22
+ #
23
+ # Htdigest is intended for use with WEBrick::HTTPAuth::DigestAuth and
24
+ # stores passwords using cryptographic hashes.
25
+ #
26
+ # htpasswd = WEBrick::HTTPAuth::Htdigest.new 'my_password_file'
27
+ # htpasswd.set_passwd 'my realm', 'username', 'password'
28
+ # htpasswd.flush
29
+
16
30
  class Htdigest
17
31
  include UserDB
18
32
 
33
+ ##
34
+ # Open a digest password database at +path+
35
+
19
36
  def initialize(path)
20
37
  @path = path
21
38
  @mtime = Time.at(0)
@@ -26,6 +43,9 @@ module WEBrick
26
43
  reload
27
44
  end
28
45
 
46
+ ##
47
+ # Reloads passwords from the database
48
+
29
49
  def reload
30
50
  mtime = File::mtime(@path)
31
51
  if mtime > @mtime
@@ -44,6 +64,10 @@ module WEBrick
44
64
  end
45
65
  end
46
66
 
67
+ ##
68
+ # Flush the password database. If +output+ is given the database will
69
+ # be written there instead of to the original path.
70
+
47
71
  def flush(output=nil)
48
72
  output ||= @path
49
73
  tmp = Tempfile.new("htpasswd", File::dirname(output))
@@ -56,6 +80,10 @@ module WEBrick
56
80
  end
57
81
  end
58
82
 
83
+ ##
84
+ # Retrieves a password from the database for +user+ in +realm+. If
85
+ # +reload_db+ is true the database will be reloaded first.
86
+
59
87
  def get_passwd(realm, user, reload_db)
60
88
  reload() if reload_db
61
89
  if hash = @digest[realm]
@@ -63,6 +91,9 @@ module WEBrick
63
91
  end
64
92
  end
65
93
 
94
+ ##
95
+ # Sets a password in the database for +user+ in +realm+ to +pass+.
96
+
66
97
  def set_passwd(realm, user, pass)
67
98
  @mutex.synchronize{
68
99
  unless @digest[realm]
@@ -72,13 +103,19 @@ module WEBrick
72
103
  }
73
104
  end
74
105
 
106
+ ##
107
+ # Removes a password from the database for +user+ in +realm+.
108
+
75
109
  def delete_passwd(realm, user)
76
110
  if hash = @digest[realm]
77
111
  hash.delete(user)
78
112
  end
79
113
  end
80
114
 
81
- def each
115
+ ##
116
+ # Iterate passwords in the database.
117
+
118
+ def each # :yields: [user, realm, password_hash]
82
119
  @digest.keys.sort.each{|realm|
83
120
  hash = @digest[realm]
84
121
  hash.keys.sort.each{|user|
@@ -11,7 +11,26 @@ require 'tempfile'
11
11
 
12
12
  module WEBrick
13
13
  module HTTPAuth
14
+
15
+ ##
16
+ # Htgroup accesses apache-compatible group files. Htgroup can be used to
17
+ # provide group-based authentication for users. Currently Htgroup is not
18
+ # directly integrated with any authenticators in WEBrick. For security,
19
+ # the path for a digest password database should be stored outside of the
20
+ # paths available to the HTTP server.
21
+ #
22
+ # Example:
23
+ #
24
+ # htgroup = WEBrick::HTTPAuth::Htgroup.new 'my_group_file'
25
+ # htgroup.add 'superheroes', %w[spiderman batman]
26
+ #
27
+ # htgroup.members('superheroes').include? 'magneto' # => false
28
+
14
29
  class Htgroup
30
+
31
+ ##
32
+ # Open a group database at +path+
33
+
15
34
  def initialize(path)
16
35
  @path = path
17
36
  @mtime = Time.at(0)
@@ -20,6 +39,9 @@ module WEBrick
20
39
  reload
21
40
  end
22
41
 
42
+ ##
43
+ # Reload groups from the database
44
+
23
45
  def reload
24
46
  if (mtime = File::mtime(@path)) > @mtime
25
47
  @group.clear
@@ -34,6 +56,10 @@ module WEBrick
34
56
  end
35
57
  end
36
58
 
59
+ ##
60
+ # Flush the group database. If +output+ is given the database will be
61
+ # written there instead of to the original path.
62
+
37
63
  def flush(output=nil)
38
64
  output ||= @path
39
65
  tmp = Tempfile.new("htgroup", File::dirname(output))
@@ -48,11 +74,17 @@ module WEBrick
48
74
  end
49
75
  end
50
76
 
77
+ ##
78
+ # Retrieve the list of members from +group+
79
+
51
80
  def members(group)
52
81
  reload
53
82
  @group[group] || []
54
83
  end
55
84
 
85
+ ##
86
+ # Add an Array of +members+ to +group+
87
+
56
88
  def add(group, members)
57
89
  @group[group] = members(group) | members
58
90
  end
@@ -13,9 +13,27 @@ require 'tempfile'
13
13
 
14
14
  module WEBrick
15
15
  module HTTPAuth
16
+
17
+ ##
18
+ # Htpasswd accesses apache-compatible password files. Passwords are
19
+ # matched to a realm where they are valid. For security, the path for a
20
+ # password database should be stored outside of the paths available to the
21
+ # HTTP server.
22
+ #
23
+ # Htpasswd is intended for use with WEBrick::HTTPAuth::BasicAuth.
24
+ #
25
+ # To create an Htpasswd database with a single user:
26
+ #
27
+ # htpasswd = WEBrick::HTTPAuth::Htpasswd.new 'my_password_file'
28
+ # htpasswd.set_passwd 'my realm', 'username', 'password'
29
+ # htpasswd.flush
30
+
16
31
  class Htpasswd
17
32
  include UserDB
18
33
 
34
+ ##
35
+ # Open a password database at +path+
36
+
19
37
  def initialize(path)
20
38
  @path = path
21
39
  @mtime = Time.at(0)
@@ -25,6 +43,9 @@ module WEBrick
25
43
  reload
26
44
  end
27
45
 
46
+ ##
47
+ # Reload passwords from the database
48
+
28
49
  def reload
29
50
  mtime = File::mtime(@path)
30
51
  if mtime > @mtime
@@ -35,7 +56,7 @@ module WEBrick
35
56
  case line
36
57
  when %r!\A[^:]+:[a-zA-Z0-9./]{13}\z!
37
58
  user, pass = line.split(":")
38
- when /:\$/, /:\{SHA\}/
59
+ when /:\$/, /:{SHA}/
39
60
  raise NotImplementedError,
40
61
  'MD5, SHA1 .htpasswd file not supported'
41
62
  else
@@ -48,6 +69,10 @@ module WEBrick
48
69
  end
49
70
  end
50
71
 
72
+ ##
73
+ # Flush the password database. If +output+ is given the database will
74
+ # be written there instead of to the original path.
75
+
51
76
  def flush(output=nil)
52
77
  output ||= @path
53
78
  tmp = Tempfile.new("htpasswd", File::dirname(output))
@@ -60,20 +85,33 @@ module WEBrick
60
85
  end
61
86
  end
62
87
 
88
+ ##
89
+ # Retrieves a password from the database for +user+ in +realm+. If
90
+ # +reload_db+ is true the database will be reloaded first.
91
+
63
92
  def get_passwd(realm, user, reload_db)
64
93
  reload() if reload_db
65
94
  @passwd[user]
66
95
  end
67
96
 
97
+ ##
98
+ # Sets a password in the database for +user+ in +realm+ to +pass+.
99
+
68
100
  def set_passwd(realm, user, pass)
69
101
  @passwd[user] = make_passwd(realm, user, pass)
70
102
  end
71
103
 
104
+ ##
105
+ # Removes a password from the database for +user+ in +realm+.
106
+
72
107
  def delete_passwd(realm, user)
73
108
  @passwd.delete(user)
74
109
  end
75
110
 
76
- def each
111
+ ##
112
+ # Iterate passwords in the database.
113
+
114
+ def each # :yields: [user, password]
77
115
  @passwd.keys.sort.each{|user|
78
116
  yield([user, @passwd[user]])
79
117
  }