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
data/lib/webrick/https.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: false
1
2
  #
2
3
  # https.rb -- SSL/TLS enhancement for HTTPServer
3
4
  #
@@ -8,15 +9,36 @@
8
9
  #
9
10
  # $IPR: https.rb,v 1.15 2003/07/22 19:20:42 gotoyuzo Exp $
10
11
 
11
- require 'webrick/ssl'
12
+ require_relative 'ssl'
13
+ require_relative 'httpserver'
12
14
 
13
15
  module WEBrick
14
16
  module Config
15
17
  HTTP.update(SSL)
16
18
  end
17
19
 
20
+ ##
21
+ #--
22
+ # Adds SSL functionality to WEBrick::HTTPRequest
23
+
18
24
  class HTTPRequest
19
- attr_reader :cipher, :server_cert, :client_cert
25
+
26
+ ##
27
+ # HTTP request SSL cipher
28
+
29
+ attr_reader :cipher
30
+
31
+ ##
32
+ # HTTP request server certificate
33
+
34
+ attr_reader :server_cert
35
+
36
+ ##
37
+ # HTTP request client certificate
38
+
39
+ attr_reader :client_cert
40
+
41
+ # :stopdoc:
20
42
 
21
43
  alias orig_parse parse
22
44
 
@@ -60,5 +82,71 @@ module WEBrick
60
82
  end
61
83
  meta
62
84
  end
85
+
86
+ # :startdoc:
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:
63
151
  end
64
152
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: false
1
2
  #
2
3
  # httpserver.rb -- HTTPServer Class
3
4
  #
@@ -8,13 +9,14 @@
8
9
  #
9
10
  # $IPR: httpserver.rb,v 1.63 2002/10/01 17:16:32 gotoyuzo Exp $
10
11
 
11
- require 'webrick/server'
12
- require 'webrick/httputils'
13
- require 'webrick/httpstatus'
14
- require 'webrick/httprequest'
15
- require 'webrick/httpresponse'
16
- require 'webrick/httpservlet'
17
- require 'webrick/accesslog'
12
+ require 'io/wait'
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'
18
20
 
19
21
  module WEBrick
20
22
  class HTTPServerError < ServerError; end
@@ -66,17 +68,17 @@ module WEBrick
66
68
 
67
69
  def run(sock)
68
70
  while true
69
- res = HTTPResponse.new(@config)
70
- req = HTTPRequest.new(@config)
71
+ req = create_request(@config)
72
+ res = create_response(@config)
71
73
  server = self
72
74
  begin
73
75
  timeout = @config[:RequestTimeout]
74
76
  while timeout > 0
75
- break if IO.select([sock], nil, nil, 0.5)
76
- timeout = 0 if @status != :Running
77
+ break if sock.to_io.wait_readable(0.5)
78
+ break if @status != :Running
77
79
  timeout -= 0.5
78
80
  end
79
- raise HTTPStatus::EOFError if timeout <= 0
81
+ raise HTTPStatus::EOFError if timeout <= 0 || @status != :Running
80
82
  raise HTTPStatus::EOFError if sock.eof?
81
83
  req.parse(sock)
82
84
  res.request_method = req.request_method
@@ -138,6 +140,10 @@ module WEBrick
138
140
  si.service(req, res)
139
141
  end
140
142
 
143
+ ##
144
+ # The default OPTIONS request handler says GET, HEAD, POST and OPTIONS
145
+ # requests are allowed.
146
+
141
147
  def do_OPTIONS(req, res)
142
148
  res["allow"] = "GET,HEAD,POST,OPTIONS"
143
149
  end
@@ -207,6 +213,10 @@ module WEBrick
207
213
  }
208
214
  end
209
215
 
216
+ ##
217
+ # Logs +req+ and +res+ in the access logs. +config+ is used for the
218
+ # server name.
219
+
210
220
  def access_log(config, req, res)
211
221
  param = AccessLog::setup_params(config, req, res)
212
222
  @config[:AccessLog].each{|logger, fmt|
@@ -214,7 +224,27 @@ module WEBrick
214
224
  }
215
225
  end
216
226
 
217
- class MountTable
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
+
241
+ ##
242
+ # Mount table for the path a servlet is mounted on in the directory space
243
+ # of the server. Users of WEBrick can only access this indirectly via
244
+ # WEBrick::HTTPServer#mount, WEBrick::HTTPServer#unmount and
245
+ # WEBrick::HTTPServer#search_servlet
246
+
247
+ class MountTable # :nodoc:
218
248
  def initialize
219
249
  @tab = Hash.new
220
250
  compile
@@ -251,12 +281,12 @@ module WEBrick
251
281
  k.sort!
252
282
  k.reverse!
253
283
  k.collect!{|path| Regexp.escape(path) }
254
- @scanner = Regexp.new("^(" + k.join("|") +")(?=/|$)")
284
+ @scanner = Regexp.new("\\A(" + k.join("|") +")(?=/|\\z)")
255
285
  end
256
286
 
257
287
  def normalize(dir)
258
288
  ret = dir ? dir.dup : ""
259
- ret.sub!(%r|/+$|, "")
289
+ ret.sub!(%r|/+\z|, "")
260
290
  ret
261
291
  end
262
292
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: false
1
2
  #
2
3
  # httpservlet.rb -- HTTPServlet Utility File
3
4
  #
@@ -8,11 +9,11 @@
8
9
  #
9
10
  # $IPR: httpservlet.rb,v 1.21 2003/02/23 12:24:46 gotoyuzo Exp $
10
11
 
11
- require 'webrick/httpservlet/abstract'
12
- require 'webrick/httpservlet/filehandler'
13
- require 'webrick/httpservlet/cgihandler'
14
- require 'webrick/httpservlet/erbhandler'
15
- require 'webrick/httpservlet/prochandler'
12
+ require_relative 'httpservlet/abstract'
13
+ require_relative 'httpservlet/filehandler'
14
+ require_relative 'httpservlet/cgihandler'
15
+ require_relative 'httpservlet/erbhandler'
16
+ require_relative 'httpservlet/prochandler'
16
17
 
17
18
  module WEBrick
18
19
  module HTTPServlet
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: false
1
2
  #
2
3
  # httpservlet.rb -- HTTPServlet Module
3
4
  #
@@ -8,11 +9,9 @@
8
9
  #
9
10
  # $IPR: abstract.rb,v 1.24 2003/07/11 11:16:46 gotoyuzo Exp $
10
11
 
11
- require 'thread'
12
-
13
- require 'webrick/htmlutils'
14
- require 'webrick/httputils'
15
- require 'webrick/httpstatus'
12
+ require_relative '../htmlutils'
13
+ require_relative '../httputils'
14
+ require_relative '../httpstatus'
16
15
 
17
16
  module WEBrick
18
17
  module HTTPServlet
@@ -54,7 +53,7 @@ module WEBrick
54
53
  # Servlets can be configured via initialize. The first argument is the
55
54
  # HTTP server the servlet is being initialized for.
56
55
  #
57
- # class Configureable < Simple
56
+ # class Configurable < Simple
58
57
  # def initialize server, color, size
59
58
  # super server
60
59
  # @color = color
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: false
1
2
  #
2
3
  # cgi_runner.rb -- CGI launcher.
3
4
  #
@@ -22,11 +23,11 @@ STDIN.binmode
22
23
 
23
24
  len = sysread(STDIN, 8).to_i
24
25
  out = sysread(STDIN, len)
25
- STDOUT.reopen(open(out, "w"))
26
+ STDOUT.reopen(File.open(out, "w"))
26
27
 
27
28
  len = sysread(STDIN, 8).to_i
28
29
  err = sysread(STDIN, len)
29
- STDERR.reopen(open(err, "w"))
30
+ STDERR.reopen(File.open(err, "w"))
30
31
 
31
32
  len = sysread(STDIN, 8).to_i
32
33
  dump = sysread(STDIN, len)
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: false
1
2
  #
2
3
  # cgihandler.rb -- CGIHandler Class
3
4
  #
@@ -10,27 +11,43 @@
10
11
 
11
12
  require 'rbconfig'
12
13
  require 'tempfile'
13
- require 'webrick/config'
14
- require 'webrick/httpservlet/abstract'
14
+ require_relative '../config'
15
+ require_relative 'abstract'
15
16
 
16
17
  module WEBrick
17
18
  module HTTPServlet
18
19
 
20
+ ##
21
+ # Servlet for handling CGI scripts
22
+ #
23
+ # Example:
24
+ #
25
+ # server.mount('/cgi/my_script', WEBrick::HTTPServlet::CGIHandler,
26
+ # '/path/to/my_script')
27
+
19
28
  class CGIHandler < AbstractServlet
20
- Ruby = RbConfig.ruby
21
- CGIRunner = "\"#{Ruby}\" \"#{WEBrick::Config::LIBDIR}/httpservlet/cgi_runner.rb\""
29
+ Ruby = RbConfig.ruby # :nodoc:
30
+ CGIRunner = "\"#{Ruby}\" \"#{WEBrick::Config::LIBDIR}/httpservlet/cgi_runner.rb\"" # :nodoc:
31
+ CGIRunnerArray = [Ruby, "#{WEBrick::Config::LIBDIR}/httpservlet/cgi_runner.rb".freeze].freeze # :nodoc:
32
+
33
+ ##
34
+ # Creates a new CGI script servlet for the script at +name+
22
35
 
23
36
  def initialize(server, name)
24
37
  super(server, name)
25
38
  @script_filename = name
26
39
  @tempdir = server[:TempDir]
27
- @cgicmd = "#{CGIRunner} #{server[:CGIInterpreter]}"
40
+ interpreter = server[:CGIInterpreter]
41
+ if interpreter.is_a?(Array)
42
+ @cgicmd = CGIRunnerArray + interpreter
43
+ else
44
+ @cgicmd = "#{CGIRunner} #{interpreter}"
45
+ end
28
46
  end
29
47
 
30
- def do_GET(req, res)
31
- data = nil
32
- status = -1
48
+ # :stopdoc:
33
49
 
50
+ def do_GET(req, res)
34
51
  cgi_in = IO::popen(@cgicmd, "wb")
35
52
  cgi_out = Tempfile.new("webrick.cgiout.", @tempdir, mode: IO::BINARY)
36
53
  cgi_out.set_encoding("ASCII-8BIT")
@@ -41,6 +58,7 @@ module WEBrick
41
58
  meta = req.meta_vars
42
59
  meta["SCRIPT_FILENAME"] = @script_filename
43
60
  meta["PATH"] = @config[:CGIPathEnv]
61
+ meta.delete("HTTP_PROXY")
44
62
  if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
45
63
  meta["SystemRoot"] = ENV["SystemRoot"]
46
64
  end
@@ -53,9 +71,7 @@ module WEBrick
53
71
  cgi_in.write("%8d" % dump.bytesize)
54
72
  cgi_in.write(dump)
55
73
 
56
- if req.body and req.body.bytesize > 0
57
- cgi_in.write(req.body)
58
- end
74
+ req.body { |chunk| cgi_in.write(chunk) }
59
75
  ensure
60
76
  cgi_in.close
61
77
  status = $?.exitstatus
@@ -102,6 +118,8 @@ module WEBrick
102
118
  res.body = body
103
119
  end
104
120
  alias do_POST do_GET
121
+
122
+ # :startdoc:
105
123
  end
106
124
 
107
125
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: false
1
2
  #
2
3
  # erbhandler.rb -- ERBHandler Class
3
4
  #
@@ -8,7 +9,7 @@
8
9
  #
9
10
  # $IPR: erbhandler.rb,v 1.25 2003/02/24 19:25:31 gotoyuzo Exp $
10
11
 
11
- require 'webrick/httpservlet/abstract.rb'
12
+ require_relative 'abstract'
12
13
 
13
14
  require 'erb'
14
15
 
@@ -52,11 +53,11 @@ module WEBrick
52
53
  raise HTTPStatus::Forbidden, "ERBHandler cannot work."
53
54
  end
54
55
  begin
55
- data = open(@script_filename){|io| io.read }
56
+ data = File.open(@script_filename, &:read)
56
57
  res.body = evaluate(ERB.new(data), req, res)
57
58
  res['content-type'] ||=
58
59
  HTTPUtils::mime_type(@script_filename, @config[:MimeTypes])
59
- rescue StandardError => ex
60
+ rescue StandardError
60
61
  raise
61
62
  rescue Exception => ex
62
63
  @logger.error(ex)
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: false
1
2
  #
2
3
  # filehandler.rb -- FileHandler Module
3
4
  #
@@ -8,22 +9,38 @@
8
9
  #
9
10
  # $IPR: filehandler.rb,v 1.44 2003/06/07 01:34:51 gotoyuzo Exp $
10
11
 
11
- require 'thread'
12
12
  require 'time'
13
13
 
14
- require 'webrick/htmlutils'
15
- require 'webrick/httputils'
16
- require 'webrick/httpstatus'
14
+ require_relative '../htmlutils'
15
+ require_relative '../httputils'
16
+ require_relative '../httpstatus'
17
17
 
18
18
  module WEBrick
19
19
  module HTTPServlet
20
20
 
21
+ ##
22
+ # Servlet for serving a single file. You probably want to use the
23
+ # FileHandler servlet instead as it handles directories and fancy indexes.
24
+ #
25
+ # Example:
26
+ #
27
+ # server.mount('/my_page.txt', WEBrick::HTTPServlet::DefaultFileHandler,
28
+ # '/path/to/my_page.txt')
29
+ #
30
+ # This servlet handles If-Modified-Since and Range requests.
31
+
21
32
  class DefaultFileHandler < AbstractServlet
33
+
34
+ ##
35
+ # Creates a DefaultFileHandler instance for the file at +local_path+.
36
+
22
37
  def initialize(server, local_path)
23
38
  super(server, local_path)
24
39
  @local_path = local_path
25
40
  end
26
41
 
42
+ # :stopdoc:
43
+
27
44
  def do_GET(req, res)
28
45
  st = File::stat(@local_path)
29
46
  mtime = st.mtime
@@ -38,9 +55,9 @@ module WEBrick
38
55
  else
39
56
  mtype = HTTPUtils::mime_type(@local_path, @config[:MimeTypes])
40
57
  res['content-type'] = mtype
41
- res['content-length'] = st.size
58
+ res['content-length'] = st.size.to_s
42
59
  res['last-modified'] = mtime.httpdate
43
- res.body = open(@local_path, "rb")
60
+ res.body = File.open(@local_path, "rb")
44
61
  end
45
62
  end
46
63
 
@@ -69,47 +86,66 @@ module WEBrick
69
86
  return false
70
87
  end
71
88
 
89
+ # returns a lambda for webrick/httpresponse.rb send_body_proc
90
+ def multipart_body(body, parts, boundary, mtype, filesize)
91
+ lambda do |socket|
92
+ begin
93
+ begin
94
+ first = parts.shift
95
+ last = parts.shift
96
+ socket.write(
97
+ "--#{boundary}#{CRLF}" \
98
+ "Content-Type: #{mtype}#{CRLF}" \
99
+ "Content-Range: bytes #{first}-#{last}/#{filesize}#{CRLF}" \
100
+ "#{CRLF}"
101
+ )
102
+
103
+ begin
104
+ IO.copy_stream(body, socket, last - first + 1, first)
105
+ rescue NotImplementedError
106
+ body.seek(first, IO::SEEK_SET)
107
+ IO.copy_stream(body, socket, last - first + 1)
108
+ end
109
+ socket.write(CRLF)
110
+ end while parts[0]
111
+ socket.write("--#{boundary}--#{CRLF}")
112
+ ensure
113
+ body.close
114
+ end
115
+ end
116
+ end
117
+
72
118
  def make_partial_content(req, res, filename, filesize)
73
119
  mtype = HTTPUtils::mime_type(filename, @config[:MimeTypes])
74
120
  unless ranges = HTTPUtils::parse_range_header(req['range'])
75
121
  raise HTTPStatus::BadRequest,
76
122
  "Unrecognized range-spec: \"#{req['range']}\""
77
123
  end
78
- open(filename, "rb"){|io|
124
+ File.open(filename, "rb"){|io|
79
125
  if ranges.size > 1
80
126
  time = Time.now
81
127
  boundary = "#{time.sec}_#{time.usec}_#{Process::pid}"
82
- body = ''
83
- ranges.each{|range|
84
- first, last = prepare_range(range, filesize)
85
- next if first < 0
86
- io.pos = first
87
- content = io.read(last-first+1)
88
- body << "--" << boundary << CRLF
89
- body << "Content-Type: #{mtype}" << CRLF
90
- body << "Content-Range: bytes #{first}-#{last}/#{filesize}" << CRLF
91
- body << CRLF
92
- body << content
93
- body << CRLF
128
+ parts = []
129
+ ranges.each {|range|
130
+ prange = prepare_range(range, filesize)
131
+ next if prange[0] < 0
132
+ parts.concat(prange)
94
133
  }
95
- raise HTTPStatus::RequestRangeNotSatisfiable if body.empty?
96
- body << "--" << boundary << "--" << CRLF
134
+ raise HTTPStatus::RequestRangeNotSatisfiable if parts.empty?
97
135
  res["content-type"] = "multipart/byteranges; boundary=#{boundary}"
98
- res.body = body
136
+ if req.http_version < '1.1'
137
+ res['connection'] = 'close'
138
+ else
139
+ res.chunked = true
140
+ end
141
+ res.body = multipart_body(io.dup, parts, boundary, mtype, filesize)
99
142
  elsif range = ranges[0]
100
143
  first, last = prepare_range(range, filesize)
101
144
  raise HTTPStatus::RequestRangeNotSatisfiable if first < 0
102
- if last == filesize - 1
103
- content = io.dup
104
- content.pos = first
105
- else
106
- io.pos = first
107
- content = io.read(last-first+1)
108
- end
109
145
  res['content-type'] = mtype
110
146
  res['content-range'] = "bytes #{first}-#{last}/#{filesize}"
111
- res['content-length'] = last - first + 1
112
- res.body = content
147
+ res['content-length'] = (last - first + 1).to_s
148
+ res.body = io.dup
113
149
  else
114
150
  raise HTTPStatus::BadRequest
115
151
  end
@@ -123,13 +159,21 @@ module WEBrick
123
159
  last = filesize - 1 if last >= filesize
124
160
  return first, last
125
161
  end
162
+
163
+ # :startdoc:
126
164
  end
127
165
 
128
166
  ##
129
- # Serves files from a directory
167
+ # Serves a directory including fancy indexing and a variety of other
168
+ # options.
169
+ #
170
+ # Example:
171
+ #
172
+ # server.mount('/assets', WEBrick::HTTPServlet::FileHandler,
173
+ # '/path/to/assets')
130
174
 
131
175
  class FileHandler < AbstractServlet
132
- HandlerTable = Hash.new
176
+ HandlerTable = Hash.new # :nodoc:
133
177
 
134
178
  ##
135
179
  # Allow custom handling of requests for files with +suffix+ by class
@@ -150,19 +194,8 @@ module WEBrick
150
194
  # Creates a FileHandler servlet on +server+ that serves files starting
151
195
  # at directory +root+
152
196
  #
153
- # If +options+ is a Hash the following keys are allowed:
154
- #
155
- # :AcceptableLanguages:: Array of languages allowed for accept-language
156
- # :DirectoryCallback:: Allows preprocessing of directory requests
157
- # :FancyIndexing:: If true, show an index for directories
158
- # :FileCallback:: Allows preprocessing of file requests
159
- # :HandlerCallback:: Allows preprocessing of requests
160
- # :HandlerTable:: Maps file suffixes to file handlers.
161
- # DefaultFileHandler is used by default but any servlet
162
- # can be used.
163
- # :NondisclosureName:: Do not show files matching this array of globs
164
- # :UserDir:: Directory inside ~user to serve content from for /~user
165
- # requests. Only works if mounted on /
197
+ # +options+ may be a Hash containing keys from
198
+ # WEBrick::Config::FileHandler or +true+ or +false+.
166
199
  #
167
200
  # If +options+ is true or false then +:FancyIndexing+ is enabled or
168
201
  # disabled respectively.
@@ -177,6 +210,8 @@ module WEBrick
177
210
  @options = default.dup.update(options)
178
211
  end
179
212
 
213
+ # :stopdoc:
214
+
180
215
  def service(req, res)
181
216
  # if this class is mounted on "/" and /~username is requested.
182
217
  # we're going to override path informations before invoking service.
@@ -409,11 +444,18 @@ module WEBrick
409
444
  }
410
445
  list.compact!
411
446
 
412
- if d0 = req.query["N"]; idx = 0
413
- elsif d0 = req.query["M"]; idx = 1
414
- elsif d0 = req.query["S"]; idx = 2
415
- else d0 = "A" ; idx = 0
447
+ query = req.query
448
+
449
+ d0 = nil
450
+ idx = nil
451
+ %w[N M S].each_with_index do |q, i|
452
+ if d = query.delete(q)
453
+ idx ||= i
454
+ d0 ||= d
455
+ end
416
456
  end
457
+ d0 ||= "A"
458
+ idx ||= 0
417
459
  d1 = (d0 == "A") ? "D" : "A"
418
460
 
419
461
  if d0 == "A"
@@ -422,38 +464,66 @@ module WEBrick
422
464
  list.sort!{|a,b| b[idx] <=> a[idx] }
423
465
  end
424
466
 
425
- res['content-type'] = "text/html"
467
+ namewidth = query["NameWidth"]
468
+ if namewidth == "*"
469
+ namewidth = nil
470
+ elsif !namewidth or (namewidth = namewidth.to_i) < 2
471
+ namewidth = 25
472
+ end
473
+ query = query.inject('') {|s, (k, v)| s << '&' << HTMLUtils::escape("#{k}=#{v}")}
474
+
475
+ type = "text/html"
476
+ case enc = Encoding.find('filesystem')
477
+ when Encoding::US_ASCII, Encoding::ASCII_8BIT
478
+ else
479
+ type << "; charset=\"#{enc.name}\""
480
+ end
481
+ res['content-type'] = type
426
482
 
483
+ title = "Index of #{HTMLUtils::escape(req.path)}"
427
484
  res.body = <<-_end_of_html_
428
485
  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
429
486
  <HTML>
430
- <HEAD><TITLE>Index of #{HTMLUtils::escape(req.path)}</TITLE></HEAD>
487
+ <HEAD>
488
+ <TITLE>#{title}</TITLE>
489
+ <style type="text/css">
490
+ <!--
491
+ .name, .mtime { text-align: left; }
492
+ .size { text-align: right; }
493
+ td { text-overflow: ellipsis; white-space: nowrap; overflow: hidden; }
494
+ table { border-collapse: collapse; }
495
+ tr th { border-bottom: 2px groove; }
496
+ //-->
497
+ </style>
498
+ </HEAD>
431
499
  <BODY>
432
- <H1>Index of #{HTMLUtils::escape(req.path)}</H1>
500
+ <H1>#{title}</H1>
433
501
  _end_of_html_
434
502
 
435
- res.body << "<PRE>\n"
436
- res.body << " <A HREF=\"?N=#{d1}\">Name</A> "
437
- res.body << "<A HREF=\"?M=#{d1}\">Last modified</A> "
438
- res.body << "<A HREF=\"?S=#{d1}\">Size</A>\n"
439
- res.body << "<HR>\n"
503
+ res.body << "<TABLE width=\"100%\"><THEAD><TR>\n"
504
+ res.body << "<TH class=\"name\"><A HREF=\"?N=#{d1}#{query}\">Name</A></TH>"
505
+ res.body << "<TH class=\"mtime\"><A HREF=\"?M=#{d1}#{query}\">Last modified</A></TH>"
506
+ res.body << "<TH class=\"size\"><A HREF=\"?S=#{d1}#{query}\">Size</A></TH>\n"
507
+ res.body << "</TR></THEAD>\n"
508
+ res.body << "<TBODY>\n"
440
509
 
510
+ query.sub!(/\A&/, '?')
441
511
  list.unshift [ "..", File::mtime(local_path+"/.."), -1 ]
442
512
  list.each{ |name, time, size|
443
513
  if name == ".."
444
514
  dname = "Parent Directory"
445
- elsif name.bytesize > 25
446
- dname = name.sub(/^(.{23})(?:.*)/, '\1..')
515
+ elsif namewidth and name.size > namewidth
516
+ dname = name[0...(namewidth - 2)] << '..'
447
517
  else
448
518
  dname = name
449
519
  end
450
- s = " <A HREF=\"#{HTTPUtils::escape(name)}\">#{HTMLUtils::escape(dname)}</A>"
451
- s << " " * (30 - dname.bytesize)
452
- s << (time ? time.strftime("%Y/%m/%d %H:%M ") : " " * 22)
453
- s << (size >= 0 ? size.to_s : "-") << "\n"
520
+ s = "<TR><TD class=\"name\"><A HREF=\"#{HTTPUtils::escape(name)}#{query if name.end_with?('/')}\">#{HTMLUtils::escape(dname)}</A></TD>"
521
+ s << "<TD class=\"mtime\">" << (time ? time.strftime("%Y/%m/%d %H:%M") : "") << "</TD>"
522
+ s << "<TD class=\"size\">" << (size >= 0 ? size.to_s : "-") << "</TD></TR>\n"
454
523
  res.body << s
455
524
  }
456
- res.body << "</PRE><HR>"
525
+ res.body << "</TBODY></TABLE>"
526
+ res.body << "<HR>"
457
527
 
458
528
  res.body << <<-_end_of_html_
459
529
  <ADDRESS>
@@ -465,6 +535,7 @@ module WEBrick
465
535
  _end_of_html_
466
536
  end
467
537
 
538
+ # :startdoc:
468
539
  end
469
540
  end
470
541
  end