# class Rack::File

`Rack::Files` serves files below the `root` directory given, according to the path info of the `Rack` request. e.g. when `Rack::Files.new`(“/etc”) is used, you can access 'passwd' file as localhost:9292/passwd

Handlers can detect if bodies are a `Rack::Files`, and use mechanisms like sendfile on the `path`.

### Constants

ALLOWED_VERBS
ALLOW_HEADER
MULTIPART_BOUNDARY

### Attributes

root[R]

### Public Class Methods

method_added(name) click to toggle source

@todo remove in 3.0

Calls superclass method

    # File lib/rack/files.rb, line 20
    def self.method_added(name)
      if name == :response_body
        raise "#{self.class}\#response_body is no longer supported."
      end
      super
    end

new(root, headers = {}, default_mime = 'text/plain') click to toggle source

    # File lib/rack/files.rb, line 29
    def initialize(root, headers = {}, default_mime = 'text/plain')
      @root = (::File.expand_path(root) if root)
      @headers = headers
      @default_mime = default_mime
      @head = Rack::Head.new(lambda { |env| get env })
    end

### Public Instance Methods

call(env) click to toggle source

    # File lib/rack/files.rb, line 36
    def call(env)
      # HEAD requests drop the response body, including 4xx error messages.
      @head.call env
    end

get(env) click to toggle source

    # File lib/rack/files.rb, line 41
    def get(env)
      request = Rack::Request.new env
      unless ALLOWED_VERBS.include? request.request_method
        return fail(405, "Method Not Allowed", { 'Allow' => ALLOW_HEADER })
      end

      path_info = Utils.unescape_path request.path_info
      return fail(400, "Bad Request") unless Utils.valid_path?(path_info)

      clean_path_info = Utils.clean_path_info(path_info)
      path = ::File.join(@root, clean_path_info)

      available = begin
        ::File.file?(path) && ::File.readable?(path)
      rescue SystemCallError
        # Not sure in what conditions this exception can occur, but this
        # is a safe way to handle such an error.
        # :nocov:
        false
        # :nocov:
      end

      if available
        serving(request, path)
      else
        fail(404, "File not found: #{path_info}")
      end
    end

serving(request, path) click to toggle source

    # File lib/rack/files.rb, line 70
    def serving(request, path)
      if request.options?
        return [200, { 'Allow' => ALLOW_HEADER, CONTENT_LENGTH => '0' }, []]
      end
      last_modified = ::File.mtime(path).httpdate
      return [304, {}, []] if request.get_header('HTTP_IF_MODIFIED_SINCE') == last_modified

      headers = { "Last-Modified" => last_modified }
      mime_type = mime_type path, @default_mime
      headers[CONTENT_TYPE] = mime_type if mime_type

      # Set custom headers
      headers.merge!(@headers) if @headers

      status = 200
      size = filesize path

      ranges = Rack::Utils.get_byte_ranges(request.get_header('HTTP_RANGE'), size)
      if ranges.nil?
        # No ranges:
        ranges = [0..size - 1]
      elsif ranges.empty?
        # Unsatisfiable. Return error, and file size:
        response = fail(416, "Byte range unsatisfiable")
        response[1]["Content-Range"] = "bytes */#{size}"
        return response
      elsif ranges.size >= 1
        # Partial content
        partial_content = true

        if ranges.size == 1
          range = ranges[0]
          headers["Content-Range"] = "bytes #{range.begin}-#{range.end}/#{size}"
        else
          headers[CONTENT_TYPE] = "multipart/byteranges; boundary=#{MULTIPART_BOUNDARY}"
        end

        status = 206
        body = BaseIterator.new(path, ranges, mime_type: mime_type, size: size)
        size = body.bytesize
      end

      headers[CONTENT_LENGTH] = size.to_s

      if request.head?
        body = []
      elsif !partial_content
        body = Iterator.new(path, ranges, mime_type: mime_type, size: size)
      end

      [status, headers, body]
    end

### Private Instance Methods

fail(status, body, headers = {}) click to toggle source

    # File lib/rack/files.rb, line 192
    def fail(status, body, headers = {})
      body += "\n"

      [
        status,
        {
          CONTENT_TYPE   => "text/plain",
          CONTENT_LENGTH => body.bytesize.to_s,
          "X-Cascade" => "pass"
        }.merge!(headers),
        [body]
      ]
    end

filesize(path) click to toggle source

    # File lib/rack/files.rb, line 211
    def filesize(path)
      #   We check via File::size? whether this file provides size info
      #   via stat (e.g. /proc files often don't), otherwise we have to
      #   figure it out by reading the whole file into memory.
      ::File.size?(path) || ::File.read(path).bytesize
    end

mime_type(path, default_mime) click to toggle source

The MIME type for the contents of the file located at @path

    # File lib/rack/files.rb, line 207
    def mime_type(path, default_mime)
      Mime.mime_type(::File.extname(path), default_mime)
    end