123456789_123456789_123456789_123456789_123456789_

Class: Rack::Directory

Relationships & Source Files
Namespace Children
Classes:
Inherits: Object
Defined in: lib/rack/directory.rb

Overview

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

Constant Summary

  • DIR_FILE =
    # File 'lib/rack/directory.rb', line 20
    "<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>\n"
  • DIR_PAGE_FOOTER =
    # File 'lib/rack/directory.rb', line 43
    <<-PAGE
    </table>
    <hr />
    </body></html>
    PAGE
  • DIR_PAGE_HEADER =
    # File 'lib/rack/directory.rb', line 21
    <<-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>
      <tr>
    <th class='name'>Name</th>
    <th class='size'>Size</th>
    <th class='type'>Type</th>
    <th class='mtime'>Last Modified</th>
      </tr>
    PAGE
  • FILESIZE_FORMAT =

    Stolen from Ramaze

    # File 'lib/rack/directory.rb', line 189
    [
      ['%.1fT', 1 << 40],
      ['%.1fG', 1 << 30],
      ['%.1fM', 1 << 20],
      ['%.1fK', 1 << 10],
    ]

Class Method Summary

Instance Attribute Summary

  • #root readonly

    The root of the directory hierarchy.

Instance Method Summary

Constructor Details

.new(root, app = nil) ⇒ Directory

Set the root directory and application for serving files.

[ GitHub ]

  
# 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

Instance Attribute Details

#root (readonly)

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

[ GitHub ]

  
# File 'lib/rack/directory.rb', line 80

attr_reader :root

Instance Method Details

#call(env)

[ GitHub ]

  
# File 'lib/rack/directory.rb', line 89

def call(env)
  # strip body if this is a HEAD call
  @head.call env
end

#check_bad_request(path_info)

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

[ GitHub ]

  
# 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

#check_forbidden(path_info)

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

[ GitHub ]

  
# 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

#entity_not_found(path_info)

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

[ GitHub ]

  
# 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

#filesize_format(int)

Provide human readable file sizes

[ GitHub ]

  
# 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

#get(env)

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

[ GitHub ]

  
# 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

#list_directory(path_info, path, script_name)

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

[ GitHub ]

  
# 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

#list_path(env, path, path_info, script_name)

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

[ GitHub ]

  
# 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

#stat(path)

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

[ GitHub ]

  
# File 'lib/rack/directory.rb', line 163

def stat(path)
  ::File.stat(path)
rescue Errno::ENOENT, Errno::ELOOP
  return nil
end