lib/rack/directory.rb in rack-0.4.0 vs lib/rack/directory.rb in rack-0.9.0

- old
+ new

@@ -1,6 +1,7 @@ require 'time' +require 'rack/mime' 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 @@ -11,14 +12,17 @@ class Directory DIR_FILE = "<tr><td class='name'><a href='%s'>%s</a></td><td class='size'>%s</td><td class='type'>%s</td><td class='mtime'>%s</td></tr>" DIR_PAGE = <<-PAGE <html><head> <title>%s</title> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <style type='text/css'> table { width:100%%; } .name { text-align:left; } .size, .mtime { text-align:right; } +.type { width:11em; } +.mtime { width:15em; } </style> </head><body> <h1>%s</h1> <hr /> <table> @@ -36,54 +40,86 @@ 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 + @root = F.expand_path(root) + @app = app || Rack::File.new(@root) 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]] + @env = env + @path_info, @script_name = env.values_at('PATH_INFO', 'SCRIPT_NAME') + + if forbidden = check_forbidden + forbidden + else + @path = F.join(@root, Utils.unescape(@path_info)) + list_path end + end - @path = F.join(@root, Utils.unescape(env['PATH_INFO'])) + def check_forbidden + return unless @path_info.include? ".." - 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 + 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 + + def list_directory + @files = [['../','Parent Directory','','','']] + glob = F.join(@path, '*') + + Dir[glob].sort.each do |node| + stat = stat(node) + next unless stat + basename = F.basename(node) + ext = F.extname(node) + + url = F.join(@script_name, @path_info, basename) + size = stat.size + type = stat.directory? ? 'directory' : Mime.mime_type(ext) + size = stat.directory? ? '-' : filesize_format(size) + mtime = stat.mtime.httpdate + + @files << [ url, basename, size, type, mtime ] end - body = "Entity not found: #{env["PATH_INFO"]}\n" + return [ 200, {'Content-Type'=>'text/html; charset=utf-8'}, self ] + end + + def stat(node, max = 10) + F.stat(node) + rescue Errno::ENOENT, Errno::ELOOP + return nil + end + + # TODO: add correct response if not readable, not sure if 404 is the best + # option + def list_path + @stat = F.stat(@path) + + if @stat.readable? + return @app.call(@env) if @stat.file? + return list_directory if @stat.directory? + else + raise Errno::ENOENT, 'No such file or directory' + end + + rescue Errno::ENOENT, Errno::ELOOP + return entity_not_found + end + + def entity_not_found + body = "Entity not found: #{@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 @@ -91,68 +127,23 @@ 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 + # Stolen from Ramaze - # 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", - } + FILESIZE_FORMAT = [ + ['%.1fT', 1 << 40], + ['%.1fG', 1 << 30], + ['%.1fM', 1 << 20], + ['%.1fK', 1 << 10], + ] + + def filesize_format(int) + FILESIZE_FORMAT.each do |format, size| + return format % (int.to_f / size) if int >= size + end + + int.to_s + 'B' + end end end