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
  # 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