Serves files from a directory

Methods
A
C
D
E
G
N
P
R
S
T
W
Constants
HandlerTable = Hash.new
 
Class Public methods
add_handler(suffix, handler)

Allow custom handling of requests for files with suffix by class handler

# File ../ruby/lib/webrick/httpservlet/filehandler.rb, line 138
def self.add_handler(suffix, handler)
  HandlerTable[suffix] = handler
end
new(server, root, options={}, default=Config::FileHandler)

Creates a FileHandler servlet on server that serves files starting at directory root

If options is a Hash the following keys are allowed:

:AcceptableLanguages

Array of languages allowed for accept-language

:DirectoryCallback

Allows preprocessing of directory requests

:FancyIndexing

If true, show an index for directories

:FileCallback

Allows preprocessing of file requests

:HandlerCallback

Allows preprocessing of requests

:HandlerTable

Maps file suffixes to file handlers. DefaultFileHandler is used by default but any servlet can be used.

:NondisclosureName

Do not show files matching this array of globs

:UserDir

Directory inside ~user to serve content from for /~user requests. Only works if mounted on /

If options is true or false then :FancyIndexing is enabled or disabled respectively.

# File ../ruby/lib/webrick/httpservlet/filehandler.rb, line 170
def initialize(server, root, options={}, default=Config::FileHandler)
  @config = server.config
  @logger = @config[:Logger]
  @root = File.expand_path(root)
  if options == true || options == false
    options = { :FancyIndexing => options }
  end
  @options = default.dup.update(options)
end
remove_handler(suffix)

Remove custom handling of requests for files with suffix

# File ../ruby/lib/webrick/httpservlet/filehandler.rb, line 145
def self.remove_handler(suffix)
  HandlerTable.delete(suffix)
end
Instance Public methods
do_GET(req, res)
# File ../ruby/lib/webrick/httpservlet/filehandler.rb, line 201
def do_GET(req, res)
  unless exec_handler(req, res)
    set_dir_list(req, res)
  end
end
do_OPTIONS(req, res)
# File ../ruby/lib/webrick/httpservlet/filehandler.rb, line 213
def do_OPTIONS(req, res)
  unless exec_handler(req, res)
    super(req, res)
  end
end
do_POST(req, res)
# File ../ruby/lib/webrick/httpservlet/filehandler.rb, line 207
def do_POST(req, res)
  unless exec_handler(req, res)
    raise HTTPStatus::NotFound, "`#{req.path}' not found."
  end
end
service(req, res)
# File ../ruby/lib/webrick/httpservlet/filehandler.rb, line 180
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
Instance Private methods
call_callback(callback_name, req, res)
# File ../ruby/lib/webrick/httpservlet/filehandler.rb, line 370
def call_callback(callback_name, req, res)
  if cb = @options[callback_name]
    cb.call(req, res)
  end
end
check_filename(req, res, name)
# File ../ruby/lib/webrick/httpservlet/filehandler.rb, line 323
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
exec_handler(req, res)
# File ../ruby/lib/webrick/httpservlet/filehandler.rb, line 265
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)
# File ../ruby/lib/webrick/httpservlet/filehandler.rb, line 278
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)
# File ../ruby/lib/webrick/httpservlet/filehandler.rb, line 382
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)
# File ../ruby/lib/webrick/httpservlet/filehandler.rb, line 242
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"
    expanded = File.expand_path(path + "x")
    expanded.chop!  # remove trailing "x"
  else
    expanded = File.expand_path(path)
  end
  expanded.force_encoding(req.path_info.encoding)
  req.path_info = expanded
end
search_file(req, res, basename)
# File ../ruby/lib/webrick/httpservlet/filehandler.rb, line 348
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)
# File ../ruby/lib/webrick/httpservlet/filehandler.rb, line 339
def search_index_file(req, res)
  @config[:DirectoryIndex].each{|index|
    if file = search_file(req, res, "/"+index)
      return file
    end
  }
  return nil
end
set_dir_list(req, res)
# File ../ruby/lib/webrick/httpservlet/filehandler.rb, line 391
      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!

        if    d0 = req.query["N"]; idx = 0
        elsif d0 = req.query["M"]; idx = 1
        elsif d0 = req.query["S"]; idx = 2
        else  d0 = "A"           ; idx = 0
        end
        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

        res['content-type'] = "text/html"

        res.body = <<-_end_of_html_
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<HTML>
  <HEAD><TITLE>Index of #{HTMLUtils::escape(req.path)}</TITLE></HEAD>
  <BODY>
    <H1>Index of #{HTMLUtils::escape(req.path)}</H1>
        _end_of_html_

        res.body << "<PRE>\n"
        res.body << " <A HREF=\"?N=#{d1}\">Name</A>                          "
        res.body << "<A HREF=\"?M=#{d1}\">Last modified</A>         "
        res.body << "<A HREF=\"?S=#{d1}\">Size</A>\n"
        res.body << "<HR>\n"

        list.unshift [ "..", File::mtime(local_path+"/.."), -1 ]
        list.each{ |name, time, size|
          if name == ".."
            dname = "Parent Directory"
          elsif name.bytesize > 25
            dname = name.sub(/^(.{23})(?:.*)/, '\1..')
          else
            dname = name
          end
          s =  " <A HREF=\"#{HTTPUtils::escape(name)}\">#{HTMLUtils::escape(dname)}</A>"
          s << " " * (30 - dname.bytesize)
          s << (time ? time.strftime("%Y/%m/%d %H:%M      ") : " " * 22)
          s << (size >= 0 ? size.to_s : "-") << "\n"
          res.body << s
        }
        res.body << "</PRE><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)
# File ../ruby/lib/webrick/httpservlet/filehandler.rb, line 291
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.expand_path(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)
# File ../ruby/lib/webrick/httpservlet/filehandler.rb, line 330
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.expand_path(res.filename + base)
  check_filename(req, res, File.basename(res.filename))
end
trailing_pathsep?(path)

RFC3253: Versioning Extensions to WebDAV

(Web Distributed Authoring and Versioning)

VERSION-CONTROL REPORT CHECKOUT CHECK_IN UNCHECKOUT MKWORKSPACE UPDATE LABEL MERGE ACTIVITY

# File ../ruby/lib/webrick/httpservlet/filehandler.rb, line 233
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)
# File ../ruby/lib/webrick/httpservlet/filehandler.rb, line 376
def windows_ambiguous_name?(name)
  return true if /[. ]+\z/ =~ name
  return true if /::\$DATA\z/ =~ name
  return false
end