mirror of
				https://github.com/ruby/ruby.git
				synced 2022-11-09 12:17:21 -05:00 
			
		
		
		
	* lib/webrick: imported.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@4130 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
		
							parent
							
								
									02f036ddbc
								
							
						
					
					
						commit
						01eba908ad
					
				
					 33 changed files with 3881 additions and 0 deletions
				
			
		
							
								
								
									
										29
									
								
								lib/webrick.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								lib/webrick.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,29 @@
 | 
			
		|||
#
 | 
			
		||||
# WEBrick -- WEB server toolkit.
 | 
			
		||||
#
 | 
			
		||||
# 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: webrick.rb,v 1.12 2002/10/01 17:16:31 gotoyuzo Exp $
 | 
			
		||||
 | 
			
		||||
require 'webrick/compat.rb'
 | 
			
		||||
 | 
			
		||||
require 'webrick/version.rb'
 | 
			
		||||
require 'webrick/config.rb'
 | 
			
		||||
require 'webrick/log.rb'
 | 
			
		||||
require 'webrick/server.rb'
 | 
			
		||||
require 'webrick/utils.rb'
 | 
			
		||||
require 'webrick/accesslog'
 | 
			
		||||
 | 
			
		||||
require 'webrick/htmlutils.rb'
 | 
			
		||||
require 'webrick/httputils.rb'
 | 
			
		||||
require 'webrick/cookie.rb'
 | 
			
		||||
require 'webrick/httpversion.rb'
 | 
			
		||||
require 'webrick/httpstatus.rb'
 | 
			
		||||
require 'webrick/httprequest.rb'
 | 
			
		||||
require 'webrick/httpresponse.rb'
 | 
			
		||||
require 'webrick/httpserver.rb'
 | 
			
		||||
require 'webrick/httpservlet.rb'
 | 
			
		||||
require 'webrick/httpauth.rb'
 | 
			
		||||
							
								
								
									
										64
									
								
								lib/webrick/accesslog.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								lib/webrick/accesslog.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,64 @@
 | 
			
		|||
#
 | 
			
		||||
# 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["o"] = res
 | 
			
		||||
      params["p"] = config[: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, ?o
 | 
			
		||||
           raise AccessLogError,
 | 
			
		||||
             "parameter is required for \"#{spec}\"" unless param
 | 
			
		||||
           params[spec][param] || "-"
 | 
			
		||||
         when ?t
 | 
			
		||||
           params[spec].strftime(param || CLF_TIME_FORMAT)
 | 
			
		||||
         else
 | 
			
		||||
           params[spec]
 | 
			
		||||
         end
 | 
			
		||||
      }
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
							
								
								
									
										30
									
								
								lib/webrick/compat.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								lib/webrick/compat.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,30 @@
 | 
			
		|||
#
 | 
			
		||||
# 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
 | 
			
		||||
 | 
			
		||||
unless File.respond_to?(:fnmatch)
 | 
			
		||||
  def File.fnmatch(pat, str)
 | 
			
		||||
    case pat[0]
 | 
			
		||||
    when nil
 | 
			
		||||
      not str[0]
 | 
			
		||||
    when ?*
 | 
			
		||||
      fnmatch(pat[1..-1], str) || str[0] && fnmatch(pat, str[1..-1])
 | 
			
		||||
    when ??
 | 
			
		||||
      str[0] && fnmatch(pat[1..-1], str[1..-1])
 | 
			
		||||
    else
 | 
			
		||||
      pat[0] == str[0] && fnmatch(pat[1..-1], str[1..-1])
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
							
								
								
									
										96
									
								
								lib/webrick/config.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								lib/webrick/config.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,96 @@
 | 
			
		|||
#
 | 
			
		||||
# 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!!
 | 
			
		||||
      :Listen         => [],    # list of pairs of alt addr/port.
 | 
			
		||||
      :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,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    # 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 },
 | 
			
		||||
 | 
			
		||||
      :RequestHandler => nil,
 | 
			
		||||
      :ProxyAuthProc  => nil,
 | 
			
		||||
      :ProxyContentHandler => nil,
 | 
			
		||||
      :ProxyVia       => true,
 | 
			
		||||
      :ProxyTimeout   => true,
 | 
			
		||||
 | 
			
		||||
      # upstream proxy server
 | 
			
		||||
      :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           => "public_html",
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
							
								
								
									
										80
									
								
								lib/webrick/cookie.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								lib/webrick/cookie.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,80 @@
 | 
			
		|||
#
 | 
			
		||||
# 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
 | 
			
		||||
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
							
								
								
									
										25
									
								
								lib/webrick/htmlutils.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								lib/webrick/htmlutils.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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, '&')
 | 
			
		||||
      str.gsub!(/\"/n, '"')
 | 
			
		||||
      str.gsub!(/>/n, '>')
 | 
			
		||||
      str.gsub!(/</n, '<')
 | 
			
		||||
      str
 | 
			
		||||
    end
 | 
			
		||||
    module_function :escape
 | 
			
		||||
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
							
								
								
									
										46
									
								
								lib/webrick/httpauth.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								lib/webrick/httpauth.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,46 @@
 | 
			
		|||
#
 | 
			
		||||
# 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 'base64'
 | 
			
		||||
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 = decode64(userpass).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
 | 
			
		||||
							
								
								
									
										79
									
								
								lib/webrick/httpauth/authenticator.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								lib/webrick/httpauth/authenticator.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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
 | 
			
		||||
							
								
								
									
										66
									
								
								lib/webrick/httpauth/basicauth.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								lib/webrick/httpauth/basicauth.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,66 @@
 | 
			
		|||
#
 | 
			
		||||
# 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'
 | 
			
		||||
require 'base64'
 | 
			
		||||
 | 
			
		||||
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 = decode64(basic_credentials).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
 | 
			
		||||
							
								
								
									
										348
									
								
								lib/webrick/httpauth/digestauth.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										348
									
								
								lib/webrick/httpauth/digestauth.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,348 @@
 | 
			
		|||
#
 | 
			
		||||
# 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'
 | 
			
		||||
require 'base64'
 | 
			
		||||
 | 
			
		||||
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 = digest("", auth_req['uri'])
 | 
			
		||||
        elsif auth_req['qop'] == "auth-int"
 | 
			
		||||
          ha2 = hexdigest(req.request_method, auth_req['uri'],
 | 
			
		||||
                          hexdigest(req.body))
 | 
			
		||||
          ha2_res = digest("", auth_req['uri'], hexdigest(req.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 = encode64(now + ":" + pk).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 = decode64(nonce).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
 | 
			
		||||
 | 
			
		||||
      def digest(*args)
 | 
			
		||||
        @h.digest(args.join(":"))
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    class ProxyDigestAuth < DigestAuth
 | 
			
		||||
      include ProxyAuthenticator
 | 
			
		||||
 | 
			
		||||
      def check_uri(req, auth_req)
 | 
			
		||||
        return true
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
							
								
								
									
										91
									
								
								lib/webrick/httpauth/htdigest.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								lib/webrick/httpauth/htdigest.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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
 | 
			
		||||
							
								
								
									
										61
									
								
								lib/webrick/httpauth/htgroup.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								lib/webrick/httpauth/htgroup.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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
 | 
			
		||||
							
								
								
									
										75
									
								
								lib/webrick/httpauth/htpasswd.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								lib/webrick/httpauth/htpasswd.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,75 @@
 | 
			
		|||
#
 | 
			
		||||
# 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!
 | 
			
		||||
              user, pass = line.split(":")
 | 
			
		||||
              @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
 | 
			
		||||
							
								
								
									
										29
									
								
								lib/webrick/httpauth/userdb.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								lib/webrick/httpauth/userdb.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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
 | 
			
		||||
							
								
								
									
										237
									
								
								lib/webrick/httpproxy.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										237
									
								
								lib/webrick/httpproxy.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,237 @@
 | 
			
		|||
#
 | 
			
		||||
# 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
 | 
			
		||||
  class HTTPProxyServer < HTTPServer
 | 
			
		||||
    def initialize(config)
 | 
			
		||||
      super
 | 
			
		||||
      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 " + encode64(proxy.userinfo)
 | 
			
		||||
          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 " + encode64(proxy.userinfo)
 | 
			
		||||
        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
 | 
			
		||||
        res.send_response(ua)
 | 
			
		||||
        access_log(@config, req, res)
 | 
			
		||||
      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
 | 
			
		||||
							
								
								
									
										339
									
								
								lib/webrick/httprequest.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										339
									
								
								lib/webrick/httprequest.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,339 @@
 | 
			
		|||
#
 | 
			
		||||
# 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, :query_string
 | 
			
		||||
    attr_accessor :script_name, :path_info
 | 
			
		||||
 | 
			
		||||
    # Header and entity body
 | 
			
		||||
    attr_reader :raw_header, :header, :cookies
 | 
			
		||||
 | 
			
		||||
    # 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 = []
 | 
			
		||||
      @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)
 | 
			
		||||
        }
 | 
			
		||||
      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 [](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.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"]       = @request_uri.host
 | 
			
		||||
      meta["SERVER_PORT"]       = @config[:Port].to_s
 | 
			
		||||
      meta["SERVER_PROTOCOL"]   = "HTTP/" + @config[:HTTPVersion].to_s
 | 
			
		||||
      meta["SERVER_SOFTWARE"]   = @config[:ServerSoftware].dup
 | 
			
		||||
 | 
			
		||||
      self.each{|key, val|
 | 
			
		||||
        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
 | 
			
		||||
							
								
								
									
										304
									
								
								lib/webrick/httpresponse.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										304
									
								
								lib/webrick/httpresponse.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,304 @@
 | 
			
		|||
#
 | 
			
		||||
# 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_reader :config, :keep_alive, :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 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
 | 
			
		||||
        @logger.error("HTTPResponse#send_response: EPIPE occured.")
 | 
			
		||||
        @keep_alive = false
 | 
			
		||||
      rescue => 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
 | 
			
		||||
      end
 | 
			
		||||
      if keep_alive?
 | 
			
		||||
        if chunked? || @header['content-length']
 | 
			
		||||
          @header['connection'] = "Keep-Alive"
 | 
			
		||||
        end
 | 
			
		||||
      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)
 | 
			
		||||
      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.to_i)
 | 
			
		||||
        @sent_size = size
 | 
			
		||||
      end
 | 
			
		||||
      @body.close
 | 
			
		||||
    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
 | 
			
		||||
							
								
								
									
										158
									
								
								lib/webrick/https.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								lib/webrick/https.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,158 @@
 | 
			
		|||
#
 | 
			
		||||
# 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'
 | 
			
		||||
require 'openssl'
 | 
			
		||||
 | 
			
		||||
module WEBrick
 | 
			
		||||
  module Config
 | 
			
		||||
    HTTP.update(
 | 
			
		||||
      :SSLEnable            => true,
 | 
			
		||||
      :SSLCertificate       => nil, 
 | 
			
		||||
      :SSLPrivateKey        => nil,
 | 
			
		||||
      :SSLClientCA          => nil,
 | 
			
		||||
      :SSLCACertificateFile => nil,
 | 
			
		||||
      :SSLCACertificatePath => nil,
 | 
			
		||||
      :SSLCertStore         => nil,
 | 
			
		||||
      :SSLVerifyClient      => ::OpenSSL::SSL::VERIFY_NONE, 
 | 
			
		||||
      :SSLVerifyDepth       => nil,
 | 
			
		||||
      :SSLVerifyCallback    => nil,   # custom verification
 | 
			
		||||
      :SSLTimeout           => nil,
 | 
			
		||||
      :SSLOptions           => nil,
 | 
			
		||||
      # Must specify if you use auto generated certificate.
 | 
			
		||||
      :SSLCertName          => nil,
 | 
			
		||||
      :SSLCertComment       => "Generated by Ruby/OpenSSL"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    osslv = ::OpenSSL::OPENSSL_VERSION.split[1]
 | 
			
		||||
    HTTP[:ServerSoftware] << " OpenSSL/#{osslv}"
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  class HTTPRequest
 | 
			
		||||
    attr_reader :cipher, :server_cert, :client_cert
 | 
			
		||||
 | 
			
		||||
    alias orig_parse parse
 | 
			
		||||
 | 
			
		||||
    def parse(socket=nil)
 | 
			
		||||
      orig_parse(socket)
 | 
			
		||||
      @cipher      = socket.respond_to?(:cipher) ? socket.cipher : nil
 | 
			
		||||
      @client_cert = socket.respond_to?(:peer_cert) ? socket.peer_cert : nil
 | 
			
		||||
      @server_cert = @config[:SSLCertificate]
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    alias orig_parse_uri parse_uri
 | 
			
		||||
 | 
			
		||||
    def parse_uri(str, scheme="https")
 | 
			
		||||
      if @config[:SSLEnable]
 | 
			
		||||
        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 @config[:SSLEnable]
 | 
			
		||||
        meta["HTTPS"] = "on"
 | 
			
		||||
        meta["SSL_CIPHER"] = @cipher ? @cipher[0] : ""
 | 
			
		||||
        meta["SSL_CLIENT_CERT"] = @client_cert ? @client_cert.to_pem : ""
 | 
			
		||||
        meta["SSL_SERVER_CERT"] = @server_cert ? @server_cert.to_pem : ""
 | 
			
		||||
      end
 | 
			
		||||
      meta
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  class HTTPServer
 | 
			
		||||
    alias orig_init initialize
 | 
			
		||||
 | 
			
		||||
    def initialize(*args)
 | 
			
		||||
      orig_init(*args)
 | 
			
		||||
 | 
			
		||||
      if @config[:SSLEnable] 
 | 
			
		||||
        unless @config[:SSLCertificate]
 | 
			
		||||
          rsa = OpenSSL::PKey::RSA.new(512){|p, n|
 | 
			
		||||
            case p
 | 
			
		||||
            when 0; $stderr.putc "."  # BN_generate_prime
 | 
			
		||||
            when 1; $stderr.putc "+"  # BN_generate_prime
 | 
			
		||||
            when 2; $stderr.putc "*"  # searching good prime,
 | 
			
		||||
                                      # n = #of try,
 | 
			
		||||
                                      # but also data from BN_generate_prime
 | 
			
		||||
            when 3; $stderr.putc "\n" # found good prime, n==0 - p, n==1 - q,
 | 
			
		||||
                                      # but also data from BN_generate_prime
 | 
			
		||||
            else;   $stderr.putc "*"  # BN_generate_prime
 | 
			
		||||
            end
 | 
			
		||||
          }
 | 
			
		||||
          cert = OpenSSL::X509::Certificate.new
 | 
			
		||||
          cert.version = 3
 | 
			
		||||
          cert.serial = 0
 | 
			
		||||
          name = OpenSSL::X509::Name.new(@config[:SSLCertName])
 | 
			
		||||
          cert.subject = name
 | 
			
		||||
          cert.issuer = name
 | 
			
		||||
          cert.not_before = Time.now
 | 
			
		||||
          cert.not_after = Time.now + (365*24*60*60)
 | 
			
		||||
          cert.public_key = rsa.public_key
 | 
			
		||||
 | 
			
		||||
          ef = OpenSSL::X509::ExtensionFactory.new(nil,cert)
 | 
			
		||||
          cert.extensions = [
 | 
			
		||||
            ef.create_extension("basicConstraints","CA:FALSE"),
 | 
			
		||||
            ef.create_extension("subjectKeyIdentifier", "hash"),
 | 
			
		||||
            ef.create_extension("extendedKeyUsage", "serverAuth")
 | 
			
		||||
          ]
 | 
			
		||||
          ef.issuer_certificate = cert
 | 
			
		||||
          ext = ef.create_extension("authorityKeyIdentifier",
 | 
			
		||||
                                    "keyid:always,issuer:always")
 | 
			
		||||
          cert.add_extension(ext)
 | 
			
		||||
          if comment = @config[:SSLCertComment]
 | 
			
		||||
            cert.add_extension(ef.create_extension("nsComment", comment))
 | 
			
		||||
          end
 | 
			
		||||
          cert.sign(rsa, OpenSSL::Digest::SHA1.new)
 | 
			
		||||
 | 
			
		||||
          @config[:SSLPrivateKey] = rsa
 | 
			
		||||
          @config[:SSLCertificate] = cert
 | 
			
		||||
          @logger.info cert.to_s
 | 
			
		||||
        end
 | 
			
		||||
        @ctx = OpenSSL::SSL::SSLContext.new
 | 
			
		||||
        set_ssl_context(@ctx, @config)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    alias orig_run run
 | 
			
		||||
 | 
			
		||||
    def run(sock)
 | 
			
		||||
      if @config[:SSLEnable] 
 | 
			
		||||
        ssl = OpenSSL::SSL::SSLSocket.new(sock, @ctx)
 | 
			
		||||
        ssl.accept
 | 
			
		||||
        Thread.current[:WEBrickSocket] = ssl
 | 
			
		||||
        orig_run(ssl)
 | 
			
		||||
        Thread.current[:WEBrickSocket] = sock
 | 
			
		||||
        ssl.close
 | 
			
		||||
      else
 | 
			
		||||
        orig_run(sock)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    private
 | 
			
		||||
 | 
			
		||||
    def set_ssl_context(ctx, config)
 | 
			
		||||
      ctx.key = config[:SSLPrivateKey]
 | 
			
		||||
      ctx.cert = config[:SSLCertificate]
 | 
			
		||||
      ctx.client_ca = config[:SSLClientCA]
 | 
			
		||||
      ctx.ca_file = config[:SSLCACertificateFile]
 | 
			
		||||
      ctx.ca_path = config[:SSLCACertificatePath]
 | 
			
		||||
      ctx.cert_store = config[:SSLCertStore]
 | 
			
		||||
      ctx.verify_mode = config[:SSLVerifyClient]
 | 
			
		||||
      ctx.verify_depth = config[:SSLVerifyDepth]
 | 
			
		||||
      ctx.verify_callback = config[:SSLVerifyCallback]
 | 
			
		||||
      ctx.timeout = config[:SSLTimeout]
 | 
			
		||||
      ctx.options = config[:SSLOptions]
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
							
								
								
									
										179
									
								
								lib/webrick/httpserver.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								lib/webrick/httpserver.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,179 @@
 | 
			
		|||
#
 | 
			
		||||
# 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
 | 
			
		||||
      @http_version = HTTPVersion::convert(@config[:HTTPVersion])
 | 
			
		||||
 | 
			
		||||
      @mount_tab = MountTable.new
 | 
			
		||||
      if @config[:DocumentRoot]
 | 
			
		||||
        mount("/", HTTPServlet::FileHandler, @config[:DocumentRoot],
 | 
			
		||||
              @config[:DocumentRootOptions])
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      unless @config[:AccessLog]
 | 
			
		||||
        basic_log = BasicLog::new
 | 
			
		||||
        @config[:AccessLog] = [
 | 
			
		||||
          [ basic_log, AccessLog::COMMON_LOG_FORMAT ],
 | 
			
		||||
          [ basic_log, AccessLog::REFERER_LOG_FORMAT ]
 | 
			
		||||
        ]
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def run(sock)
 | 
			
		||||
      while true 
 | 
			
		||||
        res = HTTPResponse.new(@config)
 | 
			
		||||
        req = HTTPRequest.new(@config)
 | 
			
		||||
        begin
 | 
			
		||||
          req.parse(sock)
 | 
			
		||||
          res.request_method = req.request_method
 | 
			
		||||
          res.request_uri = req.request_uri
 | 
			
		||||
          res.request_http_version = req.http_version
 | 
			
		||||
          if handler = @config[:RequestHandler]
 | 
			
		||||
            handler.call(req, res)
 | 
			
		||||
          end
 | 
			
		||||
          service(req, res)
 | 
			
		||||
        rescue HTTPStatus::EOFError, HTTPStatus::RequestTimeout => ex
 | 
			
		||||
          res.set_error(ex)
 | 
			
		||||
        rescue HTTPStatus::Error => ex
 | 
			
		||||
          res.set_error(ex)
 | 
			
		||||
        rescue HTTPStatus::Status => ex
 | 
			
		||||
          res.status = ex.code
 | 
			
		||||
        rescue StandardError, NameError => ex # for Ruby 1.6
 | 
			
		||||
          @logger.error(ex)
 | 
			
		||||
          res.set_error(ex, true)
 | 
			
		||||
        ensure
 | 
			
		||||
          if req.request_line
 | 
			
		||||
            req.fixup()
 | 
			
		||||
            res.send_response(sock)
 | 
			
		||||
            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 access_log(config, req, res)
 | 
			
		||||
      param = AccessLog::setup_params(config, req, res)
 | 
			
		||||
      level = Log::INFO
 | 
			
		||||
      @config[:AccessLog].each{|logger, fmt|
 | 
			
		||||
        logger.log(level, AccessLog::format(fmt, 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
 | 
			
		||||
							
								
								
									
										22
									
								
								lib/webrick/httpservlet.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								lib/webrick/httpservlet.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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
 | 
			
		||||
							
								
								
									
										71
									
								
								lib/webrick/httpservlet/abstract.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								lib/webrick/httpservlet/abstract.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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
 | 
			
		||||
							
								
								
									
										45
									
								
								lib/webrick/httpservlet/cgi_runner.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								lib/webrick/httpservlet/cgi_runner.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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"]
 | 
			
		||||
							
								
								
									
										93
									
								
								lib/webrick/httpservlet/cgihandler.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								lib/webrick/httpservlet/cgihandler.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,93 @@
 | 
			
		|||
# 
 | 
			
		||||
# 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'])
 | 
			
		||||
      CGIRunner = "#{Ruby} #{Config::LIBDIR}/httpservlet/cgi_runner.rb"
 | 
			
		||||
 | 
			
		||||
      def initialize(server, name)
 | 
			
		||||
        super
 | 
			
		||||
        @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, "w")
 | 
			
		||||
        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]
 | 
			
		||||
          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 = $? >> 8
 | 
			
		||||
          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,
 | 
			
		||||
          "The server encontered a script error." if body.nil?
 | 
			
		||||
 | 
			
		||||
        begin
 | 
			
		||||
          header = HTTPUtils::parse_header(raw_header)
 | 
			
		||||
          if /^(\d+)/ =~ header['status'][0]
 | 
			
		||||
            res.status = $1.to_i
 | 
			
		||||
            header.delete('status')
 | 
			
		||||
          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
 | 
			
		||||
							
								
								
									
										53
									
								
								lib/webrick/httpservlet/erbhandler.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								lib/webrick/httpservlet/erbhandler.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,53 @@
 | 
			
		|||
# 
 | 
			
		||||
# 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
 | 
			
		||||
        @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'] = "text/html"
 | 
			
		||||
        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
 | 
			
		||||
							
								
								
									
										330
									
								
								lib/webrick/httpservlet/filehandler.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										330
									
								
								lib/webrick/httpservlet/filehandler.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,330 @@
 | 
			
		|||
#
 | 
			
		||||
# 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
 | 
			
		||||
        @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_valie(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 BadRequest, "Unrecognized range-spec: \"#{range}\""
 | 
			
		||||
        end
 | 
			
		||||
        open(filename, "rb"){|io|
 | 
			
		||||
          if ranges.size > 1
 | 
			
		||||
            boundary = "#{time.sec}_#{time.usec}_#{Process::pid}"
 | 
			
		||||
            body = ''
 | 
			
		||||
            ranges.each{|r|
 | 
			
		||||
              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
 | 
			
		||||
          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(DefaultFileHandler)
 | 
			
		||||
 | 
			
		||||
      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 = 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)
 | 
			
		||||
          suffix = (/\.(\w+)$/ =~ res.filename) && $1
 | 
			
		||||
          handler = @options[:HandlerTable][suffix] || HandlerTable[suffix]
 | 
			
		||||
          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 set_filename(req, res)
 | 
			
		||||
        handler = nil
 | 
			
		||||
        res.filename = @root.dup
 | 
			
		||||
        path_info = req.path_info.scan(%r|/[^/]*|)
 | 
			
		||||
 | 
			
		||||
        while name = path_info.shift
 | 
			
		||||
          if name == "/"
 | 
			
		||||
            indices = @config[:DirectoryIndex]
 | 
			
		||||
            index = indices.find{|i| FileTest::file?("#{res.filename}/#{i}") }
 | 
			
		||||
            name = "/#{index}" if index
 | 
			
		||||
          end
 | 
			
		||||
          res.filename << name
 | 
			
		||||
          req.script_name << name
 | 
			
		||||
          req.path_info = path_info.join
 | 
			
		||||
 | 
			
		||||
          if File::fnmatch("/#{@options[:NondisclosureName]}", name)
 | 
			
		||||
            @logger.log(Log::WARN,
 | 
			
		||||
               "the request refers nondisclosure name `#{name}'.")
 | 
			
		||||
            raise HTTPStatus::Forbidden, "`#{req.path}' not found."
 | 
			
		||||
          end
 | 
			
		||||
          st = (File::stat(res.filename) rescue nil)
 | 
			
		||||
          raise HTTPStatus::NotFound, "`#{req.path}' not found." unless st
 | 
			
		||||
          raise HTTPStatus::Forbidden,
 | 
			
		||||
            "no access permission to `#{req.path}'." unless st.readable?
 | 
			
		||||
 | 
			
		||||
          if st.directory?
 | 
			
		||||
            call_callback(:DirectoryCallback, req, res)
 | 
			
		||||
          else
 | 
			
		||||
            call_callback(:FileCallback, req, res)
 | 
			
		||||
            return true
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
        return false
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def call_callback(callback_name, req, res)
 | 
			
		||||
        if cb = @options[callback_name]
 | 
			
		||||
          cb.call(req, res)
 | 
			
		||||
        end
 | 
			
		||||
      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 File::fnmatch(@options[:NondisclosureName], 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.request_uri.host}:#{@config[:Port]}
 | 
			
		||||
    </ADDRESS>
 | 
			
		||||
  </BODY>
 | 
			
		||||
</HTML>
 | 
			
		||||
        _end_of_html_
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
							
								
								
									
										33
									
								
								lib/webrick/httpservlet/prochandler.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								lib/webrick/httpservlet/prochandler.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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
 | 
			
		||||
							
								
								
									
										126
									
								
								lib/webrick/httpstatus.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								lib/webrick/httpstatus.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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
 | 
			
		||||
							
								
								
									
										374
									
								
								lib/webrick/httputils.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										374
									
								
								lib/webrick/httputils.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,374 @@
 | 
			
		|||
#
 | 
			
		||||
# 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",
 | 
			
		||||
      "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)
 | 
			
		||||
      if suffix = (/\.(\w+)$/ =~ filename && $1)
 | 
			
		||||
        mtype = mime_tab[suffix.downcase]
 | 
			
		||||
      end
 | 
			
		||||
      mtype || "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 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
 | 
			
		||||
        self
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def append_data(data)
 | 
			
		||||
        tmp = self
 | 
			
		||||
        while tmp
 | 
			
		||||
          unless tmp.next_data 
 | 
			
		||||
            tmp.next_data = data
 | 
			
		||||
            break
 | 
			
		||||
          end
 | 
			
		||||
          tmp = tmp.next_data
 | 
			
		||||
        end
 | 
			
		||||
        self
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def each_data
 | 
			
		||||
        tmp = self
 | 
			
		||||
        while tmp
 | 
			
		||||
          next_data = tmp.next_data
 | 
			
		||||
          yield(tmp)
 | 
			
		||||
          tmp = next_data
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      def list
 | 
			
		||||
        ret = []
 | 
			
		||||
        each_data{|data|
 | 
			
		||||
          data.next_data = nil
 | 
			
		||||
          ret << data
 | 
			
		||||
        }
 | 
			
		||||
        ret
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      alias :to_ary :list
 | 
			
		||||
 | 
			
		||||
      def to_s
 | 
			
		||||
        String.new(self)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def parse_query(str)
 | 
			
		||||
      query = Hash.new
 | 
			
		||||
      if str
 | 
			
		||||
        str.split(/[&;]/).each{|x|
 | 
			
		||||
          key, val = x.split(/=/,2)
 | 
			
		||||
          key = unescape_form(key)
 | 
			
		||||
          val = unescape_form(val.to_s)
 | 
			
		||||
          val = FormData.new(val)
 | 
			
		||||
          val.name = key
 | 
			
		||||
          if query.has_key?(key)
 | 
			
		||||
            query[key].append_data(val)
 | 
			
		||||
            next
 | 
			
		||||
          end
 | 
			
		||||
          query[key] = val
 | 
			
		||||
        }
 | 
			
		||||
      end
 | 
			
		||||
      query
 | 
			
		||||
    end
 | 
			
		||||
    module_function :parse_query
 | 
			
		||||
 | 
			
		||||
    def parse_form_data(io, boundary)
 | 
			
		||||
      boundary_regexp = /\A--#{boundary}(--)?#{CRLF}\z/
 | 
			
		||||
      form_data = Hash.new
 | 
			
		||||
      data = nil
 | 
			
		||||
      io.each{|line|
 | 
			
		||||
        if boundary_regexp =~ line
 | 
			
		||||
          if data
 | 
			
		||||
            data.chop!
 | 
			
		||||
            key = data.name
 | 
			
		||||
            if form_data.has_key?(key)
 | 
			
		||||
              form_data[key].append_data(data)
 | 
			
		||||
            else
 | 
			
		||||
              form_data[key] = data 
 | 
			
		||||
            end
 | 
			
		||||
          end
 | 
			
		||||
          data = FormData.new
 | 
			
		||||
          next
 | 
			
		||||
        else
 | 
			
		||||
          if data
 | 
			
		||||
            data << line
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
      }
 | 
			
		||||
      return form_data
 | 
			
		||||
    end
 | 
			
		||||
    module_function :parse_form_data
 | 
			
		||||
 | 
			
		||||
    #####
 | 
			
		||||
 | 
			
		||||
    reserved = ';/?:@&=+$,'
 | 
			
		||||
    num      = '0123456789'
 | 
			
		||||
    lowalpha = 'abcdefghijklmnopqrstuvwxyz'
 | 
			
		||||
    upalpha  = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
 | 
			
		||||
    mark     = '-_.!~*\'()'
 | 
			
		||||
    unreserved = num + lowalpha + upalpha + mark
 | 
			
		||||
    control  = (0x0..0x1f).collect{|c| c.chr }.join + "\x7f"
 | 
			
		||||
    space    = " "
 | 
			
		||||
    delims   = '<>#%"'
 | 
			
		||||
    unwise   = '{}|\\^[]`'
 | 
			
		||||
    nonascii = (0x80..0xff).collect{|c| c.chr }.join
 | 
			
		||||
 | 
			
		||||
    def _make_regex(str) /([#{Regexp.escape(str)}])/n end
 | 
			
		||||
    def _escape(str, regex) str.gsub(regex){ "%%%02X" % $1[0] } end
 | 
			
		||||
    def _unescape(str, regex) str.gsub(regex){ $1.hex.chr } end
 | 
			
		||||
    module_function :_make_regex, :_escape, :_unescape
 | 
			
		||||
 | 
			
		||||
    UNESCAPED = _make_regex(control+delims+unwise+nonascii)
 | 
			
		||||
    UNESCAPED_FORM = _make_regex(reserved+control+delims+unwise+nonascii)
 | 
			
		||||
    NONASCII  = _make_regex(nonascii)
 | 
			
		||||
    ESCAPED   = /%([0-9a-fA-F]{2})/
 | 
			
		||||
 | 
			
		||||
    def escape(str)
 | 
			
		||||
      _escape(str, UNESCAPED)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def unescape(str)
 | 
			
		||||
      _unescape(str, ESCAPED)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def escape_form(str)
 | 
			
		||||
      ret = _escape(str, UNESCAPED_FORM)
 | 
			
		||||
      ret.gsub!(/ /, "+")
 | 
			
		||||
      ret
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def unescape_form(str)
 | 
			
		||||
      _unescape(str.gsub(/\+/, " "), ESCAPED)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def escape8bit(str)
 | 
			
		||||
      _escape(str, NONASCII)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    module_function :escape, :unescape, :escape_form, :unescape_form,
 | 
			
		||||
                    :escape8bit
 | 
			
		||||
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
							
								
								
									
										49
									
								
								lib/webrick/httpversion.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								lib/webrick/httpversion.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,49 @@
 | 
			
		|||
#
 | 
			
		||||
# HTTPVersion.rb -- presentation of HTTP version
 | 
			
		||||
#
 | 
			
		||||
# Author: IPR -- Internet Programming with Ruby -- writers
 | 
			
		||||
# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
 | 
			
		||||
# reserved.
 | 
			
		||||
#
 | 
			
		||||
# $IPR: httpversion.rb,v 1.5 2002/09/21 12:23:37 gotoyuzo Exp $
 | 
			
		||||
 | 
			
		||||
module WEBrick
 | 
			
		||||
  class HTTPVersion
 | 
			
		||||
    include Comparable
 | 
			
		||||
 | 
			
		||||
    attr_accessor :major, :minor
 | 
			
		||||
 | 
			
		||||
    def self.convert(version)
 | 
			
		||||
      version.is_a?(self) ? version : new(version)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def initialize(version)
 | 
			
		||||
      case version
 | 
			
		||||
      when HTTPVersion
 | 
			
		||||
        @major, @minor = version.major, version.minor
 | 
			
		||||
      when String
 | 
			
		||||
        if /^(\d+)\.(\d+)$/ =~ version
 | 
			
		||||
          @major, @minor = $1.to_i, $2.to_i
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
      if @major.nil? || @minor.nil?
 | 
			
		||||
        raise ArgumentError,
 | 
			
		||||
          format("cannot convert %s into %s", version.class, self.class)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def <=>(other)
 | 
			
		||||
      unless other.is_a?(self.class)
 | 
			
		||||
        other = self.class.new(other)
 | 
			
		||||
      end
 | 
			
		||||
      if (ret = @major <=> other.major) == 0
 | 
			
		||||
        return @minor <=> other.minor
 | 
			
		||||
      end
 | 
			
		||||
      return ret
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def to_s
 | 
			
		||||
      format("%d.%d", @major, @minor)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
							
								
								
									
										83
									
								
								lib/webrick/log.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								lib/webrick/log.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,83 @@
 | 
			
		|||
#
 | 
			
		||||
# log.rb -- Log 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: log.rb,v 1.26 2002/10/06 17:06:10 gotoyuzo Exp $
 | 
			
		||||
 | 
			
		||||
module WEBrick
 | 
			
		||||
  class BasicLog
 | 
			
		||||
    # log-level constant
 | 
			
		||||
    FATAL, ERROR, WARN, INFO, DEBUG = 1, 2, 3, 4, 5
 | 
			
		||||
 | 
			
		||||
    attr_accessor :level
 | 
			
		||||
 | 
			
		||||
    def initialize(log_file=nil, level=nil)
 | 
			
		||||
      @level = level || INFO
 | 
			
		||||
      case log_file
 | 
			
		||||
      when String
 | 
			
		||||
        @log = open(log_file, "a+")
 | 
			
		||||
        @log.sync = true
 | 
			
		||||
        @opened = true
 | 
			
		||||
      when NilClass
 | 
			
		||||
        @log = $stderr
 | 
			
		||||
      else
 | 
			
		||||
        @log = log_file  # requires "<<". (see BasicLog#log)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def close
 | 
			
		||||
      @log.close if @opened
 | 
			
		||||
      @log = nil
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def log(level, data)
 | 
			
		||||
      if @log && level <= @level
 | 
			
		||||
        @log << (data + "\n")
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def fatal(msg) log(FATAL, "FATAL " << format(msg)); end
 | 
			
		||||
    def error(msg) log(ERROR, "ERROR " << format(msg)); end
 | 
			
		||||
    def warn(msg)  log(WARN,  "WARN  " << format(msg)); end
 | 
			
		||||
    def info(msg)  log(INFO,  "INFO  " << format(msg)); end
 | 
			
		||||
    def debug(msg) log(DEBUG, "DEBUG " << format(msg)); end
 | 
			
		||||
 | 
			
		||||
    def fatal?; @level >= FATAL; end
 | 
			
		||||
    def error?; @level >= ERROR; end
 | 
			
		||||
    def warn?;  @level >= WARN; end
 | 
			
		||||
    def info?;  @level >= INFO; end
 | 
			
		||||
    def debug?; @level >= DEBUG; end
 | 
			
		||||
 | 
			
		||||
    private
 | 
			
		||||
 | 
			
		||||
    def format(arg)
 | 
			
		||||
      str = if arg.is_a?(Exception)
 | 
			
		||||
        "#{arg.class}: #{arg.message}\n\t" <<
 | 
			
		||||
        arg.backtrace.join("\n\t")
 | 
			
		||||
      elsif arg.respond_to?(:to_str)
 | 
			
		||||
        arg.to_str
 | 
			
		||||
      else
 | 
			
		||||
        arg.inspect
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  class Log < BasicLog
 | 
			
		||||
    attr_accessor :time_format 
 | 
			
		||||
 | 
			
		||||
    def initialize(log_file=nil, level=nil)
 | 
			
		||||
      super(log_file, level)
 | 
			
		||||
      @time_format = "[%Y-%m-%d %H:%M:%S]"
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def log(level, data)
 | 
			
		||||
      tmp = Time.now.strftime(@time_format)
 | 
			
		||||
      tmp << " " << data
 | 
			
		||||
      super(level, tmp)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
							
								
								
									
										189
									
								
								lib/webrick/server.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								lib/webrick/server.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,189 @@
 | 
			
		|||
#
 | 
			
		||||
# server.rb -- GenericServer 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: server.rb,v 1.62 2003/07/22 19:20:43 gotoyuzo Exp $
 | 
			
		||||
 | 
			
		||||
require 'thread'
 | 
			
		||||
require 'socket'
 | 
			
		||||
require 'timeout'
 | 
			
		||||
require 'webrick/config'
 | 
			
		||||
require 'webrick/log'
 | 
			
		||||
 | 
			
		||||
module WEBrick
 | 
			
		||||
 | 
			
		||||
  class ServerError < StandardError; end
 | 
			
		||||
 | 
			
		||||
  class SimpleServer
 | 
			
		||||
    def SimpleServer.start
 | 
			
		||||
      yield
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  class Daemon
 | 
			
		||||
    def Daemon.start
 | 
			
		||||
      exit!(0) if fork
 | 
			
		||||
      Process::setsid
 | 
			
		||||
      exit!(0) if fork
 | 
			
		||||
      Dir::chdir("/")
 | 
			
		||||
      File::umask(0)
 | 
			
		||||
      [ STDIN, STDOUT, STDERR ].each{|io|
 | 
			
		||||
        io.reopen("/dev/null", "r+")
 | 
			
		||||
      }
 | 
			
		||||
      yield if block_given?
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  class GenericServer
 | 
			
		||||
    attr_reader :status, :config, :logger, :tokens, :listeners
 | 
			
		||||
 | 
			
		||||
    def initialize(config={}, default=Config::General)
 | 
			
		||||
      @config = default.dup.update(config)
 | 
			
		||||
      @status = :Stop
 | 
			
		||||
      @config[:Logger] ||= Log::new
 | 
			
		||||
      @logger = @config[:Logger]
 | 
			
		||||
 | 
			
		||||
      @tokens = SizedQueue.new(@config[:MaxClients])
 | 
			
		||||
      @config[:MaxClients].times{ @tokens.push(nil) }
 | 
			
		||||
 | 
			
		||||
      webrickv = WEBrick::VERSION
 | 
			
		||||
      rubyv = "#{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]"
 | 
			
		||||
      @logger.info("WEBrick #{webrickv}")
 | 
			
		||||
      @logger.info("ruby #{rubyv}")
 | 
			
		||||
 | 
			
		||||
      if  @config[:DoNotListen]
 | 
			
		||||
        @listeners = []
 | 
			
		||||
      else
 | 
			
		||||
        @listeners = listen(@config[:BindAddress], @config[:Port])
 | 
			
		||||
        @config[:Listen].each{|addr, port|
 | 
			
		||||
          listen(addr, port).each{|sock| @listeners << sock }
 | 
			
		||||
        }
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def [](key)
 | 
			
		||||
      @config[key]
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def listen(address, port)
 | 
			
		||||
      res = Socket::getaddrinfo(address, port,
 | 
			
		||||
                                Socket::AF_UNSPEC,   # address family
 | 
			
		||||
                                Socket::SOCK_STREAM, # socket type
 | 
			
		||||
                                0,                   # protocol
 | 
			
		||||
                                Socket::AI_PASSIVE)  # flag
 | 
			
		||||
      last_error = nil
 | 
			
		||||
      sockets = []
 | 
			
		||||
      res.each{|ai|
 | 
			
		||||
        begin
 | 
			
		||||
          @logger.debug("TCPServer.new(#{ai[3]}, #{ai[1]})")
 | 
			
		||||
          sock = TCPServer.new(ai[3], ai[1])
 | 
			
		||||
          Utils::set_close_on_exec(sock)
 | 
			
		||||
          sockets << sock
 | 
			
		||||
        rescue => ex
 | 
			
		||||
          @logger.warn("TCPServer Error: #{ex}")
 | 
			
		||||
          last_error  = ex
 | 
			
		||||
        end
 | 
			
		||||
      }
 | 
			
		||||
      raise last_error if sockets.empty?
 | 
			
		||||
      return sockets
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def start(&block)
 | 
			
		||||
      raise ServerError, "already started." if @status != :Stop
 | 
			
		||||
      server_type = @config[:ServerType] || SimpleServer
 | 
			
		||||
 | 
			
		||||
      server_type.start{
 | 
			
		||||
        @logger.info \
 | 
			
		||||
          "#{self.class}#start: pid=#{$$} port=#{@config[:Port]}"
 | 
			
		||||
        call_callback(:StartCallback)
 | 
			
		||||
 | 
			
		||||
        thgroup = ThreadGroup.new
 | 
			
		||||
        @status = :Running
 | 
			
		||||
        while @status == :Running
 | 
			
		||||
          begin
 | 
			
		||||
            if svrs = IO.select(@listeners, nil, nil, 2.0)
 | 
			
		||||
              svrs[0].each{|svr|
 | 
			
		||||
                @tokens.pop          # blocks while no token is there.
 | 
			
		||||
                sock = svr.accept
 | 
			
		||||
                sock.sync = true
 | 
			
		||||
                Utils::set_close_on_exec(sock)
 | 
			
		||||
                th = start_thread(sock, &block)
 | 
			
		||||
                th[:WEBrickThread] = true
 | 
			
		||||
                thgroup.add(th)
 | 
			
		||||
              }
 | 
			
		||||
            end
 | 
			
		||||
          rescue Errno::ECONNRESET, Errno::ECONNABORTED, Errno::EPROTO => ex
 | 
			
		||||
            msg = "#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}"
 | 
			
		||||
            @logger.error msg
 | 
			
		||||
          rescue Errno::EBADF => ex  # IO::select causes by shutdown
 | 
			
		||||
          rescue => ex
 | 
			
		||||
            @logger.error ex
 | 
			
		||||
            break
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        @logger.info "going to shutdown ..."
 | 
			
		||||
        thgroup.list.each{|th| th.join if th[:WEBrickThread] }
 | 
			
		||||
        call_callback(:StopCallback)
 | 
			
		||||
        @logger.info "#{self.class}#start done."
 | 
			
		||||
        @status = :Stop
 | 
			
		||||
      }
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def stop
 | 
			
		||||
      if @status == :Running
 | 
			
		||||
        @status = :Shutdown
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def shutdown
 | 
			
		||||
      stop
 | 
			
		||||
      @listeners.each{|s|
 | 
			
		||||
        if @logger.debug?
 | 
			
		||||
          addr = s.addr
 | 
			
		||||
          @logger.debug("close TCPSocket(#{addr[2]}, #{addr[1]})")
 | 
			
		||||
        end
 | 
			
		||||
        s.close
 | 
			
		||||
      }
 | 
			
		||||
      @listeners.clear
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def run(sock)
 | 
			
		||||
      @logger.fatal "run() must be provided by user."
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    private
 | 
			
		||||
 | 
			
		||||
    def start_thread(sock, &block)
 | 
			
		||||
      Thread.start{
 | 
			
		||||
        begin
 | 
			
		||||
          Thread.current[:WEBrickSocket] = sock
 | 
			
		||||
          addr = sock.peeraddr
 | 
			
		||||
          @logger.debug "accept: #{addr[3]}:#{addr[1]}"
 | 
			
		||||
          call_callback(:AcceptCallback, sock)
 | 
			
		||||
          block ? block.call(sock) : run(sock)
 | 
			
		||||
        rescue ServerError, Errno::ENOTCONN => ex
 | 
			
		||||
          msg = "#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}"
 | 
			
		||||
          @logger.error msg
 | 
			
		||||
        rescue Exception => ex
 | 
			
		||||
          @logger.error ex
 | 
			
		||||
        ensure
 | 
			
		||||
          Thread.current[:WEBrickSocket] = nil
 | 
			
		||||
          @logger.debug "close: #{addr[3]}:#{addr[1]}"
 | 
			
		||||
          sock.close
 | 
			
		||||
        end
 | 
			
		||||
        @tokens.push(nil)
 | 
			
		||||
      }
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def call_callback(callback_name, *args)
 | 
			
		||||
      if cb = @config[callback_name]
 | 
			
		||||
        cb.call(*args)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end    # end of GenericServer
 | 
			
		||||
end
 | 
			
		||||
							
								
								
									
										64
									
								
								lib/webrick/utils.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								lib/webrick/utils.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,64 @@
 | 
			
		|||
#
 | 
			
		||||
# utils.rb -- Miscellaneous utilities
 | 
			
		||||
#
 | 
			
		||||
# 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: utils.rb,v 1.10 2003/02/16 22:22:54 gotoyuzo Exp $
 | 
			
		||||
 | 
			
		||||
require 'socket'
 | 
			
		||||
require 'fcntl'
 | 
			
		||||
begin
 | 
			
		||||
  require 'etc'
 | 
			
		||||
rescue LoadError
 | 
			
		||||
  nil
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
module WEBrick
 | 
			
		||||
  module Utils
 | 
			
		||||
 | 
			
		||||
    def set_close_on_exec(io)
 | 
			
		||||
      if defined?(Fcntl::FD_CLOEXEC)
 | 
			
		||||
        io.fcntl(Fcntl::FD_CLOEXEC, 1)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
    module_function :set_close_on_exec
 | 
			
		||||
 | 
			
		||||
    def su(user, group=nil)
 | 
			
		||||
      if defined?(Etc)
 | 
			
		||||
        pw = Etc.getpwnam(user)
 | 
			
		||||
        gr = group ? Etc.getgrnam(group) : pw
 | 
			
		||||
        Process::gid = gr.gid
 | 
			
		||||
        Process::egid = gr.gid
 | 
			
		||||
        Process::uid = pw.uid
 | 
			
		||||
        Process::euid = pw.uid
 | 
			
		||||
      end 
 | 
			
		||||
    end   
 | 
			
		||||
    module_function :su
 | 
			
		||||
 | 
			
		||||
    def getservername
 | 
			
		||||
      host = Socket::gethostname
 | 
			
		||||
      begin
 | 
			
		||||
        Socket::gethostbyname(host)[0]
 | 
			
		||||
      rescue
 | 
			
		||||
        host
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
    module_function :getservername
 | 
			
		||||
 | 
			
		||||
    RAND_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
 | 
			
		||||
                 "0123456789" +
 | 
			
		||||
                 "abcdefghijklmnopqrstuvwxyz" 
 | 
			
		||||
 | 
			
		||||
    def random_string(len)
 | 
			
		||||
      rand_max = RAND_CHARS.size
 | 
			
		||||
      ret = "" 
 | 
			
		||||
      len.times{ ret << RAND_CHARS[rand(rand_max)] }
 | 
			
		||||
      ret 
 | 
			
		||||
    end
 | 
			
		||||
    module_function :random_string
 | 
			
		||||
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
							
								
								
									
										13
									
								
								lib/webrick/version.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								lib/webrick/version.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,13 @@
 | 
			
		|||
#
 | 
			
		||||
# version.rb -- version and release date
 | 
			
		||||
#
 | 
			
		||||
# Author: IPR -- Internet Programming with Ruby -- writers
 | 
			
		||||
# Copyright (c) 2000 TAKAHASHI Masayoshi, GOTOU YUUZOU
 | 
			
		||||
# Copyright (c) 2003 Internet Programming with Ruby writers. All rights
 | 
			
		||||
# reserved.
 | 
			
		||||
#
 | 
			
		||||
# $IPR: version.rb,v 1.74 2003/07/22 19:20:43 gotoyuzo Exp $
 | 
			
		||||
 | 
			
		||||
module WEBrick
 | 
			
		||||
  VERSION      = "1.3.1"
 | 
			
		||||
end
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue