[前][次][番号順一覧][スレッド一覧][生データ]

yarv-diff:253

From: ko1 atdot.net
Date: 14 Feb 2006 09:20:42 -0000
Subject: [yarv-diff:253] r416 - in trunk: . lib lib/webrick lib/webrick/httpauth lib/webrick/httpservlet

Author: ko1
Date: 2006-02-14 18:20:41 +0900 (Tue, 14 Feb 2006)
New Revision: 416

Added:
   trunk/lib/webrick.rb
   trunk/lib/webrick/
   trunk/lib/webrick/accesslog.rb
   trunk/lib/webrick/cgi.rb
   trunk/lib/webrick/compat.rb
   trunk/lib/webrick/config.rb
   trunk/lib/webrick/cookie.rb
   trunk/lib/webrick/htmlutils.rb
   trunk/lib/webrick/httpauth.rb
   trunk/lib/webrick/httpauth/
   trunk/lib/webrick/httpauth/authenticator.rb
   trunk/lib/webrick/httpauth/basicauth.rb
   trunk/lib/webrick/httpauth/digestauth.rb
   trunk/lib/webrick/httpauth/htdigest.rb
   trunk/lib/webrick/httpauth/htgroup.rb
   trunk/lib/webrick/httpauth/htpasswd.rb
   trunk/lib/webrick/httpauth/userdb.rb
   trunk/lib/webrick/httpproxy.rb
   trunk/lib/webrick/httprequest.rb
   trunk/lib/webrick/httpresponse.rb
   trunk/lib/webrick/https.rb
   trunk/lib/webrick/httpserver.rb
   trunk/lib/webrick/httpservlet.rb
   trunk/lib/webrick/httpservlet/
   trunk/lib/webrick/httpservlet/abstract.rb
   trunk/lib/webrick/httpservlet/cgi_runner.rb
   trunk/lib/webrick/httpservlet/cgihandler.rb
   trunk/lib/webrick/httpservlet/erbhandler.rb
   trunk/lib/webrick/httpservlet/filehandler.rb
   trunk/lib/webrick/httpservlet/prochandler.rb
   trunk/lib/webrick/httpstatus.rb
   trunk/lib/webrick/httputils.rb
   trunk/lib/webrick/httpversion.rb
   trunk/lib/webrick/log.rb
   trunk/lib/webrick/server.rb
   trunk/lib/webrick/ssl.rb
   trunk/lib/webrick/utils.rb
   trunk/lib/webrick/version.rb
Modified:
   trunk/
   trunk/ChangeLog
   trunk/test.rb
   trunk/thread.c
Log:
 r623@lermite:  ko1 | 2006-02-14 16:00:28 +0900
 	* thread.c : Change Thread.critical warnning message
 
 	* lib/webrick.rb : imported from ruby 1.9
 
 	* lib/webrick/accesslog.rb : ditto
 
 	* lib/webrick/cgi.rb : ditto
 
 	* lib/webrick/compat.rb : ditto
 
 	* lib/webrick/config.rb : ditto
 
 	* lib/webrick/cookie.rb : ditto
 
 	* lib/webrick/htmlutils.rb : ditto
 
 	* lib/webrick/httpauth.rb : ditto
 
 	* lib/webrick/httpauth/authenticator.rb : ditto
 
 	* lib/webrick/httpauth/basicauth.rb : ditto
 
 	* lib/webrick/httpauth/digestauth.rb : ditto
 
 	* lib/webrick/httpauth/htdigest.rb : ditto
 
 	* lib/webrick/httpauth/htgroup.rb : ditto
 
 	* lib/webrick/httpauth/htpasswd.rb : ditto
 
 	* lib/webrick/httpauth/userdb.rb : ditto
 
 	* lib/webrick/httpproxy.rb : ditto
 
 	* lib/webrick/httprequest.rb : ditto
 
 	* lib/webrick/httpresponse.rb : ditto
 
 	* lib/webrick/https.rb : ditto
 
 	* lib/webrick/httpserver.rb : ditto
 
 	* lib/webrick/httpservlet.rb : ditto
 
 	* lib/webrick/httpservlet/abstract.rb : ditto
 
 	* lib/webrick/httpservlet/cgi_runner.rb : ditto
 
 	* lib/webrick/httpservlet/cgihandler.rb : ditto
 
 	* lib/webrick/httpservlet/erbhandler.rb : ditto
 
 	* lib/webrick/httpservlet/filehandler.rb : ditto
 
 	* lib/webrick/httpservlet/prochandler.rb : ditto
 
 	* lib/webrick/httpstatus.rb : ditto
 
 	* lib/webrick/httputils.rb : ditto
 
 	* lib/webrick/httpversion.rb : ditto
 
 	* lib/webrick/log.rb : ditto
 
 	* lib/webrick/server.rb : ditto
 
 	* lib/webrick/ssl.rb : ditto
 
 	* lib/webrick/utils.rb : ditto
 
 	* lib/webrick/version.rb : ditto
 



Property changes on: trunk
___________________________________________________________________
Name: svk:merge
   - 81cd9672-7512-7e48-ae48-6936450e977d:/local/yarv/trunk:621
   + 81cd9672-7512-7e48-ae48-6936450e977d:/local/yarv/trunk:623

Modified: trunk/ChangeLog
===================================================================
--- trunk/ChangeLog	2006-02-14 05:59:01 UTC (rev 415)
+++ trunk/ChangeLog	2006-02-14 09:20:41 UTC (rev 416)
@@ -4,6 +4,81 @@
 #  from Mon, 03 May 2004 01:24:19 +0900
 #
 
+2006-02-14(Tue) 15:59:28 +0900  Koichi Sasada  <ko1 atdot.net>
+
+	* thread.c : Change Thread.critical warnning message
+
+	* lib/webrick.rb : imported from ruby 1.9
+
+	* lib/webrick/accesslog.rb : ditto
+
+	* lib/webrick/cgi.rb : ditto
+
+	* lib/webrick/compat.rb : ditto
+
+	* lib/webrick/config.rb : ditto
+
+	* lib/webrick/cookie.rb : ditto
+
+	* lib/webrick/htmlutils.rb : ditto
+
+	* lib/webrick/httpauth.rb : ditto
+
+	* lib/webrick/httpauth/authenticator.rb : ditto
+
+	* lib/webrick/httpauth/basicauth.rb : ditto
+
+	* lib/webrick/httpauth/digestauth.rb : ditto
+
+	* lib/webrick/httpauth/htdigest.rb : ditto
+
+	* lib/webrick/httpauth/htgroup.rb : ditto
+
+	* lib/webrick/httpauth/htpasswd.rb : ditto
+
+	* lib/webrick/httpauth/userdb.rb : ditto
+
+	* lib/webrick/httpproxy.rb : ditto
+
+	* lib/webrick/httprequest.rb : ditto
+
+	* lib/webrick/httpresponse.rb : ditto
+
+	* lib/webrick/https.rb : ditto
+
+	* lib/webrick/httpserver.rb : ditto
+
+	* lib/webrick/httpservlet.rb : ditto
+
+	* lib/webrick/httpservlet/abstract.rb : ditto
+
+	* lib/webrick/httpservlet/cgi_runner.rb : ditto
+
+	* lib/webrick/httpservlet/cgihandler.rb : ditto
+
+	* lib/webrick/httpservlet/erbhandler.rb : ditto
+
+	* lib/webrick/httpservlet/filehandler.rb : ditto
+
+	* lib/webrick/httpservlet/prochandler.rb : ditto
+
+	* lib/webrick/httpstatus.rb : ditto
+
+	* lib/webrick/httputils.rb : ditto
+
+	* lib/webrick/httpversion.rb : ditto
+
+	* lib/webrick/log.rb : ditto
+
+	* lib/webrick/server.rb : ditto
+
+	* lib/webrick/ssl.rb : ditto
+
+	* lib/webrick/utils.rb : ditto
+
+	* lib/webrick/version.rb : ditto
+
+
 2006-02-14(Tue) 14:55:51 +0900  Koichi Sasada  <ko1 atdot.net>
 
 	* compile.c, insns.def : support "defined?($1)", ...

Added: trunk/lib/webrick/accesslog.rb
===================================================================
--- trunk/lib/webrick/accesslog.rb	2006-02-14 05:59:01 UTC (rev 415)
+++ trunk/lib/webrick/accesslog.rb	2006-02-14 09:20:41 UTC (rev 416)
@@ -0,0 +1,67 @@
+#
+# accesslog.rb -- Access log handling utilities
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2002 keita yamaguchi
+# Copyright (c) 2002 Internet Programming with Ruby writers
+#
+# $IPR: accesslog.rb,v 1.1 2002/10/01 17:16:32 gotoyuzo Exp $
+
+module WEBrick
+  module AccessLog
+    class AccessLogError < StandardError; end
+
+    CLF_TIME_FORMAT     = "[%d/%b/%Y:%H:%M:%S %Z]"
+    COMMON_LOG_FORMAT   = "%h %l %u %t \"%r\" %s %b"
+    CLF                 = COMMON_LOG_FORMAT
+    REFERER_LOG_FORMAT  = "%{Referer}i -> %U"
+    AGENT_LOG_FORMAT    = "%{User-Agent}i"
+    COMBINED_LOG_FORMAT = "#{CLF} \"%{Referer}i\" \"%{User-agent}i\""
+
+    module_function
+
+    # This format specification is a subset of mod_log_config of Apache.
+    #   http://httpd.apache.org/docs/mod/mod_log_config.html#formats
+    def setup_params(config, req, res)
+      params = Hash.new("")
+      params["a"] = req.peeraddr[3]
+      params["b"] = res.sent_size
+      params["e"] = ENV
+      params["f"] = res.filename || ""
+      params["h"] = req.peeraddr[2]
+      params["i"] = req
+      params["l"] = "-"
+      params["m"] = req.request_method
+      params["n"] = req.attributes
+      params["o"] = res
+      params["p"] = req.port
+      params["q"] = req.query_string
+      params["r"] = req.request_line.sub(/\x0d?\x0a\z/o, '')
+      params["s"] = res.status       # won't support "%>s"
+      params["t"] = req.request_time
+      params["T"] = Time.now - req.request_time
+      params["u"] = req.user || "-"
+      params["U"] = req.unparsed_uri
+      params["v"] = config[:ServerName]
+      params
+    end
+
+    def format(format_string, params)
+      format_string.gsub(/\%(?:\{(.*?)\})?>?([a-zA-Z%])/){
+         param, spec = $1, $2
+         case spec[0]
+         when ?e, ?i, ?n, ?o
+           raise AccessLogError,
+             "parameter is required for \"#{spec}\"" unless param
+           params[spec][param] || "-"
+         when ?t
+           params[spec].strftime(param || CLF_TIME_FORMAT)
+         when ?%
+           "%"
+         else
+           params[spec]
+         end
+      }
+    end
+  end
+end

Added: trunk/lib/webrick/cgi.rb
===================================================================
--- trunk/lib/webrick/cgi.rb	2006-02-14 05:59:01 UTC (rev 415)
+++ trunk/lib/webrick/cgi.rb	2006-02-14 09:20:41 UTC (rev 416)
@@ -0,0 +1,256 @@
+#
+# cgi.rb -- Yet another CGI library
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2003 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $Id: cgi.rb,v 1.14 2005/09/28 06:14:30 gotoyuzo Exp $
+
+require "webrick/httprequest"
+require "webrick/httpresponse"
+require "webrick/config"
+require "stringio"
+
+module WEBrick
+  class CGI
+    CGIError = Class.new(StandardError)
+
+    attr_reader :config, :logger
+
+    def initialize(*args)
+      if defined?(MOD_RUBY)
+        unless ENV.has_key?("GATEWAY_INTERFACE")
+          Apache.request.setup_cgi_env
+        end
+      end
+      if %r{HTTP/(\d+\.\d+)} =~ ENV["SERVER_PROTOCOL"]
+        httpv = $1
+      end
+      @config = WEBrick::Config::HTTP.dup.update(
+        :ServerSoftware => ENV["SERVER_SOFTWARE"] || "null",
+        :HTTPVersion    => HTTPVersion.new(httpv || "1.0"),
+        :RunOnCGI       => true,   # to detect if it runs on CGI.
+        :NPH            => false   # set true to run as NPH script.
+      )
+      if config = args.shift
+        @config.update(config)
+      end
+      @config[:Logger] ||= WEBrick::BasicLog.new($stderr)
+      @logger = @config[:Logger]
+      @options = args
+    end
+
+    def [](key)
+      @config[key]
+    end
+
+    def start(env=ENV, stdin=$stdin, stdout=$stdout)
+      sock = WEBrick::CGI::Socket.new(@config, env, stdin, stdout)
+      req = HTTPRequest.new(@config)
+      res = HTTPResponse.new(@config)
+      unless @config[:NPH] or defined?(MOD_RUBY)
+        def res.setup_header
+          unless @header["status"]
+            phrase = HTTPStatus::reason_phrase(@status)
+            @header["status"] = "#{@status} #{phrase}"
+          end
+          super
+        end
+        def res.status_line
+          ""
+        end
+      end
+
+      begin
+        req.parse(sock)
+        req.script_name = (env["SCRIPT_NAME"] || File.expand_path($0)).dup
+        req.path_info = (env["PATH_INFO"] || "").dup
+        req.query_string = env["QUERY_STRING"]
+        req.user = env["REMOTE_USER"]
+        res.request_method = req.request_method
+        res.request_uri = req.request_uri
+        res.request_http_version = req.http_version
+        res.keep_alive = req.keep_alive?
+        self.service(req, res)
+      rescue HTTPStatus::Error => ex
+        res.set_error(ex)
+      rescue HTTPStatus::Status => ex
+        res.status = ex.code
+      rescue Exception => ex 
+        @logger.error(ex)
+        res.set_error(ex, true)
+      ensure
+        req.fixup
+        if defined?(MOD_RUBY)
+          res.setup_header
+          Apache.request.status_line = "#{res.status} #{res.reason_phrase}"
+          Apache.request.status = res.status
+          table = Apache.request.headers_out
+          res.header.each{|key, val|
+            case key
+            when /^content-encoding$/i
+              Apache::request.content_encoding = val
+            when /^content-type$/i
+              Apache::request.content_type = val
+            else
+              table[key] = val.to_s
+            end
+          }
+          res.cookies.each{|cookie|
+            table.add("Set-Cookie", cookie.to_s)
+          }
+          Apache.request.send_http_header
+          res.send_body(sock)
+        else
+          res.send_response(sock)
+        end
+      end
+    end
+
+    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
+
+    class Socket
+      include Enumerable
+
+      private
+  
+      def initialize(config, env, stdin, stdout)
+        @config = config
+        @env = env
+        @header_part = StringIO.new
+        @body_part = stdin
+        @out_port = stdout
+        @out_port.binmode
+  
+        @server_addr = @env["SERVER_ADDR"] || "0.0.0.0"
+        @server_name = @env["SERVER_NAME"]
+        @server_port = @env["SERVER_PORT"]
+        @remote_addr = @env["REMOTE_ADDR"]
+        @remote_host = @env["REMOTE_HOST"] || @remote_addr
+        @remote_port = @env["REMOTE_PORT"] || 0
+
+        begin
+          @header_part << request_line << CRLF
+          setup_header
+          @header_part << CRLF
+          @header_part.rewind
+        rescue Exception => ex
+          raise CGIError, "invalid CGI environment"
+        end
+      end
+
+      def request_line
+        meth = @env["REQUEST_METHOD"] || "GET"
+        unless url = @env["REQUEST_URI"]
+          url = (@env["SCRIPT_NAME"] || File.expand_path($0)).dup
+          url << @env["PATH_INFO"].to_s
+          url = WEBrick::HTTPUtils.escape_path(url)
+          if query_string = @env["QUERY_STRING"]
+            unless query_string.empty?
+              url << "?" << query_string
+            end
+          end
+        end
+        # we cannot get real HTTP version of client ;)
+        httpv = @config[:HTTPVersion]
+        return "#{meth} #{url} HTTP/#{httpv}"
+      end
+  
+      def setup_header
+        @env.each{|key, value|
+          case key
+          when "CONTENT_TYPE", "CONTENT_LENGTH"
+            add_header(key.gsub(/_/, "-"), value)
+          when /^HTTP_(.*)/
+            add_header($1.gsub(/_/, "-"), value)
+          end
+        }
+      end
+  
+      def add_header(hdrname, value)
+        unless value.empty?
+          @header_part << hdrname << ": " << value << CRLF
+        end
+      end
+
+      def input
+        @header_part.eof? ? @body_part : @header_part
+      end
+  
+      public
+  
+      def peeraddr
+        [nil, @remote_port, @remote_host, @remote_addr]
+      end
+  
+      def addr
+        [nil, @server_port, @server_name, @server_addr]
+      end
+  
+      def gets(eol=LF)
+        input.gets(eol)
+      end
+  
+      def read(size=nil)
+        input.read(size)
+      end
+
+      def each
+        input.each{|line| yield(line) }
+      end
+  
+      def <<(data)
+        @out_port << data
+      end
+
+      def cert
+        return nil unless defined?(OpenSSL)
+        if pem = @env["SSL_SERVER_CERT"]
+          OpenSSL::X509::Certificate.new(pem) unless pem.empty?
+        end
+      end
+
+      def peer_cert
+        return nil unless defined?(OpenSSL)
+        if pem = @env["SSL_CLIENT_CERT"]
+          OpenSSL::X509::Certificate.new(pem) unless pem.empty?
+        end
+      end
+
+      def peer_cert_chain
+        return nil unless defined?(OpenSSL)
+        if @env["SSL_CLIENT_CERT_CHAIN_0"]
+          keys = @env.keys
+          certs = keys.sort.collect{|k|
+            if /^SSL_CLIENT_CERT_CHAIN_\d+$/ =~ k
+              if pem = @env[k]
+                OpenSSL::X509::Certificate.new(pem) unless pem.empty?
+              end
+            end
+          }
+          certs.compact
+        end
+      end
+
+      def cipher
+        return nil unless defined?(OpenSSL)
+        if cipher = @env["SSL_CIPHER"]
+          ret = [ cipher ]
+          ret << @env["SSL_PROTOCOL"]
+          ret << @env["SSL_CIPHER_USEKEYSIZE"]
+          ret << @env["SSL_CIPHER_ALGKEYSIZE"]
+          ret
+        end
+      end
+    end
+  end 
+end  

Added: trunk/lib/webrick/compat.rb
===================================================================
--- trunk/lib/webrick/compat.rb	2006-02-14 05:59:01 UTC (rev 415)
+++ trunk/lib/webrick/compat.rb	2006-02-14 09:20:41 UTC (rev 416)
@@ -0,0 +1,15 @@
+#
+# compat.rb -- cross platform compatibility
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2002 GOTOU Yuuzou
+# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: compat.rb,v 1.6 2002/10/01 17:16:32 gotoyuzo Exp $
+
+module Errno
+  class EPROTO       < SystemCallError; end
+  class ECONNRESET   < SystemCallError; end
+  class ECONNABORTED < SystemCallError; end
+end

Added: trunk/lib/webrick/config.rb
===================================================================
--- trunk/lib/webrick/config.rb	2006-02-14 05:59:01 UTC (rev 415)
+++ trunk/lib/webrick/config.rb	2006-02-14 09:20:41 UTC (rev 416)
@@ -0,0 +1,97 @@
+#
+# config.rb -- Default configurations.
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
+# Copyright (c) 2003 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: config.rb,v 1.52 2003/07/22 19:20:42 gotoyuzo Exp $
+
+require 'webrick/version'
+require 'webrick/httpversion'
+require 'webrick/httputils'
+require 'webrick/utils'
+require 'webrick/log'
+
+module WEBrick
+  module Config
+    LIBDIR = File::dirname(__FILE__)
+
+    # for GenericServer
+    General = {
+      :ServerName     => Utils::getservername,
+      :BindAddress    => nil,   # "0.0.0.0" or "::" or nil
+      :Port           => nil,   # users MUST specifiy this!!
+      :MaxClients     => 100,   # maximum number of the concurrent connections
+      :ServerType     => nil,   # default: WEBrick::SimpleServer
+      :Logger         => nil,   # default: WEBrick::Log.new
+      :ServerSoftware => "WEBrick/#{WEBrick::VERSION} " +
+                         "(Ruby/#{RUBY_VERSION}/#{RUBY_RELEASE_DATE})",
+      :TempDir        => ENV['TMPDIR']||ENV['TMP']||ENV['TEMP']||'/tmp',
+      :DoNotListen    => false,
+      :StartCallback  => nil,
+      :StopCallback   => nil,
+      :AcceptCallback => nil,
+      :DoNotReverseLookup => nil,
+    }
+
+    # for HTTPServer, HTTPRequest, HTTPResponse ...
+    HTTP = General.dup.update(
+      :Port           => 80,
+      :RequestTimeout => 30,
+      :HTTPVersion    => HTTPVersion.new("1.1"),
+      :AccessLog      => nil,
+      :MimeTypes      => HTTPUtils::DefaultMimeTypes,
+      :DirectoryIndex => ["index.html","index.htm","index.cgi","index.rhtml"],
+      :DocumentRoot   => nil,
+      :DocumentRootOptions => { :FancyIndexing => true },
+      :RequestCallback => nil,
+      :ServerAlias    => nil,
+
+      # for HTTPProxyServer
+      :ProxyAuthProc  => nil,
+      :ProxyContentHandler => nil,
+      :ProxyVia       => true,
+      :ProxyTimeout   => true,
+      :ProxyURI       => nil,
+
+      :CGIInterpreter => nil,
+      :CGIPathEnv     => nil,
+
+      # workaround: if Request-URIs contain 8bit chars,
+      # they should be escaped before calling of URI::parse().
+      :Escape8bitURI  => false
+    )
+
+    FileHandler = {
+      :NondisclosureName => [".ht*", "*~"],
+      :FancyIndexing     => false,
+      :HandlerTable      => {},
+      :HandlerCallback   => nil,
+      :DirectoryCallback => nil,
+      :FileCallback      => nil,
+      :UserDir           => nil,  # e.g. "public_html"
+      :AcceptableLanguages => []  # ["en", "ja", ... ]
+    }
+
+    BasicAuth = {
+      :AutoReloadUserDB     => true,
+    }
+
+    DigestAuth = {
+      :Algorithm            => 'MD5-sess', # or 'MD5' 
+      :Domain               => nil,        # an array includes domain names.
+      :Qop                  => [ 'auth' ], # 'auth' or 'auth-int' or both.
+      :UseOpaque            => true,
+      :UseNextNonce         => false,
+      :CheckNc              => false,
+      :UseAuthenticationInfoHeader => true,
+      :AutoReloadUserDB     => true,
+      :NonceExpirePeriod    => 30*60,
+      :NonceExpireDelta     => 60,
+      :InternetExplorerHack => true,
+      :OperaHack            => true,
+    }
+  end
+end

Added: trunk/lib/webrick/cookie.rb
===================================================================
--- trunk/lib/webrick/cookie.rb	2006-02-14 05:59:01 UTC (rev 415)
+++ trunk/lib/webrick/cookie.rb	2006-02-14 09:20:41 UTC (rev 416)
@@ -0,0 +1,104 @@
+#
+# cookie.rb -- Cookie class
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
+# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: cookie.rb,v 1.16 2002/09/21 12:23:35 gotoyuzo Exp $
+
+require 'time'
+require 'webrick/httputils'
+
+module WEBrick
+  class Cookie
+
+    attr_reader :name
+    attr_accessor :value, :version
+    attr_accessor :domain, :path, :secure
+    attr_accessor :comment, :max_age
+    #attr_accessor :comment_url, :discard, :port
+
+    def initialize(name, value)
+      @name = name
+      @value = value
+      @version = 0     # Netscape Cookie
+
+      @domain = @path = @secure = @comment = @max_age =
+      @expires = @comment_url = @discard = @port = nil
+    end
+
+    def expires=(t)
+      @expires = t && (t.is_a?(Time) ? t.httpdate : t.to_s)
+    end
+
+    def expires
+      @expires && Time.parse(@expires)
+    end
+
+    def to_s
+      ret = ""
+      ret << @name << "=" << @value
+      ret << "; " << "Version=" << @version.to_s if @version > 0
+      ret << "; " << "Domain="  << @domain  if @domain
+      ret << "; " << "Expires=" << @expires if @expires
+      ret << "; " << "Max-Age=" << @max_age.to_s if @max_age
+      ret << "; " << "Comment=" << @comment if @comment
+      ret << "; " << "Path="    << @path if @path
+      ret << "; " << "Secure"   if @secure
+      ret
+    end
+
+    # Cookie::parse()
+    #   It parses Cookie field sent from the user agent.
+    def self.parse(str)
+      if str
+        ret = []
+        cookie = nil
+        ver = 0
+        str.split(/[;,]\s+/).each{|x|
+          key, val = x.split(/=/,2)
+          val = val ? HTTPUtils::dequote(val) : ""
+          case key
+          when "$Version"; ver = val.to_i
+          when "$Path";    cookie.path = val
+          when "$Domain";  cookie.domain = val
+          when "$Port";    cookie.port = val
+          else
+            ret << cookie if cookie
+            cookie = self.new(key, val)
+            cookie.version = ver
+          end
+        }
+        ret << cookie if cookie
+        ret
+      end
+    end
+
+    def self.parse_set_cookie(str)
+      cookie_elem = str.split(/;/)
+      first_elem = cookie_elem.shift
+      first_elem.strip!
+      key, value = first_elem.split(/=/, 2)
+      cookie = new(key, HTTPUtils.dequote(value))
+      cookie_elem.each{|pair|
+        pair.strip!
+        key, value = pair.split(/=/, 2)
+        if value
+          value = HTTPUtils.dequote(value.strip)
+        end
+        case key.downcase
+        when "domain"  then cookie.domain  = value
+        when "path"    then cookie.path    = value
+        when "expires" then cookie.expires = value
+        when "max-age" then cookie.max_age = Integer(value)
+        when "comment" then cookie.comment = value
+        when "version" then cookie.version = Integer(value)
+        when "secure"  then cookie.secure = true
+        end
+      }
+      return cookie
+    end
+  end
+end

Added: trunk/lib/webrick/htmlutils.rb
===================================================================
--- trunk/lib/webrick/htmlutils.rb	2006-02-14 05:59:01 UTC (rev 415)
+++ trunk/lib/webrick/htmlutils.rb	2006-02-14 09:20:41 UTC (rev 416)
@@ -0,0 +1,25 @@
+#
+# htmlutils.rb -- HTMLUtils Module
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
+# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: htmlutils.rb,v 1.7 2002/09/21 12:23:35 gotoyuzo Exp $
+
+module WEBrick
+  module HTMLUtils
+
+    def escape(string)
+      str = string ? string.dup : ""
+      str.gsub!(/&/n, '&amp;')
+      str.gsub!(/\"/n, '&quot;')
+      str.gsub!(/>/n, '&gt;')
+      str.gsub!(/</n, '&lt;')
+      str
+    end
+    module_function :escape
+
+  end
+end

Added: trunk/lib/webrick/httpauth/authenticator.rb
===================================================================
--- trunk/lib/webrick/httpauth/authenticator.rb	2006-02-14 05:59:01 UTC (rev 415)
+++ trunk/lib/webrick/httpauth/authenticator.rb	2006-02-14 09:20:41 UTC (rev 416)
@@ -0,0 +1,79 @@
+#
+# httpauth/authenticator.rb -- Authenticator mix-in module.
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2003 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: authenticator.rb,v 1.3 2003/02/20 07:15:47 gotoyuzo Exp $
+
+module WEBrick
+  module HTTPAuth
+    module Authenticator
+      RequestField      = "Authorization"
+      ResponseField     = "WWW-Authenticate"
+      ResponseInfoField = "Authentication-Info"
+      AuthException     = HTTPStatus::Unauthorized
+      AuthScheme        = nil # must override by the derived class
+
+      attr_reader :realm, :userdb, :logger
+
+      private
+
+      def check_init(config)
+        [:UserDB, :Realm].each{|sym|
+          unless config[sym]
+            raise ArgumentError, "Argument #{sym.inspect} missing."
+          end
+        } 
+        @realm     = config[:Realm]
+        @userdb    = config[:UserDB]
+        @logger    = config[:Logger] || Log::new($stderr)
+        @reload_db = config[:AutoReloadUserDB]
+        @request_field   = self::class::RequestField
+        @response_field  = self::class::ResponseField
+        @resp_info_field = self::class::ResponseInfoField
+        @auth_exception  = self::class::AuthException
+        @auth_scheme     = self::class::AuthScheme
+      end
+
+      def check_scheme(req)
+        unless credentials = req[@request_field]
+          error("no credentials in the request.")
+          return nil 
+        end  
+        unless match = /^#{ auth_scheme}\s+/.match(credentials)
+          error("invalid scheme in %s.", credentials)
+          info("%s: %s", @request_field, credentials) if $DEBUG
+          return nil
+        end
+        return match.post_match
+      end
+
+      def log(meth, fmt, *args)
+        msg = format("%s %s: ", @auth_scheme, @realm)
+        msg << fmt % args
+        @logger.send(meth, msg)
+      end
+
+      def error(fmt, *args)
+        if @logger.error?
+          log(:error, fmt, *args)
+        end
+      end                             
+
+      def info(fmt, *args)
+        if @logger.info?
+          log(:info, fmt, *args)
+        end
+      end
+    end
+
+    module ProxyAuthenticator
+      RequestField  = "Proxy-Authorization"
+      ResponseField = "Proxy-Authenticate"
+      InfoField     = "Proxy-Authentication-Info"
+      AuthException = HTTPStatus::ProxyAuthenticationRequired
+    end
+  end
+end

Added: trunk/lib/webrick/httpauth/basicauth.rb
===================================================================
--- trunk/lib/webrick/httpauth/basicauth.rb	2006-02-14 05:59:01 UTC (rev 415)
+++ trunk/lib/webrick/httpauth/basicauth.rb	2006-02-14 09:20:41 UTC (rev 416)
@@ -0,0 +1,65 @@
+#
+# httpauth/basicauth.rb -- HTTP basic access authentication
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2003 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: basicauth.rb,v 1.5 2003/02/20 07:15:47 gotoyuzo Exp $
+
+require 'webrick/config'
+require 'webrick/httpstatus'
+require 'webrick/httpauth/authenticator'
+
+module WEBrick
+  module HTTPAuth
+    class BasicAuth
+      include Authenticator
+
+      AuthScheme = "Basic"
+
+      def self.make_passwd(realm, user, pass)
+        pass ||= ""
+        pass.crypt(Utils::random_string(2))
+      end
+
+      attr_reader :realm, :userdb, :logger
+
+      def initialize(config, default=Config::BasicAuth)
+        check_init(config)
+        @config = default.dup.update(config)
+      end
+
+      def authenticate(req, res)
+        unless basic_credentials = check_scheme(req)
+          challenge(req, res)
+        end
+        userid, password = basic_credentials.unpack("m*")[0].split(":", 2)
+        password ||= ""
+        if userid.empty?
+          error("user id was not given.")
+          challenge(req, res)
+        end
+        unless encpass = @userdb.get_passwd(@realm, userid, @reload_db)
+          error("%s: the user is not allowed.", userid)
+          challenge(req, res)
+        end
+        if password.crypt(encpass) != encpass
+          error("%s: password unmatch.", userid)
+          challenge(req, res)
+        end
+        info("%s: authentication succeeded.", userid)
+        req.user = userid
+      end
+
+      def challenge(req, res)
+        res[@response_field] = "#{@auth_scheme} realm=\"#{@realm}\""
+        raise @auth_exception
+      end
+    end
+
+    class ProxyBasicAuth < BasicAuth
+      include ProxyAuthenticator
+    end
+  end
+end

Added: trunk/lib/webrick/httpauth/digestauth.rb
===================================================================
--- trunk/lib/webrick/httpauth/digestauth.rb	2006-02-14 05:59:01 UTC (rev 415)
+++ trunk/lib/webrick/httpauth/digestauth.rb	2006-02-14 09:20:41 UTC (rev 416)
@@ -0,0 +1,344 @@
+#
+# httpauth/digestauth.rb -- HTTP digest access authentication
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2003 Internet Programming with Ruby writers.
+# Copyright (c) 2003 H.M.
+#
+# The original implementation is provided by H.M.
+#   URL: http://rwiki.jin.gr.jp/cgi-bin/rw-cgi.rb?cmd=view;name=
+#        %C7%A7%BE%DA%B5%A1%C7%BD%A4%F2%B2%FE%C2%A4%A4%B7%A4%C6%A4%DF%A4%EB
+#
+# $IPR: digestauth.rb,v 1.5 2003/02/20 07:15:47 gotoyuzo Exp $
+
+require 'webrick/config'
+require 'webrick/httpstatus'
+require 'webrick/httpauth/authenticator'
+require 'digest/md5'
+require 'digest/sha1'
+
+module WEBrick
+  module HTTPAuth
+    class DigestAuth
+      include Authenticator
+
+      AuthScheme = "Digest"
+      OpaqueInfo = Struct.new(:time, :nonce, :nc)
+      attr_reader :algorithm, :qop
+
+      def self.make_passwd(realm, user, pass)
+        pass ||= ""
+        Digest::MD5::hexdigest([user, realm, pass].join(":"))
+      end
+
+      def initialize(config, default=Config::DigestAuth)
+        check_init(config)
+        @config                 = default.dup.update(config)
+        @algorithm              = @config[:Algorithm]
+        @domain                 = @config[:Domain]
+        @qop                    = @config[:Qop]
+        @use_opaque             = @config[:UseOpaque]
+        @use_next_nonce         = @config[:UseNextNonce]
+        @check_nc               = @config[:CheckNc]
+        @use_auth_info_header   = @config[:UseAuthenticationInfoHeader]
+        @nonce_expire_period    = @config[:NonceExpirePeriod]
+        @nonce_expire_delta     = @config[:NonceExpireDelta]
+        @internet_explorer_hack = @config[:InternetExplorerHack]
+        @opera_hack             = @config[:OperaHack]
+
+        case @algorithm
+        when 'MD5','MD5-sess'
+          @h = Digest::MD5
+        when 'SHA1','SHA1-sess'  # it is a bonus feature :-)
+          @h = Digest::SHA1
+        else
+          msg = format('Alogrithm "%s" is not supported.', @algorithm)
+          raise ArgumentError.new(msg)
+        end
+
+        @instance_key = hexdigest(self.__id__, Time.now.to_i, Process.pid)
+        @opaques = {}
+        @last_nonce_expire = Time.now
+        @mutex = Mutex.new
+      end
+
+      def authenticate(req, res)
+        unless result = @mutex.synchronize{ _authenticate(req, res) }
+          challenge(req, res)
+        end
+        if result == :nonce_is_stale
+          challenge(req, res, true)
+        end
+        return true
+      end
+
+      def challenge(req, res, stale=false)
+        nonce = generate_next_nonce(req)
+        if @use_opaque
+          opaque = generate_opaque(req)
+          @opaques[opaque].nonce = nonce
+        end
+
+        param = Hash.new
+        param["realm"]  = HTTPUtils::quote(@realm)
+        param["domain"] = HTTPUtils::quote( domain.to_a.join(" ")) if @domain
+        param["nonce"]  = HTTPUtils::quote(nonce)
+        param["opaque"] = HTTPUtils::quote(opaque) if opaque
+        param["stale"]  = stale.to_s
+        param["algorithm"] = @algorithm
+        param["qop"]    = HTTPUtils::quote( qop.to_a.join(",")) if @qop
+
+        res[@response_field] =
+          "#{@auth_scheme} " + param.map{|k,v| "#{k}=#{v}" }.join(", ")
+        info("%s: %s", @response_field, res[@response_field]) if $DEBUG
+        raise @auth_exception
+      end
+
+      private
+
+      MustParams = ['username','realm','nonce','uri','response']
+      MustParamsAuth = ['cnonce','nc']
+
+      def _authenticate(req, res)
+        unless digest_credentials = check_scheme(req)
+          return false
+        end
+
+        auth_req = split_param_value(digest_credentials)
+        if auth_req['qop'] == "auth" || auth_req['qop'] == "auth-int"
+          req_params = MustParams + MustParamsAuth
+        else
+          req_params = MustParams
+        end
+        req_params.each{|key|
+          unless auth_req.has_key?(key)
+            error('%s: parameter missing. "%s"', auth_req['username'], key)
+            raise HTTPStatus::BadRequest
+          end
+        }
+
+        if !check_uri(req, auth_req)
+          raise HTTPStatus::BadRequest  
+        end
+
+        if auth_req['realm'] != @realm  
+          error('%s: realm unmatch. "%s" for "%s"',
+                auth_req['username'], auth_req['realm'], @realm)
+          return false
+        end
+
+        auth_req['algorithm'] ||= 'MD5' 
+        if auth_req['algorithm'] != @algorithm &&
+           (@opera_hack && auth_req['algorithm'] != @algorithm.upcase)
+          error('%s: algorithm unmatch. "%s" for "%s"',
+                auth_req['username'], auth_req['algorithm'], @algorithm)
+          return false
+        end
+
+        if ( qop.nil? && auth_req.has_key?('qop')) ||
+           (@qop && (! @qop.member?(auth_req['qop'])))
+          error('%s: the qop is not allowed. "%s"',
+                auth_req['username'], auth_req['qop'])
+          return false
+        end
+
+        password = @userdb.get_passwd(@realm, auth_req['username'], @reload_db)
+        unless password
+          error('%s: the user is not allowd.', auth_req['username'])
+          return false
+        end
+
+        nonce_is_invalid = false
+        if @use_opaque
+          info("@opaque = %s", @opaque.inspect) if $DEBUG
+          if !(opaque = auth_req['opaque'])
+            error('%s: opaque is not given.', auth_req['username'])
+            nonce_is_invalid = true
+          elsif !(opaque_struct = @opaques[opaque])
+            error('%s: invalid opaque is given.', auth_req['username'])
+            nonce_is_invalid = true
+          elsif !check_opaque(opaque_struct, req, auth_req)
+            @opaques.delete(auth_req['opaque'])
+            nonce_is_invalid = true
+          end
+        elsif !check_nonce(req, auth_req)
+          nonce_is_invalid = true
+        end
+
+        if /-sess$/ =~ auth_req['algorithm'] ||
+           (@opera_hack && /-SESS$/ =~ auth_req['algorithm'])
+          ha1 = hexdigest(password, auth_req['nonce'], auth_req['cnonce'])
+        else
+          ha1 = password
+        end
+
+        if auth_req['qop'] == "auth" || auth_req['qop'] == nil
+          ha2 = hexdigest(req.request_method, auth_req['uri'])
+          ha2_res = hexdigest("", auth_req['uri'])
+        elsif auth_req['qop'] == "auth-int"
+          ha2 = hexdigest(req.request_method, auth_req['uri'],
+                          hexdigest(req.body))
+          ha2_res = hexdigest("", auth_req['uri'], hexdigest(res.body))
+        end
+
+        if auth_req['qop'] == "auth" || auth_req['qop'] == "auth-int"
+          param2 = ['nonce', 'nc', 'cnonce', 'qop'].map{|key|
+            auth_req[key]
+          }.join(':')
+          digest     = hexdigest(ha1, param2, ha2)
+          digest_res = hexdigest(ha1, param2, ha2_res)
+        else
+          digest     = hexdigest(ha1, auth_req['nonce'], ha2)
+          digest_res = hexdigest(ha1, auth_req['nonce'], ha2_res)
+        end
+
+        if digest != auth_req['response']
+          error("%s: digest unmatch.", auth_req['username'])
+          return false
+        elsif nonce_is_invalid
+          error('%s: digest is valid, but nonce is not valid.',
+                auth_req['username'])
+          return :nonce_is_stale
+        elsif @use_auth_info_header
+          auth_info = {
+            'nextnonce' => generate_next_nonce(req),
+            'rspauth'   => digest_res
+          }
+          if @use_opaque
+            opaque_struct.time  = req.request_time
+            opaque_struct.nonce = auth_info['nextnonce']
+            opaque_struct.nc    = "%08x" % (auth_req['nc'].hex + 1)
+          end
+          if auth_req['qop'] == "auth" || auth_req['qop'] == "auth-int"
+            ['qop','cnonce','nc'].each{|key|
+              auth_info[key] = auth_req[key]
+            }
+          end
+          res[@resp_info_field] = auth_info.keys.map{|key|
+            if key == 'nc'
+              key + '=' + auth_info[key]
+            else
+              key + "=" + HTTPUtils::quote(auth_info[key])
+            end
+          }.join(', ')
+        end
+        info('%s: authentication scceeded.', auth_req['username'])
+        req.user = auth_req['username']
+        return true
+      end
+
+      def split_param_value(string)
+        ret = {}
+        while string.size != 0
+          case string           
+          when /^\s*([\w\-\.\*\%\!]+)=\s*\"((\\.|[^\"])*)\"\s*,?/
+            key = $1
+            matched = $2
+            string = $'
+            ret[key] = matched.gsub(/\\(.)/, "\\1")
+          when /^\s*([\w\-\.\*\%\!]+)=\s*([^,\"]*),?/
+            key = $1
+            matched = $2
+            string = $'
+            ret[key] = matched.clone
+          when /^s*^,/
+            string = $'
+          else
+            break
+          end
+        end
+        ret
+      end
+
+      def generate_next_nonce(req)
+        now = "%012d" % req.request_time.to_i
+        pk  = hexdigest(now, @instance_key)[0,32]
+        nonce = [now + ":" + pk].pack("m*").chop # it has 60 length of chars.
+        nonce
+      end
+
+      def check_nonce(req, auth_req)
+        username = auth_req['username']
+        nonce = auth_req['nonce']
+
+        pub_time, pk = nonce.unpack("m*")[0].split(":", 2)
+        if (!pub_time || !pk)
+          error("%s: empty nonce is given", username)
+          return false
+        elsif (hexdigest(pub_time, @instance_key)[0,32] != pk)
+          error("%s: invalid private-key: %s for %s",
+                username, hexdigest(pub_time, @instance_key)[0,32], pk)
+          return false
+        end
+
+        diff_time = req.request_time.to_i - pub_time.to_i
+        if (diff_time < 0)
+          error("%s: difference of time-stamp is negative.", username)
+          return false
+        elsif diff_time > @nonce_expire_period
+          error("%s: nonce is expired.", username)
+          return false
+        end
+
+        return true
+      end
+
+      def generate_opaque(req)
+        @mutex.synchronize{
+          now = req.request_time
+          if now - @last_nonce_expire > @nonce_expire_delta
+            @opaques.delete_if{|key,val|
+              (now - val.time) > @nonce_expire_period
+            }
+            @last_nonce_expire = now
+          end
+          begin
+            opaque = Utils::random_string(16)
+          end while @opaques[opaque]
+          @opaques[opaque] = OpaqueInfo.new(now, nil, '00000001')
+          opaque
+        }
+      end
+
+      def check_opaque(opaque_struct, req, auth_req)
+        if (@use_next_nonce && auth_req['nonce'] != opaque_struct.nonce)
+          error('%s: nonce unmatched. "%s" for "%s"',
+                auth_req['username'], auth_req['nonce'], opaque_struct.nonce)
+          return false
+        elsif !check_nonce(req, auth_req)
+          return false
+        end
+        if (@check_nc && auth_req['nc'] != opaque_struct.nc)
+          error('%s: nc unmatched."%s" for "%s"',
+                auth_req['username'], auth_req['nc'], opaque_struct.nc)
+          return false
+        end
+        true
+      end
+
+      def check_uri(req, auth_req)
+        uri = auth_req['uri']
+        if uri != req.request_uri.to_s && uri != req.unparsed_uri &&
+           (@internet_explorer_hack && uri != req.path)
+          error('%s: uri unmatch. "%s" for "%s"', auth_req['username'], 
+                auth_req['uri'], req.request_uri.to_s)
+          return false
+        end
+        true
+      end
+
+      def hexdigest(*args)
+        @h.hexdigest(args.join(":"))
+      end
+
+    end
+
+    class ProxyDigestAuth < DigestAuth
+      include ProxyAuthenticator
+
+      def check_uri(req, auth_req)
+        return true
+      end
+    end
+  end
+end

Added: trunk/lib/webrick/httpauth/htdigest.rb
===================================================================
--- trunk/lib/webrick/httpauth/htdigest.rb	2006-02-14 05:59:01 UTC (rev 415)
+++ trunk/lib/webrick/httpauth/htdigest.rb	2006-02-14 09:20:41 UTC (rev 416)
@@ -0,0 +1,91 @@
+#
+# httpauth/htdigest.rb -- Apache compatible htdigest file
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2003 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: htdigest.rb,v 1.4 2003/07/22 19:20:45 gotoyuzo Exp $
+
+require 'webrick/httpauth/userdb'
+require 'webrick/httpauth/digestauth'
+require 'tempfile'
+
+module WEBrick
+  module HTTPAuth
+    class Htdigest
+      include UserDB
+
+      def initialize(path)
+        @path = path
+        @mtime = Time.at(0)
+        @digest = Hash.new
+        @mutex = Mutex::new
+        @auth_type = DigestAuth
+        open( path,"a").close unless File::exist?(@path)
+        reload
+      end
+
+      def reload
+        mtime = File::mtime(@path)
+        if mtime > @mtime
+          @digest.clear
+          open(@path){|io|
+            while line = io.gets
+              line.chomp!
+              user, realm, pass = line.split(/:/, 3)
+              unless @digest[realm]
+                @digest[realm] = Hash.new
+              end
+              @digest[realm][user] = pass
+            end
+          }
+          @mtime = mtime
+        end
+      end
+
+      def flush(output=nil)
+        output ||= @path
+        tmp = Tempfile.new("htpasswd", File::dirname(output))
+        begin
+          each{|item| tmp.puts(item.join(":")) }
+          tmp.close
+          File::rename(tmp.path, output)
+        rescue
+          tmp.close(true)
+        end
+      end
+
+      def get_passwd(realm, user, reload_db)
+        reload() if reload_db
+        if hash = @digest[realm]
+          hash[user]
+        end
+      end
+
+      def set_passwd(realm, user, pass)
+        @mutex.synchronize{
+          unless @digest[realm]
+            @digest[realm] = Hash.new
+          end
+          @digest[realm][user] = make_passwd(realm, user, pass)
+        }
+      end
+
+      def delete_passwd(realm, user)
+        if hash = @digest[realm]
+          hash.delete(user)
+        end
+      end
+
+      def each
+        @digest.keys.sort.each{|realm|
+          hash = @digest[realm]
+          hash.keys.sort.each{|user|
+            yield([user, realm, hash[user]])
+          }
+        }
+      end
+    end
+  end
+end

Added: trunk/lib/webrick/httpauth/htgroup.rb
===================================================================
--- trunk/lib/webrick/httpauth/htgroup.rb	2006-02-14 05:59:01 UTC (rev 415)
+++ trunk/lib/webrick/httpauth/htgroup.rb	2006-02-14 09:20:41 UTC (rev 416)
@@ -0,0 +1,61 @@
+#
+# httpauth/htgroup.rb -- Apache compatible htgroup file
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2003 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: htgroup.rb,v 1.1 2003/02/16 22:22:56 gotoyuzo Exp $
+
+require 'tempfile'
+
+module WEBrick
+  module HTTPAuth
+    class Htgroup
+      def initialize(path)
+        @path = path
+        @mtime = Time.at(0)
+        @group = Hash.new
+        open( path,"a").close unless File::exist?(@path)
+        reload
+      end
+
+      def reload
+        if (mtime = File::mtime(@path)) > @mtime
+          @group.clear
+          open(@path){|io|
+            while line = io.gets
+              line.chomp!
+              group, members = line.split(/:\s*/)
+              @group[group] = members.split(/\s+/)
+            end
+          }
+          @mtime = mtime
+        end
+      end
+
+      def flush(output=nil)
+        output ||= @path
+        tmp = Tempfile.new("htgroup", File::dirname(output))
+        begin
+          @group.keys.sort.each{|group|
+            tmp.puts(format("%s: %s", group, self.members(group).join(" ")))
+          }
+          tmp.close
+          File::rename(tmp.path, output)
+        rescue
+          tmp.close(true)
+        end
+      end
+
+      def members(group)
+        reload
+        @group[group] || []
+      end
+
+      def add(group, members)
+        @group[group] = members(group) | members
+      end
+    end
+  end
+end

Added: trunk/lib/webrick/httpauth/htpasswd.rb
===================================================================
--- trunk/lib/webrick/httpauth/htpasswd.rb	2006-02-14 05:59:01 UTC (rev 415)
+++ trunk/lib/webrick/httpauth/htpasswd.rb	2006-02-14 09:20:41 UTC (rev 416)
@@ -0,0 +1,83 @@
+#
+# httpauth/htpasswd -- Apache compatible htpasswd file
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2003 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: htpasswd.rb,v 1.4 2003/07/22 19:20:45 gotoyuzo Exp $
+
+require 'webrick/httpauth/userdb'
+require 'webrick/httpauth/basicauth'
+require 'tempfile'
+
+module WEBrick
+  module HTTPAuth
+    class Htpasswd
+      include UserDB
+
+      def initialize(path)
+        @path = path
+        @mtime = Time.at(0)
+        @passwd = Hash.new
+        @auth_type = BasicAuth
+        open( path,"a").close unless File::exist?(@path)
+        reload
+      end
+
+      def reload
+        mtime = File::mtime(@path)
+        if mtime > @mtime
+          @passwd.clear
+          open(@path){|io|
+            while line = io.gets
+              line.chomp!
+              case line
+              when %r!\A[^:]+:[a-zA-Z0-9./]{13}\z!
+                user, pass = line.split(":")
+              when /:\$/, /:{SHA}/
+                raise NotImplementedError,
+                      'MD5, SHA1 .htpasswd file not supported'
+              else
+                raise StandardError, 'bad .htpasswd file'
+              end
+              @passwd[user] = pass
+            end
+          }
+          @mtime = mtime
+        end
+      end
+
+      def flush(output=nil)
+        output ||= @path
+        tmp = Tempfile.new("htpasswd", File::dirname(output))
+        begin
+          each{|item| tmp.puts(item.join(":")) }
+          tmp.close
+          File::rename(tmp.path, output)
+        rescue
+          tmp.close(true)
+        end
+      end
+
+      def get_passwd(realm, user, reload_db)
+        reload() if reload_db
+        @passwd[user]
+      end
+
+      def set_passwd(realm, user, pass)
+        @passwd[user] = make_passwd(realm, user, pass)
+      end
+
+      def delete_passwd(realm, user)
+        @passwd.delete(user)
+      end
+
+      def each
+        @passwd.keys.sort.each{|user|
+          yield([user, @passwd[user]])
+        }
+      end
+    end
+  end
+end

Added: trunk/lib/webrick/httpauth/userdb.rb
===================================================================
--- trunk/lib/webrick/httpauth/userdb.rb	2006-02-14 05:59:01 UTC (rev 415)
+++ trunk/lib/webrick/httpauth/userdb.rb	2006-02-14 09:20:41 UTC (rev 416)
@@ -0,0 +1,29 @@
+#
+# httpauth/userdb.rb -- UserDB mix-in module.
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2003 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: userdb.rb,v 1.2 2003/02/20 07:15:48 gotoyuzo Exp $
+
+module WEBrick
+  module HTTPAuth
+    module UserDB
+      attr_accessor :auth_type # BasicAuth or DigestAuth
+
+      def make_passwd(realm, user, pass)
+        @auth_type::make_passwd(realm, user, pass)
+      end
+
+      def set_passwd(realm, user, pass)
+        self[user] = pass
+      end                             
+
+      def get_passwd(realm, user, reload_db=false)
+        # reload_db is dummy
+        make_passwd(realm, user, self[user])
+      end
+    end
+  end
+end

Added: trunk/lib/webrick/httpauth.rb
===================================================================
--- trunk/lib/webrick/httpauth.rb	2006-02-14 05:59:01 UTC (rev 415)
+++ trunk/lib/webrick/httpauth.rb	2006-02-14 09:20:41 UTC (rev 416)
@@ -0,0 +1,45 @@
+#
+# httpauth.rb -- HTTP access authentication
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
+# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: httpauth.rb,v 1.14 2003/07/22 19:20:42 gotoyuzo Exp $
+
+require 'webrick/httpauth/basicauth'
+require 'webrick/httpauth/digestauth'
+require 'webrick/httpauth/htpasswd'
+require 'webrick/httpauth/htdigest'
+require 'webrick/httpauth/htgroup'
+
+module WEBrick
+  module HTTPAuth
+    module_function
+
+    def _basic_auth(req, res, realm, req_field, res_field, err_type, block)
+      user = pass = nil
+      if /^Basic\s+(.*)/o =~ req[req_field]
+        userpass = $1
+        user, pass = userpass.unpack("m*")[0].split(":", 2)
+      end
+      if block.call(user, pass)
+        req.user = user
+        return
+      end
+      res[res_field] = "Basic realm=\"#{realm}\""
+      raise err_type
+    end
+
+    def basic_auth(req, res, realm, &block)
+      _basic_auth(req, res, realm, "Authorization", "WWW-Authenticate",
+                  HTTPStatus::Unauthorized, block)
+    end
+
+    def proxy_basic_auth(req, res, realm, &block)
+      _basic_auth(req, res, realm, "Proxy-Authorization", "Proxy-Authenticate",
+                  HTTPStatus::ProxyAuthenticationRequired, block)
+    end
+  end
+end

Added: trunk/lib/webrick/httpproxy.rb
===================================================================
--- trunk/lib/webrick/httpproxy.rb	2006-02-14 05:59:01 UTC (rev 415)
+++ trunk/lib/webrick/httpproxy.rb	2006-02-14 09:20:41 UTC (rev 416)
@@ -0,0 +1,254 @@
+#
+# httpproxy.rb -- HTTPProxy Class
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2002 GOTO Kentaro
+# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: httpproxy.rb,v 1.18 2003/03/08 18:58:10 gotoyuzo Exp $
+# $kNotwork: straw.rb,v 1.3 2002/02/12 15:13:07 gotoken Exp $
+
+require "webrick/httpserver"
+require "net/http"
+
+Net::HTTP::version_1_2 if RUBY_VERSION < "1.7"
+
+module WEBrick
+  NullReader = Object.new
+  class << NullReader
+    def read(*args)
+      nil
+    end
+    alias gets read
+  end
+
+  class HTTPProxyServer < HTTPServer
+    def initialize(config={}, default=Config::HTTP)
+      super(config, default)
+      c = @config
+      @via = "#{c[:HTTPVersion]} #{c[:ServerName]}:#{c[:Port]}"
+    end
+
+    def service(req, res)
+      if req.request_method == "CONNECT"
+        proxy_connect(req, res)
+      elsif req.unparsed_uri =~ %r!^http://!
+        proxy_service(req, res)
+      else
+        super(req, res)
+      end
+    end
+
+    def proxy_auth(req, res)
+      if proc = @config[:ProxyAuthProc]
+        proc.call(req, res)
+      end
+      req.header.delete("proxy-authorization")
+    end
+
+    # Some header fields shuold not be transfered.
+    HopByHop = %w( connection keep-alive proxy-authenticate upgrade
+                   proxy-authorization te trailers transfer-encoding )
+    ShouldNotTransfer = %w( set-cookie proxy-connection )
+    def split_field(f) f ? f.split(/,\s+/).collect{|i| i.downcase } : [] end
+
+    def choose_header(src, dst)
+      connections = split_field(src['connection'])
+      src.each{|key, value|
+        key = key.downcase
+        if HopByHop.member?(key)          || # RFC2616: 13.5.1
+           connections.member?(key)       || # RFC2616: 14.10
+           ShouldNotTransfer.member?(key)    # pragmatics
+          @logger.debug("choose_header: `#{key}: #{value}'")
+          next
+        end
+        dst[key] = value
+      }
+    end
+
+    # Net::HTTP is stupid about the multiple header fields.
+    # Here is workaround:
+    def set_cookie(src, dst)
+      if str = src['set-cookie']
+        cookies = []
+        str.split(/,\s*/).each{|token|
+          if /^[^=]+;/o =~ token
+            cookies[-1] << ", " << token
+          elsif /=/o =~ token
+            cookies << token
+          else
+            cookies[-1] << ", " << token
+          end
+        }
+        dst.cookies.replace(cookies)
+      end
+    end
+
+    def set_via(h)
+      if @config[:ProxyVia]
+        if  h['via']
+          h['via'] << ", " << @via
+        else
+          h['via'] = @via
+        end
+      end
+    end
+
+    def proxy_uri(req, res)
+      @config[:ProxyURI]
+    end
+
+    def proxy_service(req, res)
+      # Proxy Authentication
+      proxy_auth(req, res)      
+
+      # Create Request-URI to send to the origin server
+      uri  = req.request_uri
+      path = uri.path.dup
+      path << "?" << uri.query if uri.query
+
+      # Choose header fields to transfer
+      header = Hash.new
+      choose_header(req, header)
+      set_via(header)
+
+      # select upstream proxy server
+      if proxy = proxy_uri(req, res)
+        proxy_host = proxy.host
+        proxy_port = proxy.port
+        if proxy.userinfo
+          credentials = "Basic " + [proxy.userinfo].pack("m*")
+          credentials.chomp!
+          header['proxy-authorization'] = credentials
+        end
+      end
+
+      response = nil
+      begin
+        http = Net::HTTP.new(uri.host, uri.port, proxy_host, proxy_port)
+        http.start{
+          if @config[:ProxyTimeout]
+            ##################################   these issues are 
+            http.open_timeout = 30   # secs  #   necessary (maybe bacause
+            http.read_timeout = 60   # secs  #   Ruby's bug, but why?)
+            ##################################
+          end
+          case req.request_method
+          when "GET"  then response = http.get(path, header)
+          when "POST" then response = http.post(path, req.body || "", header)
+          when "HEAD" then response = http.head(path, header)
+          else
+            raise HTTPStatus::MethodNotAllowed,
+              "unsupported method `#{req.request_method}'."
+          end
+        }
+      rescue => err
+        logger.debug("#{err.class}: #{err.message}")
+        raise HTTPStatus::ServiceUnavailable, err.message
+      end
+  
+      # Persistent connction requirements are mysterious for me.
+      # So I will close the connection in every response.
+      res['proxy-connection'] = "close"
+      res['connection'] = "close"
+
+      # Convert Net::HTTP::HTTPResponse to WEBrick::HTTPProxy
+      res.status = response.code.to_i
+      choose_header(response, res)
+      set_cookie(response, res)
+      set_via(res)
+      res.body = response.body
+
+      # Process contents
+      if handler = @config[:ProxyContentHandler]
+        handler.call(req, res)
+      end
+    end
+
+    def proxy_connect(req, res)
+      # Proxy Authentication
+      proxy_auth(req, res)
+
+      ua = Thread.current[:WEBrickSocket]  # User-Agent
+      raise HTTPStatus::InternalServerError,
+        "[BUG] cannot get socket" unless ua
+
+      host, port = req.unparsed_uri.split(":", 2)
+      # Proxy authentication for upstream proxy server
+      if proxy = proxy_uri(req, res)
+        proxy_request_line = "CONNECT #{host}:#{port} HTTP/1.0"
+        if proxy.userinfo
+          credentials = "Basic " + [proxy.userinfo].pack("m*")
+          credentials.chomp!
+        end
+        host, port = proxy.host, proxy.port
+      end
+
+      begin
+        @logger.debug("CONNECT: upstream proxy is `#{host}:#{port}'.")
+        os = TCPSocket.new(host, port)     # origin server
+
+        if proxy
+          @logger.debug("CONNECT: sending a Request-Line")
+          os << proxy_request_line << CRLF
+          @logger.debug("CONNECT: > #{proxy_request_line}")
+          if credentials
+            @logger.debug("CONNECT: sending a credentials")
+            os << "Proxy-Authorization: " << credentials << CRLF
+          end
+          os << CRLF
+          proxy_status_line = os.gets(LF)
+          @logger.debug("CONNECT: read a Status-Line form the upstream server")
+          @logger.debug("CONNECT: < #{proxy_status_line}")
+          if %r{^HTTP/\d+\.\d+\s+200\s*} =~ proxy_status_line
+            while line = os.gets(LF)
+              break if /\A(#{CRLF}|#{LF})\z/om =~ line
+            end
+          else
+            raise HTTPStatus::BadGateway
+          end
+        end
+        @logger.debug("CONNECT #{host}:#{port}: succeeded")
+        res.status = HTTPStatus::RC_OK
+      rescue => ex
+        @logger.debug("CONNECT #{host}:#{port}: failed `#{ex.message}'")
+        res.set_error(ex)
+        raise HTTPStatus::EOFError
+      ensure
+        if handler = @config[:ProxyContentHandler]
+          handler.call(req, res)
+        end
+        res.send_response(ua)
+        access_log(@config, req, res)
+
+        # Should clear request-line not to send the sesponse twice.
+        # see: HTTPServer#run
+        req.parse(NullReader) rescue nil
+      end
+
+      begin
+        while fds = IO::select([ua, os])
+          if fds[0].member?(ua)
+            buf = ua.sysread(1024);
+            @logger.debug("CONNECT: #{buf.size} byte from User-Agent")
+            os.syswrite(buf)
+          elsif fds[0].member?(os)
+            buf = os.sysread(1024);
+            @logger.debug("CONNECT: #{buf.size} byte from #{host}:#{port}")
+            ua.syswrite(buf)
+          end
+        end
+      rescue => ex
+        os.close
+        @logger.debug("CONNECT #{host}:#{port}: closed")
+      end
+
+      raise HTTPStatus::EOFError
+    end
+
+    def do_OPTIONS(req, res)
+      res['allow'] = "GET,HEAD,POST,OPTIONS,CONNECT"
+    end
+  end
+end

Added: trunk/lib/webrick/httprequest.rb
===================================================================
--- trunk/lib/webrick/httprequest.rb	2006-02-14 05:59:01 UTC (rev 415)
+++ trunk/lib/webrick/httprequest.rb	2006-02-14 09:20:41 UTC (rev 416)
@@ -0,0 +1,359 @@
+#
+# httprequest.rb -- HTTPRequest Class
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
+# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: httprequest.rb,v 1.64 2003/07/13 17:18:22 gotoyuzo Exp $
+
+require 'timeout'
+require 'uri'
+
+require 'webrick/httpversion'
+require 'webrick/httpstatus'
+require 'webrick/httputils'
+require 'webrick/cookie'
+
+module WEBrick
+
+  class HTTPRequest
+    BODY_CONTAINABLE_METHODS = [ "POST", "PUT" ]
+    BUFSIZE = 1024*4
+
+    # Request line
+    attr_reader :request_line
+    attr_reader :request_method, :unparsed_uri, :http_version
+
+    # Request-URI
+    attr_reader :request_uri, :host, :port, :path
+    attr_accessor :script_name, :path_info, :query_string
+
+    # Header and entity body
+    attr_reader :raw_header, :header, :cookies
+    attr_reader :accept, :accept_charset
+    attr_reader :accept_encoding, :accept_language
+
+    # Misc
+    attr_accessor :user
+    attr_reader :addr, :peeraddr
+    attr_reader :attributes
+    attr_reader :keep_alive
+    attr_reader :request_time
+
+    def initialize(config)
+      @config = config
+      @logger = config[:Logger]
+
+      @request_line = @request_method =
+        @unparsed_uri = @http_version = nil
+
+      @request_uri = @host = @port = @path = nil
+      @script_name = @path_info = nil
+      @query_string = nil
+      @query = nil
+      @form_data = nil
+
+      @raw_header = Array.new
+      @header = nil
+      @cookies = []
+      @accept = []
+      @accept_charset = []
+      @accept_encoding = []
+      @accept_language = []
+      @body = ""
+
+      @addr = @peeraddr = nil
+      @attributes = {}
+      @user = nil
+      @keep_alive = false
+      @request_time = nil
+
+      @remaining_size = nil
+      @socket = nil
+    end
+
+    def parse(socket=nil)
+      @socket = socket
+      begin
+        @peeraddr = socket.respond_to?(:peeraddr) ? socket.peeraddr : []
+        @addr = socket.respond_to?(:addr) ? socket.addr : []
+      rescue Errno::ENOTCONN
+        raise HTTPStatus::EOFError
+      end
+
+      read_request_line(socket)
+      if @http_version.major > 0
+        read_header(socket)
+        @header['cookie'].each{|cookie|
+          @cookies += Cookie::parse(cookie)
+        }
+        @accept = HTTPUtils.parse_qvalues(self['accept'])
+        @accept_charset = HTTPUtils.parse_qvalues(self['accept-charset'])
+        @accept_encoding = HTTPUtils.parse_qvalues(self['accept-encoding'])
+        @accept_language = HTTPUtils.parse_qvalues(self['accept-language'])
+      end
+      return if @request_method == "CONNECT"
+      return if @unparsed_uri == "*"
+
+      begin
+        @request_uri = parse_uri(@unparsed_uri)
+        @path = HTTPUtils::unescape( request_uri.path)
+        @path = HTTPUtils::normalize_path(@path)
+        @host = @request_uri.host
+        @port = @request_uri.port
+        @query_string = @request_uri.query
+        @script_name = ""
+        @path_info = @path.dup
+      rescue
+        raise HTTPStatus::BadRequest, "bad URI `#{ unparsed_uri}'."
+      end
+
+      if /close/io =~ self["connection"]
+        @keep_alive = false
+      elsif /keep-alive/io =~ self["connection"]
+        @keep_alive = true
+      elsif @http_version < "1.1"
+        @keep_alive = false
+      else
+        @keep_alive = true
+      end
+    end
+
+    def body(&block)
+      block ||= Proc.new{|chunk| @body << chunk }
+      read_body(@socket, block)
+      @body.empty? ? nil : @body
+    end
+
+    def query
+      unless @query
+        parse_query()
+      end
+      @query
+    end
+
+    def content_length
+      return Integer(self['content-length'])
+    end
+
+    def content_type
+      return self['content-type']
+    end
+
+    def [](header_name)
+      if @header
+        value = @header[header_name.downcase]
+        value.empty? ? nil : value.join(", ")
+      end
+    end
+
+    def each
+      @header.each{|k, v|
+        value = @header[k]
+        yield(k, value.empty? ? nil : value.join(", "))
+      }
+    end
+
+    def keep_alive?
+      @keep_alive
+    end
+
+    def to_s
+      ret = @request_line.dup
+      @raw_header.each{|line| ret << line }
+      ret << CRLF
+      ret << body if body
+      ret
+    end
+
+    def fixup()
+      begin
+        body{|chunk| }   # read remaining body
+      rescue HTTPStatus::Error => ex
+        @logger.error("HTTPRequest#fixup: #{ex.class} occured.")
+        @keep_alive = false
+      rescue => ex
+        @logger.error(ex)
+        @keep_alive = false
+      end
+    end
+
+    def meta_vars
+      # This method provides the metavariables defined by the revision 3
+      # of ``The WWW Common Gateway Interface Version 1.1''.
+      # (http://Web.Golux.Com/coar/cgi/)
+
+      meta = Hash.new
+
+      cl = self["Content-Length"]
+      ct = self["Content-Type"]
+      meta["CONTENT_LENGTH"]    = cl if cl.to_i > 0
+      meta["CONTENT_TYPE"]      = ct.dup if ct
+      meta["GATEWAY_INTERFACE"] = "CGI/1.1"
+      meta["PATH_INFO"]         = @path_info ? @path_info.dup : ""
+     #meta["PATH_TRANSLATED"]   = nil      # no plan to be provided
+      meta["QUERY_STRING"]      = @query_string ? @query_string.dup : ""
+      meta["REMOTE_ADDR"]       = @peeraddr[3]
+      meta["REMOTE_HOST"]       = @peeraddr[2]
+     #meta["REMOTE_IDENT"]      = nil      # no plan to be provided
+      meta["REMOTE_USER"]       = @user
+      meta["REQUEST_METHOD"]    = @request_method.dup
+      meta["REQUEST_URI"]       = @request_uri.to_s
+      meta["SCRIPT_NAME"]       = @script_name.dup
+      meta["SERVER_NAME"]       = @host
+      meta["SERVER_PORT"]       = @port.to_s
+      meta["SERVER_PROTOCOL"]   = "HTTP/" + @config[:HTTPVersion].to_s
+      meta["SERVER_SOFTWARE"]   = @config[:ServerSoftware].dup
+
+      self.each{|key, val|
+        next if /^content-type$/i =~ key
+        next if /^content-length$/i =~ key
+        name = "HTTP_" + key
+        name.gsub!(/-/o, "_")
+        name.upcase!
+        meta[name] = val
+      }
+
+      meta
+    end
+
+    private
+
+    def read_request_line(socket)
+      @request_line = read_line(socket) if socket
+      @request_time = Time.now
+      raise HTTPStatus::EOFError unless @request_line
+      if /^(\S+)\s+(\S+)(?:\s+HTTP\/(\d+\.\d+))?\r?\n/mo =~ @request_line
+        @request_method = $1
+        @unparsed_uri   = $2
+        @http_version   = HTTPVersion.new($3 ? $3 : "0.9")
+      else
+        rl = @request_line.sub(/\x0d?\x0a\z/o, '')
+        raise HTTPStatus::BadRequest, "bad Request-Line `#{rl}'."
+      end
+    end
+
+    def read_header(socket)
+      if socket
+        while line = read_line(socket)
+          break if /\A(#{CRLF}|#{LF})\z/om =~ line
+          @raw_header << line
+        end
+      end
+      begin
+        @header = HTTPUtils::parse_header(@raw_header)
+      rescue => ex
+        raise  HTTPStatus::BadRequest, ex.message
+      end
+    end
+
+    def parse_uri(str, scheme="http")
+      if @config[:Escape8bitURI]
+        str = HTTPUtils::escape8bit(str)
+      end
+      uri = URI::parse(str)
+      return uri if uri.absolute?
+      if self["host"]
+        host, port = self['host'].split(":", 2)
+      elsif @addr.size > 0
+        host, port = @addr[2], @addr[1]
+      else
+        host, port = @config[:ServerName], @config[:Port]
+      end
+      uri.scheme = scheme
+      uri.host = host
+      uri.port = port ? port.to_i : nil
+      return URI::parse(uri.to_s)
+    end
+
+    def read_body(socket, block)
+      return unless socket
+      if tc = self['transfer-encoding']
+        case tc
+        when /chunked/io then read_chunked(socket, block)
+        else raise HTTPStatus::NotImplemented, "Transfer-Encoding: #{tc}."
+        end
+      elsif self['content-length'] || @remaining_size
+        @remaining_size ||= self['content-length'].to_i
+        while @remaining_size > 0 
+          sz = BUFSIZE < @remaining_size ? BUFSIZE : @remaining_size
+          break unless buf = read_data(socket, sz)
+          @remaining_size -= buf.size
+          block.call(buf)
+        end
+        if @remaining_size > 0 && @socket.eof?
+          raise HTTPStatus::BadRequest, "invalid body size."
+        end
+      elsif BODY_CONTAINABLE_METHODS.member?(@request_method)
+        raise HTTPStatus::LengthRequired
+      end
+      return @body
+    end
+
+    def read_chunk_size(socket)
+      line = read_line(socket)
+      if /^([0-9a-fA-F]+)(?:;(\S+))?/ =~ line
+        chunk_size = $1.hex
+        chunk_ext = $2
+        [ chunk_size, chunk_ext ]
+      else
+        raise HTTPStatus::BadRequest, "bad chunk `#{line}'."
+      end
+    end
+
+    def read_chunked(socket, block)
+      chunk_size, = read_chunk_size(socket)
+      while chunk_size > 0
+        data = read_data(socket, chunk_size) # read chunk-data
+        if data.nil? || data.size != chunk_size
+          raise BadRequest, "bad chunk data size."
+        end
+        read_line(socket)                    # skip CRLF
+        block.call(data)
+        chunk_size, = read_chunk_size(socket)
+      end
+      read_header(socket)                    # trailer + CRLF
+      @header.delete("transfer-encoding")
+      @remaining_size = 0
+    end
+
+    def _read_data(io, method, arg)
+      begin
+        timeout(@config[:RequestTimeout]){
+          return io.__send__(method, arg)
+        }
+      rescue Errno::ECONNRESET
+        return nil
+      rescue TimeoutError
+        raise HTTPStatus::RequestTimeout
+      end
+    end
+
+    def read_line(io)
+      _read_data(io, :gets, LF)
+    end
+
+    def read_data(io, size)
+      _read_data(io, :read, size)
+    end
+
+    def parse_query()
+      begin
+        if @request_method == "GET" || @request_method == "HEAD"
+          @query = HTTPUtils::parse_query(@query_string)
+        elsif self['content-type'] =~ /^application\/x-www-form-urlencoded/
+          @query = HTTPUtils::parse_query(body)
+        elsif self['content-type'] =~ /^multipart\/form-data; boundary=(.+)/
+          boundary = HTTPUtils::dequote($1)
+          @query = HTTPUtils::parse_form_data(body, boundary)
+        else
+          @query = Hash.new
+        end
+      rescue => ex
+        raise HTTPStatus::BadRequest, ex.message
+      end
+    end
+  end
+end

Added: trunk/lib/webrick/httpresponse.rb
===================================================================
--- trunk/lib/webrick/httpresponse.rb	2006-02-14 05:59:01 UTC (rev 415)
+++ trunk/lib/webrick/httpresponse.rb	2006-02-14 09:20:41 UTC (rev 416)
@@ -0,0 +1,327 @@
+#
+# httpresponse.rb -- HTTPResponse Class
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
+# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: httpresponse.rb,v 1.45 2003/07/11 11:02:25 gotoyuzo Exp $
+
+require 'time'
+require 'webrick/httpversion'
+require 'webrick/htmlutils'
+require 'webrick/httputils'
+require 'webrick/httpstatus'
+
+module WEBrick
+  class HTTPResponse
+    BUFSIZE = 1024*4
+
+    attr_reader :http_version, :status, :header
+    attr_reader :cookies
+    attr_accessor :reason_phrase
+    attr_accessor :body
+
+    attr_accessor :request_method, :request_uri, :request_http_version
+    attr_accessor :filename
+    attr_accessor :keep_alive
+    attr_reader :config, :sent_size
+
+    def initialize(config)
+      @config = config
+      @logger = config[:Logger]
+      @header = Hash.new
+      @status = HTTPStatus::RC_OK
+      @reason_phrase = nil
+      @http_version = HTTPVersion::convert(@config[:HTTPVersion])
+      @body = ''
+      @keep_alive = true
+      @cookies = []
+      @request_method = nil
+      @request_uri = nil
+      @request_http_version = @http_version  # temporary
+      @chunked = false
+      @filename = nil
+      @sent_size = 0
+    end
+
+    def status_line
+      "HTTP/#@http_version #@status #@reason_phrase #{CRLF}"
+    end
+
+    def status=(status)
+      @status = status
+      @reason_phrase = HTTPStatus::reason_phrase(status)
+    end
+
+    def [](field)
+      @header[field.downcase]
+    end
+
+    def []=(field, value)
+      @header[field.downcase] = value.to_s
+    end
+
+    def content_length
+      if len = self['content-length']
+        return Integer(len)
+      end
+    end
+
+    def content_length=(len)
+      self['content-length'] = len.to_s
+    end
+
+    def content_type
+      self['content-type']
+    end
+
+    def content_type=(type)
+      self['content-type'] = type
+    end
+
+    def each
+      @header.each{|k, v|  yield(k, v) }
+    end
+
+    def chunked?
+      @chunked
+    end
+
+    def chunked=(val)
+      @chunked = val ? true : false
+    end
+
+    def keep_alive?
+      @keep_alive
+    end
+
+    def send_response(socket)
+      begin
+        setup_header()
+        send_header(socket)
+        send_body(socket)
+      rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ENOTCONN => ex
+        @logger.debug(ex)
+        @keep_alive = false
+      rescue Exception => ex
+        @logger.error(ex)
+        @keep_alive = false
+      end
+    end
+
+    def setup_header()
+      @reason_phrase    ||= HTTPStatus::reason_phrase(@status)
+      @header['server'] ||= @config[:ServerSoftware]
+      @header['date']   ||= Time.now.httpdate
+
+      # HTTP/0.9 features
+      if @request_http_version < "1.0"
+        @http_version = HTTPVersion.new("0.9")
+        @keep_alive = false
+      end
+
+      # HTTP/1.0 features
+      if @request_http_version < "1.1"
+        if chunked?
+          @chunked = false
+          ver = @request_http_version.to_s
+          msg = "chunked is set for an HTTP/#{ver} request. (ignored)"
+          @logger.warn(msg)
+        end
+      end
+
+      # Determin the message length (RFC2616 -- 4.4 Message Length)
+      if @status == 304 || @status == 204 || HTTPStatus::info?(@status)
+        @header.delete('content-length')
+        @body = ""
+      elsif chunked?
+        @header["transfer-encoding"] = "chunked"
+        @header.delete('content-length')
+      elsif %r{^multipart/byteranges} =~ @header['content-type']
+        @header.delete('content-length')
+      elsif @header['content-length'].nil?
+        unless @body.is_a?(IO)
+          @header['content-length'] = @body ? @body.size : 0
+        end
+      end
+
+      # Keep-Alive connection.
+      if @header['connection'] == "close"
+         @keep_alive = false
+      elsif keep_alive?
+        if chunked? || @header['content-length']
+          @header['connection'] = "Keep-Alive"
+        end
+      else
+        @header['connection'] = "close"
+      end
+
+      # Location is a single absoluteURI.
+      if location = @header['location']
+        if @request_uri
+          @header['location'] = @request_uri.merge(location)
+        end
+      end
+    end
+
+    def send_header(socket)
+      if @http_version.major > 0
+        data = status_line()
+        @header.each{|key, value|
+          tmp = key.gsub(/\bwww|^te$|\b\w/){|s| s.upcase }
+          data << "#{tmp}: #{value}" << CRLF
+        }
+        @cookies.each{|cookie|
+          data << "Set-Cookie: " << cookie.to_s << CRLF
+        }
+        data << CRLF
+        _write_data(socket, data)
+      end
+    end
+
+    def send_body(socket)
+      case @body
+      when IO then send_body_io(socket)
+      else send_body_string(socket)
+      end
+    end
+
+    def to_s
+      ret = ""
+      send_response(ret)
+      ret
+    end
+
+    def set_redirect(status, url)
+      @body = "<HTML><A HREF=\"#{url.to_s}\">#{url.to_s}</A>.</HTML>\n"
+      @header['location'] = url.to_s
+      raise status
+    end
+
+    def set_error(ex, backtrace=false)
+      case ex
+      when HTTPStatus::Status 
+        @keep_alive = false if HTTPStatus::error?(ex.code)
+        self.status = ex.code
+      else 
+        @keep_alive = false
+        self.status = HTTPStatus::RC_INTERNAL_SERVER_ERROR
+      end
+      @header['content-type'] = "text/html"
+
+      if respond_to?(:create_error_page)
+        create_error_page()
+        return
+      end
+
+      if @request_uri
+        host, port = @request_uri.host, @request_uri.port
+      else
+        host, port = @config[:ServerName], @config[:Port]
+      end
+
+      @body = ''
+      @body << <<-_end_of_html_
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
+<HTML>
+  <HEAD><TITLE>#{HTMLUtils::escape(@reason_phrase)}</TITLE></HEAD>
+  <BODY>
+    <H1>#{HTMLUtils::escape(@reason_phrase)}</H1>
+    #{HTMLUtils::escape(ex.message)}
+    <HR>
+      _end_of_html_
+
+      if backtrace && $DEBUG
+        @body << "backtrace of `#{HTMLUtils::escape(ex.class.to_s)}' "
+        @body << "#{HTMLUtils::escape(ex.message)}"
+        @body << "<PRE>"
+        ex.backtrace.each{|line| @body << "\t#{line}\n"}
+        @body << "</PRE><HR>"
+      end
+
+      @body << <<-_end_of_html_
+    <ADDRESS>
+     #{HTMLUtils::escape(@config[:ServerSoftware])} at
+     #{host}:#{port}
+    </ADDRESS>
+  </BODY>
+</HTML>
+      _end_of_html_
+    end
+
+    private
+
+    def send_body_io(socket)
+      begin
+        if @request_method == "HEAD"
+          # do nothing
+        elsif chunked?
+          while buf = @body.read(BUFSIZE)
+            next if buf.empty?
+            data = ""
+            data << format("%x", buf.size) << CRLF
+            data << buf << CRLF
+            _write_data(socket, data)
+            @sent_size += buf.size
+          end
+          _write_data(socket, "0#{CRLF}#{CRLF}")
+        else
+          size = @header['content-length'].to_i
+          _send_file(socket, @body, 0, size)
+          @sent_size = size
+        end
+      ensure
+        @body.close
+      end
+    end
+
+    def send_body_string(socket)
+      if @request_method == "HEAD"
+        # do nothing
+      elsif chunked?
+        remain = body ? @body.size : 0
+        while buf = @body[@sent_size, BUFSIZE]
+          break if buf.empty?
+          data = ""
+          data << format("%x", buf.size) << CRLF
+          data << buf << CRLF
+          _write_data(socket, data)
+          @sent_size += buf.size
+        end
+        _write_data(socket, "0#{CRLF}#{CRLF}")
+      else
+        if @body && @body.size > 0
+          _write_data(socket, @body)
+          @sent_size = @body.size
+        end
+      end
+    end
+
+    def _send_file(output, input, offset, size)
+      while offset > 0
+        sz = BUFSIZE < offset ? BUFSIZE : offset
+        buf = input.read(sz)
+        offset -= buf.size
+      end
+
+      if size == 0
+        while buf = input.read(BUFSIZE)
+          _write_data(output, buf)
+        end
+      else
+        while size > 0
+          sz = BUFSIZE < size ? BUFSIZE : size
+          buf = input.read(sz)
+          _write_data(output, buf)
+          size -= buf.size
+        end
+      end
+    end
+
+    def _write_data(socket, data)
+      socket << data
+    end
+  end
+end

Added: trunk/lib/webrick/https.rb
===================================================================
--- trunk/lib/webrick/https.rb	2006-02-14 05:59:01 UTC (rev 415)
+++ trunk/lib/webrick/https.rb	2006-02-14 09:20:41 UTC (rev 416)
@@ -0,0 +1,63 @@
+#
+# https.rb -- SSL/TLS enhancement for HTTPServer
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2001 GOTOU Yuuzou
+# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: https.rb,v 1.15 2003/07/22 19:20:42 gotoyuzo Exp $
+
+require 'webrick/ssl'
+
+module WEBrick
+  module Config
+    HTTP.update(SSL)
+  end
+
+  class HTTPRequest
+    attr_reader :cipher, :server_cert, :client_cert
+
+    alias orig_parse parse
+
+    def parse(socket=nil)
+      if socket.respond_to?(:cert)
+        @server_cert = socket.cert || @config[:SSLCertificate]
+        @client_cert = socket.peer_cert
+        @client_cert_chain = socket.peer_cert_chain
+        @cipher      = socket.cipher
+      end
+      orig_parse(socket)
+    end
+
+    alias orig_parse_uri parse_uri
+
+    def parse_uri(str, scheme="https")
+      if @server_cert
+        return orig_parse_uri(str, scheme)
+      end
+      return orig_parse_uri(str)
+    end
+
+    alias orig_meta_vars meta_vars
+
+    def meta_vars
+      meta = orig_meta_vars
+      if @server_cert
+        meta["HTTPS"] = "on"
+        meta["SSL_SERVER_CERT"] = @server_cert.to_pem
+        meta["SSL_CLIENT_CERT"] = @client_cert ? @client_cert.to_pem : ""
+        if @client_cert_chain
+          @client_cert_chain.each_with_index{|cert, i|
+            meta["SSL_CLIENT_CERT_CHAIN_#{i}"] = cert.to_pem
+          }
+        end
+        meta["SSL_CIPHER"] = @cipher[0]
+        meta["SSL_PROTOCOL"] = @cipher[1]
+        meta["SSL_CIPHER_USEKEYSIZE"] = @cipher[2].to_s
+        meta["SSL_CIPHER_ALGKEYSIZE"] = @cipher[3].to_s
+      end
+      meta
+    end
+  end
+end

Added: trunk/lib/webrick/httpserver.rb
===================================================================
--- trunk/lib/webrick/httpserver.rb	2006-02-14 05:59:01 UTC (rev 415)
+++ trunk/lib/webrick/httpserver.rb	2006-02-14 09:20:41 UTC (rev 416)
@@ -0,0 +1,217 @@
+#
+# httpserver.rb -- HTTPServer Class
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
+# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: httpserver.rb,v 1.63 2002/10/01 17:16:32 gotoyuzo Exp $
+
+require 'webrick/server'
+require 'webrick/httputils'
+require 'webrick/httpstatus'
+require 'webrick/httprequest'
+require 'webrick/httpresponse'
+require 'webrick/httpservlet'
+require 'webrick/accesslog'
+
+module WEBrick
+  class HTTPServerError < ServerError; end
+
+  class HTTPServer < ::WEBrick::GenericServer
+    def initialize(config={}, default=Config::HTTP)
+      super(config, default)
+      @http_version = HTTPVersion::convert(@config[:HTTPVersion])
+
+      @mount_tab = MountTable.new
+      if @config[:DocumentRoot]
+        mount("/", HTTPServlet::FileHandler, @config[:DocumentRoot],
+              @config[:DocumentRootOptions])
+      end
+
+      unless @config[:AccessLog]
+        @config[:AccessLog] = [
+          [ $stderr, AccessLog::COMMON_LOG_FORMAT ],
+          [ $stderr, AccessLog::REFERER_LOG_FORMAT ]
+        ]
+      end
+ 
+      @virtual_hosts = Array.new
+    end
+
+    def run(sock)
+      while true 
+        res = HTTPResponse.new(@config)
+        req = HTTPRequest.new(@config)
+        server = self
+        begin
+          timeout = @config[:RequestTimeout]
+          while timeout > 0
+            break if IO.select([sock], nil, nil, 0.5)
+            timeout = 0 if @status != :Running
+            timeout -= 0.5
+          end
+          raise HTTPStatus::EOFError if timeout <= 0
+          raise HTTPStatus::EOFError if sock.eof?
+          req.parse(sock)
+          res.request_method = req.request_method
+          res.request_uri = req.request_uri
+          res.request_http_version = req.http_version
+          res.keep_alive = req.keep_alive?
+          server = lookup_server(req) || self
+          if callback = server[:RequestCallback]
+            callback.call(req, res)
+          elsif callback = server[:RequestHandler]
+            msg = ":RequestHandler is deprecated, please use :RequestCallback"
+            @logger.warn(msg)
+            callback.call(req, res)
+          end
+          server.service(req, res)
+        rescue HTTPStatus::EOFError, HTTPStatus::RequestTimeout => ex
+          res.set_error(ex)
+        rescue HTTPStatus::Error => ex
+          @logger.error(ex.message)
+          res.set_error(ex)
+        rescue HTTPStatus::Status => ex
+          res.status = ex.code
+        rescue StandardError => ex
+          @logger.error(ex)
+          res.set_error(ex, true)
+        ensure
+          if req.request_line
+            if req.keep_alive? && res.keep_alive?
+              req.fixup()
+            end
+            res.send_response(sock)
+            server.access_log(@config, req, res)
+          end
+        end
+        break if @http_version < "1.1"
+        break unless req.keep_alive?
+        break unless res.keep_alive?
+      end
+    end
+
+    def service(req, res)
+      if req.unparsed_uri == "*"
+        if req.request_method == "OPTIONS"
+          do_OPTIONS(req, res)
+          raise HTTPStatus::OK
+        end
+        raise HTTPStatus::NotFound, "`#{req.unparsed_uri}' not found."
+      end
+
+      servlet, options, script_name, path_info = search_servlet(req.path)
+      raise HTTPStatus::NotFound, "`#{req.path}' not found." unless servlet
+      req.script_name = script_name
+      req.path_info = path_info
+      si = servlet.get_instance(self, *options)
+      @logger.debug(format("%s is invoked.", si.class.name))
+      si.service(req, res)
+    end
+
+    def do_OPTIONS(req, res)
+      res["allow"] = "GET,HEAD,POST,OPTIONS"
+    end
+
+    def mount(dir, servlet, *options)
+      @logger.debug(sprintf("%s is mounted on %s.", servlet.inspect, dir))
+      @mount_tab[dir] = [ servlet, options ]
+    end
+
+    def mount_proc(dir, proc=nil, &block)
+      proc ||= block
+      raise HTTPServerError, "must pass a proc or block" unless proc
+      mount(dir, HTTPServlet::ProcHandler.new(proc))
+    end
+
+    def unmount(dir)
+      @logger.debug(sprintf("unmount %s.", inspect, dir))
+      @mount_tab.delete(dir)
+    end
+    alias umount unmount
+
+    def search_servlet(path)
+      script_name, path_info = @mount_tab.scan(path)
+      servlet, options = @mount_tab[script_name]
+      if servlet
+        [ servlet, options, script_name, path_info ]
+      end
+    end
+
+    def virtual_host(server)
+      @virtual_hosts << server
+      @virtual_hosts = @virtual_hosts.sort_by{|s|
+        num = 0
+        num -= 4 if s[:BindAddress]
+        num -= 2 if s[:Port]
+        num -= 1 if s[:ServerName]
+        num
+      }
+    end
+
+    def lookup_server(req)
+      @virtual_hosts.find{|s|
+        (s[:BindAddress].nil? || req.addr[3] == s[:BindAddress]) &&
+        (s[:Port].nil?        || req.port == s[:Port])           &&
+        ((s[:ServerName].nil?  || req.host == s[:ServerName]) ||
+         (!s[:ServerAlias].nil? && s[:ServerAlias].find{|h| h === req.host}))
+      }
+    end
+
+    def access_log(config, req, res)
+      param = AccessLog::setup_params(config, req, res)
+      @config[:AccessLog].each{|logger, fmt|
+        logger << AccessLog::format(fmt+"\n", param)
+      }
+    end
+
+    class MountTable
+      def initialize
+        @tab = Hash.new
+        compile
+      end
+
+      def [](dir)
+        dir = normalize(dir)
+        @tab[dir]
+      end
+
+      def []=(dir, val)
+        dir = normalize(dir)
+        @tab[dir] = val
+        compile
+        val
+      end
+
+      def delete(dir)
+        dir = normalize(dir)
+        res = @tab.delete(dir)
+        compile
+        res
+      end
+
+      def scan(path)
+        @scanner =~ path
+        [ $&, $' ]
+      end
+
+      private
+
+      def compile
+        k = @tab.keys
+        k.sort!
+        k.reverse!
+        k.collect!{|path| Regexp.escape(path) }
+        @scanner = Regexp.new("^(" + k.join("|") +")(?=/|$)")
+      end
+
+      def normalize(dir)
+        ret = dir ? dir.dup : ""
+        ret.sub!(%r|/+$|, "")
+        ret
+      end
+    end
+  end
+end

Added: trunk/lib/webrick/httpservlet/abstract.rb
===================================================================
--- trunk/lib/webrick/httpservlet/abstract.rb	2006-02-14 05:59:01 UTC (rev 415)
+++ trunk/lib/webrick/httpservlet/abstract.rb	2006-02-14 09:20:41 UTC (rev 416)
@@ -0,0 +1,71 @@
+#
+# 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
+
+    class AbstractServlet
+      def self.get_instance(config, *options)
+        self.new(config, *options)
+      end
+
+      def initialize(server, *options)
+        @server = @config = server
+        @logger = @server[:Logger]
+        @options = options
+      end
+
+      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
+
+      def do_GET(req, res)
+        raise HTTPStatus::NotFound, "not found."
+      end
+
+      def do_HEAD(req, res)
+        do_GET(req, res)
+      end
+
+      def do_OPTIONS(req, res)
+        m = self.methods.grep(/^do_[A-Z]+$/)
+        m.collect!{|i| i.sub(/do_/, "") }
+        m.sort!
+        res["allow"] = m.join(",")
+      end
+
+      private
+
+      def redirect_to_directory_uri(req, res)
+        if req.path[-1] != ?/
+          location = req.path + "/"
+          if req.query_string && req.query_string.size > 0
+            location << "?" << req.query_string
+          end
+          res.set_redirect(HTTPStatus::MovedPermanently, location)
+        end
+      end
+    end
+
+  end
+end

Added: trunk/lib/webrick/httpservlet/cgi_runner.rb
===================================================================
--- trunk/lib/webrick/httpservlet/cgi_runner.rb	2006-02-14 05:59:01 UTC (rev 415)
+++ trunk/lib/webrick/httpservlet/cgi_runner.rb	2006-02-14 09:20:41 UTC (rev 416)
@@ -0,0 +1,45 @@
+#
+# 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.size
+  end
+  return buf
+end
+
+STDIN.binmode
+
+buf = ""
+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 interpreter = ARGV[0]
+  exec(interpreter, ENV["SCRIPT_FILENAME"])
+  # NOTREACHED
+end
+exec ENV["SCRIPT_FILENAME"]

Added: trunk/lib/webrick/httpservlet/cgihandler.rb
===================================================================
--- trunk/lib/webrick/httpservlet/cgihandler.rb	2006-02-14 05:59:01 UTC (rev 415)
+++ trunk/lib/webrick/httpservlet/cgihandler.rb	2006-02-14 09:20:41 UTC (rev 416)
@@ -0,0 +1,104 @@
+# 
+# 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
+
+    class CGIHandler < AbstractServlet
+      Ruby = File::join(::Config::CONFIG['bindir'],
+                        ::Config::CONFIG['ruby_install_name'])
+      Ruby << ::Config::CONFIG['EXEEXT']
+      CGIRunner = "\"#{Ruby}\" \"#{Config::LIBDIR}/httpservlet/cgi_runner.rb\""
+
+      def initialize(server, name)
+        super(server, name)
+        @script_filename = name
+        @tempdir = server[:TempDir]
+        @cgicmd = "#{CGIRunner} #{server[:CGIInterpreter]}"
+      end
+
+      def do_GET(req, res)
+        data = nil
+        status = -1
+
+        cgi_in = IO::popen(@cgicmd, "wb")
+        cgi_out = Tempfile.new("webrick.cgiout.", @tempdir)
+        cgi_err = Tempfile.new("webrick.cgierr.", @tempdir)
+        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.size)
+          cgi_in.write(cgi_out.path)
+          cgi_in.write("%8d" % cgi_err.path.size)
+          cgi_in.write(cgi_err.path)
+          cgi_in.write("%8d" % dump.size)
+          cgi_in.write(dump)
+
+          if req.body and req.body.size > 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.size > 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]+/on, 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?('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
+    end
+
+  end
+end

Added: trunk/lib/webrick/httpservlet/erbhandler.rb
===================================================================
--- trunk/lib/webrick/httpservlet/erbhandler.rb	2006-02-14 05:59:01 UTC (rev 415)
+++ trunk/lib/webrick/httpservlet/erbhandler.rb	2006-02-14 09:20:41 UTC (rev 416)
@@ -0,0 +1,54 @@
+# 
+# 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
+
+    class ERBHandler < AbstractServlet
+      def initialize(server, name)
+        super(server, name)
+        @script_filename = name
+      end
+
+      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 => ex
+          raise
+        rescue Exception => ex
+          @logger.error(ex)
+          raise HTTPStatus::InternalServerError, ex.message
+        end
+      end
+
+      alias do_POST do_GET
+
+      private
+      def evaluate(erb, servlet_request, servlet_response)
+        Module.new.module_eval{
+          meta_vars = servlet_request.meta_vars
+          query = servlet_request.query
+          erb.result(binding)
+        }
+      end
+    end
+  end
+end

Added: trunk/lib/webrick/httpservlet/filehandler.rb
===================================================================
--- trunk/lib/webrick/httpservlet/filehandler.rb	2006-02-14 05:59:01 UTC (rev 415)
+++ trunk/lib/webrick/httpservlet/filehandler.rb	2006-02-14 09:20:41 UTC (rev 416)
@@ -0,0 +1,398 @@
+#
+# 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
+
+    class DefaultFileHandler < AbstractServlet
+      def initialize(server, local_path)
+        super(server, local_path)
+        @local_path = local_path
+      end
+
+      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: #{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'] = "#{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
+    end
+
+    class FileHandler < AbstractServlet
+      HandlerTable = Hash.new
+
+      def self.add_handler(suffix, handler)
+        HandlerTable[suffix] = handler
+      end
+
+      def self.remove_handler(suffix)
+        HandlerTable.delete(suffix)
+      end
+
+      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
+
+      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
+        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 exec_handler(req, res)
+        raise HTTPStatus::NotFound, "`#{req.path}' not found" unless @root
+        if set_filename(req, res)
+          handler = get_handler(req)
+          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)
+        suffix1 = (/\.(\w+)$/ =~ req.script_name) && $1.downcase
+        suffix2 = (/\.(\w+)\.[\w\-]+$/ =~ req.script_name) && $1.downcase
+        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
+          check_filename(req, res, base)
+          break if base == "/"
+          break unless File.directory?(res.filename + base)
+          shift_path_info(req, res, path_info)
+          call_callback(:DirectoryCallback, req, res)
+        end
+
+        if base = path_info.first
+          check_filename(req, res, base)
+          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)
+        @options[:NondisclosureName].each{|pattern|
+          if File.fnmatch("/#{pattern}", 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 << base
+      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 nondisclosure_name?(name)
+        @options[:NondisclosureName].each{|pattern|
+          if File.fnmatch(pattern, name)
+            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)
+          st = (File::stat(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.size > 25
+            dname = name.sub(/^(.{23})(.*)/){ $1 + ".." }
+          else
+            dname = name
+          end
+          s =  " <A HREF=\"#{HTTPUtils::escape(name)}\">#{dname}</A>"
+          s << " " * (30 - dname.size)
+          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
+
+    end
+  end
+end

Added: trunk/lib/webrick/httpservlet/prochandler.rb
===================================================================
--- trunk/lib/webrick/httpservlet/prochandler.rb	2006-02-14 05:59:01 UTC (rev 415)
+++ trunk/lib/webrick/httpservlet/prochandler.rb	2006-02-14 09:20:41 UTC (rev 416)
@@ -0,0 +1,33 @@
+# 
+# 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
+
+    class ProcHandler < AbstractServlet
+      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
+    end
+
+  end
+end

Added: trunk/lib/webrick/httpservlet.rb
===================================================================
--- trunk/lib/webrick/httpservlet.rb	2006-02-14 05:59:01 UTC (rev 415)
+++ trunk/lib/webrick/httpservlet.rb	2006-02-14 09:20:41 UTC (rev 416)
@@ -0,0 +1,22 @@
+#
+# httpservlet.rb -- HTTPServlet Utility File
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
+# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: httpservlet.rb,v 1.21 2003/02/23 12:24:46 gotoyuzo Exp $
+
+require 'webrick/httpservlet/abstract'
+require 'webrick/httpservlet/filehandler'
+require 'webrick/httpservlet/cgihandler'
+require 'webrick/httpservlet/erbhandler'
+require 'webrick/httpservlet/prochandler'
+
+module WEBrick
+  module HTTPServlet
+    FileHandler.add_handler("cgi", CGIHandler)
+    FileHandler.add_handler("rhtml", ERBHandler)
+  end
+end

Added: trunk/lib/webrick/httpstatus.rb
===================================================================
--- trunk/lib/webrick/httpstatus.rb	2006-02-14 05:59:01 UTC (rev 415)
+++ trunk/lib/webrick/httpstatus.rb	2006-02-14 09:20:41 UTC (rev 416)
@@ -0,0 +1,126 @@
+#
+# httpstatus.rb -- HTTPStatus Class
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
+# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: httpstatus.rb,v 1.11 2003/03/24 20:18:55 gotoyuzo Exp $
+
+module WEBrick
+
+  module HTTPStatus
+
+    class Status      < StandardError; end
+    class Info        < Status; end
+    class Success     < Status; end
+    class Redirect    < Status; end
+    class Error       < Status; end
+    class ClientError < Error; end
+    class ServerError < Error; end
+    
+    class EOFError < StandardError; end
+
+    StatusMessage = {
+      100, 'Continue',
+      101, 'Switching Protocols',
+      200, 'OK',
+      201, 'Created',
+      202, 'Accepted',
+      203, 'Non-Authoritative Information',
+      204, 'No Content',
+      205, 'Reset Content',
+      206, 'Partial Content',
+      300, 'Multiple Choices',
+      301, 'Moved Permanently',
+      302, 'Found',
+      303, 'See Other',
+      304, 'Not Modified',
+      305, 'Use Proxy',
+      307, 'Temporary Redirect',
+      400, 'Bad Request',
+      401, 'Unauthorized',
+      402, 'Payment Required',
+      403, 'Forbidden',
+      404, 'Not Found',
+      405, 'Method Not Allowed',
+      406, 'Not Acceptable',
+      407, 'Proxy Authentication Required',
+      408, 'Request Timeout',
+      409, 'Conflict',
+      410, 'Gone',
+      411, 'Length Required',
+      412, 'Precondition Failed',
+      413, 'Request Entity Too Large',
+      414, 'Request-URI Too Large',
+      415, 'Unsupported Media Type',
+      416, 'Request Range Not Satisfiable',
+      417, 'Expectation Failed',
+      500, 'Internal Server Error',
+      501, 'Not Implemented',
+      502, 'Bad Gateway',
+      503, 'Service Unavailable',
+      504, 'Gateway Timeout',
+      505, 'HTTP Version Not Supported'
+    }
+
+    CodeToError = {}
+
+    StatusMessage.each{|code, message|
+      var_name = message.gsub(/[ \-]/,'_').upcase
+      err_name = message.gsub(/[ \-]/,'')
+
+      case code
+      when 100...200; parent = Info
+      when 200...300; parent = Success
+      when 300...400; parent = Redirect
+      when 400...500; parent = ClientError
+      when 500...600; parent = ServerError
+      end
+
+      eval %-
+        RC_#{var_name} = #{code}
+        class #{err_name} < #{parent}
+          def self.code() RC_#{var_name} end
+          def self.reason_phrase() StatusMessage[code] end
+          def code() self::class::code end 
+          def reason_phrase() self::class::reason_phrase end
+          alias to_i code
+        end
+      -
+
+      CodeToError[code] = const_get(err_name)
+    }
+
+    def reason_phrase(code)
+      StatusMessage[code.to_i]
+    end
+    def info?(code)
+      code.to_i >= 100 and code.to_i < 200
+    end
+    def success?(code)
+      code.to_i >= 200 and code.to_i < 300
+    end
+    def redirect?(code)
+      code.to_i >= 300 and code.to_i < 400
+    end
+    def error?(code)
+      code.to_i >= 400 and code.to_i < 600
+    end
+    def client_error?(code)
+      code.to_i >= 400 and code.to_i < 500
+    end
+    def server_error?(code)
+      code.to_i >= 500 and code.to_i < 600
+    end
+
+    def self.[](code)
+      CodeToError[code]
+    end
+
+    module_function :reason_phrase
+    module_function :info?, :success?, :redirect?, :error?
+    module_function :client_error?, :server_error?
+  end
+end

Added: trunk/lib/webrick/httputils.rb
===================================================================
--- trunk/lib/webrick/httputils.rb	2006-02-14 05:59:01 UTC (rev 415)
+++ trunk/lib/webrick/httputils.rb	2006-02-14 09:20:41 UTC (rev 416)
@@ -0,0 +1,400 @@
+#
+# httputils.rb -- HTTPUtils Module
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
+# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: httputils.rb,v 1.34 2003/06/05 21:34:08 gotoyuzo Exp $
+
+require 'socket'
+require 'tempfile'
+
+module WEBrick
+  CR   = "\x0d"
+  LF   = "\x0a"
+  CRLF = "\x0d\x0a"
+
+  module HTTPUtils
+
+    def normalize_path(path)
+      raise "abnormal path `#{path}'" if path[0] != ?/
+      ret = path.dup
+
+      ret.gsub!(%r{/+}o, '/')                    # //      => /
+      while ret.sub!(%r{/\.(/|\Z)}o, '/'); end   # /.      => /
+      begin                                      # /foo/.. => /foo
+        match = ret.sub!(%r{/([^/]+)/\.\.(/|\Z)}o){
+          if $1 == ".."
+            raise "abnormal path `#{path}'"
+          else
+            "/"
+          end
+        }
+      end while match
+
+      raise "abnormal path `#{path}'" if %r{/\.\.(/|\Z)} =~ ret
+      ret
+    end
+    module_function :normalize_path
+
+    #####
+
+    DefaultMimeTypes = {
+      "ai"    => "application/postscript",
+      "asc"   => "text/plain",
+      "avi"   => "video/x-msvideo",
+      "bin"   => "application/octet-stream",
+      "bmp"   => "image/bmp",
+      "class" => "application/octet-stream",
+      "cer"   => "application/pkix-cert",
+      "crl"   => "application/pkix-crl",
+      "crt"   => "application/x-x509-ca-cert",
+     #"crl"   => "application/x-pkcs7-crl",
+      "css"   => "text/css",
+      "dms"   => "application/octet-stream",
+      "doc"   => "application/msword",
+      "dvi"   => "application/x-dvi",
+      "eps"   => "application/postscript",
+      "etx"   => "text/x-setext",
+      "exe"   => "application/octet-stream",
+      "gif"   => "image/gif",
+      "htm"   => "text/html",
+      "html"  => "text/html",
+      "jpe"   => "image/jpeg",
+      "jpeg"  => "image/jpeg",
+      "jpg"   => "image/jpeg",
+      "lha"   => "application/octet-stream",
+      "lzh"   => "application/octet-stream",
+      "mov"   => "video/quicktime",
+      "mpe"   => "video/mpeg",
+      "mpeg"  => "video/mpeg",
+      "mpg"   => "video/mpeg",
+      "pbm"   => "image/x-portable-bitmap",
+      "pdf"   => "application/pdf",
+      "pgm"   => "image/x-portable-graymap",
+      "png"   => "image/png",
+      "pnm"   => "image/x-portable-anymap",
+      "ppm"   => "image/x-portable-pixmap",
+      "ppt"   => "application/vnd.ms-powerpoint",
+      "ps"    => "application/postscript",
+      "qt"    => "video/quicktime",
+      "ras"   => "image/x-cmu-raster",
+      "rb"    => "text/plain",
+      "rd"    => "text/plain",
+      "rtf"   => "application/rtf",
+      "sgm"   => "text/sgml",
+      "sgml"  => "text/sgml",
+      "tif"   => "image/tiff",
+      "tiff"  => "image/tiff",
+      "txt"   => "text/plain",
+      "xbm"   => "image/x-xbitmap",
+      "xhtml" => "text/html",
+      "xls"   => "application/vnd.ms-excel",
+      "xml"   => "text/xml",
+      "xpm"   => "image/x-xpixmap",
+      "xwd"   => "image/x-xwindowdump",
+      "zip"   => "application/zip",
+    }
+
+    # Load Apache compatible mime.types file.
+    def load_mime_types(file)
+      open(file){ |io|
+        hash = Hash.new
+        io.each{ |line|
+          next if /^#/ =~ line
+          line.chomp!
+          mimetype, ext0 = line.split(/\s+/, 2)
+          next unless ext0   
+          next if ext0.empty?
+          ext0.split(/\s+/).each{ |ext| hash[ext] = mimetype }
+        }
+        hash
+      }
+    end
+    module_function :load_mime_types
+
+    def mime_type(filename, mime_tab)
+      suffix1 = (/\.(\w+)$/ =~ filename && $1.downcase)
+      suffix2 = (/\.(\w+)\.[\w\-]+$/ =~ filename && $1.downcase)
+      mime_tab[suffix1] || mime_tab[suffix2] || "application/octet-stream"
+    end
+    module_function :mime_type
+
+    #####
+
+    def parse_header(raw)
+      header = Hash.new([].freeze)
+      field = nil
+      raw.each{|line|
+        case line
+        when /^([A-Za-z0-9!\#$%&'*+\-.^_`|~]+):\s*(.*?)\s*\z/om
+          field, value = $1, $2
+          field.downcase!
+          header[field] = [] unless header.has_key?(field)
+          header[field] << value
+        when /^\s+(.*?)\s*\z/om
+          value = $1
+          unless field
+            raise "bad header '#{line.inspect}'."
+          end
+          header[field][-1] << " " << value
+        else
+          raise "bad header '#{line.inspect}'."
+        end
+      }
+      header.each{|key, values|
+        values.each{|value|
+          value.strip!
+          value.gsub!(/\s+/, " ")
+        }
+      }
+      header
+    end
+    module_function :parse_header
+
+    def split_header_value(str)
+      str.scan(/((?:"(?:\\.|[^"])+?"|[^",]+)+)
+                (?:,\s*|\Z)/xn).collect{|v| v[0] }
+    end
+    module_function :split_header_value
+
+    def parse_range_header(ranges_specifier)
+      if /^bytes=(.*)/ =~ ranges_specifier
+        byte_range_set = split_header_value($1)
+        byte_range_set.collect{|range_spec|
+          case range_spec
+          when /^(\d+)-(\d+)/ then $1.to_i .. $2.to_i
+          when /^(\d+)-/      then $1.to_i .. -1
+          when /^-(\d+)/      then -($1.to_i) .. -1
+          else return nil
+          end
+        }
+      end
+    end
+    module_function :parse_range_header
+
+    def parse_qvalues(value)
+      tmp = []
+      if value
+        parts = value.split(/,\s*/)
+        parts.each {|part|
+          if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
+            val = m[1]
+            q = (m[2] or 1).to_f
+            tmp.push([val, q])
+          end
+        }
+        tmp = tmp.sort_by{|val, q| -q}
+        tmp.collect!{|val, q| val}
+      end
+      return tmp
+    end
+    module_function :parse_qvalues
+
+    #####
+
+    def dequote(str)
+      ret = (/\A"(.*)"\Z/ =~ str) ? $1 : str.dup
+      ret.gsub!(/\\(.)/, "\\1")
+      ret
+    end
+    module_function :dequote
+
+    def quote(str)
+      '"' << str.gsub(/[\\\"]/o, "\\\1") << '"'
+    end
+    module_function :quote
+
+    #####
+
+    class FormData < String
+      EmptyRawHeader = [].freeze
+      EmptyHeader = {}.freeze
+
+      attr_accessor :name, :filename, :next_data
+      protected :next_data
+
+      def initialize(*args)
+        @name = @filename = @next_data = nil
+        if args.empty?
+          @raw_header = []
+          @header = nil
+          super("")
+        else
+          @raw_header = EmptyRawHeader
+          @header = EmptyHeader 
+          super(args.shift)
+          unless args.empty?
+            @next_data = self.class.new(*args)
+          end
+        end
+      end
+
+      def [](*key)
+        begin
+          @header[key[0].downcase].join(", ")
+        rescue StandardError, NameError
+          super
+        end
+      end
+
+      def <<(str)
+        if @header
+          super
+        elsif str == CRLF
+          @header = HTTPUtils::parse_header(@raw_header)
+          if cd = self['content-disposition']
+            if /\s+name="(.*?)"/ =~ cd then @name = $1 end
+            if /\s+filename="(.*?)"/ =~ cd then @filename = $1 end
+          end
+        else
+          @raw_header << str
+        end
+