# Mongrel Web Server - A Mostly Ruby Webserver and Library # # Copyright (C) 2005 Zed A. Shaw zedshaw AT zedshaw dot com # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA module Mongrel # You implement your application handler with this. It's very light giving # just the minimum necessary for you to handle a request and shoot back # a response. Look at the HttpRequest and HttpResponse objects for how # to use them. # # This is used for very simple handlers that don't require much to operate. # More extensive plugins or those you intend to distribute as GemPlugins # should be implemented using the HttpHandlerPlugin mixin. # class HttpHandler def process(request, response) end end # This is used when your handler is implemented as a GemPlugin. # The plugin always takes an options hash which you can modify # and then access later. They are stored by default for # the process method later. module HttpHandlerPlugin attr_reader :options def initialize(options={}) @options = options end def process(request, response) end end # The server normally returns a 404 response if an unknown URI is requested, but it # also returns a lame empty message. This lets you do a 404 response # with a custom message for special URIs. class Error404Handler < HttpHandler # Sets the message to return. This is constructed once for the handler # so it's pretty efficient. def initialize(msg) @response = Const::ERROR_404_RESPONSE + msg end # Just kicks back the standard 404 response with your special message. def process(request, response) response.socket.write(@response) end end # 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. # # 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. class DirHandler < HttpHandler attr_reader :default_content_type attr_writer :default_content_type attr_reader :path MIME_TYPES = { ".css" => "text/css", ".gif" => "image/gif", ".htm" => "text/html", ".html" => "text/html", ".jpeg" => "image/jpeg", ".jpg" => "image/jpeg", ".js" => "text/javascript", ".png" => "image/png", ".swf" => "application/x-shockwave-flash", ".txt" => "text/plain" } ONLY_HEAD_GET="Only HEAD and GET allowed.".freeze # You give it the path to the directory root and an (optional) def initialize(path, listing_allowed=true, index_html="index.html") @path = File.expand_path(path) @listing_allowed=listing_allowed @index_html = index_html @default_content_type = "text/plain; charset=ISO-8859-1".freeze end # Checks if the given path can be served and returns the full path (or nil if not). def can_serve(path_info) # TODO: investigate freezing the path_info to prevent double escaping req_path = File.expand_path(File.join(@path,HttpRequest.unescape(path_info)), @path) if req_path.index(@path) == 0 and File.exist? req_path # 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 # 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. 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 << "