webrick 1.4.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.

@@ -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
@@ -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,56 @@ module WEBrick
301
295
  return FakeProxyURI
302
296
  end
303
297
 
304
- def perform_proxy_request(req, res)
298
+ def perform_proxy_request(req, res, req_class, body_stream = nil)
305
299
  uri = req.request_uri
306
300
  path = uri.path.dup
307
301
  path << "?" << uri.query if uri.query
308
302
  header = setup_proxy_header(req, res)
309
303
  upstream = setup_upstream_proxy_authentication(req, res, header)
310
- response = nil
311
304
 
305
+ body_tmp = []
312
306
  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
- ##################################
307
+ req_fib = Fiber.new do
308
+ http.start do
309
+ if @config[:ProxyTimeout]
310
+ ################################## these issues are
311
+ http.open_timeout = 30 # secs # necessary (maybe because
312
+ http.read_timeout = 60 # secs # Ruby's bug, but why?)
313
+ ##################################
314
+ end
315
+ if body_stream && req['transfer-encoding'] =~ /\bchunked\b/i
316
+ header['Transfer-Encoding'] = 'chunked'
317
+ end
318
+ http_req = req_class.new(path, header)
319
+ http_req.body_stream = body_stream if body_stream
320
+ http.request(http_req) do |response|
321
+ # Persistent connection requirements are mysterious for me.
322
+ # So I will close the connection in every response.
323
+ res['proxy-connection'] = "close"
324
+ res['connection'] = "close"
325
+
326
+ # stream Net::HTTP::HTTPResponse to WEBrick::HTTPResponse
327
+ res.status = response.code.to_i
328
+ res.chunked = response.chunked?
329
+ choose_header(response, res)
330
+ set_cookie(response, res)
331
+ set_via(res)
332
+ response.read_body do |buf|
333
+ body_tmp << buf
334
+ Fiber.yield # wait for res.body Proc#call
335
+ end
336
+ end # http.request
337
+ end
338
+ end
339
+ req_fib.resume # read HTTP response headers and first chunk of the body
340
+ res.body = ->(socket) do
341
+ while buf = body_tmp.shift
342
+ socket.write(buf)
343
+ buf.clear
344
+ req_fib.resume # continue response.read_body
319
345
  end
320
- response = yield(http, path, header)
321
346
  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
347
  end
335
-
336
348
  # :stopdoc:
337
349
  end
338
350
  end
@@ -10,10 +10,10 @@
10
10
  # $IPR: httprequest.rb,v 1.64 2003/07/13 17:18:22 gotoyuzo Exp $
11
11
 
12
12
  require 'uri'
13
- require 'webrick/httpversion'
14
- require 'webrick/httpstatus'
15
- require 'webrick/httputils'
16
- require 'webrick/cookie'
13
+ require_relative 'httpversion'
14
+ require_relative 'httpstatus'
15
+ require_relative 'httputils'
16
+ require_relative 'cookie'
17
17
 
18
18
  module WEBrick
19
19
 
@@ -226,9 +226,9 @@ module WEBrick
226
226
  raise HTTPStatus::BadRequest, "bad URI `#{@unparsed_uri}'."
227
227
  end
228
228
 
229
- if /close/io =~ self["connection"]
229
+ if /\Aclose\z/io =~ self["connection"]
230
230
  @keep_alive = false
231
- elsif /keep-alive/io =~ self["connection"]
231
+ elsif /\Akeep-alive\z/io =~ self["connection"]
232
232
  @keep_alive = true
233
233
  elsif @http_version < "1.1"
234
234
  @keep_alive = false
@@ -257,6 +257,32 @@ module WEBrick
257
257
  @body.empty? ? nil : @body
258
258
  end
259
259
 
260
+ ##
261
+ # Prepares the HTTPRequest object for use as the
262
+ # source for IO.copy_stream
263
+
264
+ def body_reader
265
+ @body_tmp = []
266
+ @body_rd = Fiber.new do
267
+ body do |buf|
268
+ @body_tmp << buf
269
+ Fiber.yield
270
+ end
271
+ end
272
+ @body_rd.resume # grab the first chunk and yield
273
+ self
274
+ end
275
+
276
+ # for IO.copy_stream. Note: we may return a larger string than +size+
277
+ # here; but IO.copy_stream does not care.
278
+ def readpartial(size, buf = ''.b) # :nodoc
279
+ res = @body_tmp.shift or raise EOFError, 'end of file reached'
280
+ buf.replace(res)
281
+ res.clear
282
+ @body_rd.resume # get more chunks
283
+ buf
284
+ end
285
+
260
286
  ##
261
287
  # Request query as a Hash
262
288
 
@@ -414,13 +440,19 @@ module WEBrick
414
440
 
415
441
  MAX_URI_LENGTH = 2083 # :nodoc:
416
442
 
443
+ # same as Mongrel, Thin and Puma
444
+ MAX_HEADER_LENGTH = (112 * 1024) # :nodoc:
445
+
417
446
  def read_request_line(socket)
418
447
  @request_line = read_line(socket, MAX_URI_LENGTH) if socket
419
- if @request_line.bytesize >= MAX_URI_LENGTH and @request_line[-1, 1] != LF
448
+ raise HTTPStatus::EOFError unless @request_line
449
+
450
+ @request_bytes = @request_line.bytesize
451
+ if @request_bytes >= MAX_URI_LENGTH and @request_line[-1, 1] != LF
420
452
  raise HTTPStatus::RequestURITooLarge
421
453
  end
454
+
422
455
  @request_time = Time.now
423
- raise HTTPStatus::EOFError unless @request_line
424
456
  if /^(\S+)\s+(\S++)(?:\s+HTTP\/(\d+\.\d+))?\r?\n/mo =~ @request_line
425
457
  @request_method = $1
426
458
  @unparsed_uri = $2
@@ -435,6 +467,9 @@ module WEBrick
435
467
  if socket
436
468
  while line = read_line(socket)
437
469
  break if /\A(#{CRLF}|#{LF})\z/om =~ line
470
+ if (@request_bytes += line.bytesize) > MAX_HEADER_LENGTH
471
+ raise HTTPStatus::RequestEntityTooLarge, 'headers too large'
472
+ end
438
473
  @raw_header << line
439
474
  end
440
475
  end
@@ -468,7 +503,7 @@ module WEBrick
468
503
  return unless socket
469
504
  if tc = self['transfer-encoding']
470
505
  case tc
471
- when /chunked/io then read_chunked(socket, block)
506
+ when /\Achunked\z/io then read_chunked(socket, block)
472
507
  else raise HTTPStatus::NotImplemented, "Transfer-Encoding: #{tc}."
473
508
  end
474
509
  elsif self['content-length'] || @remaining_size
@@ -502,12 +537,16 @@ module WEBrick
502
537
  def read_chunked(socket, block)
503
538
  chunk_size, = read_chunk_size(socket)
504
539
  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
540
+ begin
541
+ sz = [ chunk_size, @buffer_size ].min
542
+ data = read_data(socket, sz) # read chunk-data
543
+ if data.nil? || data.bytesize != sz
544
+ raise HTTPStatus::BadRequest, "bad chunk data size."
545
+ end
546
+ block.call(data)
547
+ end while (chunk_size -= sz) > 0
548
+
509
549
  read_line(socket) # skip CRLF
510
- block.call(data)
511
550
  chunk_size, = read_chunk_size(socket)
512
551
  end
513
552
  read_header(socket) # trailer + CRLF
@@ -572,7 +611,12 @@ module WEBrick
572
611
  end
573
612
  if host_port = self["x-forwarded-host"]
574
613
  host_port = host_port.split(",", 2).first
575
- @forwarded_host, tmp = host_port.split(":", 2)
614
+ if host_port =~ /\A(\[[0-9a-fA-F:]+\])(?::(\d+))?\z/
615
+ @forwarded_host = $1
616
+ tmp = $2
617
+ else
618
+ @forwarded_host, tmp = host_port.split(":", 2)
619
+ end
576
620
  @forwarded_port = (tmp || (@forwarded_proto == "https" ? 443 : 80)).to_i
577
621
  end
578
622
  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