webrick 1.3.1 → 1.4.3

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 (64) hide show
  1. checksums.yaml +7 -0
  2. data/lib/webrick.rb +6 -6
  3. data/lib/webrick/accesslog.rb +9 -1
  4. data/lib/webrick/cgi.rb +58 -5
  5. data/lib/webrick/compat.rb +2 -1
  6. data/lib/webrick/config.rb +47 -10
  7. data/lib/webrick/cookie.rb +69 -7
  8. data/lib/webrick/htmlutils.rb +4 -2
  9. data/lib/webrick/httpauth.rb +6 -5
  10. data/lib/webrick/httpauth/authenticator.rb +13 -8
  11. data/lib/webrick/httpauth/basicauth.rb +16 -8
  12. data/lib/webrick/httpauth/digestauth.rb +35 -32
  13. data/lib/webrick/httpauth/htdigest.rb +12 -8
  14. data/lib/webrick/httpauth/htgroup.rb +10 -6
  15. data/lib/webrick/httpauth/htpasswd.rb +46 -9
  16. data/lib/webrick/httpauth/userdb.rb +1 -0
  17. data/lib/webrick/httpproxy.rb +93 -48
  18. data/lib/webrick/httprequest.rb +192 -27
  19. data/lib/webrick/httpresponse.rb +182 -62
  20. data/lib/webrick/https.rb +90 -2
  21. data/lib/webrick/httpserver.rb +45 -15
  22. data/lib/webrick/httpservlet.rb +6 -5
  23. data/lib/webrick/httpservlet/abstract.rb +5 -6
  24. data/lib/webrick/httpservlet/cgi_runner.rb +3 -2
  25. data/lib/webrick/httpservlet/cgihandler.rb +22 -10
  26. data/lib/webrick/httpservlet/erbhandler.rb +4 -3
  27. data/lib/webrick/httpservlet/filehandler.rb +136 -65
  28. data/lib/webrick/httpservlet/prochandler.rb +15 -1
  29. data/lib/webrick/httpstatus.rb +24 -14
  30. data/lib/webrick/httputils.rb +132 -13
  31. data/lib/webrick/httpversion.rb +28 -1
  32. data/lib/webrick/log.rb +25 -5
  33. data/lib/webrick/server.rb +234 -74
  34. data/lib/webrick/ssl.rb +100 -12
  35. data/lib/webrick/utils.rb +98 -69
  36. data/lib/webrick/version.rb +6 -1
  37. metadata +66 -72
  38. data/README.txt +0 -21
  39. data/sample/webrick/demo-app.rb +0 -66
  40. data/sample/webrick/demo-multipart.cgi +0 -12
  41. data/sample/webrick/demo-servlet.rb +0 -6
  42. data/sample/webrick/demo-urlencoded.cgi +0 -12
  43. data/sample/webrick/hello.cgi +0 -11
  44. data/sample/webrick/hello.rb +0 -8
  45. data/sample/webrick/httpd.rb +0 -23
  46. data/sample/webrick/httpproxy.rb +0 -25
  47. data/sample/webrick/httpsd.rb +0 -33
  48. data/test/openssl/utils.rb +0 -313
  49. data/test/ruby/envutil.rb +0 -208
  50. data/test/webrick/test_cgi.rb +0 -134
  51. data/test/webrick/test_cookie.rb +0 -131
  52. data/test/webrick/test_filehandler.rb +0 -285
  53. data/test/webrick/test_httpauth.rb +0 -167
  54. data/test/webrick/test_httpproxy.rb +0 -282
  55. data/test/webrick/test_httprequest.rb +0 -411
  56. data/test/webrick/test_httpresponse.rb +0 -49
  57. data/test/webrick/test_httpserver.rb +0 -305
  58. data/test/webrick/test_httputils.rb +0 -96
  59. data/test/webrick/test_httpversion.rb +0 -40
  60. data/test/webrick/test_server.rb +0 -67
  61. data/test/webrick/test_utils.rb +0 -64
  62. data/test/webrick/utils.rb +0 -58
  63. data/test/webrick/webrick.cgi +0 -36
  64. data/test/webrick/webrick_long_filename.cgi +0 -36
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: false
1
2
  #--
2
3
  # HTTPVersion.rb -- presentation of HTTP version
3
4
  #
@@ -8,15 +9,33 @@
8
9
  # $IPR: httpversion.rb,v 1.5 2002/09/21 12:23:37 gotoyuzo Exp $
9
10
 
10
11
  module WEBrick
12
+
13
+ ##
14
+ # Represents an HTTP protocol version
15
+
11
16
  class HTTPVersion
12
17
  include Comparable
13
18
 
14
- attr_accessor :major, :minor
19
+ ##
20
+ # The major protocol version number
21
+
22
+ attr_accessor :major
23
+
24
+ ##
25
+ # The minor protocol version number
26
+
27
+ attr_accessor :minor
28
+
29
+ ##
30
+ # Converts +version+ into an HTTPVersion
15
31
 
16
32
  def self.convert(version)
17
33
  version.is_a?(self) ? version : new(version)
18
34
  end
19
35
 
36
+ ##
37
+ # Creates a new HTTPVersion from +version+.
38
+
20
39
  def initialize(version)
21
40
  case version
22
41
  when HTTPVersion
@@ -32,6 +51,10 @@ module WEBrick
32
51
  end
33
52
  end
34
53
 
54
+ ##
55
+ # Compares this version with +other+ according to the HTTP specification
56
+ # rules.
57
+
35
58
  def <=>(other)
36
59
  unless other.is_a?(self.class)
37
60
  other = self.class.new(other)
@@ -42,6 +65,10 @@ module WEBrick
42
65
  return ret
43
66
  end
44
67
 
68
+ ##
69
+ # The HTTP version as show in the HTTP request and response. For example,
70
+ # "1.1"
71
+
45
72
  def to_s
46
73
  format("%d.%d", @major, @minor)
47
74
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: false
1
2
  #--
2
3
  # log.rb -- Log Class
3
4
  #
@@ -14,8 +15,27 @@ module WEBrick
14
15
  # A generic logging class
15
16
 
16
17
  class BasicLog
17
- # log-level constants
18
- FATAL, ERROR, WARN, INFO, DEBUG = 1, 2, 3, 4, 5
18
+
19
+ # Fatal log level which indicates a server crash
20
+
21
+ FATAL = 1
22
+
23
+ # Error log level which indicates a recoverable error
24
+
25
+ ERROR = 2
26
+
27
+ # Warning log level which indicates a possible problem
28
+
29
+ WARN = 3
30
+
31
+ # Information log level which indicates possibly useful information
32
+
33
+ INFO = 4
34
+
35
+ # Debugging error level for messages used in server development or
36
+ # debugging
37
+
38
+ DEBUG = 5
19
39
 
20
40
  # log-level, messages above this level will be logged
21
41
  attr_accessor :level
@@ -31,7 +51,7 @@ module WEBrick
31
51
  @level = level || INFO
32
52
  case log_file
33
53
  when String
34
- @log = open(log_file, "a+")
54
+ @log = File.open(log_file, "a+")
35
55
  @log.sync = true
36
56
  @opened = true
37
57
  when NilClass
@@ -98,10 +118,10 @@ module WEBrick
98
118
  # * Otherwise it will return +arg+.inspect.
99
119
  def format(arg)
100
120
  if arg.is_a?(Exception)
101
- "#{arg.class}: #{arg.message}\n\t" <<
121
+ "#{arg.class}: #{AccessLog.escape(arg.message)}\n\t" <<
102
122
  arg.backtrace.join("\n\t") << "\n"
103
123
  elsif arg.respond_to?(:to_str)
104
- arg.to_str
124
+ AccessLog.escape(arg.to_str)
105
125
  else
106
126
  arg.inspect
107
127
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: false
1
2
  #
2
3
  # server.rb -- GenericServer Class
3
4
  #
@@ -8,16 +9,25 @@
8
9
  #
9
10
  # $IPR: server.rb,v 1.62 2003/07/22 19:20:43 gotoyuzo Exp $
10
11
 
11
- require 'thread'
12
12
  require 'socket'
13
- require 'webrick/config'
14
- require 'webrick/log'
13
+ require_relative 'config'
14
+ require_relative 'log'
15
15
 
16
16
  module WEBrick
17
17
 
18
+ ##
19
+ # Server error exception
20
+
18
21
  class ServerError < StandardError; end
19
22
 
23
+ ##
24
+ # Base server class
25
+
20
26
  class SimpleServer
27
+
28
+ ##
29
+ # A SimpleServer only yields when you start it
30
+
21
31
  def SimpleServer.start
22
32
  yield
23
33
  end
@@ -33,20 +43,47 @@ module WEBrick
33
43
  # block, if given.
34
44
 
35
45
  def Daemon.start
36
- exit!(0) if fork
37
- Process::setsid
38
- exit!(0) if fork
39
- Dir::chdir("/")
40
- File::umask(0)
41
- STDIN.reopen("/dev/null")
42
- STDOUT.reopen("/dev/null", "w")
43
- STDERR.reopen("/dev/null", "w")
46
+ Process.daemon
47
+ File.umask(0)
44
48
  yield if block_given?
45
49
  end
46
50
  end
47
51
 
52
+ ##
53
+ # Base TCP server class. You must subclass GenericServer and provide a #run
54
+ # method.
55
+
48
56
  class GenericServer
49
- attr_reader :status, :config, :logger, :tokens, :listeners
57
+
58
+ ##
59
+ # The server status. One of :Stop, :Running or :Shutdown
60
+
61
+ attr_reader :status
62
+
63
+ ##
64
+ # The server configuration
65
+
66
+ attr_reader :config
67
+
68
+ ##
69
+ # The server logger. This is independent from the HTTP access log.
70
+
71
+ attr_reader :logger
72
+
73
+ ##
74
+ # Tokens control the number of outstanding clients. The
75
+ # <code>:MaxClients</code> configuration sets this.
76
+
77
+ attr_reader :tokens
78
+
79
+ ##
80
+ # Sockets listening for connections.
81
+
82
+ attr_reader :listeners
83
+
84
+ ##
85
+ # Creates a new generic server from +config+. The default configuration
86
+ # comes from +default+.
50
87
 
51
88
  def initialize(config={}, default=Config::General)
52
89
  @config = default.dup.update(config)
@@ -54,7 +91,7 @@ module WEBrick
54
91
  @config[:Logger] ||= Log::new
55
92
  @logger = @config[:Logger]
56
93
 
57
- @tokens = SizedQueue.new(@config[:MaxClients])
94
+ @tokens = Thread::SizedQueue.new(@config[:MaxClients])
58
95
  @config[:MaxClients].times{ @tokens.push(nil) }
59
96
 
60
97
  webrickv = WEBrick::VERSION
@@ -63,9 +100,10 @@ module WEBrick
63
100
  @logger.info("ruby #{rubyv}")
64
101
 
65
102
  @listeners = []
103
+ @shutdown_pipe = nil
66
104
  unless @config[:DoNotListen]
67
105
  if @config[:Listen]
68
- warn(":Listen option is deprecated; use GenericServer#listen")
106
+ warn(":Listen option is deprecated; use GenericServer#listen", uplevel: 1)
69
107
  end
70
108
  listen(@config[:BindAddress], @config[:Port])
71
109
  if @config[:Port] == 0
@@ -74,108 +112,176 @@ module WEBrick
74
112
  end
75
113
  end
76
114
 
115
+ ##
116
+ # Retrieves +key+ from the configuration
117
+
77
118
  def [](key)
78
119
  @config[key]
79
120
  end
80
121
 
122
+ ##
123
+ # Adds listeners from +address+ and +port+ to the server. See
124
+ # WEBrick::Utils::create_listeners for details.
125
+
81
126
  def listen(address, port)
82
- @listeners += Utils::create_listeners(address, port, @logger)
127
+ @listeners += Utils::create_listeners(address, port)
83
128
  end
84
129
 
130
+ ##
131
+ # Starts the server and runs the +block+ for each connection. This method
132
+ # does not return until the server is stopped from a signal handler or
133
+ # another thread using #stop or #shutdown.
134
+ #
135
+ # If the block raises a subclass of StandardError the exception is logged
136
+ # and ignored. If an IOError or Errno::EBADF exception is raised the
137
+ # exception is ignored. If an Exception subclass is raised the exception
138
+ # is logged and re-raised which stops the server.
139
+ #
140
+ # To completely shut down a server call #shutdown from ensure:
141
+ #
142
+ # server = WEBrick::GenericServer.new
143
+ # # or WEBrick::HTTPServer.new
144
+ #
145
+ # begin
146
+ # server.start
147
+ # ensure
148
+ # server.shutdown
149
+ # end
150
+
85
151
  def start(&block)
86
152
  raise ServerError, "already started." if @status != :Stop
87
153
  server_type = @config[:ServerType] || SimpleServer
88
154
 
155
+ setup_shutdown_pipe
156
+
89
157
  server_type.start{
90
158
  @logger.info \
91
159
  "#{self.class}#start: pid=#{$$} port=#{@config[:Port]}"
160
+ @status = :Running
92
161
  call_callback(:StartCallback)
93
162
 
163
+ shutdown_pipe = @shutdown_pipe
164
+
94
165
  thgroup = ThreadGroup.new
95
- @status = :Running
96
- while @status == :Running
97
- begin
98
- if svrs = IO.select(@listeners, nil, nil, 2.0)
99
- svrs[0].each{|svr|
100
- @tokens.pop # blocks while no token is there.
101
- if sock = accept_client(svr)
102
- sock.do_not_reverse_lookup = config[:DoNotReverseLookup]
103
- th = start_thread(sock, &block)
104
- th[:WEBrickThread] = true
105
- thgroup.add(th)
106
- else
107
- @tokens.push(nil)
166
+ begin
167
+ while @status == :Running
168
+ begin
169
+ sp = shutdown_pipe[0]
170
+ if svrs = IO.select([sp, *@listeners])
171
+ if svrs[0].include? sp
172
+ # swallow shutdown pipe
173
+ buf = String.new
174
+ nil while String ===
175
+ sp.read_nonblock([sp.nread, 8].max, buf, exception: false)
176
+ break
108
177
  end
109
- }
178
+ svrs[0].each{|svr|
179
+ @tokens.pop # blocks while no token is there.
180
+ if sock = accept_client(svr)
181
+ unless config[:DoNotReverseLookup].nil?
182
+ sock.do_not_reverse_lookup = !!config[:DoNotReverseLookup]
183
+ end
184
+ th = start_thread(sock, &block)
185
+ th[:WEBrickThread] = true
186
+ thgroup.add(th)
187
+ else
188
+ @tokens.push(nil)
189
+ end
190
+ }
191
+ end
192
+ rescue Errno::EBADF, Errno::ENOTSOCK, IOError => ex
193
+ # if the listening socket was closed in GenericServer#shutdown,
194
+ # IO::select raise it.
195
+ rescue StandardError => ex
196
+ msg = "#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}"
197
+ @logger.error msg
198
+ rescue Exception => ex
199
+ @logger.fatal ex
200
+ raise
110
201
  end
111
- rescue Errno::EBADF, IOError => ex
112
- # if the listening socket was closed in GenericServer#shutdown,
113
- # IO::select raise it.
114
- rescue Exception => ex
115
- msg = "#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}"
116
- @logger.error msg
117
202
  end
203
+ ensure
204
+ cleanup_shutdown_pipe(shutdown_pipe)
205
+ cleanup_listener
206
+ @status = :Shutdown
207
+ @logger.info "going to shutdown ..."
208
+ thgroup.list.each{|th| th.join if th[:WEBrickThread] }
209
+ call_callback(:StopCallback)
210
+ @logger.info "#{self.class}#start done."
211
+ @status = :Stop
118
212
  end
119
-
120
- @logger.info "going to shutdown ..."
121
- thgroup.list.each{|th| th.join if th[:WEBrickThread] }
122
- call_callback(:StopCallback)
123
- @logger.info "#{self.class}#start done."
124
- @status = :Stop
125
213
  }
126
214
  end
127
215
 
216
+ ##
217
+ # Stops the server from accepting new connections.
218
+
128
219
  def stop
129
220
  if @status == :Running
130
221
  @status = :Shutdown
131
222
  end
223
+
224
+ alarm_shutdown_pipe {|f| f.write_nonblock("\0")}
132
225
  end
133
226
 
227
+ ##
228
+ # Shuts down the server and all listening sockets. New listeners must be
229
+ # provided to restart the server.
230
+
134
231
  def shutdown
135
232
  stop
136
- @listeners.each{|s|
137
- if @logger.debug?
138
- addr = s.addr
139
- @logger.debug("close TCPSocket(#{addr[2]}, #{addr[1]})")
140
- end
141
- begin
142
- s.shutdown
143
- rescue Errno::ENOTCONN
144
- # when `Errno::ENOTCONN: Socket is not connected' on some platforms,
145
- # call #close instead of #shutdown.
146
- # (ignore @config[:ShutdownSocketWithoutClose])
147
- s.close
148
- else
149
- unless @config[:ShutdownSocketWithoutClose]
150
- s.close
151
- end
152
- end
153
- }
154
- @listeners.clear
233
+
234
+ alarm_shutdown_pipe(&:close)
155
235
  end
156
236
 
237
+ ##
238
+ # You must subclass GenericServer and implement \#run which accepts a TCP
239
+ # client socket
240
+
157
241
  def run(sock)
158
242
  @logger.fatal "run() must be provided by user."
159
243
  end
160
244
 
161
245
  private
162
246
 
247
+ # :stopdoc:
248
+
249
+ ##
250
+ # Accepts a TCP client socket from the TCP server socket +svr+ and returns
251
+ # the client socket.
252
+
163
253
  def accept_client(svr)
164
- sock = nil
165
- begin
166
- sock = svr.accept
167
- sock.sync = true
168
- Utils::set_non_blocking(sock)
169
- Utils::set_close_on_exec(sock)
170
- rescue Errno::ECONNRESET, Errno::ECONNABORTED,
171
- Errno::EPROTO, Errno::EINVAL => ex
172
- rescue Exception => ex
173
- msg = "#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}"
174
- @logger.error msg
254
+ case sock = svr.to_io.accept_nonblock(exception: false)
255
+ when :wait_readable
256
+ nil
257
+ else
258
+ if svr.respond_to?(:start_immediately)
259
+ sock = OpenSSL::SSL::SSLSocket.new(sock, ssl_context)
260
+ sock.sync_close = true
261
+ # we cannot do OpenSSL::SSL::SSLSocket#accept here because
262
+ # a slow client can prevent us from accepting connections
263
+ # from other clients
264
+ end
265
+ sock
175
266
  end
176
- return sock
267
+ rescue Errno::ECONNRESET, Errno::ECONNABORTED,
268
+ Errno::EPROTO, Errno::EINVAL
269
+ nil
270
+ rescue StandardError => ex
271
+ msg = "#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}"
272
+ @logger.error msg
273
+ nil
177
274
  end
178
275
 
276
+ ##
277
+ # Starts a server thread for the client socket +sock+ that runs the given
278
+ # +block+.
279
+ #
280
+ # Sets the socket to the <code>:WEBrickSocket</code> thread local variable
281
+ # in the thread.
282
+ #
283
+ # If any errors occur in the block they are logged and handled.
284
+
179
285
  def start_thread(sock, &block)
180
286
  Thread.start{
181
287
  begin
@@ -187,6 +293,16 @@ module WEBrick
187
293
  @logger.debug "accept: <address unknown>"
188
294
  raise
189
295
  end
296
+ if sock.respond_to?(:sync_close=) && @config[:SSLStartImmediately]
297
+ WEBrick::Utils.timeout(@config[:RequestTimeout]) do
298
+ begin
299
+ sock.accept # OpenSSL::SSL::SSLSocket#accept
300
+ rescue Errno::ECONNRESET, Errno::ECONNABORTED,
301
+ Errno::EPROTO, Errno::EINVAL
302
+ Thread.exit
303
+ end
304
+ end
305
+ end
190
306
  call_callback(:AcceptCallback, sock)
191
307
  block ? block.call(sock) : run(sock)
192
308
  rescue Errno::ENOTCONN
@@ -209,10 +325,54 @@ module WEBrick
209
325
  }
210
326
  end
211
327
 
328
+ ##
329
+ # Calls the callback +callback_name+ from the configuration with +args+
330
+
212
331
  def call_callback(callback_name, *args)
213
- if cb = @config[callback_name]
214
- cb.call(*args)
332
+ @config[callback_name]&.call(*args)
333
+ end
334
+
335
+ def setup_shutdown_pipe
336
+ return @shutdown_pipe ||= IO.pipe
337
+ end
338
+
339
+ def cleanup_shutdown_pipe(shutdown_pipe)
340
+ @shutdown_pipe = nil
341
+ shutdown_pipe&.each(&:close)
342
+ end
343
+
344
+ def alarm_shutdown_pipe
345
+ _, pipe = @shutdown_pipe # another thread may modify @shutdown_pipe.
346
+ if pipe
347
+ if !pipe.closed?
348
+ begin
349
+ yield pipe
350
+ rescue IOError # closed by another thread.
351
+ end
352
+ end
215
353
  end
216
354
  end
355
+
356
+ def cleanup_listener
357
+ @listeners.each{|s|
358
+ if @logger.debug?
359
+ addr = s.addr
360
+ @logger.debug("close TCPSocket(#{addr[2]}, #{addr[1]})")
361
+ end
362
+ begin
363
+ s.shutdown
364
+ rescue Errno::ENOTCONN
365
+ # when `Errno::ENOTCONN: Socket is not connected' on some platforms,
366
+ # call #close instead of #shutdown.
367
+ # (ignore @config[:ShutdownSocketWithoutClose])
368
+ s.close
369
+ else
370
+ unless @config[:ShutdownSocketWithoutClose]
371
+ s.close
372
+ end
373
+ end
374
+ }
375
+ @listeners.clear
376
+ end
217
377
  end # end of GenericServer
218
378
  end