Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Update webrick to 1.9.3p0.

  • Loading branch information...
commit aabd9dc30f9760c7c20fb31e40acb1470179e05d 1 parent 981a2ef
@brixen brixen authored
Showing with 1,183 additions and 65 deletions.
  1. +200 −2 lib/19/webrick.rb
  2. +79 −3 lib/19/webrick/accesslog.rb
  3. +2 −2 lib/19/webrick/cgi.rb
  4. +20 −0 lib/19/webrick/compat.rb
  5. +4 −1 lib/19/webrick/htmlutils.rb
  6. +53 −3 lib/19/webrick/httpauth.rb
  7. +36 −3 lib/19/webrick/httpauth/authenticator.rb
  8. +43 −0 lib/19/webrick/httpauth/basicauth.rb
  9. +53 −5 lib/19/webrick/httpauth/digestauth.rb
  10. +38 −1 lib/19/webrick/httpauth/htdigest.rb
  11. +32 −0 lib/19/webrick/httpauth/htgroup.rb
  12. +39 −1 lib/19/webrick/httpauth/htpasswd.rb
  13. +26 −3 lib/19/webrick/httpauth/userdb.rb
  14. +17 −0 lib/19/webrick/httpproxy.rb
  15. +75 −16 lib/19/webrick/httprequest.rb
  16. +76 −3 lib/19/webrick/httpresponse.rb
  17. +1 −0  lib/19/webrick/https.rb
  18. +47 −0 lib/19/webrick/httpserver.rb
  19. +85 −2 lib/19/webrick/httpservlet/abstract.rb
  20. +1 −2  lib/19/webrick/httpservlet/cgi_runner.rb
  21. +36 −3 lib/19/webrick/httpservlet/erbhandler.rb
  22. +31 −0 lib/19/webrick/httpservlet/filehandler.rb
  23. +58 −6 lib/19/webrick/httpstatus.rb
  24. +1 −1  lib/19/webrick/httpversion.rb
  25. +51 −3 lib/19/webrick/log.rb
  26. +8 −0 lib/19/webrick/server.rb
  27. +3 −3 lib/19/webrick/ssl.rb
  28. +67 −1 lib/19/webrick/utils.rb
  29. +1 −1  lib/19/webrick/version.rb
View
202 lib/19/webrick.rb
@@ -1,13 +1,211 @@
+##
+# = WEB server toolkit.
#
-# WEBrick -- 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'
View
82 lib/19/webrick/accesslog.rb
@@ -1,4 +1,4 @@
-#
+#--
# accesslog.rb -- Access log handling utilities
#
# Author: IPR -- Internet Programming with Ruby -- writers
@@ -8,20 +8,89 @@
# $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.
- # http://httpd.apache.org/docs/mod/mod_log_config.html#formats
+ # 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]
@@ -56,6 +125,13 @@ def format(format_string, params)
(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
View
4 lib/19/webrick/cgi.rb
@@ -5,7 +5,7 @@
# Copyright (c) 2003 Internet Programming with Ruby writers. All rights
# reserved.
#
-# $Id: cgi.rb 25189 2009-10-02 12:04:37Z akr $
+# $Id: cgi.rb 29726 2010-11-08 20:59:01Z marcandre $
require "webrick/httprequest"
require "webrick/httpresponse"
@@ -143,7 +143,7 @@ def initialize(config, env, stdin, stdout)
setup_header
@header_part << CRLF
@header_part.rewind
- rescue Exception => ex
+ rescue Exception
raise CGIError, "invalid CGI environment"
end
end
View
20 lib/19/webrick/compat.rb
@@ -8,8 +8,28 @@
#
# $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
View
5 lib/19/webrick/htmlutils.rb
@@ -1,4 +1,4 @@
-#
+#--
# htmlutils.rb -- HTMLUtils Module
#
# Author: IPR -- Internet Programming with Ruby -- writers
@@ -11,6 +11,9 @@
module WEBrick
module HTMLUtils
+ ##
+ # Escapes &, ", > and < in +string+
+
def escape(string)
str = string ? string.dup : ""
str.gsub!(/&/n, '&amp;')
View
56 lib/19/webrick/httpauth.rb
@@ -15,10 +15,46 @@
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)
+ 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
@@ -32,12 +68,26 @@ def _basic_auth(req, res, realm, req_field, res_field, err_type, block)
raise err_type
end
- def basic_auth(req, res, realm, &block)
+ ##
+ # 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
- def proxy_basic_auth(req, res, realm, &block)
+ ##
+ # 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
View
39 lib/19/webrick/httpauth/authenticator.rb
@@ -1,4 +1,4 @@
-#
+#--
# httpauth/authenticator.rb -- Authenticator mix-in module.
#
# Author: IPR -- Internet Programming with Ruby -- writers
@@ -9,17 +9,43 @@
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
- AuthScheme = nil # must override by the derived class
- attr_reader :realm, :userdb, :logger
+ ##
+ # 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]
@@ -37,6 +63,9 @@ def check_init(config)
@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.")
@@ -69,6 +98,10 @@ def info(fmt, *args)
end
end
+ ##
+ # Module providing generic support for both Digest and Basic
+ # authentication schemes for proxies.
+
module ProxyAuthenticator
RequestField = "Proxy-Authorization"
ResponseField = "Proxy-Authenticate"
View
43 lib/19/webrick/httpauth/basicauth.rb
@@ -13,11 +13,32 @@
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))
@@ -25,11 +46,26 @@ def self.make_passwd(realm, user, pass)
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)
@@ -52,12 +88,19 @@ def authenticate(req, res)
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
View
58 lib/19/webrick/httpauth/digestauth.rb
@@ -19,6 +19,29 @@
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
@@ -26,11 +49,27 @@ class DigestAuth
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)
@@ -44,7 +83,6 @@ def initialize(config, default=Config::DigestAuth)
@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'
@@ -62,6 +100,10 @@ def initialize(config, default=Config::DigestAuth)
@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)
@@ -72,6 +114,10 @@ def authenticate(req, res)
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
@@ -128,8 +174,7 @@ def _authenticate(req, res)
end
auth_req['algorithm'] ||= 'MD5'
- if auth_req['algorithm'] != @algorithm &&
- (@opera_hack && auth_req['algorithm'] != @algorithm.upcase)
+ if auth_req['algorithm'].upcase != @algorithm.upcase
error('%s: algorithm unmatch. "%s" for "%s"',
auth_req['username'], auth_req['algorithm'], @algorithm)
return false
@@ -165,8 +210,7 @@ def _authenticate(req, res)
nonce_is_invalid = true
end
- if /-sess$/ =~ auth_req['algorithm'] ||
- (@opera_hack && /-SESS$/ =~ auth_req['algorithm'])
+ if /-sess$/i =~ auth_req['algorithm']
ha1 = hexdigest(password, auth_req['nonce'], auth_req['cnonce'])
else
ha1 = password
@@ -333,9 +377,13 @@ def hexdigest(*args)
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
View
39 lib/19/webrick/httpauth/htdigest.rb
@@ -13,9 +13,26 @@
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)
@@ -26,6 +43,9 @@ def initialize(path)
reload
end
+ ##
+ # Reloads passwords from the database
+
def reload
mtime = File::mtime(@path)
if mtime > @mtime
@@ -44,6 +64,10 @@ def reload
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))
@@ -56,6 +80,10 @@ def flush(output=nil)
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]
@@ -63,6 +91,9 @@ def get_passwd(realm, user, reload_db)
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]
@@ -72,13 +103,19 @@ def set_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
- def each
+ ##
+ # 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|
View
32 lib/19/webrick/httpauth/htgroup.rb
@@ -11,7 +11,26 @@
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)
@@ -20,6 +39,9 @@ def initialize(path)
reload
end
+ ##
+ # Reload groups from the database
+
def reload
if (mtime = File::mtime(@path)) > @mtime
@group.clear
@@ -34,6 +56,10 @@ def reload
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))
@@ -48,11 +74,17 @@ def flush(output=nil)
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
View
40 lib/19/webrick/httpauth/htpasswd.rb
@@ -13,9 +13,27 @@
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)
@@ -25,6 +43,9 @@ def initialize(path)
reload
end
+ ##
+ # Reload passwords from the database
+
def reload
mtime = File::mtime(@path)
if mtime > @mtime
@@ -48,6 +69,10 @@ def reload
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))
@@ -60,20 +85,33 @@ def flush(output=nil)
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
- def each
+ ##
+ # Iterate passwords in the database.
+
+ def each # :yields: [user, password]
@passwd.keys.sort.each{|user|
yield([user, @passwd[user]])
}
View
29 lib/19/webrick/httpauth/userdb.rb
@@ -1,4 +1,4 @@
-#
+#--
# httpauth/userdb.rb -- UserDB mix-in module.
#
# Author: IPR -- Internet Programming with Ruby -- writers
@@ -9,19 +9,42 @@
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
- attr_accessor :auth_type # BasicAuth or DigestAuth
+
+ ##
+ # 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)
- # reload_db is dummy
make_passwd(realm, user, self[user])
end
end
View
17 lib/19/webrick/httpproxy.rb
@@ -33,7 +33,24 @@ def method_missing(meth, *args)
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
View
91 lib/19/webrick/httprequest.rb
@@ -15,23 +15,27 @@
require 'webrick/cookie'
module WEBrick
+
+ ##
+ # An HTTP request.
class HTTPRequest
+
BODY_CONTAINABLE_METHODS = [ "POST", "PUT" ]
- # Request line
+ # :section: Request line
attr_reader :request_line
attr_reader :request_method, :unparsed_uri, :http_version
- # Request-URI
+ # :section: Request-URI
attr_reader :request_uri, :path
attr_accessor :script_name, :path_info, :query_string
- # Header and entity body
+ # :section: Header and entity body
attr_reader :raw_header, :header, :cookies
attr_reader :accept, :accept_charset
attr_reader :accept_encoding, :accept_language
- # Misc
+ # :section:
attr_accessor :user
attr_reader :addr, :peeraddr
attr_reader :attributes
@@ -122,12 +126,24 @@ def parse(socket=nil)
end
end
+ # Generate HTTP/1.1 100 continue response if the client expects it,
+ # otherwise does nothing.
+ def continue
+ if self['expect'] == '100-continue' && @config[:HTTPVersion] >= "1.1"
+ @socket << "HTTP/#{@config[:HTTPVersion]} 100 continue#{CRLF}#{CRLF}"
+ @header.delete('expect')
+ end
+ end
+
def body(&block)
block ||= Proc.new{|chunk| @body << chunk }
read_body(@socket, block)
@body.empty? ? nil : @body
end
+ ##
+ # Request query as a Hash
+
def query
unless @query
parse_query()
@@ -135,14 +151,23 @@ def query
@query
end
+ ##
+ # The content-length header
+
def content_length
return Integer(self['content-length'])
end
+ ##
+ # The content-type header
+
def content_type
return self['content-type']
end
+ ##
+ # Retrieves +header_name+
+
def [](header_name)
if @header
value = @header[header_name.downcase]
@@ -150,38 +175,61 @@ def [](header_name)
end
end
+ ##
+ # Iterates over the request headers
+
def each
- @header.each{|k, v|
- value = @header[k]
- yield(k, value.empty? ? nil : value.join(", "))
- }
+ if @header
+ @header.each{|k, v|
+ value = @header[k]
+ yield(k, value.empty? ? nil : value.join(", "))
+ }
+ end
end
+ ##
+ # The host this request is for
+
def host
return @forwarded_host || @host
end
+ ##
+ # The port this request is for
+
def port
return @forwarded_port || @port
end
+ ##
+ # The server name this request is for
+
def server_name
return @forwarded_server || @config[:ServerName]
end
+ ##
+ # The client's IP address
+
def remote_ip
return self["client-ip"] || @forwarded_for || @peeraddr[3]
end
+ ##
+ # Is this an SSL request?
+
def ssl?
return @request_uri.scheme == "https"
end
+ ##
+ # Should the connection this request was made on be kept alive?
+
def keep_alive?
@keep_alive
end
- def to_s
+ def to_s # :nodoc:
ret = @request_line.dup
@raw_header.each{|line| ret << line }
ret << CRLF
@@ -201,11 +249,11 @@ def fixup()
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/)
+ # 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/
+ def meta_vars
meta = Hash.new
cl = self["Content-Length"]
@@ -242,9 +290,11 @@ def meta_vars
private
+ MAX_URI_LENGTH = 2083 # :nodoc:
+
def read_request_line(socket)
- @request_line = read_line(socket, 1024) if socket
- if @request_line.bytesize >= 1024 and @request_line[-1, 1] != LF
+ @request_line = read_line(socket, MAX_URI_LENGTH) if socket
+ if @request_line.bytesize >= MAX_URI_LENGTH and @request_line[-1, 1] != LF
raise HTTPStatus::RequestURITooLarge
end
@request_time = Time.now
@@ -273,6 +323,7 @@ def parse_uri(str, scheme="http")
if @config[:Escape8bitURI]
str = HTTPUtils::escape8bit(str)
end
+ str.sub!(%r{\A/+}o, '/')
uri = URI::parse(str)
return uri if uri.absolute?
if @forwarded_host
@@ -385,10 +436,18 @@ def parse_query()
^(::ffff:)?(10|172\.(1[6-9]|2[0-9]|3[01])|192\.168)\.
/ixo
+ # It's said that all X-Forwarded-* headers will contain more than one
+ # (comma-separated) value if the original request already contained one of
+ # these headers. Since we could use these values as Host header, we choose
+ # the initial(first) value. (apr_table_mergen() adds new value after the
+ # existing value with ", " prefix)
def setup_forwarded_info
- @forwarded_server = self["x-forwarded-server"]
+ if @forwarded_server = self["x-forwarded-server"]
+ @forwarded_server = @forwarded_server.split(",", 2).first
+ end
@forwarded_proto = self["x-forwarded-proto"]
if host_port = self["x-forwarded-host"]
+ host_port = host_port.split(",", 2).first
@forwarded_host, tmp = host_port.split(":", 2)
@forwarded_port = (tmp || (@forwarded_proto == "https" ? 443 : 80)).to_i
end
View
79 lib/19/webrick/httpresponse.rb
@@ -15,10 +15,17 @@
require 'webrick/httpstatus'
module WEBrick
+ ##
+ # An HTTP response.
+
class HTTPResponse
attr_reader :http_version, :status, :header
attr_reader :cookies
attr_accessor :reason_phrase
+
+ ##
+ # Body may be a String or IO subclass.
+
attr_accessor :body
attr_accessor :request_method, :request_uri, :request_http_version
@@ -26,6 +33,9 @@ class HTTPResponse
attr_accessor :keep_alive
attr_reader :config, :sent_size
+ ##
+ # Creates a new HTTP response object
+
def initialize(config)
@config = config
@buffer_size = config[:OutputBufferSize]
@@ -45,57 +55,96 @@ def initialize(config)
@sent_size = 0
end
+ ##
+ # The response's HTTP status line
+
def status_line
"HTTP/#@http_version #@status #@reason_phrase #{CRLF}"
end
+ ##
+ # Sets the response's status to the +status+ code
+
def status=(status)
@status = status
@reason_phrase = HTTPStatus::reason_phrase(status)
end
+ ##
+ # Retrieves the response header +field+
+
def [](field)
@header[field.downcase]
end
+ ##
+ # Sets the response header +field+ to +value+
+
def []=(field, value)
@header[field.downcase] = value.to_s
end
+ ##
+ # The content-length header
+
def content_length
if len = self['content-length']
return Integer(len)
end
end
+ ##
+ # Sets the content-length header to +len+
+
def content_length=(len)
self['content-length'] = len.to_s
end
+ ##
+ # The content-type header
+
def content_type
self['content-type']
end
+ ##
+ # Sets the content-type header to +type+
+
def content_type=(type)
self['content-type'] = type
end
+ ##
+ # Iterates over each header in the resopnse
+
def each
- @header.each{|k, v| yield(k, v) }
+ @header.each{|field, value| yield(field, value) }
end
+ ##
+ # Will this response body be returned using chunked transfer-encoding?
+
def chunked?
@chunked
end
+ ##
+ # Enables chunked transfer encoding.
+
def chunked=(val)
@chunked = val ? true : false
end
+ ##
+ # Will this response's connection be kept alive?
+
def keep_alive?
@keep_alive
end
+ ##
+ # Sends the response on +socket+
+
def send_response(socket)
begin
setup_header()
@@ -110,6 +159,9 @@ def send_response(socket)
end
end
+ ##
+ # Sets up the headers for sending
+
def setup_header()
@reason_phrase ||= HTTPStatus::reason_phrase(@status)
@header['server'] ||= @config[:ServerSoftware]
@@ -152,6 +204,11 @@ def setup_header()
elsif keep_alive?
if chunked? || @header['content-length']
@header['connection'] = "Keep-Alive"
+ else
+ msg = "Could not determine content-length of response body. Set content-length of the response or set Response#chunked = true"
+ @logger.warn(msg)
+ @header['connection'] = "close"
+ @keep_alive = false
end
else
@header['connection'] = "close"
@@ -165,6 +222,9 @@ def setup_header()
end
end
+ ##
+ # Sends the headers on +socket+
+
def send_header(socket)
if @http_version.major > 0
data = status_line()
@@ -180,6 +240,9 @@ def send_header(socket)
end
end
+ ##
+ # Sends the body on +socket+
+
def send_body(socket)
case @body
when IO then send_body_io(socket)
@@ -187,18 +250,28 @@ def send_body(socket)
end
end
- def to_s
+ def to_s # :nodoc:
ret = ""
send_response(ret)
ret
end
+ ##
+ # Redirects to +url+ with a WEBrick::HTTPStatus::Redirect +status+.
+ #
+ # Example:
+ #
+ # res.set_redirect WEBrick::HTTPStatus::TemporaryRedirect
+
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
+ ##
+ # Creates an error page for exception +ex+ with an optional +backtrace+
+
def set_error(ex, backtrace=false)
case ex
when HTTPStatus::Status
@@ -280,7 +353,7 @@ def send_body_string(socket)
if @request_method == "HEAD"
# do nothing
elsif chunked?
- remain = body ? @body.bytesize : 0
+ body ? @body.bytesize : 0
while buf = @body[@sent_size, @buffer_size]
break if buf.empty?
data = ""
View
1  lib/19/webrick/https.rb
@@ -38,6 +38,7 @@ def parse_uri(str, scheme="https")
end
return orig_parse_uri(str)
end
+ private :parse_uri
alias orig_meta_vars meta_vars
View
47 lib/19/webrick/httpserver.rb
@@ -19,7 +19,28 @@
module WEBrick
class HTTPServerError < ServerError; end
+ ##
+ # An HTTP Server
+
class HTTPServer < ::WEBrick::GenericServer
+ ##
+ # Creates a new HTTP server according to +config+
+ #
+ # An HTTP server uses the following attributes:
+ #
+ # :AccessLog:: An array of access logs. See WEBrick::AccessLog
+ # :BindAddress:: Local address for the server to bind to
+ # :DocumentRoot:: Root path to serve files from
+ # :DocumentRootOptions:: Options for the default HTTPServlet::FileHandler
+ # :HTTPVersion:: The HTTP version of this server
+ # :Port:: Port to listen on
+ # :RequestCallback:: Called with a request and response before each
+ # request is serviced.
+ # :RequestTimeout:: Maximum time to wait between requests
+ # :ServerAlias:: Array of alternate names for this server for virtual
+ # hosting
+ # :ServerName:: Name for this server for virtual hosting
+
def initialize(config={}, default=Config::HTTP)
super(config, default)
@http_version = HTTPVersion::convert(@config[:HTTPVersion])
@@ -40,6 +61,9 @@ def initialize(config={}, default=Config::HTTP)
@virtual_hosts = Array.new
end
+ ##
+ # Processes requests on +sock+
+
def run(sock)
while true
res = HTTPResponse.new(@config)
@@ -93,6 +117,9 @@ def run(sock)
end
end
+ ##
+ # Services +req+ and fills in +res+
+
def service(req, res)
if req.unparsed_uri == "*"
if req.request_method == "OPTIONS"
@@ -115,23 +142,37 @@ def do_OPTIONS(req, res)
res["allow"] = "GET,HEAD,POST,OPTIONS"
end
+ ##
+ # Mounts +servlet+ on +dir+ passing +options+ to the servlet at creation
+ # time
+
def mount(dir, servlet, *options)
@logger.debug(sprintf("%s is mounted on %s.", servlet.inspect, dir))
@mount_tab[dir] = [ servlet, options ]
end
+ ##
+ # Mounts +proc+ or +block+ on +dir+ and calls it with a
+ # WEBrick::HTTPRequest and WEBrick::HTTPResponse
+
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
+ ##
+ # Unmounts +dir+
+
def unmount(dir)
@logger.debug(sprintf("unmount %s.", dir))
@mount_tab.delete(dir)
end
alias umount unmount
+ ##
+ # Finds a servlet for +path+
+
def search_servlet(path)
script_name, path_info = @mount_tab.scan(path)
servlet, options = @mount_tab[script_name]
@@ -140,6 +181,9 @@ def search_servlet(path)
end
end
+ ##
+ # Adds +server+ as a virtual host.
+
def virtual_host(server)
@virtual_hosts << server
@virtual_hosts = @virtual_hosts.sort_by{|s|
@@ -151,6 +195,9 @@ def virtual_host(server)
}
end
+ ##
+ # Finds the appropriate virtual host to handle +req+
+
def lookup_server(req)
@virtual_hosts.find{|s|
(s[:BindAddress].nil? || req.addr[3] == s[:BindAddress]) &&
View
87 lib/19/webrick/httpservlet/abstract.rb
@@ -18,17 +18,88 @@ module WEBrick
module HTTPServlet
class HTTPServletError < StandardError; end
+ ##
+ # AbstractServlet allows HTTP server modules to be reused across multiple
+ # servers and allows encapsulation of functionality.
+ #
+ # By default a servlet will respond to GET, HEAD (through an alias to GET)
+ # and OPTIONS requests.
+ #
+ # By default a new servlet is initialized for every request. A servlet
+ # instance can be reused by overriding ::get_instance in the
+ # AbstractServlet subclass.
+ #
+ # == A Simple Servlet
+ #
+ # class Simple < WEBrick::HTTPServlet::AbstractServlet
+ # def do_GET request, response
+ # status, content_type, body = do_stuff_with request
+ #
+ # response.status = status
+ # response['Content-Type'] = content_type
+ # response.body = body
+ # end
+ #
+ # def do_stuff_with request
+ # return 200, 'text/plain', 'you got a page'
+ # end
+ # end
+ #
+ # This servlet can be mounted on a server at a given path:
+ #
+ # server.mount '/simple', Simple
+ #
+ # == Servlet Configuration
+ #
+ # Servlets can be configured via initialize. The first argument is the
+ # HTTP server the servlet is being initialized for.
+ #
+ # class Configureable < Simple
+ # def initialize server, color, size
+ # super server
+ # @color = color
+ # @size = size
+ # end
+ #
+ # def do_stuff_with request
+ # content = "<p " \
+ # %q{style="color: #{@color}; font-size: #{@size}"} \
+ # ">Hello, World!"
+ #
+ # return 200, "text/html", content
+ # end
+ # end
+ #
+ # This servlet must be provided two arguments at mount time:
+ #
+ # server.mount '/configurable', Configurable, 'red', '2em'
+
class AbstractServlet
- def self.get_instance(config, *options)
- self.new(config, *options)
+
+ ##
+ # Factory for servlet instances that will handle a request from +server+
+ # using +options+ from the mount point. By default a new servlet
+ # instance is created for every call.
+
+ def self.get_instance(server, *options)
+ self.new(server, *options)
end
+ ##
+ # Initializes a new servlet for +server+ using +options+ which are
+ # stored as-is in +@options+. +@logger+ is also provided.
+
def initialize(server, *options)
@server = @config = server
@logger = @server[:Logger]
@options = options
end
+ ##
+ # Dispatches to a +do_+ method based on +req+ if such a method is
+ # available. (+do_GET+ for a GET request). Raises a MethodNotAllowed
+ # exception if the method is not implemented.
+
def service(req, res)
method_name = "do_" + req.request_method.gsub(/-/, "_")
if respond_to?(method_name)
@@ -39,14 +110,23 @@ def service(req, res)
end
end
+ ##
+ # Raises a NotFound exception
+
def do_GET(req, res)
raise HTTPStatus::NotFound, "not found."
end
+ ##
+ # Dispatches to do_GET
+
def do_HEAD(req, res)
do_GET(req, res)
end
+ ##
+ # Returns the allowed HTTP request methods
+
def do_OPTIONS(req, res)
m = self.methods.grep(/\Ado_([A-Z]+)\z/) {$1}
m.sort!
@@ -55,6 +135,9 @@ def do_OPTIONS(req, res)
private
+ ##
+ # Redirects to a path ending in /
+
def redirect_to_directory_uri(req, res)
if req.path[-1] != ?/
location = WEBrick::HTTPUtils.escape_path(req.path + "/")
View
3  lib/19/webrick/httpservlet/cgi_runner.rb
@@ -20,7 +20,6 @@ def sysread(io, size)
STDIN.binmode
-buf = ""
len = sysread(STDIN, 8).to_i
out = sysread(STDIN, len)
STDOUT.reopen(open(out, "w"))
@@ -38,7 +37,7 @@ def sysread(io, size)
dir = File::dirname(ENV["SCRIPT_FILENAME"])
Dir::chdir dir
-if interpreter = ARGV[0]
+if ARGV[0]
argv = ARGV.dup
argv << ENV["SCRIPT_FILENAME"]
exec(*argv)
View
39 lib/19/webrick/httpservlet/erbhandler.rb
@@ -15,12 +15,37 @@
module WEBrick
module HTTPServlet
+ ##
+ # ERBHandler evaluates an ERB file and returns the result. This handler
+ # is automatically used if there are .rhtml files in a directory served by
+ # the FileHandler.
+ #
+ # ERBHandler supports GET and POST methods.
+ #
+ # The ERB file is evaluated with the local variables +servlet_request+ and
+ # +servlet_response+ which are a WEBrick::HTTPRequest and
+ # WEBrick::HTTPResponse respectively.
+ #
+ # Example .rhtml file:
+ #
+ # Request to <%= servlet_request.request_uri %>
+ #
+ # Query params <%= servlet_request.query.inspect %>
+
class ERBHandler < AbstractServlet
+
+ ##
+ # Creates a new ERBHandler on +server+ that will evaluate and serve the
+ # ERB file +name+
+
def initialize(server, name)
super(server, name)
@script_filename = name
end
+ ##
+ # Handles GET requests
+
def do_GET(req, res)
unless defined?(ERB)
@logger.warn "#{self.class}: ERB not defined."
@@ -29,7 +54,7 @@ def do_GET(req, res)
begin
data = open(@script_filename){|io| io.read }
res.body = evaluate(ERB.new(data), req, res)
- res['content-type'] =
+ res['content-type'] ||=
HTTPUtils::mime_type(@script_filename, @config[:MimeTypes])
rescue StandardError => ex
raise
@@ -39,13 +64,21 @@ def do_GET(req, res)
end
end
+ ##
+ # Handles POST requests
+
alias do_POST do_GET
private
+
+ ##
+ # Evaluates +erb+ providing +servlet_request+ and +servlet_response+ as
+ # local variables.
+
def evaluate(erb, servlet_request, servlet_response)
Module.new.module_eval{
- meta_vars = servlet_request.meta_vars
- query = servlet_request.query
+ servlet_request.meta_vars
+ servlet_request.query
erb.result(binding)
}
end
View
31 lib/19/webrick/httpservlet/filehandler.rb
@@ -125,17 +125,48 @@ def prepare_range(range, filesize)
end
end
+ ##
+ # Serves files from a directory
+
class FileHandler < AbstractServlet
HandlerTable = Hash.new
+ ##
+ # Allow custom handling of requests for files with +suffix+ by class
+ # +handler+
+
def self.add_handler(suffix, handler)
HandlerTable[suffix] = handler
end
+ ##
+ # Remove custom handling of requests for files with +suffix+
+
def self.remove_handler(suffix)
HandlerTable.delete(suffix)
end
+ ##
+ # Creates a FileHandler servlet on +server+ that serves files starting
+ # at directory +root+
+ #
+ # If +options+ is a Hash the following keys are allowed:
+ #
+ # :AcceptableLanguages:: Array of languages allowed for accept-language
+ # :DirectoryCallback:: Allows preprocessing of directory requests
+ # :FancyIndexing:: If true, show an index for directories
+ # :FileCallback:: Allows preprocessing of file requests
+ # :HandlerCallback:: Allows preprocessing of requests
+ # :HandlerTable:: Maps file suffixes to file handlers.
+ # DefaultFileHandler is used by default but any servlet
+ # can be used.
+ # :NondisclosureName:: Do not show files matching this array of globs
+ # :UserDir:: Directory inside ~user to serve content from for /~user
+ # requests. Only works if mounted on /
+ #
+ # If +options+ is true or false then +:FancyIndexing+ is enabled or
+ # disabled respectively.
+
def initialize(server, root, options={}, default=Config::FileHandler)
@config = server.config
@logger = @config[:Logger]
View
64 lib/19/webrick/httpstatus.rb
@@ -1,4 +1,4 @@
-#
+#--
# httpstatus.rb -- HTTPStatus Class
#
# Author: IPR -- Internet Programming with Ruby -- writers
@@ -10,30 +10,50 @@
module WEBrick
+ ##
+ # This module is used to manager HTTP status codes.
+ #
+ # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html for more
+ # information.
module HTTPStatus
+ ##
+ # Root of the HTTP status class hierarchy
class Status < StandardError
- def initialize(*args)
+ def initialize(*args) # :nodoc:
args[0] = AccessLog.escape(args[0]) unless args.empty?
super(*args)
end
class << self
- attr_reader :code, :reason_phrase
+ attr_reader :code, :reason_phrase # :nodoc:
end
+
+ # Returns the HTTP status code
def code() self::class::code end
+
+ # Returns the HTTP status description
def reason_phrase() self::class::reason_phrase end
- alias to_i code
+
+ alias to_i code # :nodoc:
end
+
+ # Root of the HTTP info statuses
class Info < Status; end
+ # Root of the HTTP sucess statuses
class Success < Status; end
+ # Root of the HTTP redirect statuses
class Redirect < Status; end
+ # Root of the HTTP error statuses
class Error < Status; end
+ # Root of the HTTP client error statuses
class ClientError < Error; end
+ # Root of the HTTP server error statuses
class ServerError < Error; end
class EOFError < StandardError; end
- StatusMessage = {
+ # HTTP status codes and descriptions
+ StatusMessage = { # :nodoc:
100 => 'Continue',
101 => 'Switching Protocols',
200 => 'OK',
@@ -76,8 +96,11 @@ class EOFError < StandardError; end
505 => 'HTTP Version Not Supported'
}
- CodeToError = {}
+ # Maps a status code to the corresponding Status class
+ CodeToError = {} # :nodoc:
+ # Creates a status or error class for each status code and
+ # populates the CodeToError map.
StatusMessage.each{|code, message|
message.freeze
var_name = message.gsub(/[ \-]/,'_').upcase
@@ -99,28 +122,57 @@ class EOFError < StandardError; end
CodeToError[code] = err_class
}
+ ##
+ # Returns the description corresponding to the HTTP status +code+
+ #
+ # WEBrick::HTTPStatus.reason_phrase 404
+ # => "Not Found"
def reason_phrase(code)
StatusMessage[code.to_i]
end
+
+ ##
+ # Is +code+ an informational status?
def info?(code)
code.to_i >= 100 and code.to_i < 200
end
+
+ ##
+ # Is +code+ a successful status?
def success?(code)
code.to_i >= 200 and code.to_i < 300
end
+
+ ##
+ # Is +code+ a redirection status?
def redirect?(code)
code.to_i >= 300 and code.to_i < 400
end
+
+ ##
+ # Is +code+ an error status?
def error?(code)
code.to_i >= 400 and code.to_i < 600
end
+
+ ##
+ # Is +code+ a client error status?
def client_error?(code)
code.to_i >= 400 and code.to_i < 500
end
+
+ ##
+ # Is +code+ a server error status?
def server_error?(code)
code.to_i >= 500 and code.to_i < 600
end
+ ##
+ # Returns the status class corresponding to +code+
+ #
+ # WEBrick::HTTPStatus[302]
+ # => WEBrick::HTTPStatus::NotFound
+ #
def self.[](code)
CodeToError[code]
end
View
2  lib/19/webrick/httpversion.rb
@@ -1,4 +1,4 @@
-#
+#--
# HTTPVersion.rb -- presentation of HTTP version
#
# Author: IPR -- Internet Programming with Ruby -- writers
View
54 lib/19/webrick/log.rb
@@ -1,4 +1,4 @@
-#
+#--
# log.rb -- Log Class
#
# Author: IPR -- Internet Programming with Ruby -- writers
@@ -9,12 +9,24 @@
# $IPR: log.rb,v 1.26 2002/10/06 17:06:10 gotoyuzo Exp $
module WEBrick
+
+ ##
+ # A generic logging class
+
class BasicLog
- # log-level constant
+ # log-level constants
FATAL, ERROR, WARN, INFO, DEBUG = 1, 2, 3, 4, 5
+ # log-level, messages above this level will be logged
attr_accessor :level
+ ##
+ # Initializes a new logger for +log_file+ that outputs messages at +level+
+ # or higher. +log_file+ can be a filename, an IO-like object that
+ # responds to #<< or nil which outputs to $stderr.
+ #
+ # If no level is given INFO is chosen by default
+
def initialize(log_file=nil, level=nil)
@level = level || INFO
case log_file
@@ -29,11 +41,17 @@ def initialize(log_file=nil, level=nil)
end
end
+ ##
+ # Closes the logger (also closes the log device associated to the logger)
def close
@log.close if @opened
@log = nil
end
+ ##
+ # Logs +data+ at +level+ if the given level is above the current log
+ # level.
+
def log(level, data)
if @log && level <= @level
data += "\n" if /\n\Z/ !~ data
@@ -41,26 +59,45 @@ def log(level, data)
end
end
+ ##
+ # Synonym for log(INFO, obj.to_s)
def <<(obj)
log(INFO, obj.to_s)
end
+ # Shortcut for logging a FATAL message
def fatal(msg) log(FATAL, "FATAL " << format(msg)); end
+ # Shortcut for logging an ERROR message
def error(msg) log(ERROR, "ERROR " << format(msg)); end
+ # Shortcut for logging a WARN message
def warn(msg) log(WARN, "WARN " << format(msg)); end
+ # Shortcut for logging an INFO message
def info(msg) log(INFO, "INFO " << format(msg)); end
+ # Shortcut for logging a DEBUG message
def debug(msg) log(DEBUG, "DEBUG " << format(msg)); end
+ # Will the logger output FATAL messages?
def fatal?; @level >= FATAL; end
+ # Will the logger output ERROR messages?
def error?; @level >= ERROR; end
+ # Will the logger output WARN messages?
def warn?; @level >= WARN; end
+ # Will the logger output INFO messages?
def info?; @level >= INFO; end
+ # Will the logger output DEBUG messages?
def debug?; @level >= DEBUG; end
private
+ ##
+ # Formats +arg+ for the logger
+ #
+ # * If +arg+ is an Exception, it will format the error message and
+ # the back trace.
+ # * If +arg+ responds to #to_str, it will return it.
+ # * Otherwise it will return +arg+.inspect.
def format(arg)
- str = if arg.is_a?(Exception)
+ if arg.is_a?(Exception)
"#{arg.class}: #{arg.message}\n\t" <<
arg.backtrace.join("\n\t") << "\n"
elsif arg.respond_to?(:to_str)
@@ -71,14 +108,25 @@ def format(arg)
end
end
+ ##
+ # A logging class that prepends a timestamp to each message.
+
class Log < BasicLog
+ # Format of the timestamp which is applied to each logged line. The
+ # default is <tt>"[%Y-%m-%d %H:%M:%S]"</tt>
attr_accessor :time_format
+ ##
+ # Same as BasicLog#initialize
+ #
+ # You can set the timestamp format through #time_format
def initialize(log_file=nil, level=nil)
super(log_file, level)
@time_format = "[%Y-%m-%d %H:%M:%S]"
end
+ ##
+ # Same as BasicLog#log
def log(level, data)
tmp = Time.now.strftime(@time_format)
tmp << " " << data
View
8 lib/19/webrick/server.rb
@@ -23,7 +23,15 @@ def SimpleServer.start
end