Class: Mongrel::DirHandler
- Inherits:
-
HttpHandler
- Object
- HttpHandler
- Mongrel::DirHandler
- 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.
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.
Constant Summary collapse
- MIME_TYPES_FILE =
"mime_types.yml"
- MIME_TYPES =
YAML.load_file(File.join(File.dirname(__FILE__), MIME_TYPES_FILE))
- ONLY_HEAD_GET =
"Only HEAD and GET allowed.".freeze
Instance Attribute Summary collapse
-
#default_content_type ⇒ Object
Returns the value of attribute default_content_type.
-
#path ⇒ Object
readonly
Returns the value of attribute path.
Attributes inherited from HttpHandler
Class Method Summary collapse
-
.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.
Instance Method Summary collapse
-
#can_serve(path_info) ⇒ Object
Checks if the given path can be served and returns the full path (or nil if not).
-
#initialize(path, listing_allowed = true, index_html = "index.html") ⇒ DirHandler
constructor
You give it the path to the directory root and and optional listing_allowed and index_html.
-
#process(request, response) ⇒ Object
Process the request to either serve a file or a directory listing if allowed (based on the listing_allowed parameter to the constructor).
-
#send_dir_listing(base, dir, response) ⇒ Object
Returns a simplistic directory listing if they’re enabled, otherwise a 403.
-
#send_file(req_path, request, response, header_only = false) ⇒ Object
Sends the contents of a file back to the user.
Methods inherited from HttpHandler
#request_begins, #request_progress
Constructor Details
#initialize(path, listing_allowed = true, index_html = "index.html") ⇒ DirHandler
You give it the path to the directory root and and optional listing_allowed and index_html
121 122 123 124 125 126 |
# File 'lib/mongrel/handlers.rb', line 121 def initialize(path, listing_allowed=true, index_html="index.html") @path = File.(path) if path @listing_allowed = listing_allowed @index_html = index_html @default_content_type = "application/octet-stream".freeze end |
Instance Attribute Details
#default_content_type ⇒ Object
Returns the value of attribute default_content_type.
112 113 114 |
# File 'lib/mongrel/handlers.rb', line 112 def default_content_type @default_content_type end |
#path ⇒ Object (readonly)
Returns the value of attribute path.
113 114 115 |
# File 'lib/mongrel/handlers.rb', line 113 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.
276 277 278 |
# File 'lib/mongrel/handlers.rb', line 276 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).
129 130 131 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 157 158 159 |
# 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. 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) ⇒ Object
Process the request to either serve a file or a directory listing if allowed (based on the listing_allowed parameter to the constructor).
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 |
# 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) ⇒ 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.
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
# 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) ⇒ 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.
193 194 195 196 197 198 199 200 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 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 |
# 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 then false when modified_since && last_response_time > Time.now then false when modified_since && mtime > last_response_time then false when none_match && none_match == '*' then false when none_match && !none_match.strip.split(/\s*,\s*/).include?(etag) then 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 |