class Rack::Directory

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::Files of the same root will be used.

Constants

DIR_FILE
DIR_PAGE_HEADER
FILESIZE_FORMAT

Stolen from Ramaze

Attributes

The root of the directory hierarchy. Only requests for files and directories inside of the root directory are supported.

Public Class Methods

Set the root directory and application for serving files.

# File lib/rack/directory.rb, line 83
def initialize(root, app = nil)
  @root = ::File.expand_path(root)
  @app = app || Files.new(@root)
  @head = Head.new(method(:get))
end

Public Instance Methods

# File lib/rack/directory.rb, line 89
def call(env)
  # strip body if this is a HEAD call
  @head.call env
end

Rack response to use for requests with invalid paths, or nil if path is valid.

# File lib/rack/directory.rb, line 109
def check_bad_request(path_info)
  return if Utils.valid_path?(path_info)

  body = "Bad Request\n"
  [400, { CONTENT_TYPE => "text/plain",
    CONTENT_LENGTH => body.bytesize.to_s,
    "x-cascade" => "pass" }, [body]]
end

Rack response to use for requests with paths outside the root, or nil if path is inside the root.

# File lib/rack/directory.rb, line 119
def check_forbidden(path_info)
  return unless path_info.include? ".."
  return if ::File.expand_path(::File.join(@root, path_info)).start_with?(@root)

  body = "Forbidden\n"
  [403, { CONTENT_TYPE => "text/plain",
    CONTENT_LENGTH => body.bytesize.to_s,
    "x-cascade" => "pass" }, [body]]
end

Rack response to use for unreadable and non-file, non-directory entries.

# File lib/rack/directory.rb, line 181
def entity_not_found(path_info)
  body = "Entity not found: #{path_info}\n"
  [404, { CONTENT_TYPE => "text/plain",
    CONTENT_LENGTH => body.bytesize.to_s,
    "x-cascade" => "pass" }, [body]]
end

Provide human readable file sizes

# File lib/rack/directory.rb, line 197
def filesize_format(int)
  FILESIZE_FORMAT.each do |format, size|
    return format % (int.to_f / size) if int >= size
  end

  "#{int}B"
end

Internals of request handling. Similar to call but does not remove body for HEAD requests.

# File lib/rack/directory.rb, line 96
def get(env)
  script_name = env[SCRIPT_NAME]
  path_info = Utils.unescape_path(env[PATH_INFO])

  if client_error_response = check_bad_request(path_info) || check_forbidden(path_info)
    client_error_response
  else
    path = ::File.join(@root, path_info)
    list_path(env, path, path_info, script_name)
  end
end

Rack response to use for directories under the root.

# File lib/rack/directory.rb, line 130
def list_directory(path_info, path, script_name)
  url_head = (script_name.split('/') + path_info.split('/')).map do |part|
    Utils.escape_path part
  end

  # Globbing not safe as path could contain glob metacharacters
  body = DirectoryBody.new(@root, path, ->(basename) do
    stat = stat(::File.join(path, basename))
    next unless stat

    url = ::File.join(*url_head + [Utils.escape_path(basename)])
    mtime = stat.mtime.httpdate
    if stat.directory?
      type = 'directory'
      size = '-'
      url << '/'
      if basename == '..'
        basename = 'Parent Directory'
      else
        basename << '/'
      end
    else
      type = Mime.mime_type(::File.extname(basename))
      size = filesize_format(stat.size)
    end

    [ url, basename, size, type, mtime ]
  end)

  [ 200, { CONTENT_TYPE => 'text/html; charset=utf-8' }, body ]
end

Rack response to use for files and directories under the root. Unreadable and non-file, non-directory entries will get a 404 response.

# File lib/rack/directory.rb, line 171
def list_path(env, path, path_info, script_name)
  if (stat = stat(path)) && stat.readable?
    return @app.call(env) if stat.file?
    return list_directory(path_info, path, script_name) if stat.directory?
  end

  entity_not_found(path_info)
end

File::Stat for the given path, but return nil for missing/bad entries.

# File lib/rack/directory.rb, line 163
def stat(path)
  ::File.stat(path)
rescue Errno::ENOENT, Errno::ELOOP
  return nil
end