# 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_FOOTER
DIR_PAGE_HEADER
FILESIZE_FORMAT

  Stolen from Ramaze

### Attributes

root[R]

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

### Public Class Methods

new(root, app = nil) click to toggle source

Set the root directory and application for serving files.

    # File lib/rack/directory.rb, line 77
    def initialize(root, app = nil)
      @root = ::File.expand_path(root)
      @root_with_separator = @root.end_with?(::File::SEPARATOR) ? @root : "#{@root}#{::File::SEPARATOR}"
      @app = app || Files.new(@root)
      @head = Head.new(method(:get))
    end

### Public Instance Methods

call(env) click to toggle source

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

check_bad_request(path_info) click to toggle source

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

    # File lib/rack/directory.rb, line 104
    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

check_forbidden(path_info) click to toggle source

`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 114
    def check_forbidden(path_info)
      return unless path_info.include? ".."

      expanded_path = ::File.expand_path(::File.join(@root, path_info))
      return if expanded_path == @root || expanded_path.start_with?(@root_with_separator)

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

entity_not_found(path_info) click to toggle source

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

    # File lib/rack/directory.rb, line 178
    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

filesize_format(int) click to toggle source

Provide human readable file sizes

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

      "#{int}B"
    end

get(env) click to toggle source

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

    # File lib/rack/directory.rb, line 91
    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

list_directory(path_info, path, script_name) click to toggle source

`Rack` response to use for directories under the root.

    # File lib/rack/directory.rb, line 127
    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

list_path(env, path, path_info, script_name) click to toggle source

`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 168
    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

stat(path) click to toggle source

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

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