Class: Mongrel::DirHandler

### Home Classes Methods

### In Files

- lib/mongrel/handlers.rb

### Parent

Mongrel::HttpHandler

### Methods

- ::add_mime_type
- ::new
- #can_serve
- #process
- #send_dir_listing
- #send_file

### Files

- CHANGELOG
- COPYING
- LICENSE
- README
- Makefile

### Class/Module Index

Quicksearch

- Mongrel
- Mongrel::CGIWrapper
- Mongrel::Camping
- Mongrel::Camping::CampingHandler
- Mongrel::Command
- Mongrel::Command::Base
- Mongrel::Command::Registry
- Mongrel::Configurator
- Mongrel::Const
- Mongrel::DeflateFilter
- Mongrel::DirHandler
- Mongrel::Error404Handler
- Mongrel::Gems
- Mongrel::HeaderOut
- Mongrel::HttpHandler
- Mongrel::HttpHandlerPlugin
- Mongrel::HttpParams
- Mongrel::HttpParser
- Mongrel::HttpParserError
- Mongrel::HttpRequest
- Mongrel::HttpResponse
- Mongrel::HttpServer
- Mongrel::Rails
- Mongrel::Rails::RailsConfigurator
- Mongrel::Rails::RailsHandler
- Mongrel::RedirectHandler
- Mongrel::StatisticsFilter
- Mongrel::Stats
- Mongrel::StatusHandler
- Mongrel::StopServer
- Mongrel::TimeoutError
- Mongrel::URIClassifier
- Mongrel::URIClassifier::RegistrationError
- Mongrel::URIClassifier::UsageError
- RequestLog
- RequestLog::Access
- RequestLog::Files
- RequestLog::Objects
- RequestLog::Params
- RequestLog::Threads
- IO
- Kernel
- MongrelDbg
- TCPServer

No matching classes.

# Mongrel::DirHandler

Serves the contents of a directory. You give it the path to the root where the files are located, and it tries to find the files based on the PATH_INFO inside the directory. If the requested path is a directory then it returns a simple directory listing.

It does a simple protection against going outside it's root path by converting all paths to an absolute expanded path, and then making sure that the final expanded path includes the root path. If it doesn't than it simply gives a 404.

If you pass nil as the root path, it will not check any locations or expand any paths. This lets you serve files from multiple drives on win32. It should probably not be used in a public-facing way without additional checks.

The default content type is "text/plain; charset=ISO-8859-1" but you can change it anything you want using the DirHandler.default_content_type attribute.

### Constants

MIME_TYPES
MIME_TYPES_FILE
ONLY_HEAD_GET

### Attributes

default_content_type[RW]
path[R]

### Public Class Methods

add_mime_type(extension, type) click to toggle source

There is a small number of default mime types for extensions, but this lets you add any others you'll need when serving content.

    # File lib/mongrel/handlers.rb, line 276
    def DirHandler::add_mime_type(extension, type)
      MIME_TYPES[extension] = type
    end

new(path, listing_allowed=true, index_html="index.html") click to toggle source

You give it the path to the directory root and and optional listing_allowed and index_html

    # File lib/mongrel/handlers.rb, line 121
    def initialize(path, listing_allowed=true, index_html="index.html")
      @path = File.expand_path(path) if path
      @listing_allowed = listing_allowed
      @index_html = index_html
      @default_content_type = "application/octet-stream".freeze
    end

### Public Instance Methods

can_serve(path_info) click to toggle source

Checks if the given path can be served and returns the full path (or nil if not).

    # File lib/mongrel/handlers.rb, line 129
    def can_serve(path_info)

      req_path = HttpRequest.unescape(path_info)
      # Add the drive letter or root path
      req_path = File.join(@path, req_path) if @path
      req_path = File.expand_path req_path

      if File.exist? req_path and (!@path or req_path.index(@path) == 0)
        # It exists and it's in the right location
        if File.directory? req_path
          # The request is for a directory
          index = File.join(req_path, @index_html)
          if File.exist? index
            # Serve the index
            return index
          elsif @listing_allowed
            # Serve the directory
            return req_path
          else
            # Do not serve anything
            return nil
          end
        else
          # It's a file and it's there
          return req_path
        end
      else
        # does not exist or isn't in the right spot
        return nil
      end
    end

process(request, response) click to toggle source

Process the request to either serve a file or a directory listing if allowed (based on the listing_allowed parameter to the constructor).

    # File lib/mongrel/handlers.rb, line 249
    def process(request, response)
      req_method = request.params[Const::REQUEST_METHOD] || Const::GET
      req_path = can_serve request.params[Const::PATH_INFO]
      if not req_path
        # not found, return a 404
        response.start(404) do |head,out|
          out << "File not found"
        end
      else
        begin
          if File.directory? req_path
            send_dir_listing(request.params[Const::REQUEST_URI], req_path, response)
          elsif req_method == Const::HEAD
            send_file(req_path, request, response, true)
          elsif req_method == Const::GET
            send_file(req_path, request, response, false)
          else
            response.start(403) {|head,out| out.write(ONLY_HEAD_GET) }
          end
        rescue => details
          STDERR.puts "Error sending file #{req_path}: #{details}"
        end
      end
    end

send_dir_listing(base, dir, response) click to toggle source

Returns a simplistic directory listing if they're enabled, otherwise a 403. Base is the base URI from the REQUEST_URI, dir is the directory to serve on the file system (comes from can_serve()), and response is the HttpResponse object to send the results on.

    # File lib/mongrel/handlers.rb, line 166
    def send_dir_listing(base, dir, response)
      # take off any trailing / so the links come out right
      base = HttpRequest.unescape(base)
      base.chop! if base[-1] == "/"[-1]

      if @listing_allowed
        response.start(200) do |head,out|
          head[Const::CONTENT_TYPE] = "text/html"
          out << "<html><head><title>Directory Listing</title></head><body>"
          Dir.entries(dir).each do |child|
            next if child == "."
            out << "<a href=\"#{base}/#{ HttpRequest.escape(child)}\">"
            out << (child == ".." ? "Up to parent.." : child)
            out << "</a><br/>"
          end
          out << "</body></html>"
        end
      else
        response.start(403) do |head,out|
          out.write("Directory listings not allowed")
        end
      end
    end

send_file(req_path, request, response, header_only=false) click to toggle source

Sends the contents of a file back to the user. Not terribly efficient since it's opening and closing the file for each read.

    # File lib/mongrel/handlers.rb, line 193
    def send_file(req_path, request, response, header_only=false)

      stat = File.stat(req_path)

      # Set the last modified times as well and etag for all files
      mtime = stat.mtime
      # Calculated the same as apache, not sure how well the works on win32
      etag = Const::ETAG_FORMAT % [mtime.to_i, stat.size, stat.ino]

      modified_since = request.params[Const::HTTP_IF_MODIFIED_SINCE]
      none_match = request.params[Const::HTTP_IF_NONE_MATCH]

      # test to see if this is a conditional request, and test if
      # the response would be identical to the last response
      same_response = case
                      when modified_since && !last_response_time = Time.httpdate(modified_since) rescue nil : false
                      when modified_since && last_response_time > Time.now                                  : false
                      when modified_since && mtime > last_response_time                                     : false
                      when none_match     && none_match == '*'                                              : false
                      when none_match     && !none_match.strip.split(/\s*,\s*/).include?(etag)              : false
                      else modified_since || none_match  # validation successful if we get this far and at least one of the header exists
                      end

      header = response.header
      header[Const::ETAG] = etag

      if same_response
        response.start(304) {}
      else

        # First we setup the headers and status then we do a very fast send on the socket directly

        # Support custom responses except 404, which is the default. A little awkward.
        response.status = 200 if response.status == 404
        header[Const::LAST_MODIFIED] = mtime.httpdate

        # Set the mime type from our map based on the ending
        dot_at = req_path.rindex('.')
        if dot_at
          header[Const::CONTENT_TYPE] = MIME_TYPES[req_path[dot_at .. -1]] || @default_content_type
        else
          header[Const::CONTENT_TYPE] = @default_content_type
        end

        # send a status with out content length
        response.send_status(stat.size)
        response.send_header

        if not header_only
          response.send_file(req_path, stat.size < Const::CHUNK_SIZE * 2)
        end
      end
    end

[Validate]

Generated with the Darkfish Rdoc Generator 2.