webrick 1.4.1 → 1.6.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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