require 'time'
module Rack
# Rack::Directory serves entries below the +root+ given, according to the
# path info of the Rack request. If a directory is found, the file's contents
# will be presented in an html based index. If a file is found, the env will
# be passed to the specified +app+.
#
# If +app+ is not specified, a Rack::File of the same +root+ will be used.
class Directory
DIR_FILE = "
%s | %s | %s | %s |
"
DIR_PAGE = <<-PAGE
%s
%s
Name |
Size |
Type |
Last Modified |
%s
PAGE
attr_reader :files
attr_accessor :root, :path
def initialize(root, app=nil)
@root = root
@app = app
unless defined? @app
@app = Rack::File.new(@root)
end
end
def call(env)
dup._call(env)
end
F = ::File
def _call(env)
if env["PATH_INFO"].include? ".."
body = "Forbidden\n"
size = body.respond_to?(:bytesize) ? body.bytesize : body.size
return [403, {"Content-Type" => "text/plain","Content-Length" => size.to_s}, [body]]
end
@path = F.join(@root, Utils.unescape(env['PATH_INFO']))
if F.exist?(@path) and F.readable?(@path)
if F.file?(@path)
return @app.call(env)
elsif F.directory?(@path)
@files = [['../','Parent Directory','','','']]
sName, pInfo = env.values_at('SCRIPT_NAME', 'PATH_INFO')
Dir.entries(@path).sort.each do |file|
next if file[0] == ?.
fl = F.join(@path, file)
sz = F.size(fl)
url = F.join(sName, pInfo, file)
type = F.directory?(fl) ? 'directory' :
MIME_TYPES.fetch(F.extname(file)[1..-1],'unknown')
size = (type!='directory' ? (sz<10240 ? "#{sz}B" : "#{sz/1024}KB") : '-')
mtime = F.mtime(fl).httpdate
@files << [ url, file, size, type, mtime ]
end
return [ 200, {'Content-Type'=>'text/html'}, self ]
end
end
body = "Entity not found: #{env["PATH_INFO"]}\n"
size = body.respond_to?(:bytesize) ? body.bytesize : body.size
return [404, {"Content-Type" => "text/plain", "Content-Length" => size.to_s}, [body]]
end
def each
show_path = @path.sub(/^#{@root}/,'')
files = @files.map{|f| DIR_FILE % f }*"\n"
page = DIR_PAGE % [ show_path, show_path , files ]
page.each_line{|l| yield l }
end
def each_entry
@files.each{|e| yield e }
end
# From WEBrick.
MIME_TYPES = {
"ai" => "application/postscript",
"asc" => "text/plain",
"avi" => "video/x-msvideo",
"bin" => "application/octet-stream",
"bmp" => "image/bmp",
"class" => "application/octet-stream",
"cer" => "application/pkix-cert",
"crl" => "application/pkix-crl",
"crt" => "application/x-x509-ca-cert",
#"crl" => "application/x-pkcs7-crl",
"css" => "text/css",
"dms" => "application/octet-stream",
"doc" => "application/msword",
"dvi" => "application/x-dvi",
"eps" => "application/postscript",
"etx" => "text/x-setext",
"exe" => "application/octet-stream",
"gif" => "image/gif",
"htm" => "text/html",
"html" => "text/html",
"jpe" => "image/jpeg",
"jpeg" => "image/jpeg",
"jpg" => "image/jpeg",
"js" => "text/javascript",
"lha" => "application/octet-stream",
"lzh" => "application/octet-stream",
"mov" => "video/quicktime",
"mpe" => "video/mpeg",
"mpeg" => "video/mpeg",
"mpg" => "video/mpeg",
"pbm" => "image/x-portable-bitmap",
"pdf" => "application/pdf",
"pgm" => "image/x-portable-graymap",
"png" => "image/png",
"pnm" => "image/x-portable-anymap",
"ppm" => "image/x-portable-pixmap",
"ppt" => "application/vnd.ms-powerpoint",
"ps" => "application/postscript",
"qt" => "video/quicktime",
"ras" => "image/x-cmu-raster",
"rb" => "text/plain",
"rd" => "text/plain",
"rtf" => "application/rtf",
"sgm" => "text/sgml",
"sgml" => "text/sgml",
"tif" => "image/tiff",
"tiff" => "image/tiff",
"txt" => "text/plain",
"xbm" => "image/x-xbitmap",
"xls" => "application/vnd.ms-excel",
"xml" => "text/xml",
"xpm" => "image/x-xpixmap",
"xwd" => "image/x-xwindowdump",
"zip" => "application/zip",
}
end
end