webrick 1.3.1 → 1.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +3 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +63 -0
  5. data/Rakefile +10 -0
  6. data/bin/console +14 -0
  7. data/bin/setup +8 -0
  8. data/lib/webrick.rb +7 -7
  9. data/lib/webrick/accesslog.rb +12 -6
  10. data/lib/webrick/cgi.rb +58 -5
  11. data/lib/webrick/compat.rb +2 -1
  12. data/lib/webrick/config.rb +47 -10
  13. data/lib/webrick/cookie.rb +69 -7
  14. data/lib/webrick/htmlutils.rb +4 -2
  15. data/lib/webrick/httpauth.rb +6 -5
  16. data/lib/webrick/httpauth/authenticator.rb +13 -8
  17. data/lib/webrick/httpauth/basicauth.rb +16 -8
  18. data/lib/webrick/httpauth/digestauth.rb +35 -32
  19. data/lib/webrick/httpauth/htdigest.rb +12 -8
  20. data/lib/webrick/httpauth/htgroup.rb +10 -6
  21. data/lib/webrick/httpauth/htpasswd.rb +46 -9
  22. data/lib/webrick/httpauth/userdb.rb +1 -0
  23. data/lib/webrick/httpproxy.rb +93 -48
  24. data/lib/webrick/httprequest.rb +201 -31
  25. data/lib/webrick/httpresponse.rb +235 -70
  26. data/lib/webrick/https.rb +90 -2
  27. data/lib/webrick/httpserver.rb +45 -15
  28. data/lib/webrick/httpservlet.rb +6 -5
  29. data/lib/webrick/httpservlet/abstract.rb +5 -6
  30. data/lib/webrick/httpservlet/cgi_runner.rb +3 -2
  31. data/lib/webrick/httpservlet/cgihandler.rb +29 -11
  32. data/lib/webrick/httpservlet/erbhandler.rb +4 -3
  33. data/lib/webrick/httpservlet/filehandler.rb +136 -65
  34. data/lib/webrick/httpservlet/prochandler.rb +15 -1
  35. data/lib/webrick/httpstatus.rb +24 -14
  36. data/lib/webrick/httputils.rb +134 -17
  37. data/lib/webrick/httpversion.rb +28 -1
  38. data/lib/webrick/log.rb +25 -5
  39. data/lib/webrick/server.rb +234 -74
  40. data/lib/webrick/ssl.rb +100 -12
  41. data/lib/webrick/utils.rb +98 -69
  42. data/lib/webrick/version.rb +6 -1
  43. data/webrick.gemspec +76 -0
  44. metadata +73 -72
  45. data/README.txt +0 -21
  46. data/sample/webrick/demo-app.rb +0 -66
  47. data/sample/webrick/demo-multipart.cgi +0 -12
  48. data/sample/webrick/demo-servlet.rb +0 -6
  49. data/sample/webrick/demo-urlencoded.cgi +0 -12
  50. data/sample/webrick/hello.cgi +0 -11
  51. data/sample/webrick/hello.rb +0 -8
  52. data/sample/webrick/httpd.rb +0 -23
  53. data/sample/webrick/httpproxy.rb +0 -25
  54. data/sample/webrick/httpsd.rb +0 -33
  55. data/test/openssl/utils.rb +0 -313
  56. data/test/ruby/envutil.rb +0 -208
  57. data/test/webrick/test_cgi.rb +0 -134
  58. data/test/webrick/test_cookie.rb +0 -131
  59. data/test/webrick/test_filehandler.rb +0 -285
  60. data/test/webrick/test_httpauth.rb +0 -167
  61. data/test/webrick/test_httpproxy.rb +0 -282
  62. data/test/webrick/test_httprequest.rb +0 -411
  63. data/test/webrick/test_httpresponse.rb +0 -49
  64. data/test/webrick/test_httpserver.rb +0 -305
  65. data/test/webrick/test_httputils.rb +0 -96
  66. data/test/webrick/test_httpversion.rb +0 -40
  67. data/test/webrick/test_server.rb +0 -67
  68. data/test/webrick/test_utils.rb +0 -64
  69. data/test/webrick/utils.rb +0 -58
  70. data/test/webrick/webrick.cgi +0 -36
  71. 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
data/lib/webrick/log.rb CHANGED
@@ -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