Class: WEBrick::HTTPServlet::FileHandler
Relationships & Source Files | |
Super Chains via Extension / Inclusion / Inheritance | |
Class Chain:
self,
AbstractServlet
|
|
Instance Chain:
self,
AbstractServlet
|
|
Inherits: |
WEBrick::HTTPServlet::AbstractServlet
|
Defined in: | lib/webrick/httpservlet/filehandler.rb |
Overview
Serves a directory including fancy indexing and a variety of other options.
Example:
server.mount('/assets', WEBrick::HTTPServlet::FileHandler,
'/path/to/assets')
Constant Summary
-
HandlerTable =
Internal use only
# File 'lib/webrick/httpservlet/filehandler.rb', line 176Hash.new
Class Method Summary
-
.add_handler(suffix, handler)
Allow custom handling of requests for files with
suffix
by classhandler
-
.new(server, root, options = {}, default = Config::FileHandler) ⇒ FileHandler
constructor
Creates a
FileHandler
servlet onserver
that serves files starting at directory::
-
.remove_handler(suffix)
Remove custom handling of requests for files with
suffix
AbstractServlet
- Inherited
.get_instance | Factory for servlet instances that will handle a request from |
.new | Initializes a new servlet for |
Instance Method Summary
- #do_GET(req, res) Internal use only
- #do_OPTIONS(req, res) Internal use only
- #do_POST(req, res) Internal use only
- #service(req, res) Internal use only
- #call_callback(callback_name, req, res) private Internal use only
- #check_filename(req, res, name) private Internal use only
- #exec_handler(req, res) private Internal use only
- #get_handler(req, res) private Internal use only
- #nondisclosure_name?(name) ⇒ Boolean private Internal use only
- #prevent_directory_traversal(req, res) private Internal use only
- #search_file(req, res, basename) private Internal use only
- #search_index_file(req, res) private Internal use only
- #set_dir_list(req, res) private Internal use only
- #set_filename(req, res) private Internal use only
- #shift_path_info(req, res, path_info, base = nil) private Internal use only
- #trailing_pathsep?(path) ⇒ Boolean private Internal use only
- #windows_ambiguous_name?(name) ⇒ Boolean private Internal use only
AbstractServlet
- Inherited
#do_GET | Raises a NotFound exception. |
#do_HEAD | Dispatches to do_GET. |
#do_OPTIONS | Returns the allowed HTTP request methods. |
#service | Dispatches to a |
#redirect_to_directory_uri | Redirects to a path ending in /. |
Constructor Details
.new(server, root, options = {}, default = Config::FileHandler) ⇒ FileHandler
Creates a FileHandler
servlet on server
that serves files starting at directory ::
options
may be a Hash containing keys from Config::FileHandler or true
or false
.
If options
is true or false then :FancyIndexing
is enabled or disabled respectively.
Class Method Details
.add_handler(suffix, handler)
Allow custom handling of requests for files with suffix
by class handler
# File 'lib/webrick/httpservlet/filehandler.rb', line 182
def self.add_handler(suffix, handler) HandlerTable[suffix] = handler end
.remove_handler(suffix)
Remove custom handling of requests for files with suffix
# File 'lib/webrick/httpservlet/filehandler.rb', line 189
def self.remove_handler(suffix) HandlerTable.delete(suffix) end
Instance Method Details
#call_callback(callback_name, req, res) (private)
# File 'lib/webrick/httpservlet/filehandler.rb', line 405
def call_callback(callback_name, req, res) if cb = @options[callback_name] cb.call(req, res) end end
#check_filename(req, res, name) (private)
# File 'lib/webrick/httpservlet/filehandler.rb', line 358
def check_filename(req, res, name) if nondisclosure_name?(name) || windows_ambiguous_name?(name) @logger.warn("the request refers nondisclosure name `#{name}'.") raise HTTPStatus::NotFound, "`#{req.path}' not found." end end
#do_GET(req, res)
# File 'lib/webrick/httpservlet/filehandler.rb', line 236
def do_GET(req, res) unless exec_handler(req, res) set_dir_list(req, res) end end
#do_OPTIONS(req, res)
# File 'lib/webrick/httpservlet/filehandler.rb', line 248
def do_OPTIONS(req, res) unless exec_handler(req, res) super(req, res) end end
#do_POST(req, res)
# File 'lib/webrick/httpservlet/filehandler.rb', line 242
def do_POST(req, res) unless exec_handler(req, res) raise HTTPStatus::NotFound, "`#{req.path}' not found." end end
#exec_handler(req, res) (private)
# File 'lib/webrick/httpservlet/filehandler.rb', line 300
def exec_handler(req, res) raise HTTPStatus::NotFound, "`#{req.path}' not found" unless @root if set_filename(req, res) handler = get_handler(req, res) call_callback(:HandlerCallback, req, res) h = handler.get_instance(@config, res.filename) h.service(req, res) return true end call_callback(:HandlerCallback, req, res) return false end
#get_handler(req, res) (private)
# File 'lib/webrick/httpservlet/filehandler.rb', line 313
def get_handler(req, res) suffix1 = (/\.(\w+)\z/ =~ res.filename) && $1.downcase if /\.(\w)\.([\w\-])\z/ =~ res.filename if @options[:AcceptableLanguages].include?($2.downcase) suffix2 = $1.downcase end end handler_table = @options[:HandlerTable] return handler_table[suffix1] || handler_table[suffix2] || HandlerTable[suffix1] || HandlerTable[suffix2] || DefaultFileHandler end
#nondisclosure_name?(name) ⇒ Boolean
(private)
# File 'lib/webrick/httpservlet/filehandler.rb', line 417
def nondisclosure_name?(name) @options[:NondisclosureName].each{|pattern| if File.fnmatch(pattern, name, File::FNM_CASEFOLD) return true end } return false end
#prevent_directory_traversal(req, res) (private)
# File 'lib/webrick/httpservlet/filehandler.rb', line 277
def prevent_directory_traversal(req, res) # Preventing directory traversal on Windows platforms; # Backslashes (0x5c) in path_info are not interpreted as special # character in URI notation. So the value of path_info should be # normalize before accessing to the filesystem. # dirty hack for filesystem encoding; in nature, File.expand_path # should not be used for path normalization. [Bug #3345] path = req.path_info.dup.force_encoding(Encoding.find("filesystem")) if trailing_pathsep?(req.path_info) # File.expand_path removes the trailing path separator. # Adding a character is a workaround to save it. # File.expand_path("/aaa/") #=> "/aaa" # File.expand_path("/aaa/" + "x") #=> "/aaa/x" = File. (path + "x") .chop! # remove trailing "x" else = File. (path) end .force_encoding(req.path_info.encoding) req.path_info = end
#search_file(req, res, basename) (private)
# File 'lib/webrick/httpservlet/filehandler.rb', line 383
def search_file(req, res, basename) langs = @options[:AcceptableLanguages] path = res.filename + basename if File.file?(path) return basename elsif langs.size > 0 req.accept_language.each{|lang| path_with_lang = path + ".#{lang}" if langs.member?(lang) && File.file?(path_with_lang) return basename + ".#{lang}" end } (langs - req.accept_language).each{|lang| path_with_lang = path + ".#{lang}" if File.file?(path_with_lang) return basename + ".#{lang}" end } end return nil end
#search_index_file(req, res) (private)
# File 'lib/webrick/httpservlet/filehandler.rb', line 374
def search_index_file(req, res) @config[:DirectoryIndex].each{|index| if file = search_file(req, res, "/"+index) return file end } return nil end
#service(req, res)
# File 'lib/webrick/httpservlet/filehandler.rb', line 215
def service(req, res) # if this class is mounted on "/" and /~username is requested. # we're going to override path informations before invoking service. if defined?(Etc) && @options[:UserDir] && req.script_name.empty? if %r|^(/~([^/]+))| =~ req.path_info script_name, user = $1, $2 path_info = $' begin passwd = Etc::getpwnam(user) @root = File::join(passwd.dir, @options[:UserDir]) req.script_name = script_name req.path_info = path_info rescue @logger.debug "#{self.class}#do_GET: getpwnam(#{user}) failed" end end end prevent_directory_traversal(req, res) super(req, res) end
#set_dir_list(req, res) (private)
# File 'lib/webrick/httpservlet/filehandler.rb', line 426
def set_dir_list(req, res) redirect_to_directory_uri(req, res) unless @options[:FancyIndexing] raise HTTPStatus::Forbidden, "no access permission to `#{req.path}'" end local_path = res.filename list = Dir::entries(local_path).collect{|name| next if name == "." || name == ".." next if nondisclosure_name?(name) next if windows_ambiguous_name?(name) st = (File::stat(File.join(local_path, name)) rescue nil) if st.nil? [ name, nil, -1 ] elsif st.directory? [ name + "/", st.mtime, -1 ] else [ name, st.mtime, st.size ] end } list.compact! query = req.query d0 = nil idx = nil %w[N M S].each_with_index do |q, i| if d = query.delete(q) idx ||= i d0 ||= d end end d0 ||= "A" idx ||= 0 d1 = (d0 == "A") ? "D" : "A" if d0 == "A" list.sort!{|a,b| a[idx] <=> b[idx] } else list.sort!{|a,b| b[idx] <=> a[idx] } end namewidth = query["NameWidth"] if namewidth == "*" namewidth = nil elsif !namewidth or (namewidth = namewidth.to_i) < 2 namewidth = 25 end query = query.inject('') {|s, (k, v)| s << '&' << HTMLUtils::escape("#{k}=#{v}")} type = "text/html" case enc = Encoding.find('filesystem') when Encoding::US_ASCII, Encoding::ASCII_8BIT else type << "; charset=\"#{enc.name}\"" end res['content-type'] = type title = "Index of #{HTMLUtils::escape(req.path)}" res.body = <<-_end_of_html_ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> <HTML> <HEAD> <TITLE>#{title}</TITLE> <style type="text/css"> <!-- .name, .mtime { text-align: left; } .size { text-align: right; } td { text-overflow: ellipsis; white-space: nowrap; overflow: hidden; } table { border-collapse: collapse; } tr th { border-bottom: 2px groove; } //--> </style> </HEAD> <BODY> <H1>#{title}</H1> _end_of_html_ res.body << "<TABLE width=\"100%\"><THEAD><TR>\n" res.body << "<TH class=\"name\"><A HREF=\"?N=#{d1}#{query}\">Name</A></TH>" res.body << "<TH class=\"mtime\"><A HREF=\"?M=#{d1}#{query}\">Last modified</A></TH>" res.body << "<TH class=\"size\"><A HREF=\"?S=#{d1}#{query}\">Size</A></TH>\n" res.body << "</TR></THEAD>\n" res.body << "<TBODY>\n" query.sub!(/\A&/, '?') list.unshift [ "..", File::mtime(local_path+"/.."), -1 ] list.each{ |name, time, size| if name == ".." dname = "Parent Directory" elsif namewidth and name.size > namewidth dname = name[0...(namewidth - 2)] << '..' else dname = name end s = "<TR><TD class=\"name\"><A HREF=\"#{HTTPUtils::escape(name)}#{query if name.end_with?('/')}\">#{HTMLUtils::escape(dname)}</A></TD>" s << "<TD class=\"mtime\">" << (time ? time.strftime("%Y/%m/%d %H:%M") : "") << "</TD>" s << "<TD class=\"size\">" << (size >= 0 ? size.to_s : "-") << "</TD></TR>\n" res.body << s } res.body << "</TBODY></TABLE>" res.body << "<HR>" res.body << <<-_end_of_html_ <ADDRESS> #{HTMLUtils::escape(@config[:ServerSoftware])}<BR> at #{req.host}:#{req.port} </ADDRESS> </BODY> </HTML> _end_of_html_ end
#set_filename(req, res) (private)
# File 'lib/webrick/httpservlet/filehandler.rb', line 326
def set_filename(req, res) res.filename = @root.dup path_info = req.path_info.scan(%r|/[^/]*|) path_info.unshift("") # dummy for checking @root dir while base = path_info.first break if base == "/" break unless File.directory?(File. (res.filename + base)) shift_path_info(req, res, path_info) call_callback(:DirectoryCallback, req, res) end if base = path_info.first if base == "/" if file = search_index_file(req, res) shift_path_info(req, res, path_info, file) call_callback(:FileCallback, req, res) return true end shift_path_info(req, res, path_info) elsif file = search_file(req, res, base) shift_path_info(req, res, path_info, file) call_callback(:FileCallback, req, res) return true else raise HTTPStatus::NotFound, "`#{req.path}' not found." end end return false end
#shift_path_info(req, res, path_info, base = nil) (private)
# File 'lib/webrick/httpservlet/filehandler.rb', line 365
def shift_path_info(req, res, path_info, base=nil) tmp = path_info.shift base = base || tmp req.path_info = path_info.join req.script_name << base res.filename = File. (res.filename + base) check_filename(req, res, File.basename(res.filename)) end
#trailing_pathsep?(path) ⇒ Boolean
(private)
# File 'lib/webrick/httpservlet/filehandler.rb', line 268
def trailing_pathsep?(path) # check for trailing path separator: # File.dirname("/aaaa/bbbb/") #=> "/aaaa") # File.dirname("/aaaa/bbbb/x") #=> "/aaaa/bbbb") # File.dirname("/aaaa/bbbb") #=> "/aaaa") # File.dirname("/aaaa/bbbbx") #=> "/aaaa") return File.dirname(path) != File.dirname(path+"x") end
#windows_ambiguous_name?(name) ⇒ Boolean
(private)
# File 'lib/webrick/httpservlet/filehandler.rb', line 411
def windows_ambiguous_name?(name) return true if /[. ]+\z/ =~ name return true if /::\$DATA\z/ =~ name return false end