webrick 1.4.2 → 1.7.0

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.

@@ -10,7 +10,7 @@
10
10
  # $IPR: httpproxy.rb,v 1.18 2003/03/08 18:58:10 gotoyuzo Exp $
11
11
  # $kNotwork: straw.rb,v 1.3 2002/02/12 15:13:07 gotoken Exp $
12
12
 
13
- require "webrick/httpserver"
13
+ require_relative "httpserver"
14
14
  require "net/http"
15
15
 
16
16
  module WEBrick
@@ -115,7 +115,7 @@ module WEBrick
115
115
  proxy_auth(req, res)
116
116
 
117
117
  begin
118
- self.send("do_#{req.request_method}", req, res)
118
+ public_send("do_#{req.request_method}", req, res)
119
119
  rescue NoMethodError
120
120
  raise HTTPStatus::MethodNotAllowed,
121
121
  "unsupported method `#{req.request_method}'."
@@ -211,21 +211,15 @@ module WEBrick
211
211
  end
212
212
 
213
213
  def do_GET(req, res)
214
- perform_proxy_request(req, res) do |http, path, header|
215
- http.get(path, header)
216
- end
214
+ perform_proxy_request(req, res, Net::HTTP::Get)
217
215
  end
218
216
 
219
217
  def do_HEAD(req, res)
220
- perform_proxy_request(req, res) do |http, path, header|
221
- http.head(path, header)
222
- end
218
+ perform_proxy_request(req, res, Net::HTTP::Head)
223
219
  end
224
220
 
225
221
  def do_POST(req, res)
226
- perform_proxy_request(req, res) do |http, path, header|
227
- http.post(path, req.body || "", header)
228
- end
222
+ perform_proxy_request(req, res, Net::HTTP::Post, req.body_reader)
229
223
  end
230
224
 
231
225
  def do_OPTIONS(req, res)
@@ -301,38 +295,60 @@ module WEBrick
301
295
  return FakeProxyURI
302
296
  end
303
297
 
304
- def perform_proxy_request(req, res)
298
+ def create_net_http(uri, upstream)
299
+ Net::HTTP.new(uri.host, uri.port, upstream.host, upstream.port)
300
+ end
301
+
302
+ def perform_proxy_request(req, res, req_class, body_stream = nil)
305
303
  uri = req.request_uri
306
304
  path = uri.path.dup
307
305
  path << "?" << uri.query if uri.query
308
306
  header = setup_proxy_header(req, res)
309
307
  upstream = setup_upstream_proxy_authentication(req, res, header)
310
- response = nil
311
-
312
- http = Net::HTTP.new(uri.host, uri.port, upstream.host, upstream.port)
313
- http.start do
314
- if @config[:ProxyTimeout]
315
- ################################## these issues are
316
- http.open_timeout = 30 # secs # necessary (maybe because
317
- http.read_timeout = 60 # secs # Ruby's bug, but why?)
318
- ##################################
308
+
309
+ body_tmp = []
310
+ http = create_net_http(uri, upstream)
311
+ req_fib = Fiber.new do
312
+ http.start do
313
+ if @config[:ProxyTimeout]
314
+ ################################## these issues are
315
+ http.open_timeout = 30 # secs # necessary (maybe because
316
+ http.read_timeout = 60 # secs # Ruby's bug, but why?)
317
+ ##################################
318
+ end
319
+ if body_stream && req['transfer-encoding'] =~ /\bchunked\b/i
320
+ header['Transfer-Encoding'] = 'chunked'
321
+ end
322
+ http_req = req_class.new(path, header)
323
+ http_req.body_stream = body_stream if body_stream
324
+ http.request(http_req) do |response|
325
+ # Persistent connection requirements are mysterious for me.
326
+ # So I will close the connection in every response.
327
+ res['proxy-connection'] = "close"
328
+ res['connection'] = "close"
329
+
330
+ # stream Net::HTTP::HTTPResponse to WEBrick::HTTPResponse
331
+ res.status = response.code.to_i
332
+ res.chunked = response.chunked?
333
+ choose_header(response, res)
334
+ set_cookie(response, res)
335
+ set_via(res)
336
+ response.read_body do |buf|
337
+ body_tmp << buf
338
+ Fiber.yield # wait for res.body Proc#call
339
+ end
340
+ end # http.request
341
+ end
342
+ end
343
+ req_fib.resume # read HTTP response headers and first chunk of the body
344
+ res.body = ->(socket) do
345
+ while buf = body_tmp.shift
346
+ socket.write(buf)
347
+ buf.clear
348
+ req_fib.resume # continue response.read_body
319
349
  end
320
- response = yield(http, path, header)
321
350
  end
322
-
323
- # Persistent connection requirements are mysterious for me.
324
- # So I will close the connection in every response.
325
- res['proxy-connection'] = "close"
326
- res['connection'] = "close"
327
-
328
- # Convert Net::HTTP::HTTPResponse to WEBrick::HTTPResponse
329
- res.status = response.code.to_i
330
- choose_header(response, res)
331
- set_cookie(response, res)
332
- set_via(res)
333
- res.body = response.body
334
351
  end
335
-
336
352
  # :stopdoc:
337
353
  end
338
354
  end
@@ -9,11 +9,12 @@
9
9
  #
10
10
  # $IPR: httprequest.rb,v 1.64 2003/07/13 17:18:22 gotoyuzo Exp $
11
11
 
12
+ require 'fiber'
12
13
  require 'uri'
13
- require 'webrick/httpversion'
14
- require 'webrick/httpstatus'
15
- require 'webrick/httputils'
16
- require 'webrick/cookie'
14
+ require_relative 'httpversion'
15
+ require_relative 'httpstatus'
16
+ require_relative 'httputils'
17
+ require_relative 'cookie'
17
18
 
18
19
  module WEBrick
19
20
 
@@ -226,9 +227,9 @@ module WEBrick
226
227
  raise HTTPStatus::BadRequest, "bad URI `#{@unparsed_uri}'."
227
228
  end
228
229
 
229
- if /close/io =~ self["connection"]
230
+ if /\Aclose\z/io =~ self["connection"]
230
231
  @keep_alive = false
231
- elsif /keep-alive/io =~ self["connection"]
232
+ elsif /\Akeep-alive\z/io =~ self["connection"]
232
233
  @keep_alive = true
233
234
  elsif @http_version < "1.1"
234
235
  @keep_alive = false
@@ -257,6 +258,36 @@ module WEBrick
257
258
  @body.empty? ? nil : @body
258
259
  end
259
260
 
261
+ ##
262
+ # Prepares the HTTPRequest object for use as the
263
+ # source for IO.copy_stream
264
+
265
+ def body_reader
266
+ @body_tmp = []
267
+ @body_rd = Fiber.new do
268
+ body do |buf|
269
+ @body_tmp << buf
270
+ Fiber.yield
271
+ end
272
+ end
273
+ @body_rd.resume # grab the first chunk and yield
274
+ self
275
+ end
276
+
277
+ # for IO.copy_stream.
278
+ def readpartial(size, buf = ''.b) # :nodoc
279
+ res = @body_tmp.shift or raise EOFError, 'end of file reached'
280
+ if res.length > size
281
+ @body_tmp.unshift(res[size..-1])
282
+ res = res[0..size - 1]
283
+ end
284
+ buf.replace(res)
285
+ res.clear
286
+ # get more chunks - check alive? because we can take a partial chunk
287
+ @body_rd.resume if @body_rd.alive?
288
+ buf
289
+ end
290
+
260
291
  ##
261
292
  # Request query as a Hash
262
293
 
@@ -414,13 +445,19 @@ module WEBrick
414
445
 
415
446
  MAX_URI_LENGTH = 2083 # :nodoc:
416
447
 
448
+ # same as Mongrel, Thin and Puma
449
+ MAX_HEADER_LENGTH = (112 * 1024) # :nodoc:
450
+
417
451
  def read_request_line(socket)
418
452
  @request_line = read_line(socket, MAX_URI_LENGTH) if socket
419
- if @request_line.bytesize >= MAX_URI_LENGTH and @request_line[-1, 1] != LF
453
+ raise HTTPStatus::EOFError unless @request_line
454
+
455
+ @request_bytes = @request_line.bytesize
456
+ if @request_bytes >= MAX_URI_LENGTH and @request_line[-1, 1] != LF
420
457
  raise HTTPStatus::RequestURITooLarge
421
458
  end
459
+
422
460
  @request_time = Time.now
423
- raise HTTPStatus::EOFError unless @request_line
424
461
  if /^(\S+)\s+(\S++)(?:\s+HTTP\/(\d+\.\d+))?\r?\n/mo =~ @request_line
425
462
  @request_method = $1
426
463
  @unparsed_uri = $2
@@ -435,6 +472,9 @@ module WEBrick
435
472
  if socket
436
473
  while line = read_line(socket)
437
474
  break if /\A(#{CRLF}|#{LF})\z/om =~ line
475
+ if (@request_bytes += line.bytesize) > MAX_HEADER_LENGTH
476
+ raise HTTPStatus::RequestEntityTooLarge, 'headers too large'
477
+ end
438
478
  @raw_header << line
439
479
  end
440
480
  end
@@ -468,7 +508,7 @@ module WEBrick
468
508
  return unless socket
469
509
  if tc = self['transfer-encoding']
470
510
  case tc
471
- when /chunked/io then read_chunked(socket, block)
511
+ when /\Achunked\z/io then read_chunked(socket, block)
472
512
  else raise HTTPStatus::NotImplemented, "Transfer-Encoding: #{tc}."
473
513
  end
474
514
  elsif self['content-length'] || @remaining_size
@@ -482,7 +522,7 @@ module WEBrick
482
522
  if @remaining_size > 0 && @socket.eof?
483
523
  raise HTTPStatus::BadRequest, "invalid body size."
484
524
  end
485
- elsif BODY_CONTAINABLE_METHODS.member?(@request_method)
525
+ elsif BODY_CONTAINABLE_METHODS.member?(@request_method) && !@socket.eof
486
526
  raise HTTPStatus::LengthRequired
487
527
  end
488
528
  return @body
@@ -502,12 +542,16 @@ module WEBrick
502
542
  def read_chunked(socket, block)
503
543
  chunk_size, = read_chunk_size(socket)
504
544
  while chunk_size > 0
505
- data = read_data(socket, chunk_size) # read chunk-data
506
- if data.nil? || data.bytesize != chunk_size
507
- raise BadRequest, "bad chunk data size."
508
- end
545
+ begin
546
+ sz = [ chunk_size, @buffer_size ].min
547
+ data = read_data(socket, sz) # read chunk-data
548
+ if data.nil? || data.bytesize != sz
549
+ raise HTTPStatus::BadRequest, "bad chunk data size."
550
+ end
551
+ block.call(data)
552
+ end while (chunk_size -= sz) > 0
553
+
509
554
  read_line(socket) # skip CRLF
510
- block.call(data)
511
555
  chunk_size, = read_chunk_size(socket)
512
556
  end
513
557
  read_header(socket) # trailer + CRLF
@@ -572,7 +616,12 @@ module WEBrick
572
616
  end
573
617
  if host_port = self["x-forwarded-host"]
574
618
  host_port = host_port.split(",", 2).first
575
- @forwarded_host, tmp = host_port.split(":", 2)
619
+ if host_port =~ /\A(\[[0-9a-fA-F:]+\])(?::(\d+))?\z/
620
+ @forwarded_host = $1
621
+ tmp = $2
622
+ else
623
+ @forwarded_host, tmp = host_port.split(":", 2)
624
+ end
576
625
  @forwarded_port = (tmp || (@forwarded_proto == "https" ? 443 : 80)).to_i
577
626
  end
578
627
  if addrs = self["x-forwarded-for"]
@@ -10,10 +10,11 @@
10
10
  # $IPR: httpresponse.rb,v 1.45 2003/07/11 11:02:25 gotoyuzo Exp $
11
11
 
12
12
  require 'time'
13
- require 'webrick/httpversion'
14
- require 'webrick/htmlutils'
15
- require 'webrick/httputils'
16
- require 'webrick/httpstatus'
13
+ require 'uri'
14
+ require_relative 'httpversion'
15
+ require_relative 'htmlutils'
16
+ require_relative 'httputils'
17
+ require_relative 'httpstatus'
17
18
 
18
19
  module WEBrick
19
20
  ##
@@ -21,6 +22,8 @@ module WEBrick
21
22
  # WEBrick HTTP Servlet.
22
23
 
23
24
  class HTTPResponse
25
+ class InvalidHeader < StandardError
26
+ end
24
27
 
25
28
  ##
26
29
  # HTTP Response version
@@ -48,8 +51,21 @@ module WEBrick
48
51
  attr_accessor :reason_phrase
49
52
 
50
53
  ##
51
- # Body may be a String or IO-like object that responds to #read and
52
- # #readpartial.
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
53
69
 
54
70
  attr_accessor :body
55
71
 
@@ -110,13 +126,14 @@ module WEBrick
110
126
  @chunked = false
111
127
  @filename = nil
112
128
  @sent_size = 0
129
+ @bodytempfile = nil
113
130
  end
114
131
 
115
132
  ##
116
133
  # The response's HTTP status line
117
134
 
118
135
  def status_line
119
- "HTTP/#@http_version #@status #@reason_phrase #{CRLF}"
136
+ "HTTP/#@http_version #@status #@reason_phrase".rstrip << CRLF
120
137
  end
121
138
 
122
139
  ##
@@ -138,6 +155,7 @@ module WEBrick
138
155
  # Sets the response header +field+ to +value+
139
156
 
140
157
  def []=(field, value)
158
+ @chunked = value.to_s.downcase == 'chunked' if field.downcase == 'transfer-encoding'
141
159
  @header[field.downcase] = value.to_s
142
160
  end
143
161
 
@@ -250,8 +268,11 @@ module WEBrick
250
268
  elsif %r{^multipart/byteranges} =~ @header['content-type']
251
269
  @header.delete('content-length')
252
270
  elsif @header['content-length'].nil?
253
- unless @body.is_a?(IO)
254
- @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
255
276
  end
256
277
  end
257
278
 
@@ -274,11 +295,38 @@ module WEBrick
274
295
  # Location is a single absoluteURI.
275
296
  if location = @header['location']
276
297
  if @request_uri
277
- @header['location'] = @request_uri.merge(location)
298
+ @header['location'] = @request_uri.merge(location).to_s
278
299
  end
279
300
  end
280
301
  end
281
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
+
282
330
  ##
283
331
  # Sends the headers on +socket+
284
332
 
@@ -287,14 +335,19 @@ module WEBrick
287
335
  data = status_line()
288
336
  @header.each{|key, value|
289
337
  tmp = key.gsub(/\bwww|^te$|\b\w/){ $&.upcase }
290
- data << "#{tmp}: #{value}" << CRLF
338
+ data << "#{tmp}: #{check_header(value)}" << CRLF
291
339
  }
292
340
  @cookies.each{|cookie|
293
- data << "Set-Cookie: " << cookie.to_s << CRLF
341
+ data << "Set-Cookie: " << check_header(cookie.to_s) << CRLF
294
342
  }
295
343
  data << CRLF
296
- _write_data(socket, data)
344
+ socket.write(data)
297
345
  end
346
+ rescue InvalidHeader => e
347
+ @header.clear
348
+ @cookies.clear
349
+ set_error e
350
+ retry
298
351
  end
299
352
 
300
353
  ##
@@ -310,12 +363,6 @@ module WEBrick
310
363
  end
311
364
  end
312
365
 
313
- def to_s # :nodoc:
314
- ret = ""
315
- send_response(ret)
316
- ret
317
- end
318
-
319
366
  ##
320
367
  # Redirects to +url+ with a WEBrick::HTTPStatus::Redirect +status+.
321
368
  #
@@ -324,8 +371,9 @@ module WEBrick
324
371
  # res.set_redirect WEBrick::HTTPStatus::TemporaryRedirect
325
372
 
326
373
  def set_redirect(status, url)
374
+ url = URI(url).to_s
327
375
  @body = "<HTML><A HREF=\"#{url}\">#{url}</A>.</HTML>\n"
328
- @header['location'] = url.to_s
376
+ @header['location'] = url
329
377
  raise status
330
378
  end
331
379
 
@@ -359,6 +407,15 @@ module WEBrick
359
407
 
360
408
  private
361
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
+
362
419
  # :stopdoc:
363
420
 
364
421
  def error_body(backtrace, ex, host, port)
@@ -401,22 +458,34 @@ module WEBrick
401
458
  @body.readpartial(@buffer_size, buf)
402
459
  size = buf.bytesize
403
460
  data = "#{size.to_s(16)}#{CRLF}#{buf}#{CRLF}"
404
- _write_data(socket, data)
461
+ socket.write(data)
405
462
  data.clear
406
463
  @sent_size += size
407
464
  rescue EOFError
408
465
  break
409
466
  end while true
410
467
  buf.clear
411
- _write_data(socket, "0#{CRLF}#{CRLF}")
468
+ socket.write("0#{CRLF}#{CRLF}")
412
469
  else
413
- size = @header['content-length'].to_i
414
- _send_file(socket, @body, 0, size)
415
- @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
416
484
  end
417
485
  ensure
418
486
  @body.close
419
487
  end
488
+ remove_body_tempfile
420
489
  end
421
490
 
422
491
  def send_body_string(socket)
@@ -429,13 +498,13 @@ module WEBrick
429
498
  size = buf.bytesize
430
499
  data = "#{size.to_s(16)}#{CRLF}#{buf}#{CRLF}"
431
500
  buf.clear
432
- _write_data(socket, data)
501
+ socket.write(data)
433
502
  @sent_size += size
434
503
  end
435
- _write_data(socket, "0#{CRLF}#{CRLF}")
504
+ socket.write("0#{CRLF}#{CRLF}")
436
505
  else
437
506
  if @body && @body.bytesize > 0
438
- _write_data(socket, @body)
507
+ socket.write(@body)
439
508
  @sent_size = @body.bytesize
440
509
  end
441
510
  end
@@ -446,10 +515,15 @@ module WEBrick
446
515
  # do nothing
447
516
  elsif chunked?
448
517
  @body.call(ChunkedWrapper.new(socket, self))
449
- _write_data(socket, "0#{CRLF}#{CRLF}")
518
+ socket.write("0#{CRLF}#{CRLF}")
450
519
  else
451
520
  size = @header['content-length'].to_i
452
- @body.call(socket)
521
+ if @bodytempfile
522
+ @bodytempfile.rewind
523
+ IO.copy_stream(@bodytempfile, socket)
524
+ else
525
+ @body.call(socket)
526
+ end
453
527
  @sent_size = size
454
528
  end
455
529
  end
@@ -461,40 +535,25 @@ module WEBrick
461
535
  end
462
536
 
463
537
  def write(buf)
464
- return if buf.empty?
538
+ return 0 if buf.empty?
465
539
  socket = @socket
466
540
  @resp.instance_eval {
467
541
  size = buf.bytesize
468
542
  data = "#{size.to_s(16)}#{CRLF}#{buf}#{CRLF}"
469
- _write_data(socket, data)
543
+ socket.write(data)
470
544
  data.clear
471
545
  @sent_size += size
546
+ size
472
547
  }
473
548
  end
474
- alias :<< :write
475
- end
476
-
477
- def _send_file(output, input, offset, size)
478
- while offset > 0
479
- sz = @buffer_size < size ? @buffer_size : size
480
- buf = input.read(sz)
481
- offset -= buf.bytesize
482
- end
483
549
 
484
- if size == 0
485
- while buf = input.read(@buffer_size)
486
- _write_data(output, buf)
487
- end
488
- else
489
- while size > 0
490
- sz = @buffer_size < size ? @buffer_size : size
491
- buf = input.read(sz)
492
- _write_data(output, buf)
493
- size -= buf.bytesize
494
- end
550
+ def <<(*buf)
551
+ write(buf)
552
+ self
495
553
  end
496
554
  end
497
555
 
556
+ # preserved for compatibility with some 3rd-party handlers
498
557
  def _write_data(socket, data)
499
558
  socket << data
500
559
  end