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.

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)



92
93
94
95
96
# File 'lib/mongrel/handlers.rb', line 92

def initialize(path, listing_allowed=true, index_html="index.html")
  @path = File.expand_path(path)
  @listing_allowed=listing_allowed
  @index_html = index_html
end

Instance Attribute Details

#pathObject (readonly)

Returns the value of attribute path.



89
90
91
# File 'lib/mongrel/handlers.rb', line 89

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.



229
230
231
# File 'lib/mongrel/handlers.rb', line 229

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).



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/mongrel/handlers.rb', line 99

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).



201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/mongrel/handlers.rb', line 201

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.



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/mongrel/handlers.rb', line 132

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.



161
162
163
164
165
166
167
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
# File 'lib/mongrel/handlers.rb', line 161

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
  
  # set the mime type from our map based on the ending
  dot_at = req.rindex(".")
  if dot_at
    ext = req[dot_at .. -1]
    if MIME_TYPES[ext]
      stat = File.stat(req)
      response.header[Const::CONTENT_TYPE] = MIME_TYPES[ext]
      # TODO: Confirm this works for rfc 1123
      response.header[Const::LAST_MODIFIED] = HttpServer.httpdate(stat.mtime)
      # TODO that this is a valid way to calculate an etag
      response.header[Const::ETAG] = Const::ETAG_FORMAT % [stat.mtime.to_i, stat.size, stat.ino]
    end
  end

  response.send_status(File.size(req))
  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
	  # ignore these since it means the client closed off early
	end
  else
    response.send_body # should send nothing
  end
end