webrick 1.3.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of webrick might be problematic. Click here for more details.

Files changed (63) hide show
  1. data/README.txt +21 -0
  2. data/lib/webrick.rb +227 -0
  3. data/lib/webrick/accesslog.rb +151 -0
  4. data/lib/webrick/cgi.rb +260 -0
  5. data/lib/webrick/compat.rb +35 -0
  6. data/lib/webrick/config.rb +121 -0
  7. data/lib/webrick/cookie.rb +110 -0
  8. data/lib/webrick/htmlutils.rb +28 -0
  9. data/lib/webrick/httpauth.rb +95 -0
  10. data/lib/webrick/httpauth/authenticator.rb +112 -0
  11. data/lib/webrick/httpauth/basicauth.rb +108 -0
  12. data/lib/webrick/httpauth/digestauth.rb +392 -0
  13. data/lib/webrick/httpauth/htdigest.rb +128 -0
  14. data/lib/webrick/httpauth/htgroup.rb +93 -0
  15. data/lib/webrick/httpauth/htpasswd.rb +121 -0
  16. data/lib/webrick/httpauth/userdb.rb +52 -0
  17. data/lib/webrick/httpproxy.rb +305 -0
  18. data/lib/webrick/httprequest.rb +461 -0
  19. data/lib/webrick/httpresponse.rb +399 -0
  20. data/lib/webrick/https.rb +64 -0
  21. data/lib/webrick/httpserver.rb +264 -0
  22. data/lib/webrick/httpservlet.rb +22 -0
  23. data/lib/webrick/httpservlet/abstract.rb +153 -0
  24. data/lib/webrick/httpservlet/cgi_runner.rb +46 -0
  25. data/lib/webrick/httpservlet/cgihandler.rb +108 -0
  26. data/lib/webrick/httpservlet/erbhandler.rb +87 -0
  27. data/lib/webrick/httpservlet/filehandler.rb +470 -0
  28. data/lib/webrick/httpservlet/prochandler.rb +33 -0
  29. data/lib/webrick/httpstatus.rb +184 -0
  30. data/lib/webrick/httputils.rb +394 -0
  31. data/lib/webrick/httpversion.rb +49 -0
  32. data/lib/webrick/log.rb +136 -0
  33. data/lib/webrick/server.rb +218 -0
  34. data/lib/webrick/ssl.rb +127 -0
  35. data/lib/webrick/utils.rb +241 -0
  36. data/lib/webrick/version.rb +13 -0
  37. data/sample/webrick/demo-app.rb +66 -0
  38. data/sample/webrick/demo-multipart.cgi +12 -0
  39. data/sample/webrick/demo-servlet.rb +6 -0
  40. data/sample/webrick/demo-urlencoded.cgi +12 -0
  41. data/sample/webrick/hello.cgi +11 -0
  42. data/sample/webrick/hello.rb +8 -0
  43. data/sample/webrick/httpd.rb +23 -0
  44. data/sample/webrick/httpproxy.rb +25 -0
  45. data/sample/webrick/httpsd.rb +33 -0
  46. data/test/openssl/utils.rb +313 -0
  47. data/test/ruby/envutil.rb +208 -0
  48. data/test/webrick/test_cgi.rb +134 -0
  49. data/test/webrick/test_cookie.rb +131 -0
  50. data/test/webrick/test_filehandler.rb +285 -0
  51. data/test/webrick/test_httpauth.rb +167 -0
  52. data/test/webrick/test_httpproxy.rb +282 -0
  53. data/test/webrick/test_httprequest.rb +411 -0
  54. data/test/webrick/test_httpresponse.rb +49 -0
  55. data/test/webrick/test_httpserver.rb +305 -0
  56. data/test/webrick/test_httputils.rb +96 -0
  57. data/test/webrick/test_httpversion.rb +40 -0
  58. data/test/webrick/test_server.rb +67 -0
  59. data/test/webrick/test_utils.rb +64 -0
  60. data/test/webrick/utils.rb +58 -0
  61. data/test/webrick/webrick.cgi +36 -0
  62. data/test/webrick/webrick_long_filename.cgi +36 -0
  63. metadata +106 -0
@@ -0,0 +1,108 @@
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
+
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
+
34
+ class BasicAuth
35
+ include Authenticator
36
+
37
+ AuthScheme = "Basic"
38
+
39
+ ##
40
+ # Used by UserDB to create a basic password entry
41
+
42
+ def self.make_passwd(realm, user, pass)
43
+ pass ||= ""
44
+ pass.crypt(Utils::random_string(2))
45
+ end
46
+
47
+ attr_reader :realm, :userdb, :logger
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
+
60
+ def initialize(config, default=Config::BasicAuth)
61
+ check_init(config)
62
+ @config = default.dup.update(config)
63
+ end
64
+
65
+ ##
66
+ # Authenticates a +req+ and returns a 401 Unauthorized using +res+ if
67
+ # the authentication was not correct.
68
+
69
+ def authenticate(req, res)
70
+ unless basic_credentials = check_scheme(req)
71
+ challenge(req, res)
72
+ end
73
+ userid, password = basic_credentials.unpack("m*")[0].split(":", 2)
74
+ password ||= ""
75
+ if userid.empty?
76
+ error("user id was not given.")
77
+ challenge(req, res)
78
+ end
79
+ unless encpass = @userdb.get_passwd(@realm, userid, @reload_db)
80
+ error("%s: the user is not allowed.", userid)
81
+ challenge(req, res)
82
+ end
83
+ if password.crypt(encpass) != encpass
84
+ error("%s: password unmatch.", userid)
85
+ challenge(req, res)
86
+ end
87
+ info("%s: authentication succeeded.", userid)
88
+ req.user = userid
89
+ end
90
+
91
+ ##
92
+ # Returns a challenge response which asks for for authentication
93
+ # information
94
+
95
+ def challenge(req, res)
96
+ res[@response_field] = "#{@auth_scheme} realm=\"#{@realm}\""
97
+ raise @auth_exception
98
+ end
99
+ end
100
+
101
+ ##
102
+ # Basic authentication for proxy servers. See BasicAuth for details.
103
+
104
+ class ProxyBasicAuth < BasicAuth
105
+ include ProxyAuthenticator
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,392 @@
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
+
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
+
45
+ class DigestAuth
46
+ include Authenticator
47
+
48
+ AuthScheme = "Digest"
49
+ OpaqueInfo = Struct.new(:time, :nonce, :nc)
50
+ attr_reader :algorithm, :qop
51
+
52
+ ##
53
+ # Used by UserDB to create a digest password entry
54
+
55
+ def self.make_passwd(realm, user, pass)
56
+ pass ||= ""
57
+ Digest::MD5::hexdigest([user, realm, pass].join(":"))
58
+ end
59
+
60
+ ##
61
+ # Creates a new DigestAuth instance. Be sure to use the same DigestAuth
62
+ # instance for multiple requests as it saves state between requests in
63
+ # order to perform authentication.
64
+ #
65
+ # See WEBrick::Config::DigestAuth for default configuration entries
66
+ #
67
+ # You must supply the following configuration entries:
68
+ #
69
+ # :Realm:: The name of the realm being protected.
70
+ # :UserDB:: A database of usernames and passwords.
71
+ # A WEBrick::HTTPAuth::Htdigest instance should be used.
72
+
73
+ def initialize(config, default=Config::DigestAuth)
74
+ check_init(config)
75
+ @config = default.dup.update(config)
76
+ @algorithm = @config[:Algorithm]
77
+ @domain = @config[:Domain]
78
+ @qop = @config[:Qop]
79
+ @use_opaque = @config[:UseOpaque]
80
+ @use_next_nonce = @config[:UseNextNonce]
81
+ @check_nc = @config[:CheckNc]
82
+ @use_auth_info_header = @config[:UseAuthenticationInfoHeader]
83
+ @nonce_expire_period = @config[:NonceExpirePeriod]
84
+ @nonce_expire_delta = @config[:NonceExpireDelta]
85
+ @internet_explorer_hack = @config[:InternetExplorerHack]
86
+
87
+ case @algorithm
88
+ when 'MD5','MD5-sess'
89
+ @h = Digest::MD5
90
+ when 'SHA1','SHA1-sess' # it is a bonus feature :-)
91
+ @h = Digest::SHA1
92
+ else
93
+ msg = format('Algorithm "%s" is not supported.', @algorithm)
94
+ raise ArgumentError.new(msg)
95
+ end
96
+
97
+ @instance_key = hexdigest(self.__id__, Time.now.to_i, Process.pid)
98
+ @opaques = {}
99
+ @last_nonce_expire = Time.now
100
+ @mutex = Mutex.new
101
+ end
102
+
103
+ ##
104
+ # Authenticates a +req+ and returns a 401 Unauthorized using +res+ if
105
+ # the authentication was not correct.
106
+
107
+ def authenticate(req, res)
108
+ unless result = @mutex.synchronize{ _authenticate(req, res) }
109
+ challenge(req, res)
110
+ end
111
+ if result == :nonce_is_stale
112
+ challenge(req, res, true)
113
+ end
114
+ return true
115
+ end
116
+
117
+ ##
118
+ # Returns a challenge response which asks for for authentication
119
+ # information
120
+
121
+ def challenge(req, res, stale=false)
122
+ nonce = generate_next_nonce(req)
123
+ if @use_opaque
124
+ opaque = generate_opaque(req)
125
+ @opaques[opaque].nonce = nonce
126
+ end
127
+
128
+ param = Hash.new
129
+ param["realm"] = HTTPUtils::quote(@realm)
130
+ param["domain"] = HTTPUtils::quote(@domain.to_a.join(" ")) if @domain
131
+ param["nonce"] = HTTPUtils::quote(nonce)
132
+ param["opaque"] = HTTPUtils::quote(opaque) if opaque
133
+ param["stale"] = stale.to_s
134
+ param["algorithm"] = @algorithm
135
+ param["qop"] = HTTPUtils::quote(@qop.to_a.join(",")) if @qop
136
+
137
+ res[@response_field] =
138
+ "#{@auth_scheme} " + param.map{|k,v| "#{k}=#{v}" }.join(", ")
139
+ info("%s: %s", @response_field, res[@response_field]) if $DEBUG
140
+ raise @auth_exception
141
+ end
142
+
143
+ private
144
+
145
+ MustParams = ['username','realm','nonce','uri','response']
146
+ MustParamsAuth = ['cnonce','nc']
147
+
148
+ def _authenticate(req, res)
149
+ unless digest_credentials = check_scheme(req)
150
+ return false
151
+ end
152
+
153
+ auth_req = split_param_value(digest_credentials)
154
+ if auth_req['qop'] == "auth" || auth_req['qop'] == "auth-int"
155
+ req_params = MustParams + MustParamsAuth
156
+ else
157
+ req_params = MustParams
158
+ end
159
+ req_params.each{|key|
160
+ unless auth_req.has_key?(key)
161
+ error('%s: parameter missing. "%s"', auth_req['username'], key)
162
+ raise HTTPStatus::BadRequest
163
+ end
164
+ }
165
+
166
+ if !check_uri(req, auth_req)
167
+ raise HTTPStatus::BadRequest
168
+ end
169
+
170
+ if auth_req['realm'] != @realm
171
+ error('%s: realm unmatch. "%s" for "%s"',
172
+ auth_req['username'], auth_req['realm'], @realm)
173
+ return false
174
+ end
175
+
176
+ auth_req['algorithm'] ||= 'MD5'
177
+ if auth_req['algorithm'].upcase != @algorithm.upcase
178
+ error('%s: algorithm unmatch. "%s" for "%s"',
179
+ auth_req['username'], auth_req['algorithm'], @algorithm)
180
+ return false
181
+ end
182
+
183
+ if (@qop.nil? && auth_req.has_key?('qop')) ||
184
+ (@qop && (! @qop.member?(auth_req['qop'])))
185
+ error('%s: the qop is not allowed. "%s"',
186
+ auth_req['username'], auth_req['qop'])
187
+ return false
188
+ end
189
+
190
+ password = @userdb.get_passwd(@realm, auth_req['username'], @reload_db)
191
+ unless password
192
+ error('%s: the user is not allowd.', auth_req['username'])
193
+ return false
194
+ end
195
+
196
+ nonce_is_invalid = false
197
+ if @use_opaque
198
+ info("@opaque = %s", @opaque.inspect) if $DEBUG
199
+ if !(opaque = auth_req['opaque'])
200
+ error('%s: opaque is not given.', auth_req['username'])
201
+ nonce_is_invalid = true
202
+ elsif !(opaque_struct = @opaques[opaque])
203
+ error('%s: invalid opaque is given.', auth_req['username'])
204
+ nonce_is_invalid = true
205
+ elsif !check_opaque(opaque_struct, req, auth_req)
206
+ @opaques.delete(auth_req['opaque'])
207
+ nonce_is_invalid = true
208
+ end
209
+ elsif !check_nonce(req, auth_req)
210
+ nonce_is_invalid = true
211
+ end
212
+
213
+ if /-sess$/i =~ auth_req['algorithm']
214
+ ha1 = hexdigest(password, auth_req['nonce'], auth_req['cnonce'])
215
+ else
216
+ ha1 = password
217
+ end
218
+
219
+ if auth_req['qop'] == "auth" || auth_req['qop'] == nil
220
+ ha2 = hexdigest(req.request_method, auth_req['uri'])
221
+ ha2_res = hexdigest("", auth_req['uri'])
222
+ elsif auth_req['qop'] == "auth-int"
223
+ ha2 = hexdigest(req.request_method, auth_req['uri'],
224
+ hexdigest(req.body))
225
+ ha2_res = hexdigest("", auth_req['uri'], hexdigest(res.body))
226
+ end
227
+
228
+ if auth_req['qop'] == "auth" || auth_req['qop'] == "auth-int"
229
+ param2 = ['nonce', 'nc', 'cnonce', 'qop'].map{|key|
230
+ auth_req[key]
231
+ }.join(':')
232
+ digest = hexdigest(ha1, param2, ha2)
233
+ digest_res = hexdigest(ha1, param2, ha2_res)
234
+ else
235
+ digest = hexdigest(ha1, auth_req['nonce'], ha2)
236
+ digest_res = hexdigest(ha1, auth_req['nonce'], ha2_res)
237
+ end
238
+
239
+ if digest != auth_req['response']
240
+ error("%s: digest unmatch.", auth_req['username'])
241
+ return false
242
+ elsif nonce_is_invalid
243
+ error('%s: digest is valid, but nonce is not valid.',
244
+ auth_req['username'])
245
+ return :nonce_is_stale
246
+ elsif @use_auth_info_header
247
+ auth_info = {
248
+ 'nextnonce' => generate_next_nonce(req),
249
+ 'rspauth' => digest_res
250
+ }
251
+ if @use_opaque
252
+ opaque_struct.time = req.request_time
253
+ opaque_struct.nonce = auth_info['nextnonce']
254
+ opaque_struct.nc = "%08x" % (auth_req['nc'].hex + 1)
255
+ end
256
+ if auth_req['qop'] == "auth" || auth_req['qop'] == "auth-int"
257
+ ['qop','cnonce','nc'].each{|key|
258
+ auth_info[key] = auth_req[key]
259
+ }
260
+ end
261
+ res[@resp_info_field] = auth_info.keys.map{|key|
262
+ if key == 'nc'
263
+ key + '=' + auth_info[key]
264
+ else
265
+ key + "=" + HTTPUtils::quote(auth_info[key])
266
+ end
267
+ }.join(', ')
268
+ end
269
+ info('%s: authentication succeeded.', auth_req['username'])
270
+ req.user = auth_req['username']
271
+ return true
272
+ end
273
+
274
+ def split_param_value(string)
275
+ ret = {}
276
+ while string.bytesize != 0
277
+ case string
278
+ when /^\s*([\w\-\.\*\%\!]+)=\s*\"((\\.|[^\"])*)\"\s*,?/
279
+ key = $1
280
+ matched = $2
281
+ string = $'
282
+ ret[key] = matched.gsub(/\\(.)/, "\\1")
283
+ when /^\s*([\w\-\.\*\%\!]+)=\s*([^,\"]*),?/
284
+ key = $1
285
+ matched = $2
286
+ string = $'
287
+ ret[key] = matched.clone
288
+ when /^s*^,/
289
+ string = $'
290
+ else
291
+ break
292
+ end
293
+ end
294
+ ret
295
+ end
296
+
297
+ def generate_next_nonce(req)
298
+ now = "%012d" % req.request_time.to_i
299
+ pk = hexdigest(now, @instance_key)[0,32]
300
+ nonce = [now + ":" + pk].pack("m*").chop # it has 60 length of chars.
301
+ nonce
302
+ end
303
+
304
+ def check_nonce(req, auth_req)
305
+ username = auth_req['username']
306
+ nonce = auth_req['nonce']
307
+
308
+ pub_time, pk = nonce.unpack("m*")[0].split(":", 2)
309
+ if (!pub_time || !pk)
310
+ error("%s: empty nonce is given", username)
311
+ return false
312
+ elsif (hexdigest(pub_time, @instance_key)[0,32] != pk)
313
+ error("%s: invalid private-key: %s for %s",
314
+ username, hexdigest(pub_time, @instance_key)[0,32], pk)
315
+ return false
316
+ end
317
+
318
+ diff_time = req.request_time.to_i - pub_time.to_i
319
+ if (diff_time < 0)
320
+ error("%s: difference of time-stamp is negative.", username)
321
+ return false
322
+ elsif diff_time > @nonce_expire_period
323
+ error("%s: nonce is expired.", username)
324
+ return false
325
+ end
326
+
327
+ return true
328
+ end
329
+
330
+ def generate_opaque(req)
331
+ @mutex.synchronize{
332
+ now = req.request_time
333
+ if now - @last_nonce_expire > @nonce_expire_delta
334
+ @opaques.delete_if{|key,val|
335
+ (now - val.time) > @nonce_expire_period
336
+ }
337
+ @last_nonce_expire = now
338
+ end
339
+ begin
340
+ opaque = Utils::random_string(16)
341
+ end while @opaques[opaque]
342
+ @opaques[opaque] = OpaqueInfo.new(now, nil, '00000001')
343
+ opaque
344
+ }
345
+ end
346
+
347
+ def check_opaque(opaque_struct, req, auth_req)
348
+ if (@use_next_nonce && auth_req['nonce'] != opaque_struct.nonce)
349
+ error('%s: nonce unmatched. "%s" for "%s"',
350
+ auth_req['username'], auth_req['nonce'], opaque_struct.nonce)
351
+ return false
352
+ elsif !check_nonce(req, auth_req)
353
+ return false
354
+ end
355
+ if (@check_nc && auth_req['nc'] != opaque_struct.nc)
356
+ error('%s: nc unmatched."%s" for "%s"',
357
+ auth_req['username'], auth_req['nc'], opaque_struct.nc)
358
+ return false
359
+ end
360
+ true
361
+ end
362
+
363
+ def check_uri(req, auth_req)
364
+ uri = auth_req['uri']
365
+ if uri != req.request_uri.to_s && uri != req.unparsed_uri &&
366
+ (@internet_explorer_hack && uri != req.path)
367
+ error('%s: uri unmatch. "%s" for "%s"', auth_req['username'],
368
+ auth_req['uri'], req.request_uri.to_s)
369
+ return false
370
+ end
371
+ true
372
+ end
373
+
374
+ def hexdigest(*args)
375
+ @h.hexdigest(args.join(":"))
376
+ end
377
+
378
+ end
379
+
380
+ ##
381
+ # Digest authentication for proxy servers. See DigestAuth for details.
382
+
383
+ class ProxyDigestAuth < DigestAuth
384
+ include ProxyAuthenticator
385
+
386
+ private
387
+ def check_uri(req, auth_req)
388
+ return true
389
+ end
390
+ end
391
+ end
392
+ end