webrick 1.3.1 → 1.6.1

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.

Potentially problematic release.


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

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
  # httpresponse.rb -- HTTPResponse Class
3
4
  #
@@ -9,32 +10,104 @@
9
10
  # $IPR: httpresponse.rb,v 1.45 2003/07/11 11:02:25 gotoyuzo Exp $
10
11
 
11
12
  require 'time'
12
- require 'webrick/httpversion'
13
- require 'webrick/htmlutils'
14
- require 'webrick/httputils'
15
- require 'webrick/httpstatus'
13
+ require 'uri'
14
+ require_relative 'httpversion'
15
+ require_relative 'htmlutils'
16
+ require_relative 'httputils'
17
+ require_relative 'httpstatus'
16
18
 
17
19
  module WEBrick
18
20
  ##
19
- # An HTTP response.
21
+ # An HTTP response. This is filled in by the service or do_* methods of a
22
+ # WEBrick HTTP Servlet.
20
23
 
21
24
  class HTTPResponse
22
- attr_reader :http_version, :status, :header
25
+ class InvalidHeader < StandardError
26
+ end
27
+
28
+ ##
29
+ # HTTP Response version
30
+
31
+ attr_reader :http_version
32
+
33
+ ##
34
+ # Response status code (200)
35
+
36
+ attr_reader :status
37
+
38
+ ##
39
+ # Response header
40
+
41
+ attr_reader :header
42
+
43
+ ##
44
+ # Response cookies
45
+
23
46
  attr_reader :cookies
47
+
48
+ ##
49
+ # Response reason phrase ("OK")
50
+
24
51
  attr_accessor :reason_phrase
25
52
 
26
53
  ##
27
- # Body may be a String or IO subclass.
54
+ # Body may be:
55
+ # * a String;
56
+ # * an IO-like object that responds to +#read+ and +#readpartial+;
57
+ # * a Proc-like object that responds to +#call+.
58
+ #
59
+ # In the latter case, either #chunked= should be set to +true+,
60
+ # or <code>header['content-length']</code> explicitly provided.
61
+ # Example:
62
+ #
63
+ # server.mount_proc '/' do |req, res|
64
+ # res.chunked = true
65
+ # # or
66
+ # # res.header['content-length'] = 10
67
+ # res.body = proc { |out| out.write(Time.now.to_s) }
68
+ # end
28
69
 
29
70
  attr_accessor :body
30
71
 
31
- attr_accessor :request_method, :request_uri, :request_http_version
72
+ ##
73
+ # Request method for this response
74
+
75
+ attr_accessor :request_method
76
+
77
+ ##
78
+ # Request URI for this response
79
+
80
+ attr_accessor :request_uri
81
+
82
+ ##
83
+ # Request HTTP version for this response
84
+
85
+ attr_accessor :request_http_version
86
+
87
+ ##
88
+ # Filename of the static file in this response. Only used by the
89
+ # FileHandler servlet.
90
+
32
91
  attr_accessor :filename
92
+
93
+ ##
94
+ # Is this a keep-alive response?
95
+
33
96
  attr_accessor :keep_alive
34
- attr_reader :config, :sent_size
35
97
 
36
98
  ##
37
- # Creates a new HTTP response object
99
+ # Configuration for this response
100
+
101
+ attr_reader :config
102
+
103
+ ##
104
+ # Bytes sent in this response
105
+
106
+ attr_reader :sent_size
107
+
108
+ ##
109
+ # Creates a new HTTP response object. WEBrick::Config::HTTP is the
110
+ # default configuration.
38
111
 
39
112
  def initialize(config)
40
113
  @config = config
@@ -53,13 +126,14 @@ module WEBrick
53
126
  @chunked = false
54
127
  @filename = nil
55
128
  @sent_size = 0
129
+ @bodytempfile = nil
56
130
  end
57
131
 
58
132
  ##
59
133
  # The response's HTTP status line
60
134
 
61
135
  def status_line
62
- "HTTP/#@http_version #@status #@reason_phrase #{CRLF}"
136
+ "HTTP/#@http_version #@status #@reason_phrase".rstrip << CRLF
63
137
  end
64
138
 
65
139
  ##
@@ -81,6 +155,7 @@ module WEBrick
81
155
  # Sets the response header +field+ to +value+
82
156
 
83
157
  def []=(field, value)
158
+ @chunked = value.to_s.downcase == 'chunked' if field.downcase == 'transfer-encoding'
84
159
  @header[field.downcase] = value.to_s
85
160
  end
86
161
 
@@ -115,7 +190,7 @@ module WEBrick
115
190
  end
116
191
 
117
192
  ##
118
- # Iterates over each header in the resopnse
193
+ # Iterates over each header in the response
119
194
 
120
195
  def each
121
196
  @header.each{|field, value| yield(field, value) }
@@ -145,7 +220,7 @@ module WEBrick
145
220
  ##
146
221
  # Sends the response on +socket+
147
222
 
148
- def send_response(socket)
223
+ def send_response(socket) # :nodoc:
149
224
  begin
150
225
  setup_header()
151
226
  send_header(socket)
@@ -162,7 +237,7 @@ module WEBrick
162
237
  ##
163
238
  # Sets up the headers for sending
164
239
 
165
- def setup_header()
240
+ def setup_header() # :nodoc:
166
241
  @reason_phrase ||= HTTPStatus::reason_phrase(@status)
167
242
  @header['server'] ||= @config[:ServerSoftware]
168
243
  @header['date'] ||= Time.now.httpdate
@@ -193,8 +268,11 @@ module WEBrick
193
268
  elsif %r{^multipart/byteranges} =~ @header['content-type']
194
269
  @header.delete('content-length')
195
270
  elsif @header['content-length'].nil?
196
- unless @body.is_a?(IO)
197
- @header['content-length'] = @body ? @body.bytesize : 0
271
+ if @body.respond_to? :readpartial
272
+ elsif @body.respond_to? :call
273
+ make_body_tempfile
274
+ else
275
+ @header['content-length'] = (@body ? @body.bytesize : 0).to_s
198
276
  end
199
277
  end
200
278
 
@@ -217,45 +295,74 @@ module WEBrick
217
295
  # Location is a single absoluteURI.
218
296
  if location = @header['location']
219
297
  if @request_uri
220
- @header['location'] = @request_uri.merge(location)
298
+ @header['location'] = @request_uri.merge(location).to_s
221
299
  end
222
300
  end
223
301
  end
224
302
 
303
+ def make_body_tempfile # :nodoc:
304
+ return if @bodytempfile
305
+ bodytempfile = Tempfile.create("webrick")
306
+ if @body.nil?
307
+ # nothing
308
+ elsif @body.respond_to? :readpartial
309
+ IO.copy_stream(@body, bodytempfile)
310
+ @body.close
311
+ elsif @body.respond_to? :call
312
+ @body.call(bodytempfile)
313
+ else
314
+ bodytempfile.write @body
315
+ end
316
+ bodytempfile.rewind
317
+ @body = @bodytempfile = bodytempfile
318
+ @header['content-length'] = bodytempfile.stat.size.to_s
319
+ end
320
+
321
+ def remove_body_tempfile # :nodoc:
322
+ if @bodytempfile
323
+ @bodytempfile.close
324
+ File.unlink @bodytempfile.path
325
+ @bodytempfile = nil
326
+ end
327
+ end
328
+
329
+
225
330
  ##
226
331
  # Sends the headers on +socket+
227
332
 
228
- def send_header(socket)
333
+ def send_header(socket) # :nodoc:
229
334
  if @http_version.major > 0
230
335
  data = status_line()
231
336
  @header.each{|key, value|
232
337
  tmp = key.gsub(/\bwww|^te$|\b\w/){ $&.upcase }
233
- data << "#{tmp}: #{value}" << CRLF
338
+ data << "#{tmp}: #{check_header(value)}" << CRLF
234
339
  }
235
340
  @cookies.each{|cookie|
236
- data << "Set-Cookie: " << cookie.to_s << CRLF
341
+ data << "Set-Cookie: " << check_header(cookie.to_s) << CRLF
237
342
  }
238
343
  data << CRLF
239
- _write_data(socket, data)
344
+ socket.write(data)
240
345
  end
346
+ rescue InvalidHeader => e
347
+ @header.clear
348
+ @cookies.clear
349
+ set_error e
350
+ retry
241
351
  end
242
352
 
243
353
  ##
244
354
  # Sends the body on +socket+
245
355
 
246
- def send_body(socket)
247
- case @body
248
- when IO then send_body_io(socket)
249
- else send_body_string(socket)
356
+ def send_body(socket) # :nodoc:
357
+ if @body.respond_to? :readpartial then
358
+ send_body_io(socket)
359
+ elsif @body.respond_to?(:call) then
360
+ send_body_proc(socket)
361
+ else
362
+ send_body_string(socket)
250
363
  end
251
364
  end
252
365
 
253
- def to_s # :nodoc:
254
- ret = ""
255
- send_response(ret)
256
- ret
257
- end
258
-
259
366
  ##
260
367
  # Redirects to +url+ with a WEBrick::HTTPStatus::Redirect +status+.
261
368
  #
@@ -264,8 +371,9 @@ module WEBrick
264
371
  # res.set_redirect WEBrick::HTTPStatus::TemporaryRedirect
265
372
 
266
373
  def set_redirect(status, url)
267
- @body = "<HTML><A HREF=\"#{url.to_s}\">#{url.to_s}</A>.</HTML>\n"
268
- @header['location'] = url.to_s
374
+ url = URI(url).to_s
375
+ @body = "<HTML><A HREF=\"#{url}\">#{url}</A>.</HTML>\n"
376
+ @header['location'] = url
269
377
  raise status
270
378
  end
271
379
 
@@ -294,6 +402,23 @@ module WEBrick
294
402
  host, port = @config[:ServerName], @config[:Port]
295
403
  end
296
404
 
405
+ error_body(backtrace, ex, host, port)
406
+ end
407
+
408
+ private
409
+
410
+ def check_header(header_value)
411
+ header_value = header_value.to_s
412
+ if /[\r\n]/ =~ header_value
413
+ raise InvalidHeader
414
+ else
415
+ header_value
416
+ end
417
+ end
418
+
419
+ # :stopdoc:
420
+
421
+ def error_body(backtrace, ex, host, port)
297
422
  @body = ''
298
423
  @body << <<-_end_of_html_
299
424
  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
@@ -323,30 +448,44 @@ module WEBrick
323
448
  _end_of_html_
324
449
  end
325
450
 
326
- private
327
-
328
451
  def send_body_io(socket)
329
452
  begin
330
453
  if @request_method == "HEAD"
331
454
  # do nothing
332
455
  elsif chunked?
333
- while buf = @body.read(@buffer_size)
334
- next if buf.empty?
335
- data = ""
336
- data << format("%x", buf.bytesize) << CRLF
337
- data << buf << CRLF
338
- _write_data(socket, data)
339
- @sent_size += buf.bytesize
340
- end
341
- _write_data(socket, "0#{CRLF}#{CRLF}")
456
+ buf = ''
457
+ begin
458
+ @body.readpartial(@buffer_size, buf)
459
+ size = buf.bytesize
460
+ data = "#{size.to_s(16)}#{CRLF}#{buf}#{CRLF}"
461
+ socket.write(data)
462
+ data.clear
463
+ @sent_size += size
464
+ rescue EOFError
465
+ break
466
+ end while true
467
+ buf.clear
468
+ socket.write("0#{CRLF}#{CRLF}")
342
469
  else
343
- size = @header['content-length'].to_i
344
- _send_file(socket, @body, 0, size)
345
- @sent_size = size
470
+ if %r{\Abytes (\d+)-(\d+)/\d+\z} =~ @header['content-range']
471
+ offset = $1.to_i
472
+ size = $2.to_i - offset + 1
473
+ else
474
+ offset = nil
475
+ size = @header['content-length']
476
+ size = size.to_i if size
477
+ end
478
+ begin
479
+ @sent_size = IO.copy_stream(@body, socket, size, offset)
480
+ rescue NotImplementedError
481
+ @body.seek(offset, IO::SEEK_SET)
482
+ @sent_size = IO.copy_stream(@body, socket, size)
483
+ end
346
484
  end
347
485
  ensure
348
486
  @body.close
349
487
  end
488
+ remove_body_tempfile
350
489
  end
351
490
 
352
491
  def send_body_string(socket)
@@ -356,44 +495,70 @@ module WEBrick
356
495
  body ? @body.bytesize : 0
357
496
  while buf = @body[@sent_size, @buffer_size]
358
497
  break if buf.empty?
359
- data = ""
360
- data << format("%x", buf.bytesize) << CRLF
361
- data << buf << CRLF
362
- _write_data(socket, data)
363
- @sent_size += buf.bytesize
498
+ size = buf.bytesize
499
+ data = "#{size.to_s(16)}#{CRLF}#{buf}#{CRLF}"
500
+ buf.clear
501
+ socket.write(data)
502
+ @sent_size += size
364
503
  end
365
- _write_data(socket, "0#{CRLF}#{CRLF}")
504
+ socket.write("0#{CRLF}#{CRLF}")
366
505
  else
367
506
  if @body && @body.bytesize > 0
368
- _write_data(socket, @body)
507
+ socket.write(@body)
369
508
  @sent_size = @body.bytesize
370
509
  end
371
510
  end
372
511
  end
373
512
 
374
- def _send_file(output, input, offset, size)
375
- while offset > 0
376
- sz = @buffer_size < size ? @buffer_size : size
377
- buf = input.read(sz)
378
- offset -= buf.bytesize
379
- end
380
-
381
- if size == 0
382
- while buf = input.read(@buffer_size)
383
- _write_data(output, buf)
384
- end
513
+ def send_body_proc(socket)
514
+ if @request_method == "HEAD"
515
+ # do nothing
516
+ elsif chunked?
517
+ @body.call(ChunkedWrapper.new(socket, self))
518
+ socket.write("0#{CRLF}#{CRLF}")
385
519
  else
386
- while size > 0
387
- sz = @buffer_size < size ? @buffer_size : size
388
- buf = input.read(sz)
389
- _write_data(output, buf)
390
- size -= buf.bytesize
520
+ size = @header['content-length'].to_i
521
+ if @bodytempfile
522
+ @bodytempfile.rewind
523
+ IO.copy_stream(@bodytempfile, socket)
524
+ else
525
+ @body.call(socket)
391
526
  end
527
+ @sent_size = size
392
528
  end
393
529
  end
394
530
 
531
+ class ChunkedWrapper
532
+ def initialize(socket, resp)
533
+ @socket = socket
534
+ @resp = resp
535
+ end
536
+
537
+ def write(buf)
538
+ return 0 if buf.empty?
539
+ socket = @socket
540
+ @resp.instance_eval {
541
+ size = buf.bytesize
542
+ data = "#{size.to_s(16)}#{CRLF}#{buf}#{CRLF}"
543
+ socket.write(data)
544
+ data.clear
545
+ @sent_size += size
546
+ size
547
+ }
548
+ end
549
+
550
+ def <<(*buf)
551
+ write(buf)
552
+ self
553
+ end
554
+ end
555
+
556
+ # preserved for compatibility with some 3rd-party handlers
395
557
  def _write_data(socket, data)
396
558
  socket << data
397
559
  end
560
+
561
+ # :startdoc:
398
562
  end
563
+
399
564
  end