webrick 1.4.0.beta1 → 1.4.4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

@@ -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
@@ -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
@@ -251,7 +254,7 @@ module WEBrick
251
254
  @header.delete('content-length')
252
255
  elsif @header['content-length'].nil?
253
256
  unless @body.is_a?(IO)
254
- @header['content-length'] = @body ? @body.bytesize : 0
257
+ @header['content-length'] = (@body ? @body.bytesize : 0).to_s
255
258
  end
256
259
  end
257
260
 
@@ -274,7 +277,7 @@ module WEBrick
274
277
  # Location is a single absoluteURI.
275
278
  if location = @header['location']
276
279
  if @request_uri
277
- @header['location'] = @request_uri.merge(location)
280
+ @header['location'] = @request_uri.merge(location).to_s
278
281
  end
279
282
  end
280
283
  end
@@ -287,14 +290,19 @@ module WEBrick
287
290
  data = status_line()
288
291
  @header.each{|key, value|
289
292
  tmp = key.gsub(/\bwww|^te$|\b\w/){ $&.upcase }
290
- data << "#{tmp}: #{value}" << CRLF
293
+ data << "#{tmp}: #{check_header(value)}" << CRLF
291
294
  }
292
295
  @cookies.each{|cookie|
293
- data << "Set-Cookie: " << cookie.to_s << CRLF
296
+ data << "Set-Cookie: " << check_header(cookie.to_s) << CRLF
294
297
  }
295
298
  data << CRLF
296
- _write_data(socket, data)
299
+ socket.write(data)
297
300
  end
301
+ rescue InvalidHeader => e
302
+ @header.clear
303
+ @cookies.clear
304
+ set_error e
305
+ retry
298
306
  end
299
307
 
300
308
  ##
@@ -303,6 +311,8 @@ module WEBrick
303
311
  def send_body(socket) # :nodoc:
304
312
  if @body.respond_to? :readpartial then
305
313
  send_body_io(socket)
314
+ elsif @body.respond_to?(:call) then
315
+ send_body_proc(socket)
306
316
  else
307
317
  send_body_string(socket)
308
318
  end
@@ -322,8 +332,9 @@ module WEBrick
322
332
  # res.set_redirect WEBrick::HTTPStatus::TemporaryRedirect
323
333
 
324
334
  def set_redirect(status, url)
335
+ url = URI(url).to_s
325
336
  @body = "<HTML><A HREF=\"#{url}\">#{url}</A>.</HTML>\n"
326
- @header['location'] = url.to_s
337
+ @header['location'] = url
327
338
  raise status
328
339
  end
329
340
 
@@ -357,6 +368,15 @@ module WEBrick
357
368
 
358
369
  private
359
370
 
371
+ def check_header(header_value)
372
+ header_value = header_value.to_s
373
+ if /[\r\n]/ =~ header_value
374
+ raise InvalidHeader
375
+ else
376
+ header_value
377
+ end
378
+ end
379
+
360
380
  # :stopdoc:
361
381
 
362
382
  def error_body(backtrace, ex, host, port)
@@ -394,24 +414,34 @@ module WEBrick
394
414
  if @request_method == "HEAD"
395
415
  # do nothing
396
416
  elsif chunked?
417
+ buf = ''
397
418
  begin
398
- buf = ''
399
- data = ''
400
- while true
401
- @body.readpartial( @buffer_size, buf ) # there is no need to clear buf?
402
- data << format("%x", buf.bytesize) << CRLF
403
- data << buf << CRLF
404
- _write_data(socket, data)
405
- data.clear
406
- @sent_size += buf.bytesize
407
- end
408
- rescue EOFError # do nothing
409
- end
410
- _write_data(socket, "0#{CRLF}#{CRLF}")
419
+ @body.readpartial(@buffer_size, buf)
420
+ size = buf.bytesize
421
+ data = "#{size.to_s(16)}#{CRLF}#{buf}#{CRLF}"
422
+ socket.write(data)
423
+ data.clear
424
+ @sent_size += size
425
+ rescue EOFError
426
+ break
427
+ end while true
428
+ buf.clear
429
+ socket.write("0#{CRLF}#{CRLF}")
411
430
  else
412
- size = @header['content-length'].to_i
413
- _send_file(socket, @body, 0, size)
414
- @sent_size = size
431
+ if %r{\Abytes (\d+)-(\d+)/\d+\z} =~ @header['content-range']
432
+ offset = $1.to_i
433
+ size = $2.to_i - offset + 1
434
+ else
435
+ offset = nil
436
+ size = @header['content-length']
437
+ size = size.to_i if size
438
+ end
439
+ begin
440
+ @sent_size = IO.copy_stream(@body, socket, size, offset)
441
+ rescue NotImplementedError
442
+ @body.seek(offset, IO::SEEK_SET)
443
+ @sent_size = IO.copy_stream(@body, socket, size)
444
+ end
415
445
  end
416
446
  ensure
417
447
  @body.close
@@ -425,42 +455,60 @@ module WEBrick
425
455
  body ? @body.bytesize : 0
426
456
  while buf = @body[@sent_size, @buffer_size]
427
457
  break if buf.empty?
428
- data = ""
429
- data << format("%x", buf.bytesize) << CRLF
430
- data << buf << CRLF
431
- _write_data(socket, data)
432
- @sent_size += buf.bytesize
458
+ size = buf.bytesize
459
+ data = "#{size.to_s(16)}#{CRLF}#{buf}#{CRLF}"
460
+ buf.clear
461
+ socket.write(data)
462
+ @sent_size += size
433
463
  end
434
- _write_data(socket, "0#{CRLF}#{CRLF}")
464
+ socket.write("0#{CRLF}#{CRLF}")
435
465
  else
436
466
  if @body && @body.bytesize > 0
437
- _write_data(socket, @body)
467
+ socket.write(@body)
438
468
  @sent_size = @body.bytesize
439
469
  end
440
470
  end
441
471
  end
442
472
 
443
- def _send_file(output, input, offset, size)
444
- while offset > 0
445
- sz = @buffer_size < size ? @buffer_size : size
446
- buf = input.read(sz)
447
- offset -= buf.bytesize
473
+ def send_body_proc(socket)
474
+ if @request_method == "HEAD"
475
+ # do nothing
476
+ elsif chunked?
477
+ @body.call(ChunkedWrapper.new(socket, self))
478
+ socket.write("0#{CRLF}#{CRLF}")
479
+ else
480
+ size = @header['content-length'].to_i
481
+ @body.call(socket)
482
+ @sent_size = size
483
+ end
484
+ end
485
+
486
+ class ChunkedWrapper
487
+ def initialize(socket, resp)
488
+ @socket = socket
489
+ @resp = resp
448
490
  end
449
491
 
450
- if size == 0
451
- while buf = input.read(@buffer_size)
452
- _write_data(output, buf)
453
- end
454
- else
455
- while size > 0
456
- sz = @buffer_size < size ? @buffer_size : size
457
- buf = input.read(sz)
458
- _write_data(output, buf)
459
- size -= buf.bytesize
460
- end
492
+ def write(buf)
493
+ return 0 if buf.empty?
494
+ socket = @socket
495
+ @resp.instance_eval {
496
+ size = buf.bytesize
497
+ data = "#{size.to_s(16)}#{CRLF}#{buf}#{CRLF}"
498
+ socket.write(data)
499
+ data.clear
500
+ @sent_size += size
501
+ size
502
+ }
503
+ end
504
+
505
+ def <<(*buf)
506
+ write(buf)
507
+ self
461
508
  end
462
509
  end
463
510
 
511
+ # preserved for compatibility with some 3rd-party handlers
464
512
  def _write_data(socket, data)
465
513
  socket << data
466
514
  end
@@ -9,7 +9,8 @@
9
9
  #
10
10
  # $IPR: https.rb,v 1.15 2003/07/22 19:20:42 gotoyuzo Exp $
11
11
 
12
- require 'webrick/ssl'
12
+ require_relative 'ssl'
13
+ require_relative 'httpserver'
13
14
 
14
15
  module WEBrick
15
16
  module Config
@@ -84,4 +85,68 @@ module WEBrick
84
85
 
85
86
  # :startdoc:
86
87
  end
88
+
89
+ ##
90
+ #--
91
+ # Fake WEBrick::HTTPRequest for lookup_server
92
+
93
+ class SNIRequest
94
+
95
+ ##
96
+ # The SNI hostname
97
+
98
+ attr_reader :host
99
+
100
+ ##
101
+ # The socket address of the server
102
+
103
+ attr_reader :addr
104
+
105
+ ##
106
+ # The port this request is for
107
+
108
+ attr_reader :port
109
+
110
+ ##
111
+ # Creates a new SNIRequest.
112
+
113
+ def initialize(sslsocket, hostname)
114
+ @host = hostname
115
+ @addr = sslsocket.addr
116
+ @port = @addr[1]
117
+ end
118
+ end
119
+
120
+
121
+ ##
122
+ #--
123
+ # Adds SSL functionality to WEBrick::HTTPServer
124
+
125
+ class HTTPServer < ::WEBrick::GenericServer
126
+ ##
127
+ # ServerNameIndication callback
128
+
129
+ def ssl_servername_callback(sslsocket, hostname = nil)
130
+ req = SNIRequest.new(sslsocket, hostname)
131
+ server = lookup_server(req)
132
+ server ? server.ssl_context : nil
133
+ end
134
+
135
+ # :stopdoc:
136
+
137
+ ##
138
+ # Check whether +server+ is also SSL server.
139
+ # Also +server+'s SSL context will be created.
140
+
141
+ alias orig_virtual_host virtual_host
142
+
143
+ def virtual_host(server)
144
+ if @config[:SSLEnable] && !server.ssl_context
145
+ raise ArgumentError, "virtual host must set SSLEnable to true"
146
+ end
147
+ orig_virtual_host(server)
148
+ end
149
+
150
+ # :startdoc:
151
+ end
87
152
  end
@@ -10,13 +10,13 @@
10
10
  # $IPR: httpserver.rb,v 1.63 2002/10/01 17:16:32 gotoyuzo Exp $
11
11
 
12
12
  require 'io/wait'
13
- require 'webrick/server'
14
- require 'webrick/httputils'
15
- require 'webrick/httpstatus'
16
- require 'webrick/httprequest'
17
- require 'webrick/httpresponse'
18
- require 'webrick/httpservlet'
19
- require 'webrick/accesslog'
13
+ require_relative 'server'
14
+ require_relative 'httputils'
15
+ require_relative 'httpstatus'
16
+ require_relative 'httprequest'
17
+ require_relative 'httpresponse'
18
+ require_relative 'httpservlet'
19
+ require_relative 'accesslog'
20
20
 
21
21
  module WEBrick
22
22
  class HTTPServerError < ServerError; end
@@ -68,8 +68,8 @@ module WEBrick
68
68
 
69
69
  def run(sock)
70
70
  while true
71
- res = HTTPResponse.new(@config)
72
- req = HTTPRequest.new(@config)
71
+ req = create_request(@config)
72
+ res = create_response(@config)
73
73
  server = self
74
74
  begin
75
75
  timeout = @config[:RequestTimeout]
@@ -224,6 +224,20 @@ module WEBrick
224
224
  }
225
225
  end
226
226
 
227
+ ##
228
+ # Creates the HTTPRequest used when handling the HTTP
229
+ # request. Can be overridden by subclasses.
230
+ def create_request(with_webrick_config)
231
+ HTTPRequest.new(with_webrick_config)
232
+ end
233
+
234
+ ##
235
+ # Creates the HTTPResponse used when handling the HTTP
236
+ # request. Can be overridden by subclasses.
237
+ def create_response(with_webrick_config)
238
+ HTTPResponse.new(with_webrick_config)
239
+ end
240
+
227
241
  ##
228
242
  # Mount table for the path a servlet is mounted on in the directory space
229
243
  # of the server. Users of WEBrick can only access this indirectly via
@@ -267,12 +281,12 @@ module WEBrick
267
281
  k.sort!
268
282
  k.reverse!
269
283
  k.collect!{|path| Regexp.escape(path) }
270
- @scanner = Regexp.new("^(" + k.join("|") +")(?=/|$)")
284
+ @scanner = Regexp.new("\\A(" + k.join("|") +")(?=/|\\z)")
271
285
  end
272
286
 
273
287
  def normalize(dir)
274
288
  ret = dir ? dir.dup : ""
275
- ret.sub!(%r|/+$|, "")
289
+ ret.sub!(%r|/+\z|, "")
276
290
  ret
277
291
  end
278
292
  end