webrick 1.3.1

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.

Files changed (63) hide show
  1. data/README.txt +21 -0
  2. data/lib/webrick.rb +227 -0
  3. data/lib/webrick/accesslog.rb +151 -0
  4. data/lib/webrick/cgi.rb +260 -0
  5. data/lib/webrick/compat.rb +35 -0
  6. data/lib/webrick/config.rb +121 -0
  7. data/lib/webrick/cookie.rb +110 -0
  8. data/lib/webrick/htmlutils.rb +28 -0
  9. data/lib/webrick/httpauth.rb +95 -0
  10. data/lib/webrick/httpauth/authenticator.rb +112 -0
  11. data/lib/webrick/httpauth/basicauth.rb +108 -0
  12. data/lib/webrick/httpauth/digestauth.rb +392 -0
  13. data/lib/webrick/httpauth/htdigest.rb +128 -0
  14. data/lib/webrick/httpauth/htgroup.rb +93 -0
  15. data/lib/webrick/httpauth/htpasswd.rb +121 -0
  16. data/lib/webrick/httpauth/userdb.rb +52 -0
  17. data/lib/webrick/httpproxy.rb +305 -0
  18. data/lib/webrick/httprequest.rb +461 -0
  19. data/lib/webrick/httpresponse.rb +399 -0
  20. data/lib/webrick/https.rb +64 -0
  21. data/lib/webrick/httpserver.rb +264 -0
  22. data/lib/webrick/httpservlet.rb +22 -0
  23. data/lib/webrick/httpservlet/abstract.rb +153 -0
  24. data/lib/webrick/httpservlet/cgi_runner.rb +46 -0
  25. data/lib/webrick/httpservlet/cgihandler.rb +108 -0
  26. data/lib/webrick/httpservlet/erbhandler.rb +87 -0
  27. data/lib/webrick/httpservlet/filehandler.rb +470 -0
  28. data/lib/webrick/httpservlet/prochandler.rb +33 -0
  29. data/lib/webrick/httpstatus.rb +184 -0
  30. data/lib/webrick/httputils.rb +394 -0
  31. data/lib/webrick/httpversion.rb +49 -0
  32. data/lib/webrick/log.rb +136 -0
  33. data/lib/webrick/server.rb +218 -0
  34. data/lib/webrick/ssl.rb +127 -0
  35. data/lib/webrick/utils.rb +241 -0
  36. data/lib/webrick/version.rb +13 -0
  37. data/sample/webrick/demo-app.rb +66 -0
  38. data/sample/webrick/demo-multipart.cgi +12 -0
  39. data/sample/webrick/demo-servlet.rb +6 -0
  40. data/sample/webrick/demo-urlencoded.cgi +12 -0
  41. data/sample/webrick/hello.cgi +11 -0
  42. data/sample/webrick/hello.rb +8 -0
  43. data/sample/webrick/httpd.rb +23 -0
  44. data/sample/webrick/httpproxy.rb +25 -0
  45. data/sample/webrick/httpsd.rb +33 -0
  46. data/test/openssl/utils.rb +313 -0
  47. data/test/ruby/envutil.rb +208 -0
  48. data/test/webrick/test_cgi.rb +134 -0
  49. data/test/webrick/test_cookie.rb +131 -0
  50. data/test/webrick/test_filehandler.rb +285 -0
  51. data/test/webrick/test_httpauth.rb +167 -0
  52. data/test/webrick/test_httpproxy.rb +282 -0
  53. data/test/webrick/test_httprequest.rb +411 -0
  54. data/test/webrick/test_httpresponse.rb +49 -0
  55. data/test/webrick/test_httpserver.rb +305 -0
  56. data/test/webrick/test_httputils.rb +96 -0
  57. data/test/webrick/test_httpversion.rb +40 -0
  58. data/test/webrick/test_server.rb +67 -0
  59. data/test/webrick/test_utils.rb +64 -0
  60. data/test/webrick/utils.rb +58 -0
  61. data/test/webrick/webrick.cgi +36 -0
  62. data/test/webrick/webrick_long_filename.cgi +36 -0
  63. metadata +106 -0
@@ -0,0 +1,108 @@
1
+ #
2
+ # cgihandler.rb -- CGIHandler Class
3
+ #
4
+ # Author: IPR -- Internet Programming with Ruby -- writers
5
+ # Copyright (c) 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
6
+ # Copyright (c) 2002 Internet Programming with Ruby writers. All rights
7
+ # reserved.
8
+ #
9
+ # $IPR: cgihandler.rb,v 1.27 2003/03/21 19:56:01 gotoyuzo Exp $
10
+
11
+ require 'rbconfig'
12
+ require 'tempfile'
13
+ require 'webrick/config'
14
+ require 'webrick/httpservlet/abstract'
15
+
16
+ module WEBrick
17
+ module HTTPServlet
18
+
19
+ class CGIHandler < AbstractServlet
20
+ Ruby = RbConfig.ruby
21
+ CGIRunner = "\"#{Ruby}\" \"#{WEBrick::Config::LIBDIR}/httpservlet/cgi_runner.rb\""
22
+
23
+ def initialize(server, name)
24
+ super(server, name)
25
+ @script_filename = name
26
+ @tempdir = server[:TempDir]
27
+ @cgicmd = "#{CGIRunner} #{server[:CGIInterpreter]}"
28
+ end
29
+
30
+ def do_GET(req, res)
31
+ data = nil
32
+ status = -1
33
+
34
+ cgi_in = IO::popen(@cgicmd, "wb")
35
+ cgi_out = Tempfile.new("webrick.cgiout.", @tempdir, mode: IO::BINARY)
36
+ cgi_out.set_encoding("ASCII-8BIT")
37
+ cgi_err = Tempfile.new("webrick.cgierr.", @tempdir, mode: IO::BINARY)
38
+ cgi_err.set_encoding("ASCII-8BIT")
39
+ begin
40
+ cgi_in.sync = true
41
+ meta = req.meta_vars
42
+ meta["SCRIPT_FILENAME"] = @script_filename
43
+ meta["PATH"] = @config[:CGIPathEnv]
44
+ if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
45
+ meta["SystemRoot"] = ENV["SystemRoot"]
46
+ end
47
+ dump = Marshal.dump(meta)
48
+
49
+ cgi_in.write("%8d" % cgi_out.path.bytesize)
50
+ cgi_in.write(cgi_out.path)
51
+ cgi_in.write("%8d" % cgi_err.path.bytesize)
52
+ cgi_in.write(cgi_err.path)
53
+ cgi_in.write("%8d" % dump.bytesize)
54
+ cgi_in.write(dump)
55
+
56
+ if req.body and req.body.bytesize > 0
57
+ cgi_in.write(req.body)
58
+ end
59
+ ensure
60
+ cgi_in.close
61
+ status = $?.exitstatus
62
+ sleep 0.1 if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
63
+ data = cgi_out.read
64
+ cgi_out.close(true)
65
+ if errmsg = cgi_err.read
66
+ if errmsg.bytesize > 0
67
+ @logger.error("CGIHandler: #{@script_filename}:\n" + errmsg)
68
+ end
69
+ end
70
+ cgi_err.close(true)
71
+ end
72
+
73
+ if status != 0
74
+ @logger.error("CGIHandler: #{@script_filename} exit with #{status}")
75
+ end
76
+
77
+ data = "" unless data
78
+ raw_header, body = data.split(/^[\xd\xa]+/, 2)
79
+ raise HTTPStatus::InternalServerError,
80
+ "Premature end of script headers: #{@script_filename}" if body.nil?
81
+
82
+ begin
83
+ header = HTTPUtils::parse_header(raw_header)
84
+ if /^(\d+)/ =~ header['status'][0]
85
+ res.status = $1.to_i
86
+ header.delete('status')
87
+ end
88
+ if header.has_key?('location')
89
+ # RFC 3875 6.2.3, 6.2.4
90
+ res.status = 302 unless (300...400) === res.status
91
+ end
92
+ if header.has_key?('set-cookie')
93
+ header['set-cookie'].each{|k|
94
+ res.cookies << Cookie.parse_set_cookie(k)
95
+ }
96
+ header.delete('set-cookie')
97
+ end
98
+ header.each{|key, val| res[key] = val.join(", ") }
99
+ rescue => ex
100
+ raise HTTPStatus::InternalServerError, ex.message
101
+ end
102
+ res.body = body
103
+ end
104
+ alias do_POST do_GET
105
+ end
106
+
107
+ end
108
+ end
@@ -0,0 +1,87 @@
1
+ #
2
+ # erbhandler.rb -- ERBHandler Class
3
+ #
4
+ # Author: IPR -- Internet Programming with Ruby -- writers
5
+ # Copyright (c) 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
6
+ # Copyright (c) 2002 Internet Programming with Ruby writers. All rights
7
+ # reserved.
8
+ #
9
+ # $IPR: erbhandler.rb,v 1.25 2003/02/24 19:25:31 gotoyuzo Exp $
10
+
11
+ require 'webrick/httpservlet/abstract.rb'
12
+
13
+ require 'erb'
14
+
15
+ module WEBrick
16
+ module HTTPServlet
17
+
18
+ ##
19
+ # ERBHandler evaluates an ERB file and returns the result. This handler
20
+ # is automatically used if there are .rhtml files in a directory served by
21
+ # the FileHandler.
22
+ #
23
+ # ERBHandler supports GET and POST methods.
24
+ #
25
+ # The ERB file is evaluated with the local variables +servlet_request+ and
26
+ # +servlet_response+ which are a WEBrick::HTTPRequest and
27
+ # WEBrick::HTTPResponse respectively.
28
+ #
29
+ # Example .rhtml file:
30
+ #
31
+ # Request to <%= servlet_request.request_uri %>
32
+ #
33
+ # Query params <%= servlet_request.query.inspect %>
34
+
35
+ class ERBHandler < AbstractServlet
36
+
37
+ ##
38
+ # Creates a new ERBHandler on +server+ that will evaluate and serve the
39
+ # ERB file +name+
40
+
41
+ def initialize(server, name)
42
+ super(server, name)
43
+ @script_filename = name
44
+ end
45
+
46
+ ##
47
+ # Handles GET requests
48
+
49
+ def do_GET(req, res)
50
+ unless defined?(ERB)
51
+ @logger.warn "#{self.class}: ERB not defined."
52
+ raise HTTPStatus::Forbidden, "ERBHandler cannot work."
53
+ end
54
+ begin
55
+ data = open(@script_filename){|io| io.read }
56
+ res.body = evaluate(ERB.new(data), req, res)
57
+ res['content-type'] ||=
58
+ HTTPUtils::mime_type(@script_filename, @config[:MimeTypes])
59
+ rescue StandardError => ex
60
+ raise
61
+ rescue Exception => ex
62
+ @logger.error(ex)
63
+ raise HTTPStatus::InternalServerError, ex.message
64
+ end
65
+ end
66
+
67
+ ##
68
+ # Handles POST requests
69
+
70
+ alias do_POST do_GET
71
+
72
+ private
73
+
74
+ ##
75
+ # Evaluates +erb+ providing +servlet_request+ and +servlet_response+ as
76
+ # local variables.
77
+
78
+ def evaluate(erb, servlet_request, servlet_response)
79
+ Module.new.module_eval{
80
+ servlet_request.meta_vars
81
+ servlet_request.query
82
+ erb.result(binding)
83
+ }
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,470 @@
1
+ #
2
+ # filehandler.rb -- FileHandler Module
3
+ #
4
+ # Author: IPR -- Internet Programming with Ruby -- writers
5
+ # Copyright (c) 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
6
+ # Copyright (c) 2003 Internet Programming with Ruby writers. All rights
7
+ # reserved.
8
+ #
9
+ # $IPR: filehandler.rb,v 1.44 2003/06/07 01:34:51 gotoyuzo Exp $
10
+
11
+ require 'thread'
12
+ require 'time'
13
+
14
+ require 'webrick/htmlutils'
15
+ require 'webrick/httputils'
16
+ require 'webrick/httpstatus'
17
+
18
+ module WEBrick
19
+ module HTTPServlet
20
+
21
+ class DefaultFileHandler < AbstractServlet
22
+ def initialize(server, local_path)
23
+ super(server, local_path)
24
+ @local_path = local_path
25
+ end
26
+
27
+ def do_GET(req, res)
28
+ st = File::stat(@local_path)
29
+ mtime = st.mtime
30
+ res['etag'] = sprintf("%x-%x-%x", st.ino, st.size, st.mtime.to_i)
31
+
32
+ if not_modified?(req, res, mtime, res['etag'])
33
+ res.body = ''
34
+ raise HTTPStatus::NotModified
35
+ elsif req['range']
36
+ make_partial_content(req, res, @local_path, st.size)
37
+ raise HTTPStatus::PartialContent
38
+ else
39
+ mtype = HTTPUtils::mime_type(@local_path, @config[:MimeTypes])
40
+ res['content-type'] = mtype
41
+ res['content-length'] = st.size
42
+ res['last-modified'] = mtime.httpdate
43
+ res.body = open(@local_path, "rb")
44
+ end
45
+ end
46
+
47
+ def not_modified?(req, res, mtime, etag)
48
+ if ir = req['if-range']
49
+ begin
50
+ if Time.httpdate(ir) >= mtime
51
+ return true
52
+ end
53
+ rescue
54
+ if HTTPUtils::split_header_value(ir).member?(res['etag'])
55
+ return true
56
+ end
57
+ end
58
+ end
59
+
60
+ if (ims = req['if-modified-since']) && Time.parse(ims) >= mtime
61
+ return true
62
+ end
63
+
64
+ if (inm = req['if-none-match']) &&
65
+ HTTPUtils::split_header_value(inm).member?(res['etag'])
66
+ return true
67
+ end
68
+
69
+ return false
70
+ end
71
+
72
+ def make_partial_content(req, res, filename, filesize)
73
+ mtype = HTTPUtils::mime_type(filename, @config[:MimeTypes])
74
+ unless ranges = HTTPUtils::parse_range_header(req['range'])
75
+ raise HTTPStatus::BadRequest,
76
+ "Unrecognized range-spec: \"#{req['range']}\""
77
+ end
78
+ open(filename, "rb"){|io|
79
+ if ranges.size > 1
80
+ time = Time.now
81
+ 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
94
+ }
95
+ raise HTTPStatus::RequestRangeNotSatisfiable if body.empty?
96
+ body << "--" << boundary << "--" << CRLF
97
+ res["content-type"] = "multipart/byteranges; boundary=#{boundary}"
98
+ res.body = body
99
+ elsif range = ranges[0]
100
+ first, last = prepare_range(range, filesize)
101
+ 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
+ res['content-type'] = mtype
110
+ res['content-range'] = "bytes #{first}-#{last}/#{filesize}"
111
+ res['content-length'] = last - first + 1
112
+ res.body = content
113
+ else
114
+ raise HTTPStatus::BadRequest
115
+ end
116
+ }
117
+ end
118
+
119
+ def prepare_range(range, filesize)
120
+ first = range.first < 0 ? filesize + range.first : range.first
121
+ return -1, -1 if first < 0 || first >= filesize
122
+ last = range.last < 0 ? filesize + range.last : range.last
123
+ last = filesize - 1 if last >= filesize
124
+ return first, last
125
+ end
126
+ end
127
+
128
+ ##
129
+ # Serves files from a directory
130
+
131
+ class FileHandler < AbstractServlet
132
+ HandlerTable = Hash.new
133
+
134
+ ##
135
+ # Allow custom handling of requests for files with +suffix+ by class
136
+ # +handler+
137
+
138
+ def self.add_handler(suffix, handler)
139
+ HandlerTable[suffix] = handler
140
+ end
141
+
142
+ ##
143
+ # Remove custom handling of requests for files with +suffix+
144
+
145
+ def self.remove_handler(suffix)
146
+ HandlerTable.delete(suffix)
147
+ end
148
+
149
+ ##
150
+ # Creates a FileHandler servlet on +server+ that serves files starting
151
+ # at directory +root+
152
+ #
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 /
166
+ #
167
+ # If +options+ is true or false then +:FancyIndexing+ is enabled or
168
+ # disabled respectively.
169
+
170
+ def initialize(server, root, options={}, default=Config::FileHandler)
171
+ @config = server.config
172
+ @logger = @config[:Logger]
173
+ @root = File.expand_path(root)
174
+ if options == true || options == false
175
+ options = { :FancyIndexing => options }
176
+ end
177
+ @options = default.dup.update(options)
178
+ end
179
+
180
+ def service(req, res)
181
+ # if this class is mounted on "/" and /~username is requested.
182
+ # we're going to override path informations before invoking service.
183
+ if defined?(Etc) && @options[:UserDir] && req.script_name.empty?
184
+ if %r|^(/~([^/]+))| =~ req.path_info
185
+ script_name, user = $1, $2
186
+ path_info = $'
187
+ begin
188
+ passwd = Etc::getpwnam(user)
189
+ @root = File::join(passwd.dir, @options[:UserDir])
190
+ req.script_name = script_name
191
+ req.path_info = path_info
192
+ rescue
193
+ @logger.debug "#{self.class}#do_GET: getpwnam(#{user}) failed"
194
+ end
195
+ end
196
+ end
197
+ prevent_directory_traversal(req, res)
198
+ super(req, res)
199
+ end
200
+
201
+ def do_GET(req, res)
202
+ unless exec_handler(req, res)
203
+ set_dir_list(req, res)
204
+ end
205
+ end
206
+
207
+ def do_POST(req, res)
208
+ unless exec_handler(req, res)
209
+ raise HTTPStatus::NotFound, "`#{req.path}' not found."
210
+ end
211
+ end
212
+
213
+ def do_OPTIONS(req, res)
214
+ unless exec_handler(req, res)
215
+ super(req, res)
216
+ end
217
+ end
218
+
219
+ # ToDo
220
+ # RFC2518: HTTP Extensions for Distributed Authoring -- WEBDAV
221
+ #
222
+ # PROPFIND PROPPATCH MKCOL DELETE PUT COPY MOVE
223
+ # LOCK UNLOCK
224
+
225
+ # RFC3253: Versioning Extensions to WebDAV
226
+ # (Web Distributed Authoring and Versioning)
227
+ #
228
+ # VERSION-CONTROL REPORT CHECKOUT CHECK_IN UNCHECKOUT
229
+ # MKWORKSPACE UPDATE LABEL MERGE ACTIVITY
230
+
231
+ private
232
+
233
+ def trailing_pathsep?(path)
234
+ # check for trailing path separator:
235
+ # File.dirname("/aaaa/bbbb/") #=> "/aaaa")
236
+ # File.dirname("/aaaa/bbbb/x") #=> "/aaaa/bbbb")
237
+ # File.dirname("/aaaa/bbbb") #=> "/aaaa")
238
+ # File.dirname("/aaaa/bbbbx") #=> "/aaaa")
239
+ return File.dirname(path) != File.dirname(path+"x")
240
+ end
241
+
242
+ def prevent_directory_traversal(req, res)
243
+ # Preventing directory traversal on Windows platforms;
244
+ # Backslashes (0x5c) in path_info are not interpreted as special
245
+ # character in URI notation. So the value of path_info should be
246
+ # normalize before accessing to the filesystem.
247
+
248
+ # dirty hack for filesystem encoding; in nature, File.expand_path
249
+ # should not be used for path normalization. [Bug #3345]
250
+ path = req.path_info.dup.force_encoding(Encoding.find("filesystem"))
251
+ if trailing_pathsep?(req.path_info)
252
+ # File.expand_path removes the trailing path separator.
253
+ # Adding a character is a workaround to save it.
254
+ # File.expand_path("/aaa/") #=> "/aaa"
255
+ # File.expand_path("/aaa/" + "x") #=> "/aaa/x"
256
+ expanded = File.expand_path(path + "x")
257
+ expanded.chop! # remove trailing "x"
258
+ else
259
+ expanded = File.expand_path(path)
260
+ end
261
+ expanded.force_encoding(req.path_info.encoding)
262
+ req.path_info = expanded
263
+ end
264
+
265
+ def exec_handler(req, res)
266
+ raise HTTPStatus::NotFound, "`#{req.path}' not found" unless @root
267
+ if set_filename(req, res)
268
+ handler = get_handler(req, res)
269
+ call_callback(:HandlerCallback, req, res)
270
+ h = handler.get_instance(@config, res.filename)
271
+ h.service(req, res)
272
+ return true
273
+ end
274
+ call_callback(:HandlerCallback, req, res)
275
+ return false
276
+ end
277
+
278
+ def get_handler(req, res)
279
+ suffix1 = (/\.(\w+)\z/ =~ res.filename) && $1.downcase
280
+ if /\.(\w+)\.([\w\-]+)\z/ =~ res.filename
281
+ if @options[:AcceptableLanguages].include?($2.downcase)
282
+ suffix2 = $1.downcase
283
+ end
284
+ end
285
+ handler_table = @options[:HandlerTable]
286
+ return handler_table[suffix1] || handler_table[suffix2] ||
287
+ HandlerTable[suffix1] || HandlerTable[suffix2] ||
288
+ DefaultFileHandler
289
+ end
290
+
291
+ def set_filename(req, res)
292
+ res.filename = @root.dup
293
+ path_info = req.path_info.scan(%r|/[^/]*|)
294
+
295
+ path_info.unshift("") # dummy for checking @root dir
296
+ while base = path_info.first
297
+ break if base == "/"
298
+ break unless File.directory?(File.expand_path(res.filename + base))
299
+ shift_path_info(req, res, path_info)
300
+ call_callback(:DirectoryCallback, req, res)
301
+ end
302
+
303
+ if base = path_info.first
304
+ if base == "/"
305
+ if file = search_index_file(req, res)
306
+ shift_path_info(req, res, path_info, file)
307
+ call_callback(:FileCallback, req, res)
308
+ return true
309
+ end
310
+ shift_path_info(req, res, path_info)
311
+ elsif file = search_file(req, res, base)
312
+ shift_path_info(req, res, path_info, file)
313
+ call_callback(:FileCallback, req, res)
314
+ return true
315
+ else
316
+ raise HTTPStatus::NotFound, "`#{req.path}' not found."
317
+ end
318
+ end
319
+
320
+ return false
321
+ end
322
+
323
+ def check_filename(req, res, name)
324
+ if nondisclosure_name?(name) || windows_ambiguous_name?(name)
325
+ @logger.warn("the request refers nondisclosure name `#{name}'.")
326
+ raise HTTPStatus::NotFound, "`#{req.path}' not found."
327
+ end
328
+ end
329
+
330
+ def shift_path_info(req, res, path_info, base=nil)
331
+ tmp = path_info.shift
332
+ base = base || tmp
333
+ req.path_info = path_info.join
334
+ req.script_name << base
335
+ res.filename = File.expand_path(res.filename + base)
336
+ check_filename(req, res, File.basename(res.filename))
337
+ end
338
+
339
+ def search_index_file(req, res)
340
+ @config[:DirectoryIndex].each{|index|
341
+ if file = search_file(req, res, "/"+index)
342
+ return file
343
+ end
344
+ }
345
+ return nil
346
+ end
347
+
348
+ def search_file(req, res, basename)
349
+ langs = @options[:AcceptableLanguages]
350
+ path = res.filename + basename
351
+ if File.file?(path)
352
+ return basename
353
+ elsif langs.size > 0
354
+ req.accept_language.each{|lang|
355
+ path_with_lang = path + ".#{lang}"
356
+ if langs.member?(lang) && File.file?(path_with_lang)
357
+ return basename + ".#{lang}"
358
+ end
359
+ }
360
+ (langs - req.accept_language).each{|lang|
361
+ path_with_lang = path + ".#{lang}"
362
+ if File.file?(path_with_lang)
363
+ return basename + ".#{lang}"
364
+ end
365
+ }
366
+ end
367
+ return nil
368
+ end
369
+
370
+ def call_callback(callback_name, req, res)
371
+ if cb = @options[callback_name]
372
+ cb.call(req, res)
373
+ end
374
+ end
375
+
376
+ def windows_ambiguous_name?(name)
377
+ return true if /[. ]+\z/ =~ name
378
+ return true if /::\$DATA\z/ =~ name
379
+ return false
380
+ end
381
+
382
+ def nondisclosure_name?(name)
383
+ @options[:NondisclosureName].each{|pattern|
384
+ if File.fnmatch(pattern, name, File::FNM_CASEFOLD)
385
+ return true
386
+ end
387
+ }
388
+ return false
389
+ end
390
+
391
+ def set_dir_list(req, res)
392
+ redirect_to_directory_uri(req, res)
393
+ unless @options[:FancyIndexing]
394
+ raise HTTPStatus::Forbidden, "no access permission to `#{req.path}'"
395
+ end
396
+ local_path = res.filename
397
+ list = Dir::entries(local_path).collect{|name|
398
+ next if name == "." || name == ".."
399
+ next if nondisclosure_name?(name)
400
+ next if windows_ambiguous_name?(name)
401
+ st = (File::stat(File.join(local_path, name)) rescue nil)
402
+ if st.nil?
403
+ [ name, nil, -1 ]
404
+ elsif st.directory?
405
+ [ name + "/", st.mtime, -1 ]
406
+ else
407
+ [ name, st.mtime, st.size ]
408
+ end
409
+ }
410
+ list.compact!
411
+
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
416
+ end
417
+ d1 = (d0 == "A") ? "D" : "A"
418
+
419
+ if d0 == "A"
420
+ list.sort!{|a,b| a[idx] <=> b[idx] }
421
+ else
422
+ list.sort!{|a,b| b[idx] <=> a[idx] }
423
+ end
424
+
425
+ res['content-type'] = "text/html"
426
+
427
+ res.body = <<-_end_of_html_
428
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
429
+ <HTML>
430
+ <HEAD><TITLE>Index of #{HTMLUtils::escape(req.path)}</TITLE></HEAD>
431
+ <BODY>
432
+ <H1>Index of #{HTMLUtils::escape(req.path)}</H1>
433
+ _end_of_html_
434
+
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"
440
+
441
+ list.unshift [ "..", File::mtime(local_path+"/.."), -1 ]
442
+ list.each{ |name, time, size|
443
+ if name == ".."
444
+ dname = "Parent Directory"
445
+ elsif name.bytesize > 25
446
+ dname = name.sub(/^(.{23})(?:.*)/, '\1..')
447
+ else
448
+ dname = name
449
+ 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"
454
+ res.body << s
455
+ }
456
+ res.body << "</PRE><HR>"
457
+
458
+ res.body << <<-_end_of_html_
459
+ <ADDRESS>
460
+ #{HTMLUtils::escape(@config[:ServerSoftware])}<BR>
461
+ at #{req.host}:#{req.port}
462
+ </ADDRESS>
463
+ </BODY>
464
+ </HTML>
465
+ _end_of_html_
466
+ end
467
+
468
+ end
469
+ end
470
+ end