Class: Mongrel::DirHandler

Inherits:
HttpHandler show all
Defined in:
lib/mongrel/handlers.rb

Overview

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.

Constant Summary collapse

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

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path, listing_allowed = true, index_html = "index.html") ⇒ DirHandler

You give it the path to the directory root and an (optional)



98
99
100
101
102
103
# File 'lib/mongrel/handlers.rb', line 98

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

Instance Attribute Details

#default_content_typeObject

Returns the value of attribute default_content_type.



78
79
80
# File 'lib/mongrel/handlers.rb', line 78

def default_content_type
  @default_content_type
end

#pathObject (readonly)

Returns the value of attribute path.



80
81
82
# File 'lib/mongrel/handlers.rb', line 80

def path
  @path
end

Class Method Details

.add_mime_type(extension, type) ⇒ Object

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



237
238
239
# File 'lib/mongrel/handlers.rb', line 237

def DirHandler::add_mime_type(extension, type)
  MIME_TYPES[extension] = type
end

Instance Method Details

#can_serve(path_info) ⇒ Object

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



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/mongrel/handlers.rb', line 106

def can_serve(path_info)
  req = File.expand_path(File.join(@path,path_info), @path)

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

#process(request, response) ⇒ Object

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



209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/mongrel/handlers.rb', line 209

def process(request, response)
  req_method = request.params[Const::REQUEST_METHOD] || Const::GET
  req = can_serve request.params[Const::PATH_INFO]
  if not req
    # not found, return a 404
    response.start(404) do |head,out|
      out << "File not found"
    end
  else
    begin
      if File.directory? req
        send_dir_listing(request.params[Const::REQUEST_URI],req, response)
      elsif req_method == Const::HEAD
        send_file(req, response, true)
	  elsif req_method == Const::GET
        send_file(req, response, false)
	  else
 response.start(403) {|head,out| out.write(ONLY_HEAD_GET) }
      end
    rescue => details
      STDERR.puts "Error accessing file #{req}: #{details}"
      STDERR.puts details.backtrace.join("\n")
    end
  end
end

#send_dir_listing(base, dir, response) ⇒ Object

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.



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/mongrel/handlers.rb', line 139

def send_dir_listing(base, dir, response)
  # take off any trailing / so the links come out right
  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 == "."

        if child == ".."
          out << "<a href=\"#{base}/#{child}\">Up to parent..</a><br/>"
        else
          out << "<a href=\"#{base}/#{child}\">#{child}</a><br/>"
        end
      end
      out << "</body></html>"
    end
  else
    response.start(403) do |head,out|
      out.write("Directory listings not allowed")
    end
  end
end

#send_file(req, response, header_only = false) ⇒ Object

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



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/mongrel/handlers.rb', line 168

def send_file(req, response, header_only=false)

  # first we setup the headers and status then we do a very fast send on the socket directly
  response.status = 200
  stat = File.stat(req)
  header = response.header

  # Set the last modified times as well and etag for all files
  header[Const::LAST_MODIFIED] = stat.mtime.httpdate
  # Calculated the same as apache, not sure how well the works on win32
  header[Const::ETAG] = Const::ETAG_FORMAT % [stat.mtime.to_i, stat.size, stat.ino]
  
  # set the mime type from our map based on the ending
  dot_at = req.rindex(".")      
  if dot_at
    header[Const::CONTENT_TYPE] = MIME_TYPES[req[dot_at .. -1]] || @default_content_type
  end

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

  if not header_only
	begin
	  if $mongrel_has_sendfile
 File.open(req, "rb") { |f| response.socket.sendfile(f) }
	  else
 File.open(req, "rb") { |f| response.socket.write(f.read) }
	  end
    rescue EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF
	  # ignore these since it means the client closed off early
      STDERR.puts "Client closed socket requesting file #{req}: #$!"
	end
  else
    response.send_body # should send nothing
  end
end