From fcbf63e62c627deae76c1b8cb8c0876c536ed811 Mon Sep 17 00:00:00 2001
From: Jari Vetoniemi <jari.vetoniemi@indooratlas.com>
Date: Mon, 16 Mar 2020 18:49:26 +0900
Subject: Fresh start

---
 jni/ruby/lib/webrick/httpservlet/abstract.rb    | 153 +++++++
 jni/ruby/lib/webrick/httpservlet/cgi_runner.rb  |  46 +++
 jni/ruby/lib/webrick/httpservlet/cgihandler.rb  | 120 ++++++
 jni/ruby/lib/webrick/httpservlet/erbhandler.rb  |  87 ++++
 jni/ruby/lib/webrick/httpservlet/filehandler.rb | 521 ++++++++++++++++++++++++
 jni/ruby/lib/webrick/httpservlet/prochandler.rb |  46 +++
 6 files changed, 973 insertions(+)
 create mode 100644 jni/ruby/lib/webrick/httpservlet/abstract.rb
 create mode 100644 jni/ruby/lib/webrick/httpservlet/cgi_runner.rb
 create mode 100644 jni/ruby/lib/webrick/httpservlet/cgihandler.rb
 create mode 100644 jni/ruby/lib/webrick/httpservlet/erbhandler.rb
 create mode 100644 jni/ruby/lib/webrick/httpservlet/filehandler.rb
 create mode 100644 jni/ruby/lib/webrick/httpservlet/prochandler.rb

(limited to 'jni/ruby/lib/webrick/httpservlet')

diff --git a/jni/ruby/lib/webrick/httpservlet/abstract.rb b/jni/ruby/lib/webrick/httpservlet/abstract.rb
new file mode 100644
index 0000000..d3b00ab
--- /dev/null
+++ b/jni/ruby/lib/webrick/httpservlet/abstract.rb
@@ -0,0 +1,153 @@
+#
+# httpservlet.rb -- HTTPServlet Module
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2000 TAKAHASHI Masayoshi, GOTOU Yuuzou
+# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: abstract.rb,v 1.24 2003/07/11 11:16:46 gotoyuzo Exp $
+
+require 'thread'
+
+require 'webrick/htmlutils'
+require 'webrick/httputils'
+require 'webrick/httpstatus'
+
+module WEBrick
+  module HTTPServlet
+    class HTTPServletError < StandardError; end
+
+    ##
+    # AbstractServlet allows HTTP server modules to be reused across multiple
+    # servers and allows encapsulation of functionality.
+    #
+    # By default a servlet will respond to GET, HEAD (through an alias to GET)
+    # and OPTIONS requests.
+    #
+    # By default a new servlet is initialized for every request.  A servlet
+    # instance can be reused by overriding ::get_instance in the
+    # AbstractServlet subclass.
+    #
+    # == A Simple Servlet
+    #
+    #  class Simple < WEBrick::HTTPServlet::AbstractServlet
+    #    def do_GET request, response
+    #      status, content_type, body = do_stuff_with request
+    #
+    #      response.status = status
+    #      response['Content-Type'] = content_type
+    #      response.body = body
+    #    end
+    #
+    #    def do_stuff_with request
+    #      return 200, 'text/plain', 'you got a page'
+    #    end
+    #  end
+    #
+    # This servlet can be mounted on a server at a given path:
+    #
+    #   server.mount '/simple', Simple
+    #
+    # == Servlet Configuration
+    #
+    # Servlets can be configured via initialize.  The first argument is the
+    # HTTP server the servlet is being initialized for.
+    #
+    #  class Configurable < Simple
+    #    def initialize server, color, size
+    #      super server
+    #      @color = color
+    #      @size = size
+    #    end
+    #
+    #    def do_stuff_with request
+    #      content = "<p " \
+    #                %q{style="color: #{@color}; font-size: #{@size}"} \
+    #                ">Hello, World!"
+    #
+    #      return 200, "text/html", content
+    #    end
+    #  end
+    #
+    # This servlet must be provided two arguments at mount time:
+    #
+    #   server.mount '/configurable', Configurable, 'red', '2em'
+
+    class AbstractServlet
+
+      ##
+      # Factory for servlet instances that will handle a request from +server+
+      # using +options+ from the mount point.  By default a new servlet
+      # instance is created for every call.
+
+      def self.get_instance(server, *options)
+        self.new(server, *options)
+      end
+
+      ##
+      # Initializes a new servlet for +server+ using +options+ which are
+      # stored as-is in +@options+.  +@logger+ is also provided.
+
+      def initialize(server, *options)
+        @server = @config = server
+        @logger = @server[:Logger]
+        @options = options
+      end
+
+      ##
+      # Dispatches to a +do_+ method based on +req+ if such a method is
+      # available.  (+do_GET+ for a GET request).  Raises a MethodNotAllowed
+      # exception if the method is not implemented.
+
+      def service(req, res)
+        method_name = "do_" + req.request_method.gsub(/-/, "_")
+        if respond_to?(method_name)
+          __send__(method_name, req, res)
+        else
+          raise HTTPStatus::MethodNotAllowed,
+                "unsupported method `#{req.request_method}'."
+        end
+      end
+
+      ##
+      # Raises a NotFound exception
+
+      def do_GET(req, res)
+        raise HTTPStatus::NotFound, "not found."
+      end
+
+      ##
+      # Dispatches to do_GET
+
+      def do_HEAD(req, res)
+        do_GET(req, res)
+      end
+
+      ##
+      # Returns the allowed HTTP request methods
+
+      def do_OPTIONS(req, res)
+        m = self.methods.grep(/\Ado_([A-Z]+)\z/) {$1}
+        m.sort!
+        res["allow"] = m.join(",")
+      end
+
+      private
+
+      ##
+      # Redirects to a path ending in /
+
+      def redirect_to_directory_uri(req, res)
+        if req.path[-1] != ?/
+          location = WEBrick::HTTPUtils.escape_path(req.path + "/")
+          if req.query_string && req.query_string.bytesize > 0
+            location << "?" << req.query_string
+          end
+          res.set_redirect(HTTPStatus::MovedPermanently, location)
+        end
+      end
+    end
+
+  end
+end
diff --git a/jni/ruby/lib/webrick/httpservlet/cgi_runner.rb b/jni/ruby/lib/webrick/httpservlet/cgi_runner.rb
new file mode 100644
index 0000000..32ecb6f
--- /dev/null
+++ b/jni/ruby/lib/webrick/httpservlet/cgi_runner.rb
@@ -0,0 +1,46 @@
+#
+# cgi_runner.rb -- CGI launcher.
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2000 TAKAHASHI Masayoshi, GOTOU YUUZOU
+# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: cgi_runner.rb,v 1.9 2002/09/25 11:33:15 gotoyuzo Exp $
+
+def sysread(io, size)
+  buf = ""
+  while size > 0
+    tmp = io.sysread(size)
+    buf << tmp
+    size -= tmp.bytesize
+  end
+  return buf
+end
+
+STDIN.binmode
+
+len = sysread(STDIN, 8).to_i
+out = sysread(STDIN, len)
+STDOUT.reopen(open(out, "w"))
+
+len = sysread(STDIN, 8).to_i
+err = sysread(STDIN, len)
+STDERR.reopen(open(err, "w"))
+
+len  = sysread(STDIN, 8).to_i
+dump = sysread(STDIN, len)
+hash = Marshal.restore(dump)
+ENV.keys.each{|name| ENV.delete(name) }
+hash.each{|k, v| ENV[k] = v if v }
+
+dir = File::dirname(ENV["SCRIPT_FILENAME"])
+Dir::chdir dir
+
+if ARGV[0]
+  argv = ARGV.dup
+  argv << ENV["SCRIPT_FILENAME"]
+  exec(*argv)
+  # NOTREACHED
+end
+exec ENV["SCRIPT_FILENAME"]
diff --git a/jni/ruby/lib/webrick/httpservlet/cgihandler.rb b/jni/ruby/lib/webrick/httpservlet/cgihandler.rb
new file mode 100644
index 0000000..3210041
--- /dev/null
+++ b/jni/ruby/lib/webrick/httpservlet/cgihandler.rb
@@ -0,0 +1,120 @@
+#
+# cgihandler.rb -- CGIHandler Class
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
+# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: cgihandler.rb,v 1.27 2003/03/21 19:56:01 gotoyuzo Exp $
+
+require 'rbconfig'
+require 'tempfile'
+require 'webrick/config'
+require 'webrick/httpservlet/abstract'
+
+module WEBrick
+  module HTTPServlet
+
+    ##
+    # Servlet for handling CGI scripts
+    #
+    # Example:
+    #
+    #  server.mount('/cgi/my_script', WEBrick::HTTPServlet::CGIHandler,
+    #               '/path/to/my_script')
+
+    class CGIHandler < AbstractServlet
+      Ruby = RbConfig.ruby # :nodoc:
+      CGIRunner = "\"#{Ruby}\" \"#{WEBrick::Config::LIBDIR}/httpservlet/cgi_runner.rb\"" # :nodoc:
+
+      ##
+      # Creates a new CGI script servlet for the script at +name+
+
+      def initialize(server, name)
+        super(server, name)
+        @script_filename = name
+        @tempdir = server[:TempDir]
+        @cgicmd = "#{CGIRunner} #{server[:CGIInterpreter]}"
+      end
+
+      # :stopdoc:
+
+      def do_GET(req, res)
+        cgi_in = IO::popen(@cgicmd, "wb")
+        cgi_out = Tempfile.new("webrick.cgiout.", @tempdir, mode: IO::BINARY)
+        cgi_out.set_encoding("ASCII-8BIT")
+        cgi_err = Tempfile.new("webrick.cgierr.", @tempdir, mode: IO::BINARY)
+        cgi_err.set_encoding("ASCII-8BIT")
+        begin
+          cgi_in.sync = true
+          meta = req.meta_vars
+          meta["SCRIPT_FILENAME"] = @script_filename
+          meta["PATH"] = @config[:CGIPathEnv]
+          if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
+            meta["SystemRoot"] = ENV["SystemRoot"]
+          end
+          dump = Marshal.dump(meta)
+
+          cgi_in.write("%8d" % cgi_out.path.bytesize)
+          cgi_in.write(cgi_out.path)
+          cgi_in.write("%8d" % cgi_err.path.bytesize)
+          cgi_in.write(cgi_err.path)
+          cgi_in.write("%8d" % dump.bytesize)
+          cgi_in.write(dump)
+
+          if req.body and req.body.bytesize > 0
+            cgi_in.write(req.body)
+          end
+        ensure
+          cgi_in.close
+          status = $?.exitstatus
+          sleep 0.1 if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
+          data = cgi_out.read
+          cgi_out.close(true)
+          if errmsg = cgi_err.read
+            if errmsg.bytesize > 0
+              @logger.error("CGIHandler: #{@script_filename}:\n" + errmsg)
+            end
+          end
+          cgi_err.close(true)
+        end
+
+        if status != 0
+          @logger.error("CGIHandler: #{@script_filename} exit with #{status}")
+        end
+
+        data = "" unless data
+        raw_header, body = data.split(/^[\xd\xa]+/, 2)
+        raise HTTPStatus::InternalServerError,
+          "Premature end of script headers: #{@script_filename}" if body.nil?
+
+        begin
+          header = HTTPUtils::parse_header(raw_header)
+          if /^(\d+)/ =~ header['status'][0]
+            res.status = $1.to_i
+            header.delete('status')
+          end
+          if header.has_key?('location')
+            # RFC 3875 6.2.3, 6.2.4
+            res.status = 302 unless (300...400) === res.status
+          end
+          if header.has_key?('set-cookie')
+            header['set-cookie'].each{|k|
+              res.cookies << Cookie.parse_set_cookie(k)
+            }
+            header.delete('set-cookie')
+          end
+          header.each{|key, val| res[key] = val.join(", ") }
+        rescue => ex
+          raise HTTPStatus::InternalServerError, ex.message
+        end
+        res.body = body
+      end
+      alias do_POST do_GET
+
+      # :startdoc:
+    end
+
+  end
+end
diff --git a/jni/ruby/lib/webrick/httpservlet/erbhandler.rb b/jni/ruby/lib/webrick/httpservlet/erbhandler.rb
new file mode 100644
index 0000000..1b8a82d
--- /dev/null
+++ b/jni/ruby/lib/webrick/httpservlet/erbhandler.rb
@@ -0,0 +1,87 @@
+#
+# erbhandler.rb -- ERBHandler Class
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
+# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: erbhandler.rb,v 1.25 2003/02/24 19:25:31 gotoyuzo Exp $
+
+require 'webrick/httpservlet/abstract.rb'
+
+require 'erb'
+
+module WEBrick
+  module HTTPServlet
+
+    ##
+    # ERBHandler evaluates an ERB file and returns the result.  This handler
+    # is automatically used if there are .rhtml files in a directory served by
+    # the FileHandler.
+    #
+    # ERBHandler supports GET and POST methods.
+    #
+    # The ERB file is evaluated with the local variables +servlet_request+ and
+    # +servlet_response+ which are a WEBrick::HTTPRequest and
+    # WEBrick::HTTPResponse respectively.
+    #
+    # Example .rhtml file:
+    #
+    #   Request to <%= servlet_request.request_uri %>
+    #
+    #   Query params <%= servlet_request.query.inspect %>
+
+    class ERBHandler < AbstractServlet
+
+      ##
+      # Creates a new ERBHandler on +server+ that will evaluate and serve the
+      # ERB file +name+
+
+      def initialize(server, name)
+        super(server, name)
+        @script_filename = name
+      end
+
+      ##
+      # Handles GET requests
+
+      def do_GET(req, res)
+        unless defined?(ERB)
+          @logger.warn "#{self.class}: ERB not defined."
+          raise HTTPStatus::Forbidden, "ERBHandler cannot work."
+        end
+        begin
+          data = open(@script_filename){|io| io.read }
+          res.body = evaluate(ERB.new(data), req, res)
+          res['content-type'] ||=
+            HTTPUtils::mime_type(@script_filename, @config[:MimeTypes])
+        rescue StandardError
+          raise
+        rescue Exception => ex
+          @logger.error(ex)
+          raise HTTPStatus::InternalServerError, ex.message
+        end
+      end
+
+      ##
+      # Handles POST requests
+
+      alias do_POST do_GET
+
+      private
+
+      ##
+      # Evaluates +erb+ providing +servlet_request+ and +servlet_response+ as
+      # local variables.
+
+      def evaluate(erb, servlet_request, servlet_response)
+        Module.new.module_eval{
+          servlet_request.meta_vars
+          servlet_request.query
+          erb.result(binding)
+        }
+      end
+    end
+  end
+end
diff --git a/jni/ruby/lib/webrick/httpservlet/filehandler.rb b/jni/ruby/lib/webrick/httpservlet/filehandler.rb
new file mode 100644
index 0000000..cc9db4a
--- /dev/null
+++ b/jni/ruby/lib/webrick/httpservlet/filehandler.rb
@@ -0,0 +1,521 @@
+#
+# filehandler.rb -- FileHandler Module
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
+# Copyright (c) 2003 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: filehandler.rb,v 1.44 2003/06/07 01:34:51 gotoyuzo Exp $
+
+require 'thread'
+require 'time'
+
+require 'webrick/htmlutils'
+require 'webrick/httputils'
+require 'webrick/httpstatus'
+
+module WEBrick
+  module HTTPServlet
+
+    ##
+    # Servlet for serving a single file.  You probably want to use the
+    # FileHandler servlet instead as it handles directories and fancy indexes.
+    #
+    # Example:
+    #
+    #   server.mount('/my_page.txt', WEBrick::HTTPServlet::DefaultFileHandler,
+    #                '/path/to/my_page.txt')
+    #
+    # This servlet handles If-Modified-Since and Range requests.
+
+    class DefaultFileHandler < AbstractServlet
+
+      ##
+      # Creates a DefaultFileHandler instance for the file at +local_path+.
+
+      def initialize(server, local_path)
+        super(server, local_path)
+        @local_path = local_path
+      end
+
+      # :stopdoc:
+
+      def do_GET(req, res)
+        st = File::stat(@local_path)
+        mtime = st.mtime
+        res['etag'] = sprintf("%x-%x-%x", st.ino, st.size, st.mtime.to_i)
+
+        if not_modified?(req, res, mtime, res['etag'])
+          res.body = ''
+          raise HTTPStatus::NotModified
+        elsif req['range']
+          make_partial_content(req, res, @local_path, st.size)
+          raise HTTPStatus::PartialContent
+        else
+          mtype = HTTPUtils::mime_type(@local_path, @config[:MimeTypes])
+          res['content-type'] = mtype
+          res['content-length'] = st.size
+          res['last-modified'] = mtime.httpdate
+          res.body = open(@local_path, "rb")
+        end
+      end
+
+      def not_modified?(req, res, mtime, etag)
+        if ir = req['if-range']
+          begin
+            if Time.httpdate(ir) >= mtime
+              return true
+            end
+          rescue
+            if HTTPUtils::split_header_value(ir).member?(res['etag'])
+              return true
+            end
+          end
+        end
+
+        if (ims = req['if-modified-since']) && Time.parse(ims) >= mtime
+          return true
+        end
+
+        if (inm = req['if-none-match']) &&
+           HTTPUtils::split_header_value(inm).member?(res['etag'])
+          return true
+        end
+
+        return false
+      end
+
+      def make_partial_content(req, res, filename, filesize)
+        mtype = HTTPUtils::mime_type(filename, @config[:MimeTypes])
+        unless ranges = HTTPUtils::parse_range_header(req['range'])
+          raise HTTPStatus::BadRequest,
+            "Unrecognized range-spec: \"#{req['range']}\""
+        end
+        open(filename, "rb"){|io|
+          if ranges.size > 1
+            time = Time.now
+            boundary = "#{time.sec}_#{time.usec}_#{Process::pid}"
+            body = ''
+            ranges.each{|range|
+              first, last = prepare_range(range, filesize)
+              next if first < 0
+              io.pos = first
+              content = io.read(last-first+1)
+              body << "--" << boundary << CRLF
+              body << "Content-Type: #{mtype}" << CRLF
+              body << "Content-Range: bytes #{first}-#{last}/#{filesize}" << CRLF
+              body << CRLF
+              body << content
+              body << CRLF
+            }
+            raise HTTPStatus::RequestRangeNotSatisfiable if body.empty?
+            body << "--" << boundary << "--" << CRLF
+            res["content-type"] = "multipart/byteranges; boundary=#{boundary}"
+            res.body = body
+          elsif range = ranges[0]
+            first, last = prepare_range(range, filesize)
+            raise HTTPStatus::RequestRangeNotSatisfiable if first < 0
+            if last == filesize - 1
+              content = io.dup
+              content.pos = first
+            else
+              io.pos = first
+              content = io.read(last-first+1)
+            end
+            res['content-type'] = mtype
+            res['content-range'] = "bytes #{first}-#{last}/#{filesize}"
+            res['content-length'] = last - first + 1
+            res.body = content
+          else
+            raise HTTPStatus::BadRequest
+          end
+        }
+      end
+
+      def prepare_range(range, filesize)
+        first = range.first < 0 ? filesize + range.first : range.first
+        return -1, -1 if first < 0 || first >= filesize
+        last = range.last < 0 ? filesize + range.last : range.last
+        last = filesize - 1 if last >= filesize
+        return first, last
+      end
+
+      # :startdoc:
+    end
+
+    ##
+    # Serves a directory including fancy indexing and a variety of other
+    # options.
+    #
+    # Example:
+    #
+    #   server.mount '/assets', WEBrick::FileHandler, '/path/to/assets'
+
+    class FileHandler < AbstractServlet
+      HandlerTable = Hash.new # :nodoc:
+
+      ##
+      # Allow custom handling of requests for files with +suffix+ by class
+      # +handler+
+
+      def self.add_handler(suffix, handler)
+        HandlerTable[suffix] = handler
+      end
+
+      ##
+      # Remove custom handling of requests for files with +suffix+
+
+      def self.remove_handler(suffix)
+        HandlerTable.delete(suffix)
+      end
+
+      ##
+      # Creates a FileHandler servlet on +server+ that serves files starting
+      # at directory +root+
+      #
+      # +options+ may be a Hash containing keys from
+      # WEBrick::Config::FileHandler or +true+ or +false+.
+      #
+      # If +options+ is true or false then +:FancyIndexing+ is enabled or
+      # disabled respectively.
+
+      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
+
+      # :stopdoc:
+
+      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
+
+      def do_GET(req, res)
+        unless exec_handler(req, res)
+          set_dir_list(req, res)
+        end
+      end
+
+      def do_POST(req, res)
+        unless exec_handler(req, res)
+          raise HTTPStatus::NotFound, "`#{req.path}' not found."
+        end
+      end
+
+      def do_OPTIONS(req, res)
+        unless exec_handler(req, res)
+          super(req, res)
+        end
+      end
+
+      # ToDo
+      # RFC2518: HTTP Extensions for Distributed Authoring -- WEBDAV
+      #
+      # PROPFIND PROPPATCH MKCOL DELETE PUT COPY MOVE
+      # LOCK UNLOCK
+
+      # RFC3253: Versioning Extensions to WebDAV
+      #          (Web Distributed Authoring and Versioning)
+      #
+      # VERSION-CONTROL REPORT CHECKOUT CHECK_IN UNCHECKOUT
+      # MKWORKSPACE UPDATE LABEL MERGE ACTIVITY
+
+      private
+
+      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
+
+      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
+
+      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
+
+      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
+
+      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
+
+      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
+
+      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
+
+      def search_index_file(req, res)
+        @config[:DirectoryIndex].each{|index|
+          if file = search_file(req, res, "/"+index)
+            return file
+          end
+        }
+        return nil
+      end
+
+      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
+
+      def call_callback(callback_name, req, res)
+        if cb = @options[callback_name]
+          cb.call(req, res)
+        end
+      end
+
+      def windows_ambiguous_name?(name)
+        return true if /[. ]+\z/ =~ name
+        return true if /::\$DATA\z/ =~ name
+        return false
+      end
+
+      def nondisclosure_name?(name)
+        @options[:NondisclosureName].each{|pattern|
+          if File.fnmatch(pattern, name, File::FNM_CASEFOLD)
+            return true
+          end
+        }
+        return false
+      end
+
+      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
+
+      # :startdoc:
+    end
+  end
+end
diff --git a/jni/ruby/lib/webrick/httpservlet/prochandler.rb b/jni/ruby/lib/webrick/httpservlet/prochandler.rb
new file mode 100644
index 0000000..2f5aa66
--- /dev/null
+++ b/jni/ruby/lib/webrick/httpservlet/prochandler.rb
@@ -0,0 +1,46 @@
+#
+# prochandler.rb -- ProcHandler Class
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
+# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: prochandler.rb,v 1.7 2002/09/21 12:23:42 gotoyuzo Exp $
+
+require 'webrick/httpservlet/abstract.rb'
+
+module WEBrick
+  module HTTPServlet
+
+    ##
+    # Mounts a proc at a path that accepts a request and response.
+    #
+    # Instead of mounting this servlet with WEBrick::HTTPServer#mount use
+    # WEBrick::HTTPServer#mount_proc:
+    #
+    #   server.mount_proc '/' do |req, res|
+    #     res.body = 'it worked!'
+    #     res.status = 200
+    #   end
+
+    class ProcHandler < AbstractServlet
+      # :stopdoc:
+      def get_instance(server, *options)
+        self
+      end
+
+      def initialize(proc)
+        @proc = proc
+      end
+
+      def do_GET(request, response)
+        @proc.call(request, response)
+      end
+
+      alias do_POST do_GET
+      # :startdoc:
+    end
+
+  end
+end
-- 
cgit v1.2.3-70-g09d2