Permalink
Browse files

Import from Ruby repository

  • Loading branch information...
0 parents commit 1c3191a551846d5b8c8541d7160509ff54244d14 @nahi committed Dec 28, 2011
Showing with 8,734 additions and 0 deletions.
  1. +21 −0 README.txt
  2. +14 −0 Rakefile
  3. +227 −0 lib/webrick.rb
  4. BIN lib/webrick/.httputils.rb.swp
  5. +151 −0 lib/webrick/accesslog.rb
  6. +260 −0 lib/webrick/cgi.rb
  7. +35 −0 lib/webrick/compat.rb
  8. +121 −0 lib/webrick/config.rb
  9. +110 −0 lib/webrick/cookie.rb
  10. +28 −0 lib/webrick/htmlutils.rb
  11. +95 −0 lib/webrick/httpauth.rb
  12. +112 −0 lib/webrick/httpauth/authenticator.rb
  13. +108 −0 lib/webrick/httpauth/basicauth.rb
  14. +392 −0 lib/webrick/httpauth/digestauth.rb
  15. +128 −0 lib/webrick/httpauth/htdigest.rb
  16. +93 −0 lib/webrick/httpauth/htgroup.rb
  17. +121 −0 lib/webrick/httpauth/htpasswd.rb
  18. +52 −0 lib/webrick/httpauth/userdb.rb
  19. +305 −0 lib/webrick/httpproxy.rb
  20. +461 −0 lib/webrick/httprequest.rb
  21. +399 −0 lib/webrick/httpresponse.rb
  22. +64 −0 lib/webrick/https.rb
  23. +264 −0 lib/webrick/httpserver.rb
  24. +22 −0 lib/webrick/httpservlet.rb
  25. +153 −0 lib/webrick/httpservlet/abstract.rb
  26. +46 −0 lib/webrick/httpservlet/cgi_runner.rb
  27. +108 −0 lib/webrick/httpservlet/cgihandler.rb
  28. +87 −0 lib/webrick/httpservlet/erbhandler.rb
  29. +470 −0 lib/webrick/httpservlet/filehandler.rb
  30. +33 −0 lib/webrick/httpservlet/prochandler.rb
  31. +184 −0 lib/webrick/httpstatus.rb
  32. +394 −0 lib/webrick/httputils.rb
  33. +49 −0 lib/webrick/httpversion.rb
  34. +136 −0 lib/webrick/log.rb
  35. +218 −0 lib/webrick/server.rb
  36. +127 −0 lib/webrick/ssl.rb
  37. +241 −0 lib/webrick/utils.rb
  38. +13 −0 lib/webrick/version.rb
  39. +66 −0 sample/webrick/demo-app.rb
  40. +12 −0 sample/webrick/demo-multipart.cgi
  41. +6 −0 sample/webrick/demo-servlet.rb
  42. +12 −0 sample/webrick/demo-urlencoded.cgi
  43. +11 −0 sample/webrick/hello.cgi
  44. +8 −0 sample/webrick/hello.rb
  45. +23 −0 sample/webrick/httpd.rb
  46. +25 −0 sample/webrick/httpproxy.rb
  47. +33 −0 sample/webrick/httpsd.rb
  48. +313 −0 test/openssl/utils.rb
  49. +208 −0 test/ruby/envutil.rb
  50. +1 −0 test/webrick/.htaccess
  51. +134 −0 test/webrick/test_cgi.rb
  52. +131 −0 test/webrick/test_cookie.rb
  53. +285 −0 test/webrick/test_filehandler.rb
  54. +167 −0 test/webrick/test_httpauth.rb
  55. +282 −0 test/webrick/test_httpproxy.rb
  56. +411 −0 test/webrick/test_httprequest.rb
  57. +49 −0 test/webrick/test_httpresponse.rb
  58. +305 −0 test/webrick/test_httpserver.rb
  59. +96 −0 test/webrick/test_httputils.rb
  60. +40 −0 test/webrick/test_httpversion.rb
  61. +67 −0 test/webrick/test_server.rb
  62. +64 −0 test/webrick/test_utils.rb
  63. +58 −0 test/webrick/utils.rb
  64. +36 −0 test/webrick/webrick.cgi
  65. +36 −0 test/webrick/webrick_long_filename.cgi
  66. +13 −0 webrick.gemspec
21 README.txt
@@ -0,0 +1,21 @@
+= WEB server toolkit.
+
+WEBrick is an HTTP server toolkit that can be configured as an HTTPS server,
+a proxy server, and a virtual-host server. WEBrick features complete
+logging of both server operations and HTTP access. WEBrick supports both
+basic and digest authentication in addition to algorithms not in RFC 2617.
+
+A WEBrick servers can be composed of multiple WEBrick servers or servlets to
+provide differing behavior on a per-host or per-path basis. WEBrick
+includes servlets for handling CGI scripts, ERb pages, ruby blocks and
+directory listings.
+
+WEBrick also includes tools for daemonizing a process and starting a process
+at a higher privilege level and dropping permissions.
+
+== Copyright
+
+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.
14 Rakefile
@@ -0,0 +1,14 @@
+require 'rake'
+require 'rake/testtask'
+require 'rubygems/package_task'
+
+task :default => :test
+
+require 'bundler'
+Bundler::GemHelper.install_tasks
+
+Rake::TestTask.new("test") do |test|
+ test.libs << 'lib'
+ test.verbose = true
+ test.test_files = Dir.glob('test/webrick/test_*.rb')
+end
227 lib/webrick.rb
@@ -0,0 +1,227 @@
+##
+# = WEB server toolkit.
+#
+# WEBrick is an HTTP server toolkit that can be configured as an HTTPS server,
+# a proxy server, and a virtual-host server. WEBrick features complete
+# logging of both server operations and HTTP access. WEBrick supports both
+# basic and digest authentication in addition to algorithms not in RFC 2617.
+#
+# A WEBrick servers can be composed of multiple WEBrick servers or servlets to
+# provide differing behavior on a per-host or per-path basis. WEBrick
+# includes servlets for handling CGI scripts, ERb pages, ruby blocks and
+# directory listings.
+#
+# WEBrick also includes tools for daemonizing a process and starting a process
+# at a higher privilege level and dropping permissions.
+#
+# == Starting an HTTP server
+#
+# To create a new WEBrick::HTTPServer that will listen to connections on port
+# 8000 and serve documents from the current user's public_html folder:
+#
+# require 'webrick'
+#
+# root = File.expand_path '~/public_html'
+# server = WEBrick::HTTPServer.new :Port => 8000, :DocumentRoot => root
+#
+# To run the server you will need to provide a suitable shutdown hook as
+# starting the server blocks the current thread:
+#
+# trap 'INT' do server.shutdown end
+#
+# server.start
+#
+# == Custom Behavior
+#
+# The easiest way to have a server perform custom operations is through
+# WEBrick::HTTPServer#mount_proc. The block given will be called with a
+# WEBrick::HTTPRequest with request info and a WEBrick::HTTPResponse which
+# must be filled in appropriately:
+#
+# server.mount_proc '/' do |req, res|
+# res.body = 'Hello, world!'
+# end
+#
+# Remember that <tt>server.mount_proc</tt> must <tt>server.start</tt>.
+#
+# == Servlets
+#
+# Advanced custom behavior can be obtained through mounting a subclass of
+# WEBrick::HTTPServlet::AbstractServlet. Servlets provide more modularity
+# when writing an HTTP server than mount_proc allows. Here is a simple
+# servlet:
+#
+# class Simple < WEBrick::HTTPServlet::AbstractServlet
+# def do_GET request, response
+# status, content_type, body = do_stuff_with request
+#
+# response.status = 200
+# response['Content-Type'] = 'text/plain'
+# response.body = 'Hello, World!'
+# end
+# end
+#
+# To initialize the servlet you mount it on the server:
+#
+# server.mount '/simple', Simple
+#
+# See WEBrick::HTTPServlet::AbstractServlet for more details.
+#
+# == Virtual Hosts
+#
+# A server can act as a virtual host for multiple host names. After creating
+# the listening host, additional hosts that do not listen can be created and
+# attached as virtual hosts:
+#
+# server = WEBrick::HTTPServer.new # ...
+#
+# vhost = WEBrick::HTTPServer.new :ServerName => 'vhost.example',
+# :DoNotListen => true, # ...
+# vhost.mount '/', ...
+#
+# server.virtual_host vhost
+#
+# If no +:DocumentRoot+ is provided and no servlets or procs are mounted on the
+# main server it will return 404 for all URLs.
+#
+# == HTTPS
+#
+# To create an HTTPS server you only need to enable SSL and provide an SSL
+# certificate name:
+#
+# require 'webrick'
+# require 'webrick/https'
+#
+# cert_name = [
+# %w[CN localhost],
+# ]
+#
+# server = WEBrick::HTTPServer.new(:Port => 8000,
+# :SSLEnable => true,
+# :SSLCertName => cert_name)
+#
+# This will start the server with a self-generated self-signed certificate.
+# The certificate will be changed every time the server is restarted.
+#
+# To create a server with a pre-determined key and certificate you can provide
+# them:
+#
+# require 'webrick'
+# require 'webrick/https'
+# require 'openssl'
+#
+# cert = OpenSSL::X509::Certificate.new File.read '/path/to/cert.pem'
+# pkey = OpenSSL::PKey::RSA.new File.read '/path/to/pkey.pem'
+#
+# server = WEBrick::HTTPServer.new(:Port => 8000,
+# :SSLEnable => true,
+# :SSLCertificate => cert,
+# :SSLPrivateKey => pkey)
+#
+# == Proxy Server
+#
+# WEBrick can act as a proxy server:
+#
+# require 'webrick'
+# require 'webrick/httpproxy'
+#
+# proxy = WEBrick::HTTPProxyServer.new :Port => 8000
+#
+# trap 'INT' do proxy.shutdown end
+#
+# Proxies may modifier the content of the response through the
+# +:ProxyContentHandler+ callback which will be invoked with the request and
+# respone after the remote content has been fetched.
+#
+# == Basic and Digest authentication
+#
+# WEBrick provides both Basic and Digest authentication for regular and proxy
+# servers. See WEBrick::HTTPAuth, WEBrick::HTTPAuth::BasicAuth and
+# WEBrick::HTTPAuth::DigestAuth.
+#
+# == WEBrick as a Production Web Server
+#
+# WEBrick can be run as a production server for small loads.
+#
+# === Daemonizing
+#
+# To start a WEBrick server as a daemon simple run WEBrick::Daemon.start
+# before starting the server.
+#
+# === Dropping Permissions
+#
+# WEBrick can be started as one user to gain permission to bind to port 80 or
+# 443 for serving HTTP or HTTPS traffic then can drop these permissions for
+# regular operation. To listen on all interfaces for HTTP traffic:
+#
+# sockets = WEBrick::Utils.create_listeners nil, 80
+#
+# Then drop privileges:
+#
+# WEBrick::Utils.su 'www'
+#
+# Then create a server that does not listen by default:
+#
+# server = WEBrick::HTTPServer.new :DoNotListen => true, # ...
+#
+# Then overwrite the listening sockets with the port 80 sockets:
+#
+# server.listeners.replace sockets
+#
+# === Logging
+#
+# WEBrick can separately log server operations and end-user access. For
+# server operations:
+#
+# log_file = File.open '/var/log/webrick.log', 'a+'
+# log = WEBrick::Log.new log_file
+#
+# For user access logging:
+#
+# access_log = [
+# [log_file, WEBrick::AccessLog::COMBINED_LOG_FORMAT],
+# ]
+#
+# server = WEBrick::HTTPServer.new :Logger => log, :AccessLog => access_log
+#
+# See WEBrick::AccessLog for further log formats.
+#
+# === Log Rotation
+#
+# To rotate logs in WEBrick on a HUP signal (like syslogd can send), open the
+# log file in 'a+' mode (as above) and trap 'HUP' to reopen the log file:
+#
+# trap 'HUP' do log_file.reopen '/path/to/webrick.log', 'a+'
+#
+# == Copyright
+#
+# 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 $
+
+module WEBrick
+end
+
+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'
BIN lib/webrick/.httputils.rb.swp
Binary file not shown.
151 lib/webrick/accesslog.rb
@@ -0,0 +1,151 @@
+#--
+# 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
+
+ ##
+ # AccessLog provides logging to various files in various formats.
+ #
+ # Multiple logs may be written to at the same time:
+ #
+ # access_log = [
+ # [$stderr, WEBrick::AccessLog::COMMON_LOG_FORMAT],
+ # [$stderr, WEBrick::AccessLog::REFERER_LOG_FORMAT],
+ # ]
+ #
+ # server = WEBrick::HTTPServer.new :AccessLog => access_log
+ #
+ # Custom log formats may be defined. WEBrick::AccessLog provides a subset
+ # of the formatting from Apache's mod_log_config
+ # http://httpd.apache.org/docs/mod/mod_log_config.html#formats. See
+ # AccessLog::setup_params for a list of supported options
+
+ module AccessLog
+
+ ##
+ # Raised if a parameter such as %e, %i, %o or %n is used without fetching
+ # a specific field.
+
+ class AccessLogError < StandardError; end
+
+ ##
+ # The Common Log Format's time format
+
+ CLF_TIME_FORMAT = "[%d/%b/%Y:%H:%M:%S %Z]"
+
+ ##
+ # Common Log Format
+
+ COMMON_LOG_FORMAT = "%h %l %u %t \"%r\" %s %b"
+
+ ##
+ # Short alias for Common Log Format
+
+ CLF = COMMON_LOG_FORMAT
+
+ ##
+ # Referer Log Format
+
+ REFERER_LOG_FORMAT = "%{Referer}i -> %U"
+
+ ##
+ # User-Agent Log Format
+
+ AGENT_LOG_FORMAT = "%{User-Agent}i"
+
+ ##
+ # Combined Log Format
+
+ COMBINED_LOG_FORMAT = "#{CLF} \"%{Referer}i\" \"%{User-agent}i\""
+
+ module_function
+
+ # This format specification is a subset of mod_log_config of Apache:
+ #
+ # %a:: Remote IP address
+ # %b:: Total response size
+ # %e{variable}:: Given variable in ENV
+ # %f:: Response filename
+ # %h:: Remote host name
+ # %{header}i:: Given request header
+ # %l:: Remote logname, always "-"
+ # %m:: Request method
+ # %{attr}n:: Given request attribute from <tt>req.attributes</tt>
+ # %{header}o:: Given response header
+ # %p:: Server's request port
+ # %{format}p:: The canonical port of the server serving the request or the
+ # actual port or the client's actual port. Valid formats are
+ # canonical, local or remote.
+ # %q:: Request query string
+ # %r:: First line of the request
+ # %s:: Request status
+ # %t:: Time the request was recieved
+ # %T:: Time taken to process the request
+ # %u:: Remote user from auth
+ # %U:: Unparsed URI
+ # %%:: Literal %
+
+ def setup_params(config, req, res)
+ params = Hash.new("")
+ params["a"] = req.peeraddr[3]
+ params["b"] = res.sent_size
+ params["e"] = ENV
+ params["f"] = res.filename || ""
+ params["h"] = req.peeraddr[2]
+ params["i"] = req
+ params["l"] = "-"
+ params["m"] = req.request_method
+ params["n"] = req.attributes
+ params["o"] = res
+ params["p"] = req.port
+ params["q"] = req.query_string
+ params["r"] = req.request_line.sub(/\x0d?\x0a\z/o, '')
+ params["s"] = res.status # won't support "%>s"
+ params["t"] = req.request_time
+ params["T"] = Time.now - req.request_time
+ params["u"] = req.user || "-"
+ params["U"] = req.unparsed_uri
+ params["v"] = config[:ServerName]
+ params
+ end
+
+ def format(format_string, params)
+ format_string.gsub(/\%(?:\{(.*?)\})?>?([a-zA-Z%])/){
+ param, spec = $1, $2
+ case spec[0]
+ when ?e, ?i, ?n, ?o
+ raise AccessLogError,
+ "parameter is required for \"#{spec}\"" unless param
+ (param = params[spec][param]) ? escape(param) : "-"
+ when ?t
+ params[spec].strftime(param || CLF_TIME_FORMAT)
+ when ?p
+ case param
+ when 'remote'
+ escape(params["i"].peeraddr[1].to_s)
+ else
+ escape(params["p"].to_s)
+ end
+ when ?%
+ "%"
+ else
+ escape(params[spec].to_s)
+ end
+ }
+ end
+
+ def escape(data)
+ if data.tainted?
+ data.gsub(/[[:cntrl:]\\]+/) {$&.dump[1...-1]}.untaint
+ else
+ data
+ end
+ end
+ end
+end
260 lib/webrick/cgi.rb
@@ -0,0 +1,260 @@
+#
+# cgi.rb -- Yet another CGI library
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2003 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $Id$
+
+require "webrick/httprequest"
+require "webrick/httpresponse"
+require "webrick/config"
+require "stringio"
+
+module WEBrick
+ class CGI
+ CGIError = Class.new(StandardError)
+
+ attr_reader :config, :logger
+
+ def initialize(*args)
+ if defined?(MOD_RUBY)
+ unless ENV.has_key?("GATEWAY_INTERFACE")
+ Apache.request.setup_cgi_env
+ end
+ end
+ if %r{HTTP/(\d+\.\d+)} =~ ENV["SERVER_PROTOCOL"]
+ httpv = $1
+ end
+ @config = WEBrick::Config::HTTP.dup.update(
+ :ServerSoftware => ENV["SERVER_SOFTWARE"] || "null",
+ :HTTPVersion => HTTPVersion.new(httpv || "1.0"),
+ :RunOnCGI => true, # to detect if it runs on CGI.
+ :NPH => false # set true to run as NPH script.
+ )
+ if config = args.shift
+ @config.update(config)
+ end
+ @config[:Logger] ||= WEBrick::BasicLog.new($stderr)
+ @logger = @config[:Logger]
+ @options = args
+ end
+
+ def [](key)
+ @config[key]
+ end
+
+ def start(env=ENV, stdin=$stdin, stdout=$stdout)
+ sock = WEBrick::CGI::Socket.new(@config, env, stdin, stdout)
+ req = HTTPRequest.new(@config)
+ res = HTTPResponse.new(@config)
+ unless @config[:NPH] or defined?(MOD_RUBY)
+ def res.setup_header
+ unless @header["status"]
+ phrase = HTTPStatus::reason_phrase(@status)
+ @header["status"] = "#{@status} #{phrase}"
+ end
+ super
+ end
+ def res.status_line
+ ""
+ end
+ end
+
+ begin
+ req.parse(sock)
+ req.script_name = (env["SCRIPT_NAME"] || File.expand_path($0)).dup
+ req.path_info = (env["PATH_INFO"] || "").dup
+ req.query_string = env["QUERY_STRING"]
+ req.user = env["REMOTE_USER"]
+ res.request_method = req.request_method
+ res.request_uri = req.request_uri
+ res.request_http_version = req.http_version
+ res.keep_alive = req.keep_alive?
+ self.service(req, res)
+ rescue HTTPStatus::Error => ex
+ res.set_error(ex)
+ rescue HTTPStatus::Status => ex
+ res.status = ex.code
+ rescue Exception => ex
+ @logger.error(ex)
+ res.set_error(ex, true)
+ ensure
+ req.fixup
+ if defined?(MOD_RUBY)
+ res.setup_header
+ Apache.request.status_line = "#{res.status} #{res.reason_phrase}"
+ Apache.request.status = res.status
+ table = Apache.request.headers_out
+ res.header.each{|key, val|
+ case key
+ when /^content-encoding$/i
+ Apache::request.content_encoding = val
+ when /^content-type$/i
+ Apache::request.content_type = val
+ else
+ table[key] = val.to_s
+ end
+ }
+ res.cookies.each{|cookie|
+ table.add("Set-Cookie", cookie.to_s)
+ }
+ Apache.request.send_http_header
+ res.send_body(sock)
+ else
+ res.send_response(sock)
+ end
+ end
+ end
+
+ def service(req, res)
+ method_name = "do_" + req.request_method.gsub(/-/, "_")
+ if respond_to?(method_name)
+ __send__(method_name, req, res)
+ else
+ raise HTTPStatus::MethodNotAllowed,
+ "unsupported method `#{req.request_method}'."
+ end
+ end
+
+ class Socket
+ include Enumerable
+
+ private
+
+ def initialize(config, env, stdin, stdout)
+ @config = config
+ @env = env
+ @header_part = StringIO.new
+ @body_part = stdin
+ @out_port = stdout
+ @out_port.binmode
+
+ @server_addr = @env["SERVER_ADDR"] || "0.0.0.0"
+ @server_name = @env["SERVER_NAME"]
+ @server_port = @env["SERVER_PORT"]
+ @remote_addr = @env["REMOTE_ADDR"]
+ @remote_host = @env["REMOTE_HOST"] || @remote_addr
+ @remote_port = @env["REMOTE_PORT"] || 0
+
+ begin
+ @header_part << request_line << CRLF
+ setup_header
+ @header_part << CRLF
+ @header_part.rewind
+ rescue Exception
+ raise CGIError, "invalid CGI environment"
+ end
+ end
+
+ def request_line
+ meth = @env["REQUEST_METHOD"] || "GET"
+ unless url = @env["REQUEST_URI"]
+ url = (@env["SCRIPT_NAME"] || File.expand_path($0)).dup
+ url << @env["PATH_INFO"].to_s
+ url = WEBrick::HTTPUtils.escape_path(url)
+ if query_string = @env["QUERY_STRING"]
+ unless query_string.empty?
+ url << "?" << query_string
+ end
+ end
+ end
+ # we cannot get real HTTP version of client ;)
+ httpv = @config[:HTTPVersion]
+ return "#{meth} #{url} HTTP/#{httpv}"
+ end
+
+ def setup_header
+ @env.each{|key, value|
+ case key
+ when "CONTENT_TYPE", "CONTENT_LENGTH"
+ add_header(key.gsub(/_/, "-"), value)
+ when /^HTTP_(.*)/
+ add_header($1.gsub(/_/, "-"), value)
+ end
+ }
+ end
+
+ def add_header(hdrname, value)
+ unless value.empty?
+ @header_part << hdrname << ": " << value << CRLF
+ end
+ end
+
+ def input
+ @header_part.eof? ? @body_part : @header_part
+ end
+
+ public
+
+ def peeraddr
+ [nil, @remote_port, @remote_host, @remote_addr]
+ end
+
+ def addr
+ [nil, @server_port, @server_name, @server_addr]
+ end
+
+ def gets(eol=LF, size=nil)
+ input.gets(eol, size)
+ end
+
+ def read(size=nil)
+ input.read(size)
+ end
+
+ def each
+ input.each{|line| yield(line) }
+ end
+
+ def eof?
+ input.eof?
+ end
+
+ def <<(data)
+ @out_port << data
+ end
+
+ def cert
+ return nil unless defined?(OpenSSL)
+ if pem = @env["SSL_SERVER_CERT"]
+ OpenSSL::X509::Certificate.new(pem) unless pem.empty?
+ end
+ end
+
+ def peer_cert
+ return nil unless defined?(OpenSSL)
+ if pem = @env["SSL_CLIENT_CERT"]
+ OpenSSL::X509::Certificate.new(pem) unless pem.empty?
+ end
+ end
+
+ def peer_cert_chain
+ return nil unless defined?(OpenSSL)
+ if @env["SSL_CLIENT_CERT_CHAIN_0"]
+ keys = @env.keys
+ certs = keys.sort.collect{|k|
+ if /^SSL_CLIENT_CERT_CHAIN_\d+$/ =~ k
+ if pem = @env[k]
+ OpenSSL::X509::Certificate.new(pem) unless pem.empty?
+ end
+ end
+ }
+ certs.compact
+ end
+ end
+
+ def cipher
+ return nil unless defined?(OpenSSL)
+ if cipher = @env["SSL_CIPHER"]
+ ret = [ cipher ]
+ ret << @env["SSL_PROTOCOL"]
+ ret << @env["SSL_CIPHER_USEKEYSIZE"]
+ ret << @env["SSL_CIPHER_ALGKEYSIZE"]
+ ret
+ end
+ end
+ end
+ end
+end
35 lib/webrick/compat.rb
@@ -0,0 +1,35 @@
+#
+# 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 $
+
+##
+# System call error module used by webrick for cross platform compatability.
+#
+# EPROTO:: protocol error
+# ECONNRESET:: remote host reset the connection request
+# ECONNABORTED:: Client sent TCP reset (RST) before server has accepted the
+# connection requested by client.
+#
+module Errno
+ ##
+ # Protocol error.
+
+ class EPROTO < SystemCallError; end
+
+ ##
+ # Remote host reset the connection request.
+
+ class ECONNRESET < SystemCallError; end
+
+ ##
+ # Client sent TCP reset (RST) before server has accepted the connection
+ # requested by client.
+
+ class ECONNABORTED < SystemCallError; end
+end
121 lib/webrick/config.rb
@@ -0,0 +1,121 @@
+#
+# 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 specify this!!
+ :MaxClients => 100, # maximum number of the concurrent connections
+ :ServerType => nil, # default: WEBrick::SimpleServer
+ :Logger => nil, # default: WEBrick::Log.new
+ :ServerSoftware => "WEBrick/#{WEBrick::VERSION} " +
+ "(Ruby/#{RUBY_VERSION}/#{RUBY_RELEASE_DATE})",
+ :TempDir => ENV['TMPDIR']||ENV['TMP']||ENV['TEMP']||'/tmp',
+ :DoNotListen => false,
+ :StartCallback => nil,
+ :StopCallback => nil,
+ :AcceptCallback => nil,
+ :DoNotReverseLookup => nil,
+ :ShutdownSocketWithoutClose => false,
+ }
+
+ # for HTTPServer, HTTPRequest, HTTPResponse ...
+ HTTP = General.dup.update(
+ :Port => 80,
+ :RequestTimeout => 30,
+ :HTTPVersion => HTTPVersion.new("1.1"),
+ :AccessLog => nil,
+ :MimeTypes => HTTPUtils::DefaultMimeTypes,
+ :DirectoryIndex => ["index.html","index.htm","index.cgi","index.rhtml"],
+ :DocumentRoot => nil,
+ :DocumentRootOptions => { :FancyIndexing => true },
+ :RequestCallback => nil,
+ :ServerAlias => nil,
+ :InputBufferSize => 65536, # input buffer size in reading request body
+ :OutputBufferSize => 65536, # output buffer size in sending File or IO
+
+ # for HTTPProxyServer
+ :ProxyAuthProc => nil,
+ :ProxyContentHandler => nil,
+ :ProxyVia => true,
+ :ProxyTimeout => true,
+ :ProxyURI => nil,
+
+ :CGIInterpreter => nil,
+ :CGIPathEnv => nil,
+
+ # workaround: if Request-URIs contain 8bit chars,
+ # they should be escaped before calling of URI::parse().
+ :Escape8bitURI => false
+ )
+
+ FileHandler = {
+ :NondisclosureName => [".ht*", "*~"],
+ :FancyIndexing => false,
+ :HandlerTable => {},
+ :HandlerCallback => nil,
+ :DirectoryCallback => nil,
+ :FileCallback => nil,
+ :UserDir => nil, # e.g. "public_html"
+ :AcceptableLanguages => [] # ["en", "ja", ... ]
+ }
+
+ BasicAuth = {
+ :AutoReloadUserDB => true,
+ }
+
+ ##
+ # Default configuration for WEBrick::HTTPAuth::DigestAuth.
+ #
+ # :Algorithm:: MD5, MD5-sess (default), SHA1, SHA1-sess
+ # :Domain:: An Array of URIs that define the protected space
+ # :Qop:: 'auth' for authentication, 'auth-int' for integrity protection or
+ # both
+ # :UseOpaque:: Should the server send opaque values to the client? This
+ # helps prevent replay attacks.
+ # :CheckNc:: Should the server check the nonce count? This helps the
+ # server detect replay attacks.
+ # :UseAuthenticationInfoHeader:: Should the server send an
+ # AuthenticationInfo header?
+ # :AutoReloadUserDB:: Reload the user database provided by :UserDB
+ # automatically?
+ # :NonceExpirePeriod:: How long should we store used nonces? Default is
+ # 30 minutes.
+ # :NonceExpireDelta:: How long is a nonce valid? Default is 1 minute
+ # :InternetExplorerHack:: Hack which allows Internet Explorer to work.
+ # :OperaHack:: Hack which allows Opera to work.
+
+ 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
110 lib/webrick/cookie.rb
@@ -0,0 +1,110 @@
+#
+# cookie.rb -- Cookie class
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
+# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: cookie.rb,v 1.16 2002/09/21 12:23:35 gotoyuzo Exp $
+
+require 'time'
+require 'webrick/httputils'
+
+module WEBrick
+ class Cookie
+
+ attr_reader :name
+ attr_accessor :value, :version
+ attr_accessor :domain, :path, :secure
+ attr_accessor :comment, :max_age
+ #attr_accessor :comment_url, :discard, :port
+
+ def initialize(name, value)
+ @name = name
+ @value = value
+ @version = 0 # Netscape Cookie
+
+ @domain = @path = @secure = @comment = @max_age =
+ @expires = @comment_url = @discard = @port = nil
+ end
+
+ def expires=(t)
+ @expires = t && (t.is_a?(Time) ? t.httpdate : t.to_s)
+ end
+
+ def expires
+ @expires && Time.parse(@expires)
+ end
+
+ def to_s
+ ret = ""
+ ret << @name << "=" << @value
+ ret << "; " << "Version=" << @version.to_s if @version > 0
+ ret << "; " << "Domain=" << @domain if @domain
+ ret << "; " << "Expires=" << @expires if @expires
+ ret << "; " << "Max-Age=" << @max_age.to_s if @max_age
+ ret << "; " << "Comment=" << @comment if @comment
+ ret << "; " << "Path=" << @path if @path
+ ret << "; " << "Secure" if @secure
+ ret
+ end
+
+ # Cookie::parse()
+ # It parses Cookie field sent from the user agent.
+ def self.parse(str)
+ if str
+ ret = []
+ cookie = nil
+ ver = 0
+ str.split(/[;,]\s+/).each{|x|
+ key, val = x.split(/=/,2)
+ val = val ? HTTPUtils::dequote(val) : ""
+ case key
+ when "$Version"; ver = val.to_i
+ when "$Path"; cookie.path = val
+ when "$Domain"; cookie.domain = val
+ when "$Port"; cookie.port = val
+ else
+ ret << cookie if cookie
+ cookie = self.new(key, val)
+ cookie.version = ver
+ end
+ }
+ ret << cookie if cookie
+ ret
+ end
+ end
+
+ def self.parse_set_cookie(str)
+ cookie_elem = str.split(/;/)
+ first_elem = cookie_elem.shift
+ first_elem.strip!
+ key, value = first_elem.split(/=/, 2)
+ cookie = new(key, HTTPUtils.dequote(value))
+ cookie_elem.each{|pair|
+ pair.strip!
+ key, value = pair.split(/=/, 2)
+ if value
+ value = HTTPUtils.dequote(value.strip)
+ end
+ case key.downcase
+ when "domain" then cookie.domain = value
+ when "path" then cookie.path = value
+ when "expires" then cookie.expires = value
+ when "max-age" then cookie.max_age = Integer(value)
+ when "comment" then cookie.comment = value
+ when "version" then cookie.version = Integer(value)
+ when "secure" then cookie.secure = true
+ end
+ }
+ return cookie
+ end
+
+ def self.parse_set_cookies(str)
+ return str.split(/,(?=[^;,]*=)|,$/).collect{|c|
+ parse_set_cookie(c)
+ }
+ end
+ end
+end
28 lib/webrick/htmlutils.rb
@@ -0,0 +1,28 @@
+#--
+# 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
+
+ ##
+ # Escapes &, ", > and < in +string+
+
+ def escape(string)
+ str = string ? string.dup : ""
+ str.gsub!(/&/n, '&amp;')
+ str.gsub!(/\"/n, '&quot;')
+ str.gsub!(/>/n, '&gt;')
+ str.gsub!(/</n, '&lt;')
+ str
+ end
+ module_function :escape
+
+ end
+end
95 lib/webrick/httpauth.rb
@@ -0,0 +1,95 @@
+#
+# httpauth.rb -- HTTP access authentication
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2000, 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
+# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: httpauth.rb,v 1.14 2003/07/22 19:20:42 gotoyuzo Exp $
+
+require 'webrick/httpauth/basicauth'
+require 'webrick/httpauth/digestauth'
+require 'webrick/httpauth/htpasswd'
+require 'webrick/httpauth/htdigest'
+require 'webrick/httpauth/htgroup'
+
+module WEBrick
+
+ ##
+ # HTTPAuth provides both basic and digest authentication.
+ #
+ # To enable authentication for requests in WEBrick you will need a user
+ # database and an authenticator. To start, here's an Htpasswd database for
+ # use with a DigestAuth authenticator:
+ #
+ # config = { :Realm => 'DigestAuth example realm' }
+ #
+ # htpasswd = WEBrick::HTTPAuth::Htpasswd.new 'my_password_file'
+ # htpasswd.auth_type = WEBrick::HTTPAuth::DigestAuth
+ # htpasswd.set_passwd config[:Realm], 'username', 'password'
+ # htpasswd.flush
+ #
+ # The +:Realm+ is used to provide different access to different groups
+ # across several resources on a server. Typically you'll need only one
+ # realm for a server.
+ #
+ # This database can be used to create an authenticator:
+ #
+ # config[:UserDB] = htpasswd
+ #
+ # digest_auth = WEBrick::HTTPAuth::DigestAuth.new config
+ #
+ # To authenticate a request call #authenticate with a request and response
+ # object in a servlet:
+ #
+ # def do_GET req, res
+ # @authenticator.authenticate req, res
+ # end
+ #
+ # For digest authentication the authenticator must not be created every
+ # request, it must be passed in as an option via WEBrick::HTTPServer#mount.
+
+ module HTTPAuth
+ module_function
+
+ def _basic_auth(req, res, realm, req_field, res_field, err_type,
+ block) # :nodoc:
+ user = pass = nil
+ if /^Basic\s+(.*)/o =~ req[req_field]
+ userpass = $1
+ user, pass = userpass.unpack("m*")[0].split(":", 2)
+ end
+ if block.call(user, pass)
+ req.user = user
+ return
+ end
+ res[res_field] = "Basic realm=\"#{realm}\""
+ raise err_type
+ end
+
+ ##
+ # Simple wrapper for providing basic authentication for a request. When
+ # called with a request +req+, response +res+, authentication +realm+ and
+ # +block+ the block will be called with a +username+ and +password+. If
+ # the block returns true the request is allowed to continue, otherwise an
+ # HTTPStatus::Unauthorized error is raised.
+
+ def basic_auth(req, res, realm, &block) # :yield: username, password
+ _basic_auth(req, res, realm, "Authorization", "WWW-Authenticate",
+ HTTPStatus::Unauthorized, block)
+ end
+
+ ##
+ # Simple wrapper for providing basic authentication for a proxied request.
+ # When called with a request +req+, response +res+, authentication +realm+
+ # and +block+ the block will be called with a +username+ and +password+.
+ # If the block returns true the request is allowed to continue, otherwise
+ # an HTTPStatus::ProxyAuthenticationRequired error is raised.
+
+ def proxy_basic_auth(req, res, realm, &block) # :yield: username, password
+ _basic_auth(req, res, realm, "Proxy-Authorization", "Proxy-Authenticate",
+ HTTPStatus::ProxyAuthenticationRequired, block)
+ end
+ end
+end
112 lib/webrick/httpauth/authenticator.rb
@@ -0,0 +1,112 @@
+#--
+# 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 providing generic support for both Digest and Basic
+ # authentication schemes.
+
+ module Authenticator
+
+ RequestField = "Authorization"
+ ResponseField = "WWW-Authenticate"
+ ResponseInfoField = "Authentication-Info"
+ AuthException = HTTPStatus::Unauthorized
+
+ ##
+ # Method of authentication, must be overridden by the including class
+
+ AuthScheme = nil
+
+ ##
+ # The realm this authenticator covers
+
+ attr_reader :realm
+
+ ##
+ # The user database for this authenticator
+
+ attr_reader :userdb
+
+ ##
+ # The logger for this authenticator
+
+ attr_reader :logger
+
+ private
+
+ ##
+ # Initializes the authenticator from +config+
+
+ 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
+
+ ##
+ # Ensures +req+ has credentials that can be authenticated.
+
+ def check_scheme(req)
+ unless credentials = req[@request_field]
+ error("no credentials in the request.")
+ return nil
+ end
+ unless match = /^#{@auth_scheme}\s+/i.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 providing generic support for both Digest and Basic
+ # authentication schemes for proxies.
+
+ module ProxyAuthenticator
+ RequestField = "Proxy-Authorization"
+ ResponseField = "Proxy-Authenticate"
+ InfoField = "Proxy-Authentication-Info"
+ AuthException = HTTPStatus::ProxyAuthenticationRequired
+ end
+ end
+end
108 lib/webrick/httpauth/basicauth.rb
@@ -0,0 +1,108 @@
+#
+# httpauth/basicauth.rb -- HTTP basic access authentication
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2003 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: basicauth.rb,v 1.5 2003/02/20 07:15:47 gotoyuzo Exp $
+
+require 'webrick/config'
+require 'webrick/httpstatus'
+require 'webrick/httpauth/authenticator'
+
+module WEBrick
+ module HTTPAuth
+
+ ##
+ # Basic Authentication for WEBrick
+ #
+ # Use this class to add basic authentication to a WEBrick servlet.
+ #
+ # Here is an example of how to set up a BasicAuth:
+ #
+ # config = { :Realm => 'BasicAuth example realm' }
+ #
+ # htpasswd = WEBrick::HTTPAuth::Htpasswd.new 'my_password_file'
+ # htpasswd.set_passwd config[:Realm], 'username', 'password'
+ # htpasswd.flush
+ #
+ # config[:UserDB] = htpasswd
+ #
+ # basic_auth = WEBrick::HTTPAuth::BasicAuth.new config
+
+ class BasicAuth
+ include Authenticator
+
+ AuthScheme = "Basic"
+
+ ##
+ # Used by UserDB to create a basic password entry
+
+ def self.make_passwd(realm, user, pass)
+ pass ||= ""
+ pass.crypt(Utils::random_string(2))
+ end
+
+ attr_reader :realm, :userdb, :logger
+
+ ##
+ # Creates a new BasicAuth instance.
+ #
+ # See WEBrick::Config::BasicAuth for default configuration entries
+ #
+ # You must supply the following configuration entries:
+ #
+ # :Realm:: The name of the realm being protected.
+ # :UserDB:: A database of usernames and passwords.
+ # A WEBrick::HTTPAuth::Htpasswd instance should be used.
+
+ def initialize(config, default=Config::BasicAuth)
+ check_init(config)
+ @config = default.dup.update(config)
+ end
+
+ ##
+ # Authenticates a +req+ and returns a 401 Unauthorized using +res+ if
+ # the authentication was not correct.
+
+ def authenticate(req, res)
+ unless basic_credentials = check_scheme(req)
+ challenge(req, res)
+ end
+ userid, password = basic_credentials.unpack("m*")[0].split(":", 2)
+ password ||= ""
+ if userid.empty?
+ error("user id was not given.")
+ challenge(req, res)
+ end
+ unless encpass = @userdb.get_passwd(@realm, userid, @reload_db)
+ error("%s: the user is not allowed.", userid)
+ challenge(req, res)
+ end
+ if password.crypt(encpass) != encpass
+ error("%s: password unmatch.", userid)
+ challenge(req, res)
+ end
+ info("%s: authentication succeeded.", userid)
+ req.user = userid
+ end
+
+ ##
+ # Returns a challenge response which asks for for authentication
+ # information
+
+ def challenge(req, res)
+ res[@response_field] = "#{@auth_scheme} realm=\"#{@realm}\""
+ raise @auth_exception
+ end
+ end
+
+ ##
+ # Basic authentication for proxy servers. See BasicAuth for details.
+
+ class ProxyBasicAuth < BasicAuth
+ include ProxyAuthenticator
+ end
+ end
+end
392 lib/webrick/httpauth/digestauth.rb
@@ -0,0 +1,392 @@
+#
+# httpauth/digestauth.rb -- HTTP digest access authentication
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2003 Internet Programming with Ruby writers.
+# Copyright (c) 2003 H.M.
+#
+# The original implementation is provided by H.M.
+# URL: http://rwiki.jin.gr.jp/cgi-bin/rw-cgi.rb?cmd=view;name=
+# %C7%A7%BE%DA%B5%A1%C7%BD%A4%F2%B2%FE%C2%A4%A4%B7%A4%C6%A4%DF%A4%EB
+#
+# $IPR: digestauth.rb,v 1.5 2003/02/20 07:15:47 gotoyuzo Exp $
+
+require 'webrick/config'
+require 'webrick/httpstatus'
+require 'webrick/httpauth/authenticator'
+require 'digest/md5'
+require 'digest/sha1'
+
+module WEBrick
+ module HTTPAuth
+
+ ##
+ # RFC 2617 Digest Access Authentication for WEBrick
+ #
+ # Use this class to add digest authentication to a WEBrick servlet.
+ #
+ # Here is an example of how to set up DigestAuth:
+ #
+ # config = { :Realm => 'DigestAuth example realm' }
+ #
+ # htdigest = WEBrick::HTTPAuth::Htdigest.new 'my_password_file'
+ # htdigest.set_passwd config[:Realm], 'username', 'password'
+ # htdigest.flush
+ #
+ # config[:UserDB] = htdigest
+ #
+ # digest_auth = WEBrick::HTTPAuth::DigestAuth.new config
+ #
+ # When using this as with a servlet be sure not to create a new DigestAuth
+ # object in the servlet's #initialize. By default WEBrick creates a new
+ # servlet instance for every request and the DigestAuth object must be
+ # used across requests.
+
+ class DigestAuth
+ include Authenticator
+
+ AuthScheme = "Digest"
+ OpaqueInfo = Struct.new(:time, :nonce, :nc)
+ attr_reader :algorithm, :qop
+
+ ##
+ # Used by UserDB to create a digest password entry
+
+ def self.make_passwd(realm, user, pass)
+ pass ||= ""
+ Digest::MD5::hexdigest([user, realm, pass].join(":"))
+ end
+
+ ##
+ # Creates a new DigestAuth instance. Be sure to use the same DigestAuth
+ # instance for multiple requests as it saves state between requests in
+ # order to perform authentication.
+ #
+ # See WEBrick::Config::DigestAuth for default configuration entries
+ #
+ # You must supply the following configuration entries:
+ #
+ # :Realm:: The name of the realm being protected.
+ # :UserDB:: A database of usernames and passwords.
+ # A WEBrick::HTTPAuth::Htdigest instance should be used.
+
+ 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]
+
+ case @algorithm
+ when 'MD5','MD5-sess'
+ @h = Digest::MD5
+ when 'SHA1','SHA1-sess' # it is a bonus feature :-)
+ @h = Digest::SHA1
+ else
+ msg = format('Algorithm "%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
+
+ ##
+ # Authenticates a +req+ and returns a 401 Unauthorized using +res+ if
+ # the authentication was not correct.
+
+ 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
+
+ ##
+ # Returns a challenge response which asks for for authentication
+ # information
+
+ 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'].upcase != @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$/i =~ auth_req['algorithm']
+ ha1 = hexdigest(password, auth_req['nonce'], auth_req['cnonce'])
+ else
+ ha1 = password
+ end
+
+ if auth_req['qop'] == "auth" || auth_req['qop'] == nil
+ ha2 = hexdigest(req.request_method, auth_req['uri'])
+ ha2_res = hexdigest("", auth_req['uri'])
+ elsif auth_req['qop'] == "auth-int"
+ ha2 = hexdigest(req.request_method, auth_req['uri'],
+ hexdigest(req.body))
+ ha2_res = hexdigest("", auth_req['uri'], hexdigest(res.body))
+ end
+
+ if auth_req['qop'] == "auth" || auth_req['qop'] == "auth-int"
+ param2 = ['nonce', 'nc', 'cnonce', 'qop'].map{|key|
+ auth_req[key]
+ }.join(':')
+ digest = hexdigest(ha1, param2, ha2)
+ digest_res = hexdigest(ha1, param2, ha2_res)
+ else
+ digest = hexdigest(ha1, auth_req['nonce'], ha2)
+ digest_res = hexdigest(ha1, auth_req['nonce'], ha2_res)
+ end
+
+ if digest != auth_req['response']
+ error("%s: digest unmatch.", auth_req['username'])
+ return false
+ elsif nonce_is_invalid
+ error('%s: digest is valid, but nonce is not valid.',
+ auth_req['username'])
+ return :nonce_is_stale
+ elsif @use_auth_info_header
+ auth_info = {
+ 'nextnonce' => generate_next_nonce(req),
+ 'rspauth' => digest_res
+ }
+ if @use_opaque
+ opaque_struct.time = req.request_time
+ opaque_struct.nonce = auth_info['nextnonce']
+ opaque_struct.nc = "%08x" % (auth_req['nc'].hex + 1)
+ end
+ if auth_req['qop'] == "auth" || auth_req['qop'] == "auth-int"
+ ['qop','cnonce','nc'].each{|key|
+ auth_info[key] = auth_req[key]
+ }
+ end
+ res[@resp_info_field] = auth_info.keys.map{|key|
+ if key == 'nc'
+ key + '=' + auth_info[key]
+ else
+ key + "=" + HTTPUtils::quote(auth_info[key])
+ end
+ }.join(', ')
+ end
+ info('%s: authentication succeeded.', auth_req['username'])
+ req.user = auth_req['username']
+ return true
+ end
+
+ def split_param_value(string)
+ ret = {}
+ while string.bytesize != 0
+ case string
+ when /^\s*([\w\-\.\*\%\!]+)=\s*\"((\\.|[^\"])*)\"\s*,?/
+ key = $1
+ matched = $2
+ string = $'
+ ret[key] = matched.gsub(/\\(.)/, "\\1")
+ when /^\s*([\w\-\.\*\%\!]+)=\s*([^,\"]*),?/
+ key = $1
+ matched = $2
+ string = $'
+ ret[key] = matched.clone
+ when /^s*^,/
+ string = $'
+ else
+ break
+ end
+ end
+ ret
+ end
+
+ def generate_next_nonce(req)
+ now = "%012d" % req.request_time.to_i
+ pk = hexdigest(now, @instance_key)[0,32]
+ nonce = [now + ":" + pk].pack("m*").chop # it has 60 length of chars.
+ nonce
+ end
+
+ def check_nonce(req, auth_req)
+ username = auth_req['username']
+ nonce = auth_req['nonce']
+
+ pub_time, pk = nonce.unpack("m*")[0].split(":", 2)
+ if (!pub_time || !pk)
+ error("%s: empty nonce is given", username)
+ return false
+ elsif (hexdigest(pub_time, @instance_key)[0,32] != pk)
+ error("%s: invalid private-key: %s for %s",
+ username, hexdigest(pub_time, @instance_key)[0,32], pk)
+ return false
+ end
+
+ diff_time = req.request_time.to_i - pub_time.to_i
+ if (diff_time < 0)
+ error("%s: difference of time-stamp is negative.", username)
+ return false
+ elsif diff_time > @nonce_expire_period
+ error("%s: nonce is expired.", username)
+ return false
+ end
+
+ return true
+ end
+
+ def generate_opaque(req)
+ @mutex.synchronize{
+ now = req.request_time
+ if now - @last_nonce_expire > @nonce_expire_delta
+ @opaques.delete_if{|key,val|
+ (now - val.time) > @nonce_expire_period
+ }
+ @last_nonce_expire = now
+ end
+ begin
+ opaque = Utils::random_string(16)
+ end while @opaques[opaque]
+ @opaques[opaque] = OpaqueInfo.new(now, nil, '00000001')
+ opaque
+ }
+ end
+
+ def check_opaque(opaque_struct, req, auth_req)
+ if (@use_next_nonce && auth_req['nonce'] != opaque_struct.nonce)
+ error('%s: nonce unmatched. "%s" for "%s"',
+ auth_req['username'], auth_req['nonce'], opaque_struct.nonce)
+ return false
+ elsif !check_nonce(req, auth_req)
+ return false
+ end
+ if (@check_nc && auth_req['nc'] != opaque_struct.nc)
+ error('%s: nc unmatched."%s" for "%s"',
+ auth_req['username'], auth_req['nc'], opaque_struct.nc)
+ return false
+ end
+ true
+ end
+
+ def check_uri(req, auth_req)
+ uri = auth_req['uri']
+ if uri != req.request_uri.to_s && uri != req.unparsed_uri &&
+ (@internet_explorer_hack && uri != req.path)
+ error('%s: uri unmatch. "%s" for "%s"', auth_req['username'],
+ auth_req['uri'], req.request_uri.to_s)
+ return false
+ end
+ true
+ end
+
+ def hexdigest(*args)
+ @h.hexdigest(args.join(":"))
+ end
+
+ end
+
+ ##
+ # Digest authentication for proxy servers. See DigestAuth for details.
+
+ class ProxyDigestAuth < DigestAuth
+ include ProxyAuthenticator
+
+ private
+ def check_uri(req, auth_req)
+ return true
+ end
+ end
+ end
+end
128 lib/webrick/httpauth/htdigest.rb
@@ -0,0 +1,128 @@
+#
+# 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
+
+ ##
+ # Htdigest accesses apache-compatible digest password files. Passwords are
+ # matched to a realm where they are valid. For security, the path for a
+ # digest password database should be stored outside of the paths available
+ # to the HTTP server.
+ #
+ # Htdigest is intended for use with WEBrick::HTTPAuth::DigestAuth and
+ # stores passwords using cryptographic hashes.
+ #
+ # htpasswd = WEBrick::HTTPAuth::Htdigest.new 'my_password_file'
+ # htpasswd.set_passwd 'my realm', 'username', 'password'
+ # htpasswd.flush
+
+ class Htdigest
+ include UserDB
+
+ ##
+ # Open a digest password database at +path+
+
+ 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
+
+ ##
+ # Reloads passwords from the database
+
+ 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
+
+ ##
+ # Flush the password database. If +output+ is given the database will
+ # be written there instead of to the original path.
+
+ 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
+
+ ##
+ # Retrieves a password from the database for +user+ in +realm+. If
+ # +reload_db+ is true the database will be reloaded first.
+
+ def get_passwd(realm, user, reload_db)
+ reload() if reload_db
+ if hash = @digest[realm]
+ hash[user]
+ end
+ end
+
+ ##
+ # Sets a password in the database for +user+ in +realm+ to +pass+.
+
+ 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
+
+ ##
+ # Removes a password from the database for +user+ in +realm+.
+
+ def delete_passwd(realm, user)
+ if hash = @digest[realm]
+ hash.delete(user)
+ end
+ end
+
+ ##
+ # Iterate passwords in the database.
+
+ def each # :yields: [user, realm, password_hash]
+ @digest.keys.sort.each{|realm|
+ hash = @digest[realm]
+ hash.keys.sort.each{|user|
+ yield([user, realm, hash[user]])
+ }
+ }
+ end
+ end
+ end
+end
93 lib/webrick/httpauth/htgroup.rb
@@ -0,0 +1,93 @@
+#
+# 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
+
+ ##
+ # Htgroup accesses apache-compatible group files. Htgroup can be used to
+ # provide group-based authentication for users. Currently Htgroup is not
+ # directly integrated with any authenticators in WEBrick. For security,
+ # the path for a digest password database should be stored outside of the
+ # paths available to the HTTP server.
+ #
+ # Example:
+ #
+ # htgroup = WEBrick::HTTPAuth::Htgroup.new 'my_group_file'
+ # htgroup.add 'superheroes', %w[spiderman batman]
+ #
+ # htgroup.members('superheroes').include? 'magneto' # => false
+
+ class Htgroup
+
+ ##
+ # Open a group database at +path+
+
+ def initialize(path)
+ @path = path
+ @mtime = Time.at(0)
+ @group = Hash.new
+ open(@path,"a").close unless File::exist?(@path)
+ reload
+ end
+
+ ##
+ # Reload groups from the database
+
+ 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
+
+ ##
+ # Flush the group database. If +output+ is given the database will be
+ # written there instead of to the original path.
+
+ 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
+
+ ##
+ # Retrieve the list of members from +group+
+
+ def members(group)
+ reload
+ @group[group] || []
+ end
+
+ ##
+ # Add an Array of +members+ to +group+
+
+ def add(group, members)
+ @group[group] = members(group) | members
+ end
+ end
+ end
+end
121 lib/webrick/httpauth/htpasswd.rb
@@ -0,0 +1,121 @@
+#
+# 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
+
+ ##
+ # Htpasswd accesses apache-compatible password files. Passwords are
+ # matched to a realm where they are valid. For security, the path for a
+ # password database should be stored outside of the paths available to the
+ # HTTP server.
+ #
+ # Htpasswd is intended for use with WEBrick::HTTPAuth::BasicAuth.
+ #
+ # To create an Htpasswd database with a single user:
+ #
+ # htpasswd = WEBrick::HTTPAuth::Htpasswd.new 'my_password_file'
+ # htpasswd.set_passwd 'my realm', 'username', 'password'
+ # htpasswd.flush
+
+ class Htpasswd
+ include UserDB
+
+ ##
+ # Open a password database at +path+
+
+ 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
+
+ ##
+ # Reload passwords from the database
+
+ def reload
+ mtime = File::mtime(@path)
+ if mtime > @mtime
+ @passwd.clear
+ open(@path){|io|
+ while line = io.gets
+ line.chomp!
+ case line
+ when %r!\A[^:]+:[a-zA-Z0-9./]{13}\z!
+ user, pass = line.split(":")
+ when /:\$/, /:{SHA}/
+ raise NotImplementedError,
+ 'MD5, SHA1 .htpasswd file not supported'
+ else
+ raise StandardError, 'bad .htpasswd file'
+ end
+ @passwd[user] = pass
+ end
+ }
+ @mtime = mtime
+ end
+ end
+
+ ##
+ # Flush the password database. If +output+ is given the database will
+ # be written there instead of to the original path.
+
+ 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
+
+ ##
+ # Retrieves a password from the database for +user+ in +realm+. If
+ # +reload_db+ is true the database will be reloaded first.
+
+ def get_passwd(realm, user, reload_db)
+ reload() if reload_db
+ @passwd[user]
+ end
+
+ ##
+ # Sets a password in the database for +user+ in +realm+ to +pass+.
+
+ def set_passwd(realm, user, pass)
+ @passwd[user] = make_passwd(realm, user, pass)
+ end
+
+ ##
+ # Removes a password from the database for +user+ in +realm+.
+
+ def delete_passwd(realm, user)
+ @passwd.delete(user)
+ end
+
+ ##
+ # Iterate passwords in the database.
+
+ def each # :yields: [user, password]
+ @passwd.keys.sort.each{|user|
+ yield([user, @passwd[user]])
+ }
+ end
+ end
+ end
+end
52 lib/webrick/httpauth/userdb.rb
@@ -0,0 +1,52 @@
+#--
+# 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
+
+ ##
+ # User database mixin for HTTPAuth. This mixin dispatches user record
+ # access to the underlying auth_type for this database.
+
+ module UserDB
+
+ ##
+ # The authentication type.
+ #
+ # WEBrick::HTTPAuth::BasicAuth or WEBrick::HTTPAuth::DigestAuth are
+ # built-in.
+
+ attr_accessor :auth_type
+
+ ##
+ # Creates an obscured password in +realm+ with +user+ and +password+
+ # using the auth_type of this database.
+
+ def make_passwd(realm, user, pass)
+ @auth_type::make_passwd(realm, user, pass)
+ end
+
+ ##
+ # Sets a password in +realm+ with +user+ and +password+ for the
+ # auth_type of this database.
+
+ def set_passwd(realm, user, pass)
+ self[user] = pass
+ end
+
+ ##
+ # Retrieves a password in +realm+ for +user+ for the auth_type of this
+ # database. +reload_db+ is a dummy value.
+
+ def get_passwd(realm, user, reload_db=false)
+ make_passwd(realm, user, self[user])
+ end
+ end
+ end
+end
305 lib/webrick/httpproxy.rb
@@ -0,0 +1,305 @@
+#
+# httpproxy.rb -- HTTPProxy Class
+#
+# Author: IPR -- Internet Programming with Ruby -- writers
+# Copyright (c) 2002 GOTO Kentaro
+# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
+# reserved.
+#
+# $IPR: httpproxy.rb,v 1.18 2003/03/08 18:58:10 gotoyuzo Exp $
+# $kNotwork: straw.rb,v 1.3 2002/02/12 15:13:07 gotoken Exp $
+
+require "webrick/httpserver"
+require "net/http"
+
+Net::HTTP::version_1_2 if RUBY_VERSION < "1.7"
+
+module WEBrick
+ NullReader = Object.new
+ class << NullReader
+ def read(*args)
+ nil
+ end
+ alias gets read
+ end
+
+ FakeProxyURI = Object.new
+ class << FakeProxyURI
+ def method_missing(meth, *args)
+ if %w(scheme host port path query userinfo).member?(meth.to_s)
+ return nil
+ end
+ super
+ end
+ end
+
+ ##
+ # An HTTP Proxy server which proxies GET, HEAD and POST requests.
+
+ class HTTPProxyServer < HTTPServer
+
+ ##
+ # Proxy server configurations. The proxy server handles the following
+ # configuration items in addition to those supported by HTTPServer:
+ #
+ # :ProxyAuthProc:: Called with a request and response to authorize a
+ # request
+ # :ProxyVia:: Appended to the via header
+ # :ProxyURI:: The proxy server's URI
+ # :ProxyContentHandler:: Called with a request and resopnse and allows
+ # modification of the response
+ # :ProxyTimeout:: Sets the proxy timeouts to 30 seconds for open and 60
+ # seconds for read operations
+
+ def initialize(config={}, default=Config::HTTP)
+ super(config, default)
+ c = @config
+ @via = "#{c[:HTTPVersion]} #{c[:ServerName]}:#{c[:Port]}"
+ end
+
+ def service(req, res)
+ if req.request_method == "CONNECT"
+ do_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
+
+ def proxy_uri(req, res)
+ # should return upstream proxy server's URI
+ return @config[:ProxyURI]
+ end
+
+ def proxy_service(req, res)
+ # Proxy Authentication
+ proxy_auth(req, res)
+
+ begin
+ self.send("do_#{req.request_method}", req, res)
+ rescue NoMethodError
+ raise HTTPStatus::MethodNotAllowed,
+ "unsupported method `#{req.request_method}'."
+ rescue => err
+ logger.debug("#{err.class}: #{err.message}")
+ raise HTTPStatus::ServiceUnavailable, err.message
+ end
+
+ # Process contents
+ if handler = @config[:ProxyContentHandler]
+ handler.call(req, res)
+ end
+ end
+
+ def do_CONNECT(req, res)
+ # Proxy Authentication
+ proxy_auth(req, res)
+
+ ua = Thread.current[:WEBrickSocket] # User-Agent
+ raise HTTPStatus::InternalServerError,
+ "[BUG] cannot get socket" unless ua
+
+ host, port = req.unparsed_uri.split(":", 2)
+ # Proxy authentication for upstream proxy server
+ if proxy = proxy_uri(req, res)
+ proxy_request_line = "CONNECT #{host}:#{port} HTTP/1.0"
+ if proxy.userinfo
+ credentials = "Basic " + [proxy.userinfo].pack("m").delete("\n")
+ end
+ host, port = proxy.host, proxy.port
+ end
+
+ begin
+ @logger.debug("CONNECT: upstream proxy is `#{host}:#{port}'.")
+ os = TCPSocket.new(host, port) # origin server
+
+ if proxy
+ @logger.debug("CONNECT: sending a Request-Line")
+ os << proxy_request_line << CRLF
+ @logger.debug("CONNECT: > #{proxy_request_line}")
+ if credentials
+ @logger.debug("CONNECT: sending a credentials")
+ os << "Proxy-Authorization: " << credentials << CRLF
+ end
+ os << CRLF
+ proxy_status_line = os.gets(LF)
+ @logger.debug("CONNECT: read a Status-Line form the upstream server")
+ @logger.debug("CONNECT: < #{proxy_status_line}")
+ if %r{^HTTP/\d+\.\d+\s+200\s*} =~ proxy_status_line
+ while line = os.gets(LF)
+ break if /\A(#{CRLF}|#{LF})\z/om =~ line
+ end
+ else
+ raise HTTPStatus::BadGateway
+ end
+ end
+ @logger.debug("CONNECT #{host}:#{port}: succeeded")
+ res.status = HTTPStatus::RC_OK
+ rescue => ex
+ @logger.debug("CONNECT #{host}:#{port}: failed `#{ex.message}'")
+ res.set_error(ex)
+ raise HTTPStatus::EOFError
+ ensure
+ if handler = @config[:ProxyContentHandler]
+ handler.call(req, res)
+ end
+ res.send_response(ua)
+ access_log(@config, req, res)
+
+ # Should clear request-line not to send the sesponse twice.
+ # see: HTTPServer#run
+ req.parse(NullReader) rescue nil
+ end
+
+ begin
+ while fds = IO::select([ua, os])
+ if fds[0].member?(ua)
+ buf = ua.sysread(1024);
+ @logger.debug("CONNECT: #{buf.bytesize} byte from User-Agent")
+ os.syswrite(buf)
+ elsif fds[0].member?(os)
+ buf = os.sysread(1024);
+ @logger.debug("CONNECT: #{buf.bytesize} 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_GET(req, res)
+ perform_proxy_request(req, res) do |http, path, header|
+ http.get(path, header)
+ end
+ end
+
+ def do_HEAD(req, res)
+ perform_proxy_request(req, res) do |http, path, header|
+ http.head(path, header)
+ end
+ end
+
+ def do_POST(req, res)
+ perform_proxy_request(req, res) do |http, path, header|
+ http.post(path, req.body || "", header)
+ end
+ end
+
+ def do_OPTIONS(req, res)